Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions cockroachkvs/blockproperties.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package cockroachkvs
import (
"encoding/binary"
"math"
"unsafe"

"github.com/cockroachdb/errors"
"github.com/cockroachdb/pebble/sstable"
Expand Down Expand Up @@ -144,6 +145,82 @@ func mapSuffixToInterval(b []byte) (sstable.BlockInterval, error) {
return sstable.BlockInterval{}, nil
}

// MakeSuffixMaskBlockPropertyFilter creates a block property filter for use
// with the SuffixMask transform. It skips blocks where all MVCC wall times
// are strictly greater than the bound's wall time.
func MakeSuffixMaskBlockPropertyFilter(suffixUpperBound []byte) sstable.BlockPropertyFilter {
wall, _, err := DecodeMVCCTimestampSuffix(suffixUpperBound)
if err != nil || wall == 0 {
return nil
}
return NewMVCCTimeIntervalFilter(0, wall)
}

// SuffixRangeIntersectsTable reports whether a table's MVCCTimeInterval
// block-property aggregate could intersect a DeleteSuffixRange's suffix
// range [lower, upper). Lower and upper are MVCC-encoded suffixes; per
// the suffix mask convention, lower is suffix-order inclusive (the newer
// wall time) and upper is suffix-order exclusive (the older wall time).
//
// userProperties is the table-level UserProperties map (as exposed by
// sstable.Reader.UserProperties). If the MVCCTimeInterval property is
// missing (the collector was not configured when the table was written),
// the function returns true: the optimization fails open and the table
// is processed as usual.
//
// The function also returns true if lower or upper cannot be decoded as
// MVCC suffixes — DeleteSuffixRange would still apply the mask in that
// case, and the optimization conservatively assumes intersection rather
// than skipping a file that may legitimately need the mask.
func SuffixRangeIntersectsTable(userProperties map[string]string, lower, upper []byte) bool {
lowerWall, _, err := DecodeMVCCTimestampSuffix(lower)
if err != nil || lowerWall == 0 {
// A wall-time of zero (or a non-MVCC suffix) cannot be reasoned
// about by the wall-only aggregate. Fail open.
return true
}
upperWall, _, err := DecodeMVCCTimestampSuffix(upper)
if err != nil {
return true
}
// The mask matches keys with wall times in (upperWall, lowerWall].
// As a half-open uint64 interval that is [upperWall+1, lowerWall+1).
filterLower := upperWall + 1
filterUpper := lowerWall + 1
if filterLower >= filterUpper {
// Inverted/empty range; conservatively assume intersection so
// DeleteSuffixRange retains its existing behavior.
return true
}
prop, ok := userProperties[mvccWallTimeIntervalCollector]
if !ok {
// Collector was not configured when the table was written. Fail
// open: the caller must process the table.
return true
}
if len(prop) < 1 {
// Defensive: a present-but-empty entry would corrupt our shortID
// stripping. Fail open.
return true
}
// First byte is the shortID; the remainder is the encoded interval.
// Use unsafe.Slice to avoid allocating: DecodeBlockInterval does not
// modify the buffer.
propBytes := unsafe.Slice(unsafe.StringData(prop), len(prop))
interval, err := sstable.DecodeBlockInterval(propBytes[1:])
if err != nil {
// Corrupt aggregate: fail open rather than skipping a file that
// may legitimately need the mask.
return true
}
if interval.IsEmpty() {
// The collector ran but recorded no MVCC keys. No key in the
// file can possibly fall in the mask range.
return false
}
return interval.Upper > filterLower && interval.Lower < filterUpper
}

type MaxMVCCTimestampProperty struct{}

// Name implements pebble.MaximumSuffixProperty interface.
Expand Down
54 changes: 54 additions & 0 deletions cockroachkvs/cockroachkvs.go
Original file line number Diff line number Diff line change
Expand Up @@ -1087,6 +1087,60 @@ func (ks *cockroachKeySeeker) MaterializeUserKeyWithSyntheticSuffix(
return res
}

// IsMaskedBySuffixMask implements colblk.KeySeeker
// interface. The bounds are encoded MVCC suffixes; we inline the decode to
// avoid per-row function call overhead.
//
// Fail-open on malformed bounds is intentional: bounds originate inside
// Pebble (DSR's validation at the API boundary rejects empty/zero-length
// bounds, and the manifest decoder rejects them on load), so a bound that
// is too short to decode here is a programming bug, not user input.
// Returning false leaves the row visible — strictly safer than the
// alternative of silently hiding rows due to corrupt internal state.
// Compare with `defaultKeySeeker.IsMaskedBySuffixMask` which panics on
// missing `ComparePointSuffixes`; both share the principle that an
// internal-invariant violation should not silently mask data.
func (ks *cockroachKeySeeker) IsMaskedBySuffixMask(row int, lower, upper []byte) bool {
rowWall := ks.mvccWallTimes.At(row)
rowLogical := uint32(ks.mvccLogical.At(row))
if rowWall == 0 && rowLogical == 0 {
return false
}
// Decode lower bound.
if len(lower) < suffixLenWithWall {
return false
}
lowerWall := binary.BigEndian.Uint64(lower[:8])
var lowerLogical uint32
if len(lower) >= suffixLenWithLogical {
lowerLogical = binary.BigEndian.Uint32(lower[8:12])
}
// Decode upper bound.
if len(upper) < suffixLenWithWall {
return false
}
upperWall := binary.BigEndian.Uint64(upper[:8])
var upperLogical uint32
if len(upper) >= suffixLenWithLogical {
upperLogical = binary.BigEndian.Uint32(upper[8:12])
}
// Mask range is [lower, upper) in comparer order. The comparer sorts
// newer (larger wall time) first, so lower has the larger wall time.
// row >= lower in comparer: row wall time <= lower wall time.
// row < upper in comparer: row wall time > upper wall time.
geLower := rowWall < lowerWall || (rowWall == lowerWall && rowLogical <= lowerLogical)
ltUpper := rowWall > upperWall || (rowWall == upperWall && rowLogical > upperLogical)
return geLower && ltUpper
}

// HasNonEmptySuffix implements colblk.KeySeeker. A row has a non-empty
// MVCC suffix iff either the wall time or the logical timestamp is
// non-zero (matching the "suffixless" sentinel used by
// IsMaskedBySuffixMask).
func (ks *cockroachKeySeeker) HasNonEmptySuffix(row int) bool {
return ks.mvccWallTimes.At(row) != 0 || uint32(ks.mvccLogical.At(row)) != 0
}

//go:linkname memmove runtime.memmove
func memmove(to, from unsafe.Pointer, n uintptr)

Expand Down
Loading
Loading