Upgrade versions of vendored libs
This commit is contained in:
301
vendor/github.com/cockroachdb/pebble/sstable/block.go
generated
vendored
301
vendor/github.com/cockroachdb/pebble/sstable/block.go
generated
vendored
@@ -47,7 +47,10 @@ type blockWriter struct {
|
||||
// ensuring that any of the keys with the same prefix can be used to
|
||||
// assemble the full key when the prefix does change.
|
||||
restarts []uint32
|
||||
curKey []byte
|
||||
// Do not read curKey directly from outside blockWriter since it can have
|
||||
// the InternalKeyKindSSTableInternalObsoleteBit set. Use getCurKey() or
|
||||
// getCurUserKey() instead.
|
||||
curKey []byte
|
||||
// curValue excludes the optional prefix provided to
|
||||
// storeWithOptionalValuePrefix.
|
||||
curValue []byte
|
||||
@@ -79,6 +82,20 @@ const setHasSameKeyPrefixRestartMask uint32 = 1 << 31
|
||||
const restartMaskLittleEndianHighByteWithoutSetHasSamePrefix byte = 0b0111_1111
|
||||
const restartMaskLittleEndianHighByteOnlySetHasSamePrefix byte = 0b1000_0000
|
||||
|
||||
func (w *blockWriter) getCurKey() InternalKey {
|
||||
k := base.DecodeInternalKey(w.curKey)
|
||||
k.Trailer = k.Trailer & trailerObsoleteMask
|
||||
return k
|
||||
}
|
||||
|
||||
func (w *blockWriter) getCurUserKey() []byte {
|
||||
n := len(w.curKey) - base.InternalTrailerLen
|
||||
if n < 0 {
|
||||
panic(errors.AssertionFailedf("corrupt key in blockWriter buffer"))
|
||||
}
|
||||
return w.curKey[:n:n]
|
||||
}
|
||||
|
||||
// If !addValuePrefix, the valuePrefix is ignored.
|
||||
func (w *blockWriter) storeWithOptionalValuePrefix(
|
||||
keySize int,
|
||||
@@ -191,12 +208,21 @@ func (w *blockWriter) storeWithOptionalValuePrefix(
|
||||
|
||||
func (w *blockWriter) add(key InternalKey, value []byte) {
|
||||
w.addWithOptionalValuePrefix(
|
||||
key, value, len(key.UserKey), false, 0, false)
|
||||
key, false, value, len(key.UserKey), false, 0, false)
|
||||
}
|
||||
|
||||
// Callers that always set addValuePrefix to false should use add() instead.
|
||||
//
|
||||
// isObsolete indicates whether this key-value pair is obsolete in this
|
||||
// sstable (only applicable when writing data blocks) -- see the comment in
|
||||
// table.go and the longer one in format.go. addValuePrefix adds a 1 byte
|
||||
// prefix to the value, specified in valuePrefix -- this is used for data
|
||||
// blocks in TableFormatPebblev3 onwards for SETs (see the comment in
|
||||
// format.go, with more details in value_block.go). setHasSameKeyPrefix is
|
||||
// also used in TableFormatPebblev3 onwards for SETs.
|
||||
func (w *blockWriter) addWithOptionalValuePrefix(
|
||||
key InternalKey,
|
||||
isObsolete bool,
|
||||
value []byte,
|
||||
maxSharedKeyLen int,
|
||||
addValuePrefix bool,
|
||||
@@ -210,6 +236,9 @@ func (w *blockWriter) addWithOptionalValuePrefix(
|
||||
w.curKey = make([]byte, 0, size*2)
|
||||
}
|
||||
w.curKey = w.curKey[:size]
|
||||
if isObsolete {
|
||||
key.Trailer = key.Trailer | trailerObsoleteBit
|
||||
}
|
||||
key.Encode(w.curKey)
|
||||
|
||||
w.storeWithOptionalValuePrefix(
|
||||
@@ -374,13 +403,14 @@ type blockIter struct {
|
||||
cached []blockEntry
|
||||
cachedBuf []byte
|
||||
cacheHandle cache.Handle
|
||||
// The first key in the block. This is used by the caller to set bounds
|
||||
// The first user key in the block. This is used by the caller to set bounds
|
||||
// for block iteration for already loaded blocks.
|
||||
firstKey InternalKey
|
||||
firstUserKey []byte
|
||||
lazyValueHandling struct {
|
||||
vbr *valueBlockReader
|
||||
hasValuePrefix bool
|
||||
}
|
||||
hideObsoletePoints bool
|
||||
}
|
||||
|
||||
// blockIter implements the base.InternalIterator interface.
|
||||
@@ -388,14 +418,16 @@ var _ base.InternalIterator = (*blockIter)(nil)
|
||||
|
||||
func newBlockIter(cmp Compare, block block) (*blockIter, error) {
|
||||
i := &blockIter{}
|
||||
return i, i.init(cmp, block, 0)
|
||||
return i, i.init(cmp, block, 0, false)
|
||||
}
|
||||
|
||||
func (i *blockIter) String() string {
|
||||
return "block"
|
||||
}
|
||||
|
||||
func (i *blockIter) init(cmp Compare, block block, globalSeqNum uint64) error {
|
||||
func (i *blockIter) init(
|
||||
cmp Compare, block block, globalSeqNum uint64, hideObsoletePoints bool,
|
||||
) error {
|
||||
numRestarts := int32(binary.LittleEndian.Uint32(block[len(block)-4:]))
|
||||
if numRestarts == 0 {
|
||||
return base.CorruptionErrorf("pebble/table: invalid table (block has no restart points)")
|
||||
@@ -408,6 +440,7 @@ func (i *blockIter) init(cmp Compare, block block, globalSeqNum uint64) error {
|
||||
i.data = block
|
||||
i.fullKey = i.fullKey[:0]
|
||||
i.val = nil
|
||||
i.hideObsoletePoints = hideObsoletePoints
|
||||
i.clearCache()
|
||||
if i.restarts > 0 {
|
||||
if err := i.readFirstKey(); err != nil {
|
||||
@@ -415,15 +448,21 @@ func (i *blockIter) init(cmp Compare, block block, globalSeqNum uint64) error {
|
||||
}
|
||||
} else {
|
||||
// Block is empty.
|
||||
i.firstKey = InternalKey{}
|
||||
i.firstUserKey = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *blockIter) initHandle(cmp Compare, block cache.Handle, globalSeqNum uint64) error {
|
||||
// NB: two cases of hideObsoletePoints:
|
||||
// - Local sstable iteration: globalSeqNum will be set iff the sstable was
|
||||
// ingested.
|
||||
// - Foreign sstable iteration: globalSeqNum is always set.
|
||||
func (i *blockIter) initHandle(
|
||||
cmp Compare, block cache.Handle, globalSeqNum uint64, hideObsoletePoints bool,
|
||||
) error {
|
||||
i.cacheHandle.Release()
|
||||
i.cacheHandle = block
|
||||
return i.init(cmp, block.Get(), globalSeqNum)
|
||||
return i.init(cmp, block.Get(), globalSeqNum, hideObsoletePoints)
|
||||
}
|
||||
|
||||
func (i *blockIter) invalidate() {
|
||||
@@ -590,24 +629,27 @@ func (i *blockIter) readFirstKey() error {
|
||||
// Manually inlining base.DecodeInternalKey provides a 5-10% speedup on
|
||||
// BlockIter benchmarks.
|
||||
if n := len(firstKey) - 8; n >= 0 {
|
||||
i.firstKey.Trailer = binary.LittleEndian.Uint64(firstKey[n:])
|
||||
i.firstKey.UserKey = firstKey[:n:n]
|
||||
if i.globalSeqNum != 0 {
|
||||
i.firstKey.SetSeqNum(i.globalSeqNum)
|
||||
}
|
||||
i.firstUserKey = firstKey[:n:n]
|
||||
} else {
|
||||
i.firstKey.Trailer = uint64(InternalKeyKindInvalid)
|
||||
i.firstKey.UserKey = nil
|
||||
i.firstUserKey = nil
|
||||
return base.CorruptionErrorf("pebble/table: invalid firstKey in block")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *blockIter) decodeInternalKey(key []byte) {
|
||||
// The sstable internal obsolete bit is set when writing a block and unset by
|
||||
// blockIter, so no code outside block writing/reading code ever sees it.
|
||||
const trailerObsoleteBit = uint64(base.InternalKeyKindSSTableInternalObsoleteBit)
|
||||
const trailerObsoleteMask = (InternalKeySeqNumMax << 8) | uint64(base.InternalKeyKindSSTableInternalObsoleteMask)
|
||||
|
||||
func (i *blockIter) decodeInternalKey(key []byte) (hiddenPoint bool) {
|
||||
// Manually inlining base.DecodeInternalKey provides a 5-10% speedup on
|
||||
// BlockIter benchmarks.
|
||||
if n := len(key) - 8; n >= 0 {
|
||||
i.ikey.Trailer = binary.LittleEndian.Uint64(key[n:])
|
||||
trailer := binary.LittleEndian.Uint64(key[n:])
|
||||
hiddenPoint = i.hideObsoletePoints &&
|
||||
(trailer&trailerObsoleteBit != 0)
|
||||
i.ikey.Trailer = trailer & trailerObsoleteMask
|
||||
i.ikey.UserKey = key[:n:n]
|
||||
if i.globalSeqNum != 0 {
|
||||
i.ikey.SetSeqNum(i.globalSeqNum)
|
||||
@@ -616,6 +658,7 @@ func (i *blockIter) decodeInternalKey(key []byte) {
|
||||
i.ikey.Trailer = uint64(InternalKeyKindInvalid)
|
||||
i.ikey.UserKey = nil
|
||||
}
|
||||
return hiddenPoint
|
||||
}
|
||||
|
||||
func (i *blockIter) clearCache() {
|
||||
@@ -640,13 +683,15 @@ func (i *blockIter) cacheEntry() {
|
||||
i.cachedBuf = append(i.cachedBuf, i.key...)
|
||||
}
|
||||
|
||||
func (i *blockIter) getFirstUserKey() []byte {
|
||||
return i.firstUserKey
|
||||
}
|
||||
|
||||
// SeekGE implements internalIterator.SeekGE, as documented in the pebble
|
||||
// package.
|
||||
func (i *blockIter) SeekGE(key []byte, flags base.SeekGEFlags) (*InternalKey, base.LazyValue) {
|
||||
i.clearCache()
|
||||
|
||||
ikey := base.MakeSearchKey(key)
|
||||
|
||||
// Find the index of the smallest restart point whose key is > the key
|
||||
// sought; index will be numRestarts if there is no such restart point.
|
||||
i.offset = 0
|
||||
@@ -700,23 +745,23 @@ func (i *blockIter) SeekGE(key []byte, flags base.SeekGEFlags) (*InternalKey, ba
|
||||
ptr = unsafe.Pointer(uintptr(ptr) + 5)
|
||||
}
|
||||
|
||||
// Manually inlining base.DecodeInternalKey provides a 5-10% speedup on
|
||||
// BlockIter benchmarks.
|
||||
// Manually inlining part of base.DecodeInternalKey provides a 5-10%
|
||||
// speedup on BlockIter benchmarks.
|
||||
s := getBytes(ptr, int(v1))
|
||||
var k InternalKey
|
||||
var k []byte
|
||||
if n := len(s) - 8; n >= 0 {
|
||||
k.Trailer = binary.LittleEndian.Uint64(s[n:])
|
||||
k.UserKey = s[:n:n]
|
||||
// NB: We can't have duplicate keys if the globalSeqNum != 0, so we
|
||||
// leave the seqnum on this key as 0 as it won't affect our search
|
||||
// since ikey has the maximum seqnum.
|
||||
} else {
|
||||
k.Trailer = uint64(InternalKeyKindInvalid)
|
||||
k = s[:n:n]
|
||||
}
|
||||
// Else k is invalid, and left as nil
|
||||
|
||||
if base.InternalCompare(i.cmp, ikey, k) >= 0 {
|
||||
if i.cmp(key, k) > 0 {
|
||||
// The search key is greater than the user key at this restart point.
|
||||
// Search beyond this restart point, since we are trying to find the
|
||||
// first restart point with a user key >= the search key.
|
||||
index = h + 1 // preserves f(i-1) == false
|
||||
} else {
|
||||
// k >= search key, so prune everything after index (since index
|
||||
// satisfies the property we are looking for).
|
||||
upper = h // preserves f(j) == true
|
||||
}
|
||||
}
|
||||
@@ -724,21 +769,25 @@ func (i *blockIter) SeekGE(key []byte, flags base.SeekGEFlags) (*InternalKey, ba
|
||||
// => answer is index.
|
||||
}
|
||||
|
||||
// Since keys are strictly increasing, if index > 0 then the restart point at
|
||||
// index-1 will be the largest whose key is <= the key sought. If index ==
|
||||
// 0, then all keys in this block are larger than the key sought, and offset
|
||||
// remains at zero.
|
||||
// index is the first restart point with key >= search key. Define the keys
|
||||
// between a restart point and the next restart point as belonging to that
|
||||
// restart point.
|
||||
//
|
||||
// Since keys are strictly increasing, if index > 0 then the restart point
|
||||
// at index-1 will be the first one that has some keys belonging to it that
|
||||
// could be equal to the search key. If index == 0, then all keys in this
|
||||
// block are larger than the key sought, and offset remains at zero.
|
||||
if index > 0 {
|
||||
i.offset = decodeRestart(i.data[i.restarts+4*(index-1):])
|
||||
}
|
||||
i.readEntry()
|
||||
i.decodeInternalKey(i.key)
|
||||
hiddenPoint := i.decodeInternalKey(i.key)
|
||||
|
||||
// Iterate from that restart point to somewhere >= the key sought.
|
||||
if !i.valid() {
|
||||
return nil, base.LazyValue{}
|
||||
}
|
||||
if base.InternalCompare(i.cmp, i.ikey, ikey) >= 0 {
|
||||
if !hiddenPoint && i.cmp(i.ikey.UserKey, key) >= 0 {
|
||||
// Initialize i.lazyValue
|
||||
if !i.lazyValueHandling.hasValuePrefix ||
|
||||
base.TrailerKind(i.ikey.Trailer) != InternalKeyKindSet {
|
||||
@@ -751,7 +800,7 @@ func (i *blockIter) SeekGE(key []byte, flags base.SeekGEFlags) (*InternalKey, ba
|
||||
return &i.ikey, i.lazyValue
|
||||
}
|
||||
for i.Next(); i.valid(); i.Next() {
|
||||
if base.InternalCompare(i.cmp, i.ikey, ikey) >= 0 {
|
||||
if i.cmp(i.ikey.UserKey, key) >= 0 {
|
||||
// i.Next() has already initialized i.lazyValue.
|
||||
return &i.ikey, i.lazyValue
|
||||
}
|
||||
@@ -773,8 +822,6 @@ func (i *blockIter) SeekPrefixGE(
|
||||
func (i *blockIter) SeekLT(key []byte, flags base.SeekLTFlags) (*InternalKey, base.LazyValue) {
|
||||
i.clearCache()
|
||||
|
||||
ikey := base.MakeSearchKey(key)
|
||||
|
||||
// Find the index of the smallest restart point whose key is >= the key
|
||||
// sought; index will be numRestarts if there is no such restart point.
|
||||
i.offset = 0
|
||||
@@ -828,23 +875,23 @@ func (i *blockIter) SeekLT(key []byte, flags base.SeekLTFlags) (*InternalKey, ba
|
||||
ptr = unsafe.Pointer(uintptr(ptr) + 5)
|
||||
}
|
||||
|
||||
// Manually inlining base.DecodeInternalKey provides a 5-10% speedup on
|
||||
// BlockIter benchmarks.
|
||||
// Manually inlining part of base.DecodeInternalKey provides a 5-10%
|
||||
// speedup on BlockIter benchmarks.
|
||||
s := getBytes(ptr, int(v1))
|
||||
var k InternalKey
|
||||
var k []byte
|
||||
if n := len(s) - 8; n >= 0 {
|
||||
k.Trailer = binary.LittleEndian.Uint64(s[n:])
|
||||
k.UserKey = s[:n:n]
|
||||
// NB: We can't have duplicate keys if the globalSeqNum != 0, so we
|
||||
// leave the seqnum on this key as 0 as it won't affect our search
|
||||
// since ikey has the maximum seqnum.
|
||||
} else {
|
||||
k.Trailer = uint64(InternalKeyKindInvalid)
|
||||
k = s[:n:n]
|
||||
}
|
||||
// Else k is invalid, and left as nil
|
||||
|
||||
if base.InternalCompare(i.cmp, ikey, k) > 0 {
|
||||
if i.cmp(key, k) > 0 {
|
||||
// The search key is greater than the user key at this restart point.
|
||||
// Search beyond this restart point, since we are trying to find the
|
||||
// first restart point with a user key >= the search key.
|
||||
index = h + 1 // preserves f(i-1) == false
|
||||
} else {
|
||||
// k >= search key, so prune everything after index (since index
|
||||
// satisfies the property we are looking for).
|
||||
upper = h // preserves f(j) == true
|
||||
}
|
||||
}
|
||||
@@ -852,8 +899,15 @@ func (i *blockIter) SeekLT(key []byte, flags base.SeekLTFlags) (*InternalKey, ba
|
||||
// => answer is index.
|
||||
}
|
||||
|
||||
// Since keys are strictly increasing, if index > 0 then the restart point at
|
||||
// index-1 will be the largest whose key is < the key sought.
|
||||
// index is the first restart point with key >= search key. Define the keys
|
||||
// between a restart point and the next restart point as belonging to that
|
||||
// restart point. Note that index could be equal to i.numRestarts, i.e., we
|
||||
// are past the last restart.
|
||||
//
|
||||
// Since keys are strictly increasing, if index > 0 then the restart point
|
||||
// at index-1 will be the first one that has some keys belonging to it that
|
||||
// are less than the search key. If index == 0, then all keys in this block
|
||||
// are larger than the search key, so there is no match.
|
||||
targetOffset := i.restarts
|
||||
if index > 0 {
|
||||
i.offset = decodeRestart(i.data[i.restarts+4*(index-1):])
|
||||
@@ -876,24 +930,41 @@ func (i *blockIter) SeekLT(key []byte, flags base.SeekLTFlags) (*InternalKey, ba
|
||||
for {
|
||||
i.offset = i.nextOffset
|
||||
i.readEntry()
|
||||
i.decodeInternalKey(i.key)
|
||||
// When hidden keys are common, there is additional optimization possible
|
||||
// by not caching entries that are hidden (note that some calls to
|
||||
// cacheEntry don't decode the internal key before caching, but checking
|
||||
// whether a key is hidden does not require full decoding). However, we do
|
||||
// need to use the blockEntry.offset in the cache for the first entry at
|
||||
// the reset point to do the binary search when the cache is empty -- so
|
||||
// we would need to cache that first entry (though not the key) even if
|
||||
// was hidden. Our current assumption is that if there are large numbers
|
||||
// of hidden keys we will be able to skip whole blocks (using block
|
||||
// property filters) so we don't bother optimizing.
|
||||
hiddenPoint := i.decodeInternalKey(i.key)
|
||||
|
||||
if i.cmp(i.ikey.UserKey, ikey.UserKey) >= 0 {
|
||||
// NB: we don't use the hiddenPoint return value of decodeInternalKey
|
||||
// since we want to stop as soon as we reach a key >= ikey.UserKey, so
|
||||
// that we can reverse.
|
||||
if i.cmp(i.ikey.UserKey, key) >= 0 {
|
||||
// The current key is greater than or equal to our search key. Back up to
|
||||
// the previous key which was less than our search key. Note that this for
|
||||
// loop will execute at least once with this if-block not being true, so
|
||||
// the key we are backing up to is the last one this loop cached.
|
||||
i.Prev()
|
||||
// i.Prev() has already initialized i.lazyValue.
|
||||
return &i.ikey, i.lazyValue
|
||||
return i.Prev()
|
||||
}
|
||||
|
||||
if i.nextOffset >= targetOffset {
|
||||
// We've reached the end of the current restart block. Return the current
|
||||
// key. When the restart interval is 1, the first iteration of the for
|
||||
// loop will bring us here. In that case ikey is backed by the block so
|
||||
// we get the desired key stability guarantee for the lifetime of the
|
||||
// blockIter.
|
||||
// We've reached the end of the current restart block. Return the
|
||||
// current key if not hidden, else call Prev().
|
||||
//
|
||||
// When the restart interval is 1, the first iteration of the for loop
|
||||
// will bring us here. In that case ikey is backed by the block so we
|
||||
// get the desired key stability guarantee for the lifetime of the
|
||||
// blockIter. That is, we never cache anything and therefore never
|
||||
// return a key backed by cachedBuf.
|
||||
if hiddenPoint {
|
||||
return i.Prev()
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
@@ -923,7 +994,10 @@ func (i *blockIter) First() (*InternalKey, base.LazyValue) {
|
||||
}
|
||||
i.clearCache()
|
||||
i.readEntry()
|
||||
i.decodeInternalKey(i.key)
|
||||
hiddenPoint := i.decodeInternalKey(i.key)
|
||||
if hiddenPoint {
|
||||
return i.Next()
|
||||
}
|
||||
if !i.lazyValueHandling.hasValuePrefix ||
|
||||
base.TrailerKind(i.ikey.Trailer) != InternalKeyKindSet {
|
||||
i.lazyValue = base.MakeInPlaceValue(i.val)
|
||||
@@ -958,7 +1032,10 @@ func (i *blockIter) Last() (*InternalKey, base.LazyValue) {
|
||||
i.readEntry()
|
||||
}
|
||||
|
||||
i.decodeInternalKey(i.key)
|
||||
hiddenPoint := i.decodeInternalKey(i.key)
|
||||
if hiddenPoint {
|
||||
return i.Prev()
|
||||
}
|
||||
if !i.lazyValueHandling.hasValuePrefix ||
|
||||
base.TrailerKind(i.ikey.Trailer) != InternalKeyKindSet {
|
||||
i.lazyValue = base.MakeInPlaceValue(i.val)
|
||||
@@ -988,6 +1065,7 @@ func (i *blockIter) Next() (*InternalKey, base.LazyValue) {
|
||||
i.clearCache()
|
||||
}
|
||||
|
||||
start:
|
||||
i.offset = i.nextOffset
|
||||
if !i.valid() {
|
||||
return nil, base.LazyValue{}
|
||||
@@ -995,11 +1073,17 @@ func (i *blockIter) Next() (*InternalKey, base.LazyValue) {
|
||||
i.readEntry()
|
||||
// Manually inlined version of i.decodeInternalKey(i.key).
|
||||
if n := len(i.key) - 8; n >= 0 {
|
||||
i.ikey.Trailer = binary.LittleEndian.Uint64(i.key[n:])
|
||||
trailer := binary.LittleEndian.Uint64(i.key[n:])
|
||||
hiddenPoint := i.hideObsoletePoints &&
|
||||
(trailer&trailerObsoleteBit != 0)
|
||||
i.ikey.Trailer = trailer & trailerObsoleteMask
|
||||
i.ikey.UserKey = i.key[:n:n]
|
||||
if i.globalSeqNum != 0 {
|
||||
i.ikey.SetSeqNum(i.globalSeqNum)
|
||||
}
|
||||
if hiddenPoint {
|
||||
goto start
|
||||
}
|
||||
} else {
|
||||
i.ikey.Trailer = uint64(InternalKeyKindInvalid)
|
||||
i.ikey.UserKey = nil
|
||||
@@ -1015,8 +1099,8 @@ func (i *blockIter) Next() (*InternalKey, base.LazyValue) {
|
||||
return &i.ikey, i.lazyValue
|
||||
}
|
||||
|
||||
// nextPrefix is used for implementing NPrefix.
|
||||
func (i *blockIter) nextPrefix(succKey []byte) (*InternalKey, base.LazyValue) {
|
||||
// NextPrefix implements (base.InternalIterator).NextPrefix.
|
||||
func (i *blockIter) NextPrefix(succKey []byte) (*InternalKey, base.LazyValue) {
|
||||
if i.lazyValueHandling.hasValuePrefix {
|
||||
return i.nextPrefixV3(succKey)
|
||||
}
|
||||
@@ -1141,6 +1225,7 @@ func (i *blockIter) nextPrefixV3(succKey []byte) (*InternalKey, base.LazyValue)
|
||||
// The trailer is written in little endian, so the key kind is the first
|
||||
// byte in the trailer that is encoded in the slice [unshared-8:unshared].
|
||||
keyKind := InternalKeyKind((*[manual.MaxArrayLen]byte)(ptr)[unshared-8])
|
||||
keyKind = keyKind & base.InternalKeyKindSSTableInternalObsoleteMask
|
||||
prefixChanged := false
|
||||
if keyKind == InternalKeyKindSet {
|
||||
if invariants.Enabled && value == 0 {
|
||||
@@ -1256,8 +1341,12 @@ func (i *blockIter) nextPrefixV3(succKey []byte) (*InternalKey, base.LazyValue)
|
||||
i.key = i.fullKey
|
||||
}
|
||||
// Manually inlined version of i.decodeInternalKey(i.key).
|
||||
hiddenPoint := false
|
||||
if n := len(i.key) - 8; n >= 0 {
|
||||
i.ikey.Trailer = binary.LittleEndian.Uint64(i.key[n:])
|
||||
trailer := binary.LittleEndian.Uint64(i.key[n:])
|
||||
hiddenPoint = i.hideObsoletePoints &&
|
||||
(trailer&trailerObsoleteBit != 0)
|
||||
i.ikey.Trailer = trailer & trailerObsoleteMask
|
||||
i.ikey.UserKey = i.key[:n:n]
|
||||
if i.globalSeqNum != 0 {
|
||||
i.ikey.SetSeqNum(i.globalSeqNum)
|
||||
@@ -1273,6 +1362,9 @@ func (i *blockIter) nextPrefixV3(succKey []byte) (*InternalKey, base.LazyValue)
|
||||
}
|
||||
if prefixChanged || i.cmp(i.ikey.UserKey, succKey) >= 0 {
|
||||
// Prefix has changed.
|
||||
if hiddenPoint {
|
||||
return i.Next()
|
||||
}
|
||||
if invariants.Enabled && !i.lazyValueHandling.hasValuePrefix {
|
||||
panic(errors.AssertionFailedf("nextPrefixV3 being run for non-v3 sstable"))
|
||||
}
|
||||
@@ -1294,23 +1386,11 @@ func (i *blockIter) nextPrefixV3(succKey []byte) (*InternalKey, base.LazyValue)
|
||||
return i.SeekGE(succKey, base.SeekGEFlagsNone)
|
||||
}
|
||||
|
||||
// NextPrefix implements (base.InternalIterator).NextPrefix
|
||||
func (i *blockIter) NextPrefix(succKey []byte) (*InternalKey, base.LazyValue) {
|
||||
const nextsBeforeSeek = 3
|
||||
k, v := i.Next()
|
||||
for j := 1; k != nil && i.cmp(k.UserKey, succKey) < 0; j++ {
|
||||
if j >= nextsBeforeSeek {
|
||||
return i.SeekGE(succKey, base.SeekGEFlagsNone)
|
||||
}
|
||||
k, v = i.Next()
|
||||
}
|
||||
return k, v
|
||||
}
|
||||
|
||||
// Prev implements internalIterator.Prev, as documented in the pebble
|
||||
// package.
|
||||
func (i *blockIter) Prev() (*InternalKey, base.LazyValue) {
|
||||
if n := len(i.cached) - 1; n >= 0 {
|
||||
start:
|
||||
for n := len(i.cached) - 1; n >= 0; n-- {
|
||||
i.nextOffset = i.offset
|
||||
e := &i.cached[n]
|
||||
i.offset = e.offset
|
||||
@@ -1318,7 +1398,13 @@ func (i *blockIter) Prev() (*InternalKey, base.LazyValue) {
|
||||
// Manually inlined version of i.decodeInternalKey(i.key).
|
||||
i.key = i.cachedBuf[e.keyStart:e.keyEnd]
|
||||
if n := len(i.key) - 8; n >= 0 {
|
||||
i.ikey.Trailer = binary.LittleEndian.Uint64(i.key[n:])
|
||||
trailer := binary.LittleEndian.Uint64(i.key[n:])
|
||||
hiddenPoint := i.hideObsoletePoints &&
|
||||
(trailer&trailerObsoleteBit != 0)
|
||||
if hiddenPoint {
|
||||
continue
|
||||
}
|
||||
i.ikey.Trailer = trailer & trailerObsoleteMask
|
||||
i.ikey.UserKey = i.key[:n:n]
|
||||
if i.globalSeqNum != 0 {
|
||||
i.ikey.SetSeqNum(i.globalSeqNum)
|
||||
@@ -1360,6 +1446,8 @@ func (i *blockIter) Prev() (*InternalKey, base.LazyValue) {
|
||||
// index ≤ h < upper
|
||||
offset := decodeRestart(i.data[i.restarts+4*h:])
|
||||
if offset < targetOffset {
|
||||
// Looking for the first restart that has offset >= targetOffset, so
|
||||
// ignore h and earlier.
|
||||
index = h + 1 // preserves f(i-1) == false
|
||||
} else {
|
||||
upper = h // preserves f(j) == true
|
||||
@@ -1369,20 +1457,35 @@ func (i *blockIter) Prev() (*InternalKey, base.LazyValue) {
|
||||
// => answer is index.
|
||||
}
|
||||
|
||||
// index is first restart with offset >= targetOffset. Note that
|
||||
// targetOffset may not be at a restart point since one can call Prev()
|
||||
// after Next() (so the cache was not populated) and targetOffset refers to
|
||||
// the current entry. index-1 must have an offset < targetOffset (it can't
|
||||
// be equal to targetOffset since the binary search would have selected that
|
||||
// as the index).
|
||||
i.offset = 0
|
||||
if index > 0 {
|
||||
i.offset = decodeRestart(i.data[i.restarts+4*(index-1):])
|
||||
}
|
||||
// TODO(sumeer): why is the else case not an error given targetOffset is a
|
||||
// valid offset.
|
||||
|
||||
i.readEntry()
|
||||
|
||||
// We stop when i.nextOffset == targetOffset since the targetOffset is the
|
||||
// entry we are stepping back from, and we don't need to cache the entry
|
||||
// before it, since it is the candidate to return.
|
||||
for i.nextOffset < targetOffset {
|
||||
i.cacheEntry()
|
||||
i.offset = i.nextOffset
|
||||
i.readEntry()
|
||||
}
|
||||
|
||||
i.decodeInternalKey(i.key)
|
||||
hiddenPoint := i.decodeInternalKey(i.key)
|
||||
if hiddenPoint {
|
||||
// Use the cache.
|
||||
goto start
|
||||
}
|
||||
if !i.lazyValueHandling.hasValuePrefix ||
|
||||
base.TrailerKind(i.ikey.Trailer) != InternalKeyKindSet {
|
||||
i.lazyValue = base.MakeInPlaceValue(i.val)
|
||||
@@ -1455,6 +1558,10 @@ type fragmentBlockIter struct {
|
||||
err error
|
||||
dir int8
|
||||
closeHook func(i keyspan.FragmentIterator) error
|
||||
|
||||
// elideSameSeqnum, if true, returns only the first-occurring (in forward
|
||||
// order) Key for each sequence number.
|
||||
elideSameSeqnum bool
|
||||
}
|
||||
|
||||
func (i *fragmentBlockIter) resetForReuse() fragmentBlockIter {
|
||||
@@ -1483,6 +1590,24 @@ func (i *fragmentBlockIter) decodeSpanKeys(k *InternalKey, internalValue []byte)
|
||||
}
|
||||
}
|
||||
|
||||
func (i *fragmentBlockIter) elideKeysOfSameSeqNum() {
|
||||
if invariants.Enabled {
|
||||
if !i.elideSameSeqnum || len(i.span.Keys) == 0 {
|
||||
panic("elideKeysOfSameSeqNum called when it should not be")
|
||||
}
|
||||
}
|
||||
lastSeqNum := i.span.Keys[0].SeqNum()
|
||||
k := 1
|
||||
for j := 1; j < len(i.span.Keys); j++ {
|
||||
if lastSeqNum != i.span.Keys[j].SeqNum() {
|
||||
lastSeqNum = i.span.Keys[j].SeqNum()
|
||||
i.span.Keys[k] = i.span.Keys[j]
|
||||
k++
|
||||
}
|
||||
}
|
||||
i.span.Keys = i.span.Keys[:k]
|
||||
}
|
||||
|
||||
// gatherForward gathers internal keys with identical bounds. Keys defined over
|
||||
// spans of the keyspace are fragmented such that any overlapping key spans have
|
||||
// identical bounds. When these spans are persisted to a range deletion or range
|
||||
@@ -1532,6 +1657,9 @@ func (i *fragmentBlockIter) gatherForward(k *InternalKey, lazyValue base.LazyVal
|
||||
k, lazyValue = i.blockIter.Next()
|
||||
internalValue = lazyValue.InPlaceValue()
|
||||
}
|
||||
if i.elideSameSeqnum && len(i.span.Keys) > 0 {
|
||||
i.elideKeysOfSameSeqNum()
|
||||
}
|
||||
// i.blockIter is positioned over the first internal key for the next span.
|
||||
return &i.span
|
||||
}
|
||||
@@ -1591,6 +1719,9 @@ func (i *fragmentBlockIter) gatherBackward(k *InternalKey, lazyValue base.LazyVa
|
||||
// Backwards iteration encounters internal keys in the wrong order.
|
||||
keyspan.SortKeysByTrailer(&i.span.Keys)
|
||||
|
||||
if i.elideSameSeqnum && len(i.span.Keys) > 0 {
|
||||
i.elideKeysOfSameSeqNum()
|
||||
}
|
||||
return &i.span
|
||||
}
|
||||
|
||||
|
||||
2
vendor/github.com/cockroachdb/pebble/sstable/block_property.go
generated
vendored
2
vendor/github.com/cockroachdb/pebble/sstable/block_property.go
generated
vendored
@@ -117,7 +117,7 @@ type BlockPropertyCollector interface {
|
||||
}
|
||||
|
||||
// SuffixReplaceableBlockCollector is an extension to the BlockPropertyCollector
|
||||
// interface that allows a block property collector to indicate the it supports
|
||||
// interface that allows a block property collector to indicate that it supports
|
||||
// being *updated* during suffix replacement, i.e. when an existing SST in which
|
||||
// all keys have the same key suffix is updated to have a new suffix.
|
||||
//
|
||||
|
||||
152
vendor/github.com/cockroachdb/pebble/sstable/format.go
generated
vendored
152
vendor/github.com/cockroachdb/pebble/sstable/format.go
generated
vendored
@@ -24,11 +24,161 @@ const (
|
||||
TableFormatPebblev1 // Block properties.
|
||||
TableFormatPebblev2 // Range keys.
|
||||
TableFormatPebblev3 // Value blocks.
|
||||
TableFormatPebblev4 // Reserved.
|
||||
TableFormatPebblev4 // DELSIZED tombstones.
|
||||
|
||||
TableFormatMax = TableFormatPebblev4
|
||||
)
|
||||
|
||||
// TableFormatPebblev4, in addition to DELSIZED, introduces the use of
|
||||
// InternalKeyKindSSTableInternalObsoleteBit.
|
||||
//
|
||||
// 1. Motivation
|
||||
//
|
||||
// We have various related problems caused by Pebble snapshots:
|
||||
//
|
||||
// - P1: RANGEDELs that delete points in the same sstable, but the points
|
||||
// happen to not get deleted during compactions because of an open snapshot.
|
||||
// This causes very expensive iteration, that has been observed in
|
||||
// production deployments
|
||||
//
|
||||
// - P2: When iterating over a foreign sstable (in disaggregated storage), we
|
||||
// need to do (a) point collapsing to expose at most one point per user key,
|
||||
// (b) apply RANGEDELs in the sstable to hide deleted points in the same
|
||||
// sstable. This per-sstable point collapsing iteration needs to be very
|
||||
// efficient (ideally as efficient from a CPU perspective as iteration over
|
||||
// regular sstables) since foreign sstables can be very long-lived -- one of
|
||||
// the goals of disaggregated storage is to scale compute and disk bandwidth
|
||||
// resources as a function of the hot (from a write perspective) data and
|
||||
// not the whole data, so we don't want to have to rewrite foreign sstables
|
||||
// solely to improve read performance.
|
||||
//
|
||||
// The ideal solution for P2 would allow user-facing reads to utilize the
|
||||
// existing SST iterators (with slight modifications) and with no loss of
|
||||
// efficiency. And for P1 and P2 we would like to skip whole blocks of
|
||||
// overwritten/deleted points. Even when we can't skip whole blocks, avoiding
|
||||
// key comparisons at iteration time to discover what points are deleted is
|
||||
// very desirable, since keys can be long.
|
||||
//
|
||||
// We observe that:
|
||||
//
|
||||
// - Reads:
|
||||
// - All user-facing reads in CockroachDB use iterators over the DB, hence
|
||||
// have a higher read seqnum than all sstables (there are some rare cases
|
||||
// that can violate this, but those are not important from a performance
|
||||
// optimization perspective).
|
||||
//
|
||||
// - Certain internal-facing reads in CockroachDB use snapshots, but the
|
||||
// snapshots are shortlived enough that most L5 and L6 sstables will have
|
||||
// all seqnums lower than the snapshot seqnum.
|
||||
//
|
||||
// - Writes:
|
||||
// - We already do key comparisons between points when writing the sstable
|
||||
// to ensure that the sstable invariant (monotonically increasing internal
|
||||
// keys) is not violated. So we know which points share the same userkey,
|
||||
// and thereby which points are obsolete because there is a more recent
|
||||
// point in the same sstable.
|
||||
//
|
||||
// - The compactionIter knows which point id deleted by a RANGEDEL even if
|
||||
// the point does need to be written because of a snapshot.
|
||||
//
|
||||
// So this known information can be encoded in the sstable at write time and
|
||||
// utilized for optimized reading.
|
||||
//
|
||||
// 2. Solution
|
||||
//
|
||||
// We primarily scope the solution to the following point kinds: SET,
|
||||
// SETWITHDEL, DEL, DELSIZED, SINGLEDEL. These are the ones marked locally
|
||||
// obsolete, i.e., obsolete within the sstable, and we can guarantee that at
|
||||
// most one point will be exposed per user key. MERGE keys create more
|
||||
// complexity: MERGE followed by MERGE causes multiple keys to not be
|
||||
// obsolete. Same applies for MERGE followed by SET/SETWITHDEL. Note that:
|
||||
//
|
||||
// - For regular sst iteration, the obsolete marking is a performance
|
||||
// optimization, and multiple keys for the same userkey can be handled by
|
||||
// higher layers in the iterator tree (specifically pebble.Iterator).
|
||||
//
|
||||
// - For foreign sst iteration, we disallow MERGEs to be written to such
|
||||
// shared ssts (details below).
|
||||
//
|
||||
// The key kinds are marked with an obsolete bit
|
||||
// (InternalKeyKindSSTableInternalObsoleteBit) when the key-value pair is
|
||||
// obsolete. This marking is done within blockWriter, based on information
|
||||
// passed to it by Writer. In turn, Writer uses a combination of key
|
||||
// comparisons, and information provided by compactionIter to decide whether a
|
||||
// key-value pair is obsolete. Additionally, a Pebble-internal
|
||||
// BlockPropertyCollector (obsoleteKeyBlockPropertyCollector) is used to mark
|
||||
// blocks where all key-value pairs are obsolete. Since the common case is
|
||||
// non-obsolete blocks, this block property collector uses the empty byte
|
||||
// slice to represent a non-obsolete block, which consumes no space in
|
||||
// BlockHandleWithProperties.Props.
|
||||
//
|
||||
// At read time, the obsolete bit is only visible to the blockIter, which can
|
||||
// be optionally configured to hide obsolete points. This hiding is only
|
||||
// configured for data block iterators for sstables being read by user-facing
|
||||
// iterators at a seqnum greater than the max seqnum in the sstable.
|
||||
// Additionally, when this hiding is configured, a Pebble-internal block
|
||||
// property filter (obsoleteKeyBlockPropertyFilter), is used to skip whole
|
||||
// blocks that are obsolete.
|
||||
//
|
||||
// 2.1 Correctness
|
||||
//
|
||||
// Due to the level invariant, the sequence of seqnums for a user key in a
|
||||
// sstable represents a contiguous subsequence of the seqnums for the userkey
|
||||
// across the whole LSM, and is more recent than the seqnums in a sstable in a
|
||||
// lower level. So exposing exactly one point from a sstable for a userkey
|
||||
// will also mask the points for the userkey in lower levels. If we expose no
|
||||
// point, because of RANGEDELs, that RANGEDEL will also mask the points in
|
||||
// lower levels.
|
||||
//
|
||||
// Note that we do not need to do anything special at write time for
|
||||
// SETWITHDEL and SINGLEDEL. This is because these key kinds are treated
|
||||
// specially only by compactions, which do not hide obsolete points. For
|
||||
// regular reads, SETWITHDEL behaves the same as SET and SINGLEDEL behaves the
|
||||
// same as DEL.
|
||||
//
|
||||
// 2.2 Strictness and MERGE
|
||||
//
|
||||
// Setting the obsolete bit on point keys is advanced usage, so we support two
|
||||
// modes, both of which must be truthful when setting the obsolete bit, but
|
||||
// vary in when they don't set the obsolete bit.
|
||||
//
|
||||
// - Non-strict: In this mode, the bit does not need to be set for keys that
|
||||
// are obsolete. Additionally, any sstable containing MERGE keys can only
|
||||
// use this mode. An iterator over such an sstable, when configured to
|
||||
// hideObsoletePoints, can expose multiple internal keys per user key, and
|
||||
// can expose keys that are deleted by rangedels in the same sstable. This
|
||||
// is the mode that non-advanced users should use. Pebble without
|
||||
// disaggregated storage will also use this mode and will best-effort set
|
||||
// the obsolete bit, to optimize iteration when snapshots have retained many
|
||||
// obsolete keys.
|
||||
//
|
||||
// - Strict: In this mode, every obsolete key must have the obsolete bit set,
|
||||
// and no MERGE keys are permitted. An iterator over such an sstable, when
|
||||
// configured to hideObsoletePoints satisfies two properties:
|
||||
// - S1: will expose at most one internal key per user key, which is the
|
||||
// most recent one.
|
||||
// - S2: will never expose keys that are deleted by rangedels in the same
|
||||
// sstable.
|
||||
//
|
||||
// This is the mode for two use cases in disaggregated storage (which will
|
||||
// exclude parts of the key space that has MERGEs), for levels that contain
|
||||
// sstables that can become foreign sstables:
|
||||
// - Pebble compaction output to these levels that can become foreign
|
||||
// sstables.
|
||||
//
|
||||
// - CockroachDB ingest operations that can ingest into the levels that can
|
||||
// become foreign sstables. Note, these are not sstables corresponding to
|
||||
// copied data for CockroachDB range snapshots. This case occurs for
|
||||
// operations like index backfills: these trivially satisfy the strictness
|
||||
// criteria since they only write one key per userkey.
|
||||
//
|
||||
// TODO(sumeer): this latter case is not currently supported, since only
|
||||
// Writer.AddWithForceObsolete calls are permitted for writing strict
|
||||
// obsolete sstables. This is done to reduce the likelihood of bugs. One
|
||||
// simple way to lift this limitation would be to disallow adding any
|
||||
// RANGEDELs when a Pebble-external writer is trying to construct a strict
|
||||
// obsolete sstable.
|
||||
|
||||
// ParseTableFormat parses the given magic bytes and version into its
|
||||
// corresponding internal TableFormat.
|
||||
func ParseTableFormat(magic []byte, version uint32) (TableFormat, error) {
|
||||
|
||||
2
vendor/github.com/cockroachdb/pebble/sstable/internal.go
generated
vendored
2
vendor/github.com/cockroachdb/pebble/sstable/internal.go
generated
vendored
@@ -23,6 +23,8 @@ const (
|
||||
InternalKeyKindLogData = base.InternalKeyKindLogData
|
||||
InternalKeyKindSingleDelete = base.InternalKeyKindSingleDelete
|
||||
InternalKeyKindRangeDelete = base.InternalKeyKindRangeDelete
|
||||
InternalKeyKindSetWithDelete = base.InternalKeyKindSetWithDelete
|
||||
InternalKeyKindDeleteSized = base.InternalKeyKindDeleteSized
|
||||
InternalKeyKindMax = base.InternalKeyKindMax
|
||||
InternalKeyKindInvalid = base.InternalKeyKindInvalid
|
||||
InternalKeySeqNumBatch = base.InternalKeySeqNumBatch
|
||||
|
||||
34
vendor/github.com/cockroachdb/pebble/sstable/options.go
generated
vendored
34
vendor/github.com/cockroachdb/pebble/sstable/options.go
generated
vendored
@@ -21,6 +21,17 @@ const (
|
||||
NCompression
|
||||
)
|
||||
|
||||
var ignoredInternalProperties = map[string]struct{}{
|
||||
"rocksdb.column.family.id": {},
|
||||
"rocksdb.fixed.key.length": {},
|
||||
"rocksdb.index.key.is.user.key": {},
|
||||
"rocksdb.index.value.is.delta.encoded": {},
|
||||
"rocksdb.oldest.key.time": {},
|
||||
"rocksdb.creation.time": {},
|
||||
"rocksdb.file.creation.time": {},
|
||||
"rocksdb.format.version": {},
|
||||
}
|
||||
|
||||
func (c Compression) String() string {
|
||||
switch c {
|
||||
case DefaultCompression:
|
||||
@@ -100,6 +111,9 @@ type ReaderOptions struct {
|
||||
// The default cache size is a zero-size cache.
|
||||
Cache *cache.Cache
|
||||
|
||||
// User properties specified in this map will not be added to sst.Properties.UserProperties.
|
||||
DeniedUserProperties map[string]struct{}
|
||||
|
||||
// Comparer defines a total ordering over the space of []byte keys: a 'less
|
||||
// than' relationship. The same comparison algorithm must be used for reads
|
||||
// and writes over the lifetime of the DB.
|
||||
@@ -107,6 +121,9 @@ type ReaderOptions struct {
|
||||
// The default value uses the same ordering as bytes.Compare.
|
||||
Comparer *Comparer
|
||||
|
||||
// Merge defines the Merge function in use for this keyspace.
|
||||
Merge base.Merge
|
||||
|
||||
// Filters is a map from filter policy name to filter policy. It is used for
|
||||
// debugging tools which may be used on multiple databases configured with
|
||||
// different filter policies. It is not necessary to populate this filters
|
||||
@@ -126,12 +143,18 @@ func (o ReaderOptions) ensureDefaults() ReaderOptions {
|
||||
if o.Comparer == nil {
|
||||
o.Comparer = base.DefaultComparer
|
||||
}
|
||||
if o.Merge == nil {
|
||||
o.Merge = base.DefaultMerger.Merge
|
||||
}
|
||||
if o.MergerName == "" {
|
||||
o.MergerName = base.DefaultMerger.Name
|
||||
}
|
||||
if o.LoggerAndTracer == nil {
|
||||
o.LoggerAndTracer = base.NoopLoggerAndTracer{}
|
||||
}
|
||||
if o.DeniedUserProperties == nil {
|
||||
o.DeniedUserProperties = ignoredInternalProperties
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
@@ -210,6 +233,17 @@ type WriterOptions struct {
|
||||
// by a wider range of tools and libraries.
|
||||
TableFormat TableFormat
|
||||
|
||||
// IsStrictObsolete is only relevant for >= TableFormatPebblev4. See comment
|
||||
// in format.go. Must be false if format < TableFormatPebblev4.
|
||||
//
|
||||
// TODO(bilal): set this when writing shared ssts.
|
||||
IsStrictObsolete bool
|
||||
|
||||
// WritingToLowestLevel is only relevant for >= TableFormatPebblev4. It is
|
||||
// used to set the obsolete bit on DEL/DELSIZED/SINGLEDEL if they are the
|
||||
// youngest for a userkey.
|
||||
WritingToLowestLevel bool
|
||||
|
||||
// TablePropertyCollectors is a list of TablePropertyCollector creation
|
||||
// functions. A new TablePropertyCollector is created for each sstable built
|
||||
// and lives for the lifetime of the table.
|
||||
|
||||
98
vendor/github.com/cockroachdb/pebble/sstable/properties.go
generated
vendored
98
vendor/github.com/cockroachdb/pebble/sstable/properties.go
generated
vendored
@@ -23,14 +23,6 @@ var propTagMap = make(map[string]reflect.StructField)
|
||||
var propBoolTrue = []byte{'1'}
|
||||
var propBoolFalse = []byte{'0'}
|
||||
|
||||
var columnFamilyIDField = func() reflect.StructField {
|
||||
f, ok := reflect.TypeOf(Properties{}).FieldByName("ColumnFamilyID")
|
||||
if !ok {
|
||||
panic("Properties.ColumnFamilyID field not found")
|
||||
}
|
||||
return f
|
||||
}()
|
||||
|
||||
var propOffsetTagMap = make(map[uintptr]string)
|
||||
|
||||
func init() {
|
||||
@@ -56,51 +48,35 @@ func init() {
|
||||
// automatically populated during sstable creation and load from the properties
|
||||
// meta block when an sstable is opened.
|
||||
type Properties struct {
|
||||
// ID of column family for this SST file, corresponding to the CF identified
|
||||
// by column_family_name.
|
||||
ColumnFamilyID uint64 `prop:"rocksdb.column.family.id"`
|
||||
// Name of the column family with which this SST file is associated. Empty if
|
||||
// the column family is unknown.
|
||||
ColumnFamilyName string `prop:"rocksdb.column.family.name"`
|
||||
// The name of the comparer used in this table.
|
||||
ComparerName string `prop:"rocksdb.comparator"`
|
||||
// The compression algorithm used to compress blocks.
|
||||
CompressionName string `prop:"rocksdb.compression"`
|
||||
// The compression options used to compress blocks.
|
||||
CompressionOptions string `prop:"rocksdb.compression_options"`
|
||||
// The time when the SST file was created. Since SST files are immutable,
|
||||
// this is equivalent to last modified time.
|
||||
CreationTime uint64 `prop:"rocksdb.creation.time"`
|
||||
// The total size of all data blocks.
|
||||
DataSize uint64 `prop:"rocksdb.data.size"`
|
||||
// The external sstable version format. Version 2 is the one RocksDB has been
|
||||
// using since 5.13. RocksDB only uses the global sequence number for an
|
||||
// sstable if this property has been set.
|
||||
ExternalFormatVersion uint32 `prop:"rocksdb.external_sst_file.version"`
|
||||
// Actual SST file creation time. 0 means unknown.
|
||||
FileCreationTime uint64 `prop:"rocksdb.file.creation.time"`
|
||||
// The name of the filter policy used in this table. Empty if no filter
|
||||
// policy is used.
|
||||
FilterPolicyName string `prop:"rocksdb.filter.policy"`
|
||||
// The size of filter block.
|
||||
FilterSize uint64 `prop:"rocksdb.filter.size"`
|
||||
// If 0, key is variable length. Otherwise number of bytes for each key.
|
||||
FixedKeyLen uint64 `prop:"rocksdb.fixed.key.length"`
|
||||
// Format version, reserved for backward compatibility.
|
||||
FormatVersion uint64 `prop:"rocksdb.format.version"`
|
||||
// The global sequence number to use for all entries in the table. Present if
|
||||
// the table was created externally and ingested whole.
|
||||
GlobalSeqNum uint64 `prop:"rocksdb.external_sst_file.global_seqno"`
|
||||
// Whether the index key is user key or an internal key.
|
||||
IndexKeyIsUserKey uint64 `prop:"rocksdb.index.key.is.user.key"`
|
||||
// Total number of index partitions if kTwoLevelIndexSearch is used.
|
||||
IndexPartitions uint64 `prop:"rocksdb.index.partitions"`
|
||||
// The size of index block.
|
||||
IndexSize uint64 `prop:"rocksdb.index.size"`
|
||||
// The index type. TODO(peter): add a more detailed description.
|
||||
IndexType uint32 `prop:"rocksdb.block.based.table.index.type"`
|
||||
// Whether delta encoding is used to encode the index values.
|
||||
IndexValueIsDeltaEncoded uint64 `prop:"rocksdb.index.value.is.delta.encoded"`
|
||||
// For formats >= TableFormatPebblev4, this is set to true if the obsolete
|
||||
// bit is strict for all the point keys.
|
||||
IsStrictObsolete bool `prop:"pebble.obsolete.is_strict"`
|
||||
// The name of the merger used in this table. Empty if no merger is used.
|
||||
MergerName string `prop:"rocksdb.merge.operator"`
|
||||
// The number of blocks in this table.
|
||||
@@ -108,6 +84,9 @@ type Properties struct {
|
||||
// The number of deletion entries in this table, including both point and
|
||||
// range deletions.
|
||||
NumDeletions uint64 `prop:"rocksdb.deleted.keys"`
|
||||
// The number of point deletion entries ("tombstones") in this table that
|
||||
// carry a size hint indicating the size of the value the tombstone deletes.
|
||||
NumSizedDeletions uint64 `prop:"pebble.num.deletions.sized"`
|
||||
// The number of entries in this table.
|
||||
NumEntries uint64 `prop:"rocksdb.num.entries"`
|
||||
// The number of merge operands in the table.
|
||||
@@ -124,8 +103,6 @@ type Properties struct {
|
||||
NumValueBlocks uint64 `prop:"pebble.num.value-blocks"`
|
||||
// The number of values stored in value blocks. Only serialized if > 0.
|
||||
NumValuesInValueBlocks uint64 `prop:"pebble.num.values.in.value-blocks"`
|
||||
// Timestamp of the earliest key. 0 if unknown.
|
||||
OldestKeyTime uint64 `prop:"rocksdb.oldest.key.time"`
|
||||
// The name of the prefix extractor used in this table. Empty if no prefix
|
||||
// extractor is used.
|
||||
PrefixExtractorName string `prop:"rocksdb.prefix.extractor.name"`
|
||||
@@ -139,6 +116,10 @@ type Properties struct {
|
||||
// Total raw key size of point deletion tombstones. This value is comparable
|
||||
// to RawKeySize.
|
||||
RawPointTombstoneKeySize uint64 `prop:"pebble.raw.point-tombstone.key.size"`
|
||||
// Sum of the raw value sizes carried by point deletion tombstones
|
||||
// containing size estimates. See the DeleteSized key kind. This value is
|
||||
// comparable to Raw{Key,Value}Size.
|
||||
RawPointTombstoneValueSize uint64 `prop:"pebble.raw.point-tombstone.value.size"`
|
||||
// Total raw rangekey key size.
|
||||
RawRangeKeyKeySize uint64 `prop:"pebble.raw.range-key.key.size"`
|
||||
// Total raw rangekey value size.
|
||||
@@ -206,12 +187,7 @@ func (p *Properties) String() string {
|
||||
case reflect.Uint32:
|
||||
fmt.Fprintf(&buf, "%d\n", f.Uint())
|
||||
case reflect.Uint64:
|
||||
u := f.Uint()
|
||||
if ft.Offset == columnFamilyIDField.Offset && u == math.MaxInt32 {
|
||||
fmt.Fprintf(&buf, "-\n")
|
||||
} else {
|
||||
fmt.Fprintf(&buf, "%d\n", f.Uint())
|
||||
}
|
||||
fmt.Fprintf(&buf, "%d\n", f.Uint())
|
||||
case reflect.String:
|
||||
fmt.Fprintf(&buf, "%s\n", f.String())
|
||||
default:
|
||||
@@ -229,7 +205,9 @@ func (p *Properties) String() string {
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (p *Properties) load(b block, blockOffset uint64) error {
|
||||
func (p *Properties) load(
|
||||
b block, blockOffset uint64, deniedUserProperties map[string]struct{},
|
||||
) error {
|
||||
i, err := newRawBlockIter(bytes.Compare, b)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -237,8 +215,7 @@ func (p *Properties) load(b block, blockOffset uint64) error {
|
||||
p.Loaded = make(map[uintptr]struct{})
|
||||
v := reflect.ValueOf(p).Elem()
|
||||
for valid := i.First(); valid; valid = i.Next() {
|
||||
tag := intern.Bytes(i.Key().UserKey)
|
||||
if f, ok := propTagMap[tag]; ok {
|
||||
if f, ok := propTagMap[string(i.Key().UserKey)]; ok {
|
||||
p.Loaded[f.Offset] = struct{}{}
|
||||
field := v.FieldByIndex(f.Index)
|
||||
switch f.Type.Kind() {
|
||||
@@ -248,7 +225,7 @@ func (p *Properties) load(b block, blockOffset uint64) error {
|
||||
field.SetUint(uint64(binary.LittleEndian.Uint32(i.Value())))
|
||||
case reflect.Uint64:
|
||||
var n uint64
|
||||
if tag == propGlobalSeqnumName {
|
||||
if string(i.Key().UserKey) == propGlobalSeqnumName {
|
||||
n = binary.LittleEndian.Uint64(i.Value())
|
||||
} else {
|
||||
n, _ = binary.Uvarint(i.Value())
|
||||
@@ -264,7 +241,10 @@ func (p *Properties) load(b block, blockOffset uint64) error {
|
||||
if p.UserProperties == nil {
|
||||
p.UserProperties = make(map[string]string)
|
||||
}
|
||||
p.UserProperties[tag] = string(i.Value())
|
||||
|
||||
if _, denied := deniedUserProperties[string(i.Key().UserKey)]; !denied {
|
||||
p.UserProperties[intern.Bytes(i.Key().UserKey)] = string(i.Value())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -306,10 +286,6 @@ func (p *Properties) save(tblFormat TableFormat, w *rawBlockWriter) {
|
||||
m[k] = []byte(v)
|
||||
}
|
||||
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.ColumnFamilyID), p.ColumnFamilyID)
|
||||
if p.ColumnFamilyName != "" {
|
||||
p.saveString(m, unsafe.Offsetof(p.ColumnFamilyName), p.ColumnFamilyName)
|
||||
}
|
||||
if p.ComparerName != "" {
|
||||
p.saveString(m, unsafe.Offsetof(p.ComparerName), p.ComparerName)
|
||||
}
|
||||
@@ -319,44 +295,45 @@ func (p *Properties) save(tblFormat TableFormat, w *rawBlockWriter) {
|
||||
if p.CompressionOptions != "" {
|
||||
p.saveString(m, unsafe.Offsetof(p.CompressionOptions), p.CompressionOptions)
|
||||
}
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.CreationTime), p.CreationTime)
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.DataSize), p.DataSize)
|
||||
if p.ExternalFormatVersion != 0 {
|
||||
p.saveUint32(m, unsafe.Offsetof(p.ExternalFormatVersion), p.ExternalFormatVersion)
|
||||
p.saveUint64(m, unsafe.Offsetof(p.GlobalSeqNum), p.GlobalSeqNum)
|
||||
}
|
||||
if p.FileCreationTime > 0 {
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.FileCreationTime), p.FileCreationTime)
|
||||
}
|
||||
if p.FilterPolicyName != "" {
|
||||
p.saveString(m, unsafe.Offsetof(p.FilterPolicyName), p.FilterPolicyName)
|
||||
}
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.FilterSize), p.FilterSize)
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.FixedKeyLen), p.FixedKeyLen)
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.FormatVersion), p.FormatVersion)
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.IndexKeyIsUserKey), p.IndexKeyIsUserKey)
|
||||
if p.IndexPartitions != 0 {
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.IndexPartitions), p.IndexPartitions)
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.TopLevelIndexSize), p.TopLevelIndexSize)
|
||||
}
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.IndexSize), p.IndexSize)
|
||||
p.saveUint32(m, unsafe.Offsetof(p.IndexType), p.IndexType)
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.IndexValueIsDeltaEncoded), p.IndexValueIsDeltaEncoded)
|
||||
if p.IsStrictObsolete {
|
||||
p.saveBool(m, unsafe.Offsetof(p.IsStrictObsolete), p.IsStrictObsolete)
|
||||
}
|
||||
if p.MergerName != "" {
|
||||
p.saveString(m, unsafe.Offsetof(p.MergerName), p.MergerName)
|
||||
}
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.NumDataBlocks), p.NumDataBlocks)
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.NumEntries), p.NumEntries)
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.NumDeletions), p.NumDeletions)
|
||||
if p.NumSizedDeletions > 0 {
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.NumSizedDeletions), p.NumSizedDeletions)
|
||||
}
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.NumMergeOperands), p.NumMergeOperands)
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.NumRangeDeletions), p.NumRangeDeletions)
|
||||
// NB: We only write out the RawTombstoneKeySize for Pebble formats. This
|
||||
// isn't strictly necessary because unrecognized properties are interpreted
|
||||
// as user-defined properties, however writing them prevents byte-for-byte
|
||||
// NB: We only write out some properties for Pebble formats. This isn't
|
||||
// strictly necessary because unrecognized properties are interpreted as
|
||||
// user-defined properties, however writing them prevents byte-for-byte
|
||||
// equivalence with RocksDB files that some of our testing requires.
|
||||
if p.RawPointTombstoneKeySize > 0 && tblFormat >= TableFormatPebblev1 {
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.RawPointTombstoneKeySize), p.RawPointTombstoneKeySize)
|
||||
}
|
||||
if p.RawPointTombstoneValueSize > 0 {
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.RawPointTombstoneValueSize), p.RawPointTombstoneValueSize)
|
||||
}
|
||||
if p.NumRangeKeys() > 0 {
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.NumRangeKeyDels), p.NumRangeKeyDels)
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.NumRangeKeySets), p.NumRangeKeySets)
|
||||
@@ -370,7 +347,6 @@ func (p *Properties) save(tblFormat TableFormat, w *rawBlockWriter) {
|
||||
if p.NumValuesInValueBlocks > 0 {
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.NumValuesInValueBlocks), p.NumValuesInValueBlocks)
|
||||
}
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.OldestKeyTime), p.OldestKeyTime)
|
||||
if p.PrefixExtractorName != "" {
|
||||
p.saveString(m, unsafe.Offsetof(p.PrefixExtractorName), p.PrefixExtractorName)
|
||||
}
|
||||
@@ -390,6 +366,16 @@ func (p *Properties) save(tblFormat TableFormat, w *rawBlockWriter) {
|
||||
}
|
||||
p.saveBool(m, unsafe.Offsetof(p.WholeKeyFiltering), p.WholeKeyFiltering)
|
||||
|
||||
if tblFormat < TableFormatPebblev1 {
|
||||
m["rocksdb.column.family.id"] = binary.AppendUvarint([]byte(nil), math.MaxInt32)
|
||||
m["rocksdb.fixed.key.length"] = []byte{0x00}
|
||||
m["rocksdb.index.key.is.user.key"] = []byte{0x00}
|
||||
m["rocksdb.index.value.is.delta.encoded"] = []byte{0x00}
|
||||
m["rocksdb.oldest.key.time"] = []byte{0x00}
|
||||
m["rocksdb.creation.time"] = []byte{0x00}
|
||||
m["rocksdb.format.version"] = []byte{0x00}
|
||||
}
|
||||
|
||||
keys := make([]string, 0, len(m))
|
||||
for key := range m {
|
||||
keys = append(keys, key)
|
||||
|
||||
95
vendor/github.com/cockroachdb/pebble/sstable/reader.go
generated
vendored
95
vendor/github.com/cockroachdb/pebble/sstable/reader.go
generated
vendored
@@ -323,6 +323,8 @@ type singleLevelIterator struct {
|
||||
// is high).
|
||||
useFilter bool
|
||||
lastBloomFilterMatched bool
|
||||
|
||||
hideObsoletePoints bool
|
||||
}
|
||||
|
||||
// singleLevelIterator implements the base.InternalIterator interface.
|
||||
@@ -404,7 +406,7 @@ func (i *singleLevelIterator) init(
|
||||
v *virtualState,
|
||||
lower, upper []byte,
|
||||
filterer *BlockPropertiesFilterer,
|
||||
useFilter bool,
|
||||
useFilter, hideObsoletePoints bool,
|
||||
stats *base.InternalIteratorStats,
|
||||
rp ReaderProvider,
|
||||
) error {
|
||||
@@ -428,7 +430,8 @@ func (i *singleLevelIterator) init(
|
||||
i.reader = r
|
||||
i.cmp = r.Compare
|
||||
i.stats = stats
|
||||
err = i.index.initHandle(i.cmp, indexH, r.Properties.GlobalSeqNum)
|
||||
i.hideObsoletePoints = hideObsoletePoints
|
||||
err = i.index.initHandle(i.cmp, indexH, r.Properties.GlobalSeqNum, false)
|
||||
if err != nil {
|
||||
// blockIter.Close releases indexH and always returns a nil error
|
||||
_ = i.index.Close()
|
||||
@@ -464,9 +467,9 @@ func (i *singleLevelIterator) init(
|
||||
// setupForCompaction sets up the singleLevelIterator for use with compactionIter.
|
||||
// Currently, it skips readahead ramp-up. It should be called after init is called.
|
||||
func (i *singleLevelIterator) setupForCompaction() {
|
||||
i.dataRH.MaxReadahead()
|
||||
i.dataRH.SetupForCompaction()
|
||||
if i.vbRH != nil {
|
||||
i.vbRH.MaxReadahead()
|
||||
i.vbRH.SetupForCompaction()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -566,7 +569,7 @@ func (i *singleLevelIterator) loadBlock(dir int8) loadBlockResult {
|
||||
i.err = err
|
||||
return loadBlockFailed
|
||||
}
|
||||
i.err = i.data.initHandle(i.cmp, block, i.reader.Properties.GlobalSeqNum)
|
||||
i.err = i.data.initHandle(i.cmp, block, i.reader.Properties.GlobalSeqNum, i.hideObsoletePoints)
|
||||
if i.err != nil {
|
||||
// The block is partially loaded, and we don't want it to appear valid.
|
||||
i.data.invalidate()
|
||||
@@ -653,12 +656,13 @@ func (i *singleLevelIterator) resolveMaybeExcluded(dir int8) intersectsResult {
|
||||
}
|
||||
|
||||
func (i *singleLevelIterator) initBoundsForAlreadyLoadedBlock() {
|
||||
if i.data.firstKey.UserKey == nil {
|
||||
if i.data.getFirstUserKey() == nil {
|
||||
panic("initBoundsForAlreadyLoadedBlock must not be called on empty or corrupted block")
|
||||
}
|
||||
i.blockLower = i.lower
|
||||
if i.blockLower != nil {
|
||||
if i.data.firstKey.UserKey != nil && i.cmp(i.blockLower, i.data.firstKey.UserKey) < 0 {
|
||||
firstUserKey := i.data.getFirstUserKey()
|
||||
if firstUserKey != nil && i.cmp(i.blockLower, firstUserKey) < 0 {
|
||||
// The lower-bound is less than the first key in the block. No need
|
||||
// to check the lower-bound again for this block.
|
||||
i.blockLower = nil
|
||||
@@ -1066,7 +1070,7 @@ func (i *singleLevelIterator) SeekLT(
|
||||
|
||||
var dontSeekWithinBlock bool
|
||||
if !i.data.isDataInvalidated() && !i.index.isDataInvalidated() && i.data.valid() && i.index.valid() &&
|
||||
boundsCmp < 0 && i.cmp(i.data.firstKey.UserKey, key) < 0 {
|
||||
boundsCmp < 0 && i.cmp(i.data.getFirstUserKey(), key) < 0 {
|
||||
// Fast-path: The bounds have moved backward, and this SeekLT is
|
||||
// respecting the upper bound (guaranteed by Iterator). We know that
|
||||
// the iterator must already be positioned within or just outside the
|
||||
@@ -1314,7 +1318,7 @@ func (i *singleLevelIterator) NextPrefix(succKey []byte) (*InternalKey, base.Laz
|
||||
if i.err != nil {
|
||||
return nil, base.LazyValue{}
|
||||
}
|
||||
if key, val := i.data.nextPrefix(succKey); key != nil {
|
||||
if key, val := i.data.NextPrefix(succKey); key != nil {
|
||||
if i.blockUpper != nil {
|
||||
cmp := i.cmp(key.UserKey, i.blockUpper)
|
||||
if (!i.endKeyInclusive && cmp >= 0) || cmp > 0 {
|
||||
@@ -1763,8 +1767,7 @@ func (i *twoLevelIterator) loadIndex(dir int8) loadBlockResult {
|
||||
i.err = err
|
||||
return loadBlockFailed
|
||||
}
|
||||
if i.err = i.index.initHandle(
|
||||
i.cmp, indexBlock, i.reader.Properties.GlobalSeqNum); i.err == nil {
|
||||
if i.err = i.index.initHandle(i.cmp, indexBlock, i.reader.Properties.GlobalSeqNum, false); i.err == nil {
|
||||
return loadBlockOK
|
||||
}
|
||||
return loadBlockFailed
|
||||
@@ -1842,7 +1845,7 @@ func (i *twoLevelIterator) init(
|
||||
v *virtualState,
|
||||
lower, upper []byte,
|
||||
filterer *BlockPropertiesFilterer,
|
||||
useFilter bool,
|
||||
useFilter, hideObsoletePoints bool,
|
||||
stats *base.InternalIteratorStats,
|
||||
rp ReaderProvider,
|
||||
) error {
|
||||
@@ -1867,7 +1870,8 @@ func (i *twoLevelIterator) init(
|
||||
i.reader = r
|
||||
i.cmp = r.Compare
|
||||
i.stats = stats
|
||||
err = i.topLevelIndex.initHandle(i.cmp, topLevelIndexH, r.Properties.GlobalSeqNum)
|
||||
i.hideObsoletePoints = hideObsoletePoints
|
||||
err = i.topLevelIndex.initHandle(i.cmp, topLevelIndexH, r.Properties.GlobalSeqNum, false)
|
||||
if err != nil {
|
||||
// blockIter.Close releases topLevelIndexH and always returns a nil error
|
||||
_ = i.topLevelIndex.Close()
|
||||
@@ -2907,21 +2911,20 @@ func (v *VirtualReader) NewCompactionIter(
|
||||
return v.reader.newCompactionIter(bytesIterated, rp, &v.vState)
|
||||
}
|
||||
|
||||
// NewIterWithBlockPropertyFiltersAndContext wraps
|
||||
// NewIterWithBlockPropertyFiltersAndContextEtc wraps
|
||||
// Reader.NewIterWithBlockPropertyFiltersAndContext. We assume that the passed
|
||||
// in [lower, upper) bounds will have at least some overlap with the virtual
|
||||
// sstable bounds. No overlap is not currently supported in the iterator.
|
||||
func (v *VirtualReader) NewIterWithBlockPropertyFiltersAndContext(
|
||||
func (v *VirtualReader) NewIterWithBlockPropertyFiltersAndContextEtc(
|
||||
ctx context.Context,
|
||||
lower, upper []byte,
|
||||
filterer *BlockPropertiesFilterer,
|
||||
useFilterBlock bool,
|
||||
hideObsoletePoints, useFilterBlock bool,
|
||||
stats *base.InternalIteratorStats,
|
||||
rp ReaderProvider,
|
||||
) (Iterator, error) {
|
||||
return v.reader.newIterWithBlockPropertyFiltersAndContext(
|
||||
ctx,
|
||||
lower, upper, filterer, useFilterBlock, stats, rp, &v.vState,
|
||||
ctx, lower, upper, filterer, hideObsoletePoints, useFilterBlock, stats, rp, &v.vState,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3060,31 +3063,51 @@ func (r *Reader) NewIterWithBlockPropertyFilters(
|
||||
) (Iterator, error) {
|
||||
return r.newIterWithBlockPropertyFiltersAndContext(
|
||||
context.Background(),
|
||||
lower, upper, filterer, useFilterBlock, stats, rp, nil,
|
||||
lower, upper, filterer, false, useFilterBlock, stats, rp, nil,
|
||||
)
|
||||
}
|
||||
|
||||
// NewIterWithBlockPropertyFiltersAndContext is similar to
|
||||
// NewIterWithBlockPropertyFiltersAndContextEtc is similar to
|
||||
// NewIterWithBlockPropertyFilters and additionally accepts a context for
|
||||
// tracing.
|
||||
func (r *Reader) NewIterWithBlockPropertyFiltersAndContext(
|
||||
//
|
||||
// If hideObsoletePoints, the callee assumes that filterer already includes
|
||||
// obsoleteKeyBlockPropertyFilter. The caller can satisfy this contract by
|
||||
// first calling TryAddBlockPropertyFilterForHideObsoletePoints.
|
||||
func (r *Reader) NewIterWithBlockPropertyFiltersAndContextEtc(
|
||||
ctx context.Context,
|
||||
lower, upper []byte,
|
||||
filterer *BlockPropertiesFilterer,
|
||||
useFilterBlock bool,
|
||||
hideObsoletePoints, useFilterBlock bool,
|
||||
stats *base.InternalIteratorStats,
|
||||
rp ReaderProvider,
|
||||
) (Iterator, error) {
|
||||
return r.newIterWithBlockPropertyFiltersAndContext(
|
||||
ctx,
|
||||
lower, upper, filterer, useFilterBlock, stats, rp, nil,
|
||||
ctx, lower, upper, filterer, hideObsoletePoints, useFilterBlock, stats, rp, nil,
|
||||
)
|
||||
}
|
||||
|
||||
// TryAddBlockPropertyFilterForHideObsoletePoints is expected to be called
|
||||
// before the call to NewIterWithBlockPropertyFiltersAndContextEtc, to get the
|
||||
// value of hideObsoletePoints and potentially add a block property filter.
|
||||
func (r *Reader) TryAddBlockPropertyFilterForHideObsoletePoints(
|
||||
snapshotForHideObsoletePoints uint64,
|
||||
fileLargestSeqNum uint64,
|
||||
pointKeyFilters []BlockPropertyFilter,
|
||||
) (hideObsoletePoints bool, filters []BlockPropertyFilter) {
|
||||
hideObsoletePoints = r.tableFormat >= TableFormatPebblev4 &&
|
||||
snapshotForHideObsoletePoints > fileLargestSeqNum
|
||||
if hideObsoletePoints {
|
||||
pointKeyFilters = append(pointKeyFilters, obsoleteKeyBlockPropertyFilter{})
|
||||
}
|
||||
return hideObsoletePoints, pointKeyFilters
|
||||
}
|
||||
|
||||
func (r *Reader) newIterWithBlockPropertyFiltersAndContext(
|
||||
ctx context.Context,
|
||||
lower, upper []byte,
|
||||
filterer *BlockPropertiesFilterer,
|
||||
hideObsoletePoints bool,
|
||||
useFilterBlock bool,
|
||||
stats *base.InternalIteratorStats,
|
||||
rp ReaderProvider,
|
||||
@@ -3095,7 +3118,7 @@ func (r *Reader) newIterWithBlockPropertyFiltersAndContext(
|
||||
// until the final iterator closes.
|
||||
if r.Properties.IndexType == twoLevelIndex {
|
||||
i := twoLevelIterPool.Get().(*twoLevelIterator)
|
||||
err := i.init(ctx, r, v, lower, upper, filterer, useFilterBlock, stats, rp)
|
||||
err := i.init(ctx, r, v, lower, upper, filterer, useFilterBlock, hideObsoletePoints, stats, rp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -3103,7 +3126,7 @@ func (r *Reader) newIterWithBlockPropertyFiltersAndContext(
|
||||
}
|
||||
|
||||
i := singleLevelIterPool.Get().(*singleLevelIterator)
|
||||
err := i.init(ctx, r, v, lower, upper, filterer, useFilterBlock, stats, rp)
|
||||
err := i.init(ctx, r, v, lower, upper, filterer, useFilterBlock, hideObsoletePoints, stats, rp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -3135,7 +3158,8 @@ func (r *Reader) newCompactionIter(
|
||||
err := i.init(
|
||||
context.Background(),
|
||||
r, v, nil /* lower */, nil /* upper */, nil,
|
||||
false /* useFilter */, nil /* stats */, rp,
|
||||
false /* useFilter */, false, /* hideObsoletePoints */
|
||||
nil /* stats */, rp,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -3149,7 +3173,8 @@ func (r *Reader) newCompactionIter(
|
||||
i := singleLevelIterPool.Get().(*singleLevelIterator)
|
||||
err := i.init(
|
||||
context.Background(), r, v, nil /* lower */, nil, /* upper */
|
||||
nil, false /* useFilter */, nil /* stats */, rp,
|
||||
nil, false /* useFilter */, false, /* hideObsoletePoints */
|
||||
nil /* stats */, rp,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -3175,8 +3200,8 @@ func (r *Reader) NewRawRangeDelIter() (keyspan.FragmentIterator, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
i := &fragmentBlockIter{}
|
||||
if err := i.blockIter.initHandle(r.Compare, h, r.Properties.GlobalSeqNum); err != nil {
|
||||
i := &fragmentBlockIter{elideSameSeqnum: true}
|
||||
if err := i.blockIter.initHandle(r.Compare, h, r.Properties.GlobalSeqNum, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return i, nil
|
||||
@@ -3197,7 +3222,7 @@ func (r *Reader) NewRawRangeKeyIter() (keyspan.FragmentIterator, error) {
|
||||
return nil, err
|
||||
}
|
||||
i := rangeKeyFragmentBlockIterPool.Get().(*rangeKeyFragmentBlockIter)
|
||||
if err := i.blockIter.initHandle(r.Compare, h, r.Properties.GlobalSeqNum); err != nil {
|
||||
if err := i.blockIter.initHandle(r.Compare, h, r.Properties.GlobalSeqNum, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return i, nil
|
||||
@@ -3357,7 +3382,7 @@ func (r *Reader) transformRangeDelV1(b []byte) ([]byte, error) {
|
||||
// tombstones. We need properly fragmented and sorted range tombstones in
|
||||
// order to serve from them directly.
|
||||
iter := &blockIter{}
|
||||
if err := iter.init(r.Compare, b, r.Properties.GlobalSeqNum); err != nil {
|
||||
if err := iter.init(r.Compare, b, r.Properties.GlobalSeqNum, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var tombstones []keyspan.Span
|
||||
@@ -3444,7 +3469,7 @@ func (r *Reader) readMetaindex(metaindexBH BlockHandle) error {
|
||||
return err
|
||||
}
|
||||
r.propertiesBH = bh
|
||||
err := r.Properties.load(b.Get(), bh.Offset)
|
||||
err := r.Properties.load(b.Get(), bh.Offset, r.opts.DeniedUserProperties)
|
||||
b.Release()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -3549,7 +3574,8 @@ func (r *Reader) Layout() (*Layout, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := iter.init(r.Compare, subIndex.Get(), 0 /* globalSeqNum */); err != nil {
|
||||
if err := iter.init(r.Compare, subIndex.Get(), 0, /* globalSeqNum */
|
||||
false /* hideObsoletePoints */); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for key, value := iter.First(); key != nil; key, value = iter.Next() {
|
||||
@@ -3867,6 +3893,7 @@ func NewReader(f objstorage.Readable, o ReaderOptions, extraOpts ...ReaderOption
|
||||
if r.err != nil {
|
||||
return nil, r.Close()
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
|
||||
37
vendor/github.com/cockroachdb/pebble/sstable/suffix_rewriter.go
generated
vendored
37
vendor/github.com/cockroachdb/pebble/sstable/suffix_rewriter.go
generated
vendored
@@ -43,6 +43,9 @@ func RewriteKeySuffixes(
|
||||
// modification as they are not affected by the suffix, while block and table
|
||||
// properties are only minimally recomputed.
|
||||
//
|
||||
// TODO(sumeer): document limitations, if any, due to this limited
|
||||
// re-computation of properties (is there any loss of fidelity?).
|
||||
//
|
||||
// Any block and table property collectors configured in the WriterOptions must
|
||||
// implement SuffixReplaceableTableCollector/SuffixReplaceableBlockCollector.
|
||||
//
|
||||
@@ -50,6 +53,26 @@ func RewriteKeySuffixes(
|
||||
// same TableFormat as the input, which is returned in case the caller wants
|
||||
// to do some error checking. Suffix rewriting is meant to be efficient, and
|
||||
// allowing changes in the TableFormat detracts from that efficiency.
|
||||
//
|
||||
// Any obsolete bits that key-value pairs may be annotated with are ignored
|
||||
// and lost during the rewrite. Additionally, the output sstable has the
|
||||
// pebble.obsolete.is_strict property set to false. These limitations could be
|
||||
// removed if needed. The current use case for
|
||||
// RewriteKeySuffixesAndReturnFormat in CockroachDB is for MVCC-compliant file
|
||||
// ingestion, where these files do not contain RANGEDELs and have one
|
||||
// key-value pair per userkey -- so they trivially satisfy the strict
|
||||
// criteria, and we don't need the obsolete bit as a performance optimization.
|
||||
// For disaggregated storage, strict obsolete sstables are needed for L5 and
|
||||
// L6, but at the time of writing, we expect such MVCC-compliant file
|
||||
// ingestion to only ingest into levels L4 and higher. If this changes, we can
|
||||
// do one of two things to get rid of this limitation:
|
||||
// - Validate that there are no duplicate userkeys and no RANGEDELs/MERGEs
|
||||
// in the sstable to be rewritten. Validating no duplicate userkeys is
|
||||
// non-trivial when rewriting blocks in parallel, so we could encode the
|
||||
// pre-existing condition in the (existing) SnapshotPinnedKeys property --
|
||||
// we need to update the external sst writer to calculate and encode this
|
||||
// property.
|
||||
// - Preserve the obsolete bit (with changes to the blockIter).
|
||||
func RewriteKeySuffixesAndReturnFormat(
|
||||
sst []byte,
|
||||
rOpts ReaderOptions,
|
||||
@@ -184,7 +207,7 @@ func rewriteBlocks(
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := iter.init(r.Compare, inputBlock, r.Properties.GlobalSeqNum); err != nil {
|
||||
if err := iter.init(r.Compare, inputBlock, r.Properties.GlobalSeqNum, false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -433,8 +456,13 @@ func (c copyFilterWriter) policyName() string { return c.origPolicyName }
|
||||
// RewriteKeySuffixesViaWriter is similar to RewriteKeySuffixes but uses just a
|
||||
// single loop over the Reader that writes each key to the Writer with the new
|
||||
// suffix. The is significantly slower than the parallelized rewriter, and does
|
||||
// more work to rederive filters, props, etc, however re-doing that work makes
|
||||
// it less restrictive -- props no longer need to
|
||||
// more work to rederive filters, props, etc.
|
||||
//
|
||||
// Any obsolete bits that key-value pairs may be annotated with are ignored
|
||||
// and lost during the rewrite. Some of the obsolete bits may be recreated --
|
||||
// specifically when there are multiple keys with the same user key.
|
||||
// Additionally, the output sstable has the pebble.obsolete.is_strict property
|
||||
// set to false. See the longer comment at RewriteKeySuffixesAndReturnFormat.
|
||||
func RewriteKeySuffixesViaWriter(
|
||||
r *Reader, out objstorage.Writable, o WriterOptions, from, to []byte,
|
||||
) (*WriterMetadata, error) {
|
||||
@@ -442,6 +470,7 @@ func RewriteKeySuffixesViaWriter(
|
||||
return nil, errors.New("a valid splitter is required to rewrite suffixes")
|
||||
}
|
||||
|
||||
o.IsStrictObsolete = false
|
||||
w := NewWriter(out, o)
|
||||
defer func() {
|
||||
if w != nil {
|
||||
@@ -472,7 +501,7 @@ func RewriteKeySuffixesViaWriter(
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if w.addPoint(scratch, val); err != nil {
|
||||
if w.addPoint(scratch, val, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
k, v = i.Next()
|
||||
|
||||
21
vendor/github.com/cockroachdb/pebble/sstable/table.go
generated
vendored
21
vendor/github.com/cockroachdb/pebble/sstable/table.go
generated
vendored
@@ -160,8 +160,27 @@ The metaindex block also contains block handles as values, with keys being
|
||||
the names of the meta blocks.
|
||||
|
||||
For a description of value blocks and the meta value index block, see
|
||||
value_block.go
|
||||
value_block.go.
|
||||
|
||||
Data blocks have some additional features:
|
||||
- For TableFormatPebblev3 onwards:
|
||||
- For SETs, the value has a 1 byte value prefix, which indicates whether the
|
||||
value is inline, or in a separate value block, and indicates whether the
|
||||
prefix of the userkey (as defined by split) has changed or not. See
|
||||
value_block.go for details.
|
||||
- The most significant bit of the restart points is used to indicate whether
|
||||
userkey prefix has changed since the last restart point. See the detailed
|
||||
comment in blockWriter.
|
||||
- The maximum length of the "shared prefix" when encoding the key, is the
|
||||
length of the prefix of the userkey (as defined by split) of the previous
|
||||
key.
|
||||
|
||||
- For TableFormatPebblev4 onwards:
|
||||
- The key kinds may be altered to set the
|
||||
InternalKeyKindSSTableInternalObsoleteBit if the key-value pair is obsolete
|
||||
in the context of that sstable (for a reader that reads at a higher seqnum
|
||||
than the highest seqnum in the sstable). For details, see the comment in
|
||||
format.go.
|
||||
*/
|
||||
|
||||
const (
|
||||
|
||||
307
vendor/github.com/cockroachdb/pebble/sstable/writer.go
generated
vendored
307
vendor/github.com/cockroachdb/pebble/sstable/writer.go
generated
vendored
@@ -132,6 +132,8 @@ type Writer struct {
|
||||
separator Separator
|
||||
successor Successor
|
||||
tableFormat TableFormat
|
||||
isStrictObsolete bool
|
||||
writingToLowestLevel bool
|
||||
cache *cache.Cache
|
||||
restartInterval int
|
||||
checksumType ChecksumType
|
||||
@@ -166,6 +168,7 @@ type Writer struct {
|
||||
props Properties
|
||||
propCollectors []TablePropertyCollector
|
||||
blockPropCollectors []BlockPropertyCollector
|
||||
obsoleteCollector obsoleteKeyBlockPropertyCollector
|
||||
blockPropsEncoder blockPropertiesEncoder
|
||||
// filter accumulates the filter block. If populated, the filter ingests
|
||||
// either the output of w.split (i.e. a prefix extractor) if w.split is not
|
||||
@@ -215,6 +218,8 @@ type pointKeyInfo struct {
|
||||
// prefixLen uses w.split, if not nil. Only computed when w.valueBlockWriter
|
||||
// is not nil.
|
||||
prefixLen int
|
||||
// True iff the point was marked obsolete.
|
||||
isObsolete bool
|
||||
}
|
||||
|
||||
type coordinationState struct {
|
||||
@@ -673,7 +678,12 @@ func (w *Writer) Set(key, value []byte) error {
|
||||
if w.err != nil {
|
||||
return w.err
|
||||
}
|
||||
return w.addPoint(base.MakeInternalKey(key, 0, InternalKeyKindSet), value)
|
||||
if w.isStrictObsolete {
|
||||
return errors.Errorf("use AddWithForceObsolete")
|
||||
}
|
||||
// forceObsolete is false based on the assumption that no RANGEDELs in the
|
||||
// sstable delete the added points.
|
||||
return w.addPoint(base.MakeInternalKey(key, 0, InternalKeyKindSet), value, false)
|
||||
}
|
||||
|
||||
// Delete deletes the value for the given key. The sequence number is set to
|
||||
@@ -685,7 +695,12 @@ func (w *Writer) Delete(key []byte) error {
|
||||
if w.err != nil {
|
||||
return w.err
|
||||
}
|
||||
return w.addPoint(base.MakeInternalKey(key, 0, InternalKeyKindDelete), nil)
|
||||
if w.isStrictObsolete {
|
||||
return errors.Errorf("use AddWithForceObsolete")
|
||||
}
|
||||
// forceObsolete is false based on the assumption that no RANGEDELs in the
|
||||
// sstable delete the added points.
|
||||
return w.addPoint(base.MakeInternalKey(key, 0, InternalKeyKindDelete), nil, false)
|
||||
}
|
||||
|
||||
// DeleteRange deletes all of the keys (and values) in the range [start,end)
|
||||
@@ -711,7 +726,13 @@ func (w *Writer) Merge(key, value []byte) error {
|
||||
if w.err != nil {
|
||||
return w.err
|
||||
}
|
||||
return w.addPoint(base.MakeInternalKey(key, 0, InternalKeyKindMerge), value)
|
||||
if w.isStrictObsolete {
|
||||
return errors.Errorf("use AddWithForceObsolete")
|
||||
}
|
||||
// forceObsolete is false based on the assumption that no RANGEDELs in the
|
||||
// sstable that delete the added points. If the user configured this writer
|
||||
// to be strict-obsolete, addPoint will reject the addition of this MERGE.
|
||||
return w.addPoint(base.MakeInternalKey(key, 0, InternalKeyKindMerge), value, false)
|
||||
}
|
||||
|
||||
// Add adds a key/value pair to the table being written. For a given Writer,
|
||||
@@ -721,6 +742,24 @@ func (w *Writer) Merge(key, value []byte) error {
|
||||
// point entries. Additionally, range deletion tombstones must be fragmented
|
||||
// (i.e. by keyspan.Fragmenter).
|
||||
func (w *Writer) Add(key InternalKey, value []byte) error {
|
||||
if w.isStrictObsolete {
|
||||
return errors.Errorf("use AddWithForceObsolete")
|
||||
}
|
||||
return w.AddWithForceObsolete(key, value, false)
|
||||
}
|
||||
|
||||
// AddWithForceObsolete must be used when writing a strict-obsolete sstable.
|
||||
//
|
||||
// forceObsolete indicates whether the caller has determined that this key is
|
||||
// obsolete even though it may be the latest point key for this userkey. This
|
||||
// should be set to true for keys obsoleted by RANGEDELs, and is required for
|
||||
// strict-obsolete sstables.
|
||||
//
|
||||
// Note that there are two properties, S1 and S2 (see comment in format.go)
|
||||
// that strict-obsolete ssts must satisfy. S2, due to RANGEDELs, is solely the
|
||||
// responsibility of the caller. S1 is solely the responsibility of the
|
||||
// callee.
|
||||
func (w *Writer) AddWithForceObsolete(key InternalKey, value []byte, forceObsolete bool) error {
|
||||
if w.err != nil {
|
||||
return w.err
|
||||
}
|
||||
@@ -735,7 +774,7 @@ func (w *Writer) Add(key InternalKey, value []byte) error {
|
||||
"pebble: range keys must be added via one of the RangeKey* functions")
|
||||
return w.err
|
||||
}
|
||||
return w.addPoint(key, value)
|
||||
return w.addPoint(key, value, forceObsolete)
|
||||
}
|
||||
|
||||
func (w *Writer) makeAddPointDecisionV2(key InternalKey) error {
|
||||
@@ -745,12 +784,7 @@ func (w *Writer) makeAddPointDecisionV2(key InternalKey) error {
|
||||
return nil
|
||||
}
|
||||
if !w.disableKeyOrderChecks {
|
||||
prevPointUserKey := w.dataBlockBuf.dataBlock.curKey
|
||||
n := len(prevPointUserKey) - base.InternalTrailerLen
|
||||
if n < 0 {
|
||||
panic("corrupt key in blockWriter buffer")
|
||||
}
|
||||
prevPointUserKey = prevPointUserKey[:n:n]
|
||||
prevPointUserKey := w.dataBlockBuf.dataBlock.getCurUserKey()
|
||||
cmpUser := w.compare(prevPointUserKey, key.UserKey)
|
||||
if cmpUser > 0 || (cmpUser == 0 && prevTrailer <= key.Trailer) {
|
||||
return errors.Errorf(
|
||||
@@ -762,9 +796,17 @@ func (w *Writer) makeAddPointDecisionV2(key InternalKey) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// REQUIRES: at least one point has been written to the Writer.
|
||||
func (w *Writer) getLastPointUserKey() []byte {
|
||||
if w.dataBlockBuf.dataBlock.nEntries == 0 {
|
||||
panic(errors.AssertionFailedf("no point keys added to writer"))
|
||||
}
|
||||
return w.dataBlockBuf.dataBlock.getCurUserKey()
|
||||
}
|
||||
|
||||
func (w *Writer) makeAddPointDecisionV3(
|
||||
key InternalKey, valueLen int,
|
||||
) (setHasSamePrefix bool, writeToValueBlock bool, err error) {
|
||||
) (setHasSamePrefix bool, writeToValueBlock bool, isObsolete bool, err error) {
|
||||
prevPointKeyInfo := w.lastPointKeyInfo
|
||||
w.lastPointKeyInfo.userKeyLen = len(key.UserKey)
|
||||
w.lastPointKeyInfo.prefixLen = w.lastPointKeyInfo.userKeyLen
|
||||
@@ -772,18 +814,16 @@ func (w *Writer) makeAddPointDecisionV3(
|
||||
w.lastPointKeyInfo.prefixLen = w.split(key.UserKey)
|
||||
}
|
||||
w.lastPointKeyInfo.trailer = key.Trailer
|
||||
if w.dataBlockBuf.dataBlock.nEntries == 0 {
|
||||
return false, false, nil
|
||||
w.lastPointKeyInfo.isObsolete = false
|
||||
if !w.meta.HasPointKeys {
|
||||
return false, false, false, nil
|
||||
}
|
||||
prevPointUserKey := w.dataBlockBuf.dataBlock.curKey
|
||||
n := len(prevPointUserKey) - base.InternalTrailerLen
|
||||
if n < 0 {
|
||||
panic("corrupt key in blockWriter buffer")
|
||||
}
|
||||
prevPointUserKey = prevPointUserKey[:n:n]
|
||||
keyKind := base.TrailerKind(key.Trailer)
|
||||
prevPointUserKey := w.getLastPointUserKey()
|
||||
prevPointKey := InternalKey{UserKey: prevPointUserKey, Trailer: prevPointKeyInfo.trailer}
|
||||
considerWriteToValueBlock := base.TrailerKind(prevPointKeyInfo.trailer) == InternalKeyKindSet &&
|
||||
base.TrailerKind(key.Trailer) == InternalKeyKindSet
|
||||
prevKeyKind := base.TrailerKind(prevPointKeyInfo.trailer)
|
||||
considerWriteToValueBlock := prevKeyKind == InternalKeyKindSet &&
|
||||
keyKind == InternalKeyKindSet
|
||||
if considerWriteToValueBlock && !w.requiredInPlaceValueBound.IsEmpty() {
|
||||
keyPrefix := key.UserKey[:w.lastPointKeyInfo.prefixLen]
|
||||
cmpUpper := w.compare(
|
||||
@@ -812,14 +852,59 @@ func (w *Writer) makeAddPointDecisionV3(
|
||||
} else {
|
||||
cmpUser = w.compare(prevPointUserKey, key.UserKey)
|
||||
}
|
||||
keyKindIsSetOrMerge := keyKind == InternalKeyKindSet || keyKind == InternalKeyKindSetWithDelete ||
|
||||
keyKind == InternalKeyKindMerge
|
||||
keyKindIsPointDelete := keyKind == InternalKeyKindDelete ||
|
||||
keyKind == InternalKeyKindSingleDelete || keyKind == InternalKeyKindDeleteSized
|
||||
if !keyKindIsSetOrMerge && !keyKindIsPointDelete {
|
||||
panic(errors.AssertionFailedf("unexpected key kind %s", keyKind.String()))
|
||||
}
|
||||
// If same user key, then the current key is obsolete if any of the
|
||||
// following is true:
|
||||
// C1 The prev key was obsolete.
|
||||
// C2 The prev key was not a MERGE
|
||||
// C3 The current key is not a SET or SETWITHDELETE or MERGE, then it is
|
||||
// obsolete. This rule excludes SET, SETWITHDELETE, MERGE, since they
|
||||
// will be merged with the previous key.
|
||||
//
|
||||
// Regardless of whether it is the same user key or not
|
||||
// C4 The current key is some kind of point delete, and we are writing to
|
||||
// the lowest level, then it is also obsolete.
|
||||
//
|
||||
// C1 ensures that for a user key there is at most one transition from
|
||||
// !obsolete to obsolete. Consider a user key k, for which the first n keys
|
||||
// are not obsolete. We consider the various value of n:
|
||||
//
|
||||
// n = 0: This happens due to forceObsolete being set by the caller, or due
|
||||
// to C4. forceObsolete must only be set due a RANGEDEL, and that RANGEDEL
|
||||
// must also delete all the lower seqnums for the same user key. C4 triggers
|
||||
// due to a point delete and that deletes all the lower seqnums for the same
|
||||
// user key.
|
||||
//
|
||||
// n = 1: This is the common case. It happens when the first key is not a
|
||||
// MERGE, or the current key is some kind of point delete.
|
||||
//
|
||||
// n > 1: This is due to a sequence of MERGE keys, potentially followed by a
|
||||
// single SET or SETWITHDELETE.
|
||||
isObsolete = (cmpUser == 0 &&
|
||||
(prevKeyKind != InternalKeyKindMerge || prevPointKeyInfo.isObsolete || !keyKindIsSetOrMerge)) ||
|
||||
(w.writingToLowestLevel &&
|
||||
(keyKind == InternalKeyKindDelete || keyKind == InternalKeyKindSingleDelete ||
|
||||
keyKind == InternalKeyKindDeleteSized))
|
||||
// TODO(sumeer): storing isObsolete SET and SETWITHDEL in value blocks is
|
||||
// possible, but requires some care in documenting and checking invariants.
|
||||
// There is code that assumes nothing in value blocks because of single MVCC
|
||||
// version (those should be ok). We have to ensure setHasSamePrefix is
|
||||
// correctly initialized here etc.
|
||||
|
||||
if !w.disableKeyOrderChecks &&
|
||||
(cmpUser > 0 || (cmpUser == 0 && prevPointKeyInfo.trailer <= key.Trailer)) {
|
||||
return false, false, errors.Errorf(
|
||||
return false, false, false, errors.Errorf(
|
||||
"pebble: keys must be added in strictly increasing order: %s, %s",
|
||||
prevPointKey.Pretty(w.formatKey), key.Pretty(w.formatKey))
|
||||
}
|
||||
if !considerWriteToValueBlock {
|
||||
return false, false, nil
|
||||
return false, false, isObsolete, nil
|
||||
}
|
||||
// NB: it is possible that cmpUser == 0, i.e., these two SETs have identical
|
||||
// user keys (because of an open snapshot). This should be the rare case.
|
||||
@@ -832,19 +917,24 @@ func (w *Writer) makeAddPointDecisionV3(
|
||||
if considerWriteToValueBlock && valueLen <= tinyValueThreshold {
|
||||
considerWriteToValueBlock = false
|
||||
}
|
||||
return setHasSamePrefix, considerWriteToValueBlock, nil
|
||||
return setHasSamePrefix, considerWriteToValueBlock, isObsolete, nil
|
||||
}
|
||||
|
||||
func (w *Writer) addPoint(key InternalKey, value []byte) error {
|
||||
func (w *Writer) addPoint(key InternalKey, value []byte, forceObsolete bool) error {
|
||||
if w.isStrictObsolete && key.Kind() == InternalKeyKindMerge {
|
||||
return errors.Errorf("MERGE not supported in a strict-obsolete sstable")
|
||||
}
|
||||
var err error
|
||||
var setHasSameKeyPrefix, writeToValueBlock, addPrefixToValueStoredWithKey bool
|
||||
var isObsolete bool
|
||||
maxSharedKeyLen := len(key.UserKey)
|
||||
if w.valueBlockWriter != nil {
|
||||
// maxSharedKeyLen is limited to the prefix of the preceding key. If the
|
||||
// preceding key was in a different block, then the blockWriter will
|
||||
// ignore this maxSharedKeyLen.
|
||||
maxSharedKeyLen = w.lastPointKeyInfo.prefixLen
|
||||
setHasSameKeyPrefix, writeToValueBlock, err = w.makeAddPointDecisionV3(key, len(value))
|
||||
setHasSameKeyPrefix, writeToValueBlock, isObsolete, err =
|
||||
w.makeAddPointDecisionV3(key, len(value))
|
||||
addPrefixToValueStoredWithKey = base.TrailerKind(key.Trailer) == InternalKeyKindSet
|
||||
} else {
|
||||
err = w.makeAddPointDecisionV2(key)
|
||||
@@ -852,6 +942,8 @@ func (w *Writer) addPoint(key InternalKey, value []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
isObsolete = w.tableFormat >= TableFormatPebblev4 && (isObsolete || forceObsolete)
|
||||
w.lastPointKeyInfo.isObsolete = isObsolete
|
||||
var valueStoredWithKey []byte
|
||||
var prefix valuePrefix
|
||||
var valueStoredWithKeyLen int
|
||||
@@ -907,16 +999,19 @@ func (w *Writer) addPoint(key InternalKey, value []byte) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if w.tableFormat >= TableFormatPebblev4 {
|
||||
w.obsoleteCollector.AddPoint(isObsolete)
|
||||
}
|
||||
|
||||
w.maybeAddToFilter(key.UserKey)
|
||||
w.dataBlockBuf.dataBlock.addWithOptionalValuePrefix(
|
||||
key, valueStoredWithKey, maxSharedKeyLen, addPrefixToValueStoredWithKey, prefix,
|
||||
key, isObsolete, valueStoredWithKey, maxSharedKeyLen, addPrefixToValueStoredWithKey, prefix,
|
||||
setHasSameKeyPrefix)
|
||||
|
||||
w.meta.updateSeqNum(key.SeqNum())
|
||||
|
||||
if !w.meta.HasPointKeys {
|
||||
k := base.DecodeInternalKey(w.dataBlockBuf.dataBlock.curKey)
|
||||
k := w.dataBlockBuf.dataBlock.getCurKey()
|
||||
// NB: We need to ensure that SmallestPoint.UserKey is set, so we create
|
||||
// an InternalKey which is semantically identical to the key, but won't
|
||||
// have a nil UserKey. We do this, because key.UserKey could be nil, and
|
||||
@@ -932,7 +1027,22 @@ func (w *Writer) addPoint(key InternalKey, value []byte) error {
|
||||
switch key.Kind() {
|
||||
case InternalKeyKindDelete, InternalKeyKindSingleDelete:
|
||||
w.props.NumDeletions++
|
||||
w.props.RawPointTombstoneKeySize += uint64(key.Size())
|
||||
w.props.RawPointTombstoneKeySize += uint64(len(key.UserKey))
|
||||
case InternalKeyKindDeleteSized:
|
||||
var size uint64
|
||||
if len(value) > 0 {
|
||||
var n int
|
||||
size, n = binary.Uvarint(value)
|
||||
if n <= 0 {
|
||||
w.err = errors.Newf("%s key's value (%x) does not parse as uvarint",
|
||||
errors.Safe(key.Kind().String()), value)
|
||||
return w.err
|
||||
}
|
||||
}
|
||||
w.props.NumDeletions++
|
||||
w.props.NumSizedDeletions++
|
||||
w.props.RawPointTombstoneKeySize += uint64(len(key.UserKey))
|
||||
w.props.RawPointTombstoneValueSize += size
|
||||
case InternalKeyKindMerge:
|
||||
w.props.NumMergeOperands++
|
||||
}
|
||||
@@ -953,7 +1063,7 @@ func (w *Writer) addTombstone(key InternalKey, value []byte) error {
|
||||
if !w.disableKeyOrderChecks && !w.rangeDelV1Format && w.rangeDelBlock.nEntries > 0 {
|
||||
// Check that tombstones are being added in fragmented order. If the two
|
||||
// tombstones overlap, their start and end keys must be identical.
|
||||
prevKey := base.DecodeInternalKey(w.rangeDelBlock.curKey)
|
||||
prevKey := w.rangeDelBlock.getCurKey()
|
||||
switch c := w.compare(prevKey.UserKey, key.UserKey); {
|
||||
case c > 0:
|
||||
w.err = errors.Errorf("pebble: keys must be added in order: %s, %s",
|
||||
@@ -1149,7 +1259,7 @@ func (w *Writer) encodeRangeKeySpan(span keyspan.Span) {
|
||||
|
||||
func (w *Writer) addRangeKey(key InternalKey, value []byte) error {
|
||||
if !w.disableKeyOrderChecks && w.rangeKeyBlock.nEntries > 0 {
|
||||
prevStartKey := base.DecodeInternalKey(w.rangeKeyBlock.curKey)
|
||||
prevStartKey := w.rangeKeyBlock.getCurKey()
|
||||
prevEndKey, _, ok := rangekey.DecodeEndKey(prevStartKey.Kind(), w.rangeKeyBlock.curValue)
|
||||
if !ok {
|
||||
// We panic here as we should have previously decoded and validated this
|
||||
@@ -1295,7 +1405,7 @@ func (w *Writer) flush(key InternalKey) error {
|
||||
// dataBlockBuf is added back to a sync.Pool. In this particular case, the
|
||||
// byte slice which supports "sep" will eventually be copied when "sep" is
|
||||
// added to the index block.
|
||||
prevKey := base.DecodeInternalKey(w.dataBlockBuf.dataBlock.curKey)
|
||||
prevKey := w.dataBlockBuf.dataBlock.getCurKey()
|
||||
sep := w.indexEntrySep(prevKey, key, w.dataBlockBuf)
|
||||
// We determine that we should flush an index block from the Writer client
|
||||
// goroutine, but we actually finish the index block from the writeQueue.
|
||||
@@ -1585,7 +1695,7 @@ func (w *Writer) finishIndexBlock(indexBuf *indexBlockBuf, props []byte) error {
|
||||
nEntries: indexBuf.block.nEntries, properties: props,
|
||||
}
|
||||
w.indexSepAlloc, part.sep = cloneKeyWithBuf(
|
||||
base.DecodeInternalKey(indexBuf.block.curKey), w.indexSepAlloc,
|
||||
indexBuf.block.getCurKey(), w.indexSepAlloc,
|
||||
)
|
||||
bk := indexBuf.finish()
|
||||
if len(w.indexBlockAlloc) < len(bk) {
|
||||
@@ -1729,12 +1839,20 @@ func (w *Writer) assertFormatCompatibility() error {
|
||||
)
|
||||
}
|
||||
|
||||
// PebbleDBv3: value blocks.
|
||||
if (w.props.NumValueBlocks > 0 || w.props.NumValuesInValueBlocks > 0 ||
|
||||
w.props.ValueBlocksSize > 0) && w.tableFormat < TableFormatPebblev3 {
|
||||
return errors.Newf(
|
||||
"table format version %s is less than the minimum required version %s for value blocks",
|
||||
w.tableFormat, TableFormatPebblev3)
|
||||
}
|
||||
|
||||
// PebbleDBv4: DELSIZED tombstones.
|
||||
if w.props.NumSizedDeletions > 0 && w.tableFormat < TableFormatPebblev4 {
|
||||
return errors.Newf(
|
||||
"table format version %s is less than the minimum required version %s for sized deletion tombstones",
|
||||
w.tableFormat, TableFormatPebblev4)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1781,7 +1899,7 @@ func (w *Writer) Close() (err error) {
|
||||
// however, if a dataBlock is flushed, then we add a key to the new w.dataBlockBuf in the
|
||||
// addPoint function after the flush occurs.
|
||||
if w.dataBlockBuf.dataBlock.nEntries >= 1 {
|
||||
w.meta.SetLargestPointKey(base.DecodeInternalKey(w.dataBlockBuf.dataBlock.curKey).Clone())
|
||||
w.meta.SetLargestPointKey(w.dataBlockBuf.dataBlock.getCurKey().Clone())
|
||||
}
|
||||
|
||||
// Finish the last data block, or force an empty data block if there
|
||||
@@ -1795,7 +1913,7 @@ func (w *Writer) Close() (err error) {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
prevKey := base.DecodeInternalKey(w.dataBlockBuf.dataBlock.curKey)
|
||||
prevKey := w.dataBlockBuf.dataBlock.getCurKey()
|
||||
if err := w.addIndexEntrySync(prevKey, InternalKey{}, bhp, w.dataBlockBuf.tmp[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1874,7 +1992,7 @@ func (w *Writer) Close() (err error) {
|
||||
|
||||
var rangeKeyBH BlockHandle
|
||||
if w.props.NumRangeKeys() > 0 {
|
||||
key := base.DecodeInternalKey(w.rangeKeyBlock.curKey)
|
||||
key := w.rangeKeyBlock.getCurKey()
|
||||
kind := key.Kind()
|
||||
endKey, _, ok := rangekey.DecodeEndKey(kind, w.rangeKeyBlock.curValue)
|
||||
if !ok {
|
||||
@@ -2063,7 +2181,7 @@ func (o PreviousPointKeyOpt) UnsafeKey() base.InternalKey {
|
||||
if o.w.dataBlockBuf.dataBlock.nEntries >= 1 {
|
||||
// o.w.dataBlockBuf.dataBlock.curKey is guaranteed to point to the last point key
|
||||
// which was added to the Writer.
|
||||
return base.DecodeInternalKey(o.w.dataBlockBuf.dataBlock.curKey)
|
||||
return o.w.dataBlockBuf.dataBlock.getCurKey()
|
||||
}
|
||||
return base.InternalKey{}
|
||||
}
|
||||
@@ -2092,6 +2210,8 @@ func NewWriter(writable objstorage.Writable, o WriterOptions, extraOpts ...Write
|
||||
separator: o.Comparer.Separator,
|
||||
successor: o.Comparer.Successor,
|
||||
tableFormat: o.TableFormat,
|
||||
isStrictObsolete: o.IsStrictObsolete,
|
||||
writingToLowestLevel: o.WritingToLowestLevel,
|
||||
cache: o.Cache,
|
||||
restartInterval: o.BlockRestartInterval,
|
||||
checksumType: o.Checksum,
|
||||
@@ -2158,14 +2278,14 @@ func NewWriter(writable objstorage.Writable, o WriterOptions, extraOpts ...Write
|
||||
}
|
||||
}
|
||||
|
||||
w.props.ColumnFamilyID = math.MaxInt32
|
||||
w.props.ComparerName = o.Comparer.Name
|
||||
w.props.CompressionName = o.Compression.String()
|
||||
w.props.MergerName = o.MergerName
|
||||
w.props.PropertyCollectorNames = "[]"
|
||||
w.props.ExternalFormatVersion = rocksDBExternalFormatVersion
|
||||
|
||||
if len(o.TablePropertyCollectors) > 0 || len(o.BlockPropertyCollectors) > 0 {
|
||||
if len(o.TablePropertyCollectors) > 0 || len(o.BlockPropertyCollectors) > 0 ||
|
||||
w.tableFormat >= TableFormatPebblev4 {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString("[")
|
||||
if len(o.TablePropertyCollectors) > 0 {
|
||||
@@ -2178,16 +2298,22 @@ func NewWriter(writable objstorage.Writable, o WriterOptions, extraOpts ...Write
|
||||
buf.WriteString(w.propCollectors[i].Name())
|
||||
}
|
||||
}
|
||||
numBlockPropertyCollectors := len(o.BlockPropertyCollectors)
|
||||
if w.tableFormat >= TableFormatPebblev4 {
|
||||
numBlockPropertyCollectors++
|
||||
}
|
||||
// shortID is a uint8, so we cannot exceed that number of block
|
||||
// property collectors.
|
||||
if numBlockPropertyCollectors > math.MaxUint8 {
|
||||
w.err = errors.New("pebble: too many block property collectors")
|
||||
return w
|
||||
}
|
||||
if numBlockPropertyCollectors > 0 {
|
||||
w.blockPropCollectors = make([]BlockPropertyCollector, numBlockPropertyCollectors)
|
||||
}
|
||||
if len(o.BlockPropertyCollectors) > 0 {
|
||||
// shortID is a uint8, so we cannot exceed that number of block
|
||||
// property collectors.
|
||||
if len(o.BlockPropertyCollectors) > math.MaxUint8 {
|
||||
w.err = errors.New("pebble: too many block property collectors")
|
||||
return w
|
||||
}
|
||||
// The shortID assigned to a collector is the same as its index in
|
||||
// this slice.
|
||||
w.blockPropCollectors = make([]BlockPropertyCollector, len(o.BlockPropertyCollectors))
|
||||
for i := range o.BlockPropertyCollectors {
|
||||
w.blockPropCollectors[i] = o.BlockPropertyCollectors[i]()
|
||||
if i > 0 || len(o.TablePropertyCollectors) > 0 {
|
||||
@@ -2196,6 +2322,13 @@ func NewWriter(writable objstorage.Writable, o WriterOptions, extraOpts ...Write
|
||||
buf.WriteString(w.blockPropCollectors[i].Name())
|
||||
}
|
||||
}
|
||||
if w.tableFormat >= TableFormatPebblev4 {
|
||||
if numBlockPropertyCollectors > 1 || len(o.TablePropertyCollectors) > 0 {
|
||||
buf.WriteString(",")
|
||||
}
|
||||
w.blockPropCollectors[numBlockPropertyCollectors-1] = &w.obsoleteCollector
|
||||
buf.WriteString(w.obsoleteCollector.Name())
|
||||
}
|
||||
buf.WriteString("]")
|
||||
w.props.PropertyCollectorNames = buf.String()
|
||||
}
|
||||
@@ -2229,3 +2362,85 @@ func init() {
|
||||
}
|
||||
private.SSTableInternalProperties = internalGetProperties
|
||||
}
|
||||
|
||||
type obsoleteKeyBlockPropertyCollector struct {
|
||||
blockIsNonObsolete bool
|
||||
indexIsNonObsolete bool
|
||||
tableIsNonObsolete bool
|
||||
}
|
||||
|
||||
func encodeNonObsolete(isNonObsolete bool, buf []byte) []byte {
|
||||
if isNonObsolete {
|
||||
return buf
|
||||
}
|
||||
return append(buf, 't')
|
||||
}
|
||||
|
||||
func (o *obsoleteKeyBlockPropertyCollector) Name() string {
|
||||
return "obsolete-key"
|
||||
}
|
||||
|
||||
func (o *obsoleteKeyBlockPropertyCollector) Add(key InternalKey, value []byte) error {
|
||||
// Ignore.
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *obsoleteKeyBlockPropertyCollector) AddPoint(isObsolete bool) {
|
||||
o.blockIsNonObsolete = o.blockIsNonObsolete || !isObsolete
|
||||
}
|
||||
|
||||
func (o *obsoleteKeyBlockPropertyCollector) FinishDataBlock(buf []byte) ([]byte, error) {
|
||||
o.tableIsNonObsolete = o.tableIsNonObsolete || o.blockIsNonObsolete
|
||||
return encodeNonObsolete(o.blockIsNonObsolete, buf), nil
|
||||
}
|
||||
|
||||
func (o *obsoleteKeyBlockPropertyCollector) AddPrevDataBlockToIndexBlock() {
|
||||
o.indexIsNonObsolete = o.indexIsNonObsolete || o.blockIsNonObsolete
|
||||
o.blockIsNonObsolete = false
|
||||
}
|
||||
|
||||
func (o *obsoleteKeyBlockPropertyCollector) FinishIndexBlock(buf []byte) ([]byte, error) {
|
||||
indexIsNonObsolete := o.indexIsNonObsolete
|
||||
o.indexIsNonObsolete = false
|
||||
return encodeNonObsolete(indexIsNonObsolete, buf), nil
|
||||
}
|
||||
|
||||
func (o *obsoleteKeyBlockPropertyCollector) FinishTable(buf []byte) ([]byte, error) {
|
||||
return encodeNonObsolete(o.tableIsNonObsolete, buf), nil
|
||||
}
|
||||
|
||||
func (o *obsoleteKeyBlockPropertyCollector) UpdateKeySuffixes(
|
||||
oldProp []byte, oldSuffix, newSuffix []byte,
|
||||
) error {
|
||||
_, err := propToIsObsolete(oldProp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Suffix rewriting currently loses the obsolete bit.
|
||||
o.blockIsNonObsolete = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// NB: obsoleteKeyBlockPropertyFilter is stateless. This aspect of the filter
|
||||
// is used in table_cache.go for in-place modification of a filters slice.
|
||||
type obsoleteKeyBlockPropertyFilter struct{}
|
||||
|
||||
func (o obsoleteKeyBlockPropertyFilter) Name() string {
|
||||
return "obsolete-key"
|
||||
}
|
||||
|
||||
// Intersects returns true if the set represented by prop intersects with
|
||||
// the set in the filter.
|
||||
func (o obsoleteKeyBlockPropertyFilter) Intersects(prop []byte) (bool, error) {
|
||||
return propToIsObsolete(prop)
|
||||
}
|
||||
|
||||
func propToIsObsolete(prop []byte) (bool, error) {
|
||||
if len(prop) == 0 {
|
||||
return true, nil
|
||||
}
|
||||
if len(prop) > 1 || prop[0] != 't' {
|
||||
return false, errors.Errorf("unexpected property %x", prop)
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user