Upgrade versions of vendored libs
This commit is contained in:
333
vendor/github.com/cockroachdb/pebble/sstable/block.go
generated
vendored
333
vendor/github.com/cockroachdb/pebble/sstable/block.go
generated
vendored
@@ -10,7 +10,6 @@ import (
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/cockroachdb/pebble/internal/base"
|
||||
"github.com/cockroachdb/pebble/internal/cache"
|
||||
"github.com/cockroachdb/pebble/internal/invariants"
|
||||
"github.com/cockroachdb/pebble/internal/keyspan"
|
||||
"github.com/cockroachdb/pebble/internal/manual"
|
||||
@@ -47,7 +46,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 +81,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 +207,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 +235,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(
|
||||
@@ -371,16 +399,16 @@ type blockIter struct {
|
||||
// For a block encoded with a restart interval of 1, cached and cachedBuf
|
||||
// will not be used as there are no prefix compressed entries between the
|
||||
// restart points.
|
||||
cached []blockEntry
|
||||
cachedBuf []byte
|
||||
cacheHandle cache.Handle
|
||||
// The first key in the block. This is used by the caller to set bounds
|
||||
cached []blockEntry
|
||||
cachedBuf []byte
|
||||
handle bufferHandle
|
||||
// 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 +416,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 +438,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 +446,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 {
|
||||
i.cacheHandle.Release()
|
||||
i.cacheHandle = block
|
||||
return i.init(cmp, block.Get(), globalSeqNum)
|
||||
// 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 bufferHandle, globalSeqNum uint64, hideObsoletePoints bool,
|
||||
) error {
|
||||
i.handle.Release()
|
||||
i.handle = block
|
||||
return i.init(cmp, block.Get(), globalSeqNum, hideObsoletePoints)
|
||||
}
|
||||
|
||||
func (i *blockIter) invalidate() {
|
||||
@@ -590,24 +627,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 +656,7 @@ func (i *blockIter) decodeInternalKey(key []byte) {
|
||||
i.ikey.Trailer = uint64(InternalKeyKindInvalid)
|
||||
i.ikey.UserKey = nil
|
||||
}
|
||||
return hiddenPoint
|
||||
}
|
||||
|
||||
func (i *blockIter) clearCache() {
|
||||
@@ -640,13 +681,18 @@ 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) {
|
||||
if invariants.Enabled && i.isDataInvalidated() {
|
||||
panic(errors.AssertionFailedf("invalidated blockIter used"))
|
||||
}
|
||||
|
||||
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 +746,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 +770,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 +801,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
|
||||
}
|
||||
@@ -771,10 +821,11 @@ func (i *blockIter) SeekPrefixGE(
|
||||
// SeekLT implements internalIterator.SeekLT, as documented in the pebble
|
||||
// package.
|
||||
func (i *blockIter) SeekLT(key []byte, flags base.SeekLTFlags) (*InternalKey, base.LazyValue) {
|
||||
if invariants.Enabled && i.isDataInvalidated() {
|
||||
panic(errors.AssertionFailedf("invalidated blockIter used"))
|
||||
}
|
||||
|
||||
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 +879,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 +903,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 +934,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
|
||||
}
|
||||
|
||||
@@ -917,13 +992,20 @@ func (i *blockIter) SeekLT(key []byte, flags base.SeekLTFlags) (*InternalKey, ba
|
||||
// First implements internalIterator.First, as documented in the pebble
|
||||
// package.
|
||||
func (i *blockIter) First() (*InternalKey, base.LazyValue) {
|
||||
if invariants.Enabled && i.isDataInvalidated() {
|
||||
panic(errors.AssertionFailedf("invalidated blockIter used"))
|
||||
}
|
||||
|
||||
i.offset = 0
|
||||
if !i.valid() {
|
||||
return nil, 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)
|
||||
@@ -943,6 +1025,10 @@ func decodeRestart(b []byte) int32 {
|
||||
|
||||
// Last implements internalIterator.Last, as documented in the pebble package.
|
||||
func (i *blockIter) Last() (*InternalKey, base.LazyValue) {
|
||||
if invariants.Enabled && i.isDataInvalidated() {
|
||||
panic(errors.AssertionFailedf("invalidated blockIter used"))
|
||||
}
|
||||
|
||||
// Seek forward from the last restart point.
|
||||
i.offset = decodeRestart(i.data[i.restarts+4*(i.numRestarts-1):])
|
||||
if !i.valid() {
|
||||
@@ -958,7 +1044,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 +1077,7 @@ func (i *blockIter) Next() (*InternalKey, base.LazyValue) {
|
||||
i.clearCache()
|
||||
}
|
||||
|
||||
start:
|
||||
i.offset = i.nextOffset
|
||||
if !i.valid() {
|
||||
return nil, base.LazyValue{}
|
||||
@@ -995,11 +1085,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 +1111,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 +1237,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 +1353,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 +1374,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 +1398,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 +1410,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 +1458,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 +1469,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)
|
||||
@@ -1412,8 +1527,8 @@ func (i *blockIter) Error() error {
|
||||
// Close implements internalIterator.Close, as documented in the pebble
|
||||
// package.
|
||||
func (i *blockIter) Close() error {
|
||||
i.cacheHandle.Release()
|
||||
i.cacheHandle = cache.Handle{}
|
||||
i.handle.Release()
|
||||
i.handle = bufferHandle{}
|
||||
i.val = nil
|
||||
i.lazyValue = base.LazyValue{}
|
||||
i.lazyValueHandling.vbr = nil
|
||||
@@ -1455,6 +1570,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 +1602,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 +1669,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 +1731,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
|
||||
}
|
||||
|
||||
|
||||
30
vendor/github.com/cockroachdb/pebble/sstable/block_property.go
generated
vendored
30
vendor/github.com/cockroachdb/pebble/sstable/block_property.go
generated
vendored
@@ -9,6 +9,7 @@ import (
|
||||
"fmt"
|
||||
"math"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"github.com/cockroachdb/pebble/internal/base"
|
||||
"github.com/cockroachdb/pebble/internal/rangekey"
|
||||
@@ -117,7 +118,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.
|
||||
//
|
||||
@@ -189,13 +190,13 @@ type BoundLimitedBlockPropertyFilter interface {
|
||||
// indicates that the filter may be used to filter blocks that exclusively
|
||||
// contain keys ≥ `key`, so long as the blocks' keys also satisfy the upper
|
||||
// bound.
|
||||
KeyIsWithinLowerBound(key *InternalKey) bool
|
||||
KeyIsWithinLowerBound(key []byte) bool
|
||||
// KeyIsWithinUpperBound tests whether the provided internal key falls
|
||||
// within the current upper bound of the filter. A true return value
|
||||
// indicates that the filter may be used to filter blocks that exclusively
|
||||
// contain keys ≤ `key`, so long as the blocks' keys also satisfy the lower
|
||||
// bound.
|
||||
KeyIsWithinUpperBound(key *InternalKey) bool
|
||||
KeyIsWithinUpperBound(key []byte) bool
|
||||
}
|
||||
|
||||
// BlockIntervalCollector is a helper implementation of BlockPropertyCollector
|
||||
@@ -643,15 +644,21 @@ func (f *BlockPropertiesFilterer) intersectsUserPropsAndFinishInit(
|
||||
// considered intersecting.
|
||||
continue
|
||||
}
|
||||
byteProps := []byte(props)
|
||||
if len(byteProps) < 1 {
|
||||
if len(props) < 1 {
|
||||
return false, base.CorruptionErrorf(
|
||||
"block properties for %s is corrupted", f.filters[i].Name())
|
||||
}
|
||||
shortID := shortID(byteProps[0])
|
||||
intersects, err := f.filters[i].Intersects(byteProps[1:])
|
||||
if err != nil || !intersects {
|
||||
return false, err
|
||||
shortID := shortID(props[0])
|
||||
{
|
||||
// Use an unsafe conversion to avoid allocating. Intersects() is not
|
||||
// supposed to modify the given slice.
|
||||
// Note that unsafe.StringData only works if the string is not empty
|
||||
// (which we already checked).
|
||||
byteProps := unsafe.Slice(unsafe.StringData(props), len(props))
|
||||
intersects, err := f.filters[i].Intersects(byteProps[1:])
|
||||
if err != nil || !intersects {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
// Intersects the sstable, so need to use this filter when
|
||||
// deciding whether to read blocks.
|
||||
@@ -684,12 +691,11 @@ func (f *BlockPropertiesFilterer) intersectsUserPropsAndFinishInit(
|
||||
// be unused within this file.
|
||||
return true, nil
|
||||
}
|
||||
byteProps := []byte(props)
|
||||
if len(byteProps) < 1 {
|
||||
if len(props) < 1 {
|
||||
return false, base.CorruptionErrorf(
|
||||
"block properties for %s is corrupted", f.boundLimitedFilter.Name())
|
||||
}
|
||||
f.boundLimitedShortID = int(byteProps[0])
|
||||
f.boundLimitedShortID = int(props[0])
|
||||
|
||||
// We don't check for table-level intersection for the bound-limited filter.
|
||||
// The bound-limited filter is treated as vacuously intersecting.
|
||||
|
||||
148
vendor/github.com/cockroachdb/pebble/sstable/buffer_pool.go
generated
vendored
Normal file
148
vendor/github.com/cockroachdb/pebble/sstable/buffer_pool.go
generated
vendored
Normal file
@@ -0,0 +1,148 @@
|
||||
// Copyright 2023 The LevelDB-Go and Pebble Authors. All rights reserved. Use
|
||||
// of this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
|
||||
package sstable
|
||||
|
||||
import (
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/cockroachdb/pebble/internal/cache"
|
||||
)
|
||||
|
||||
// A bufferHandle is a handle to manually-managed memory. The handle may point
|
||||
// to a block in the block cache (h.Get() != nil), or a buffer that exists
|
||||
// outside the block cache allocated from a BufferPool (b.Valid()).
|
||||
type bufferHandle struct {
|
||||
h cache.Handle
|
||||
b Buf
|
||||
}
|
||||
|
||||
// Get retrieves the underlying buffer referenced by the handle.
|
||||
func (bh bufferHandle) Get() []byte {
|
||||
if v := bh.h.Get(); v != nil {
|
||||
return v
|
||||
} else if bh.b.p != nil {
|
||||
return bh.b.p.pool[bh.b.i].b
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Release releases the buffer, either back to the block cache or BufferPool.
|
||||
func (bh bufferHandle) Release() {
|
||||
bh.h.Release()
|
||||
bh.b.Release()
|
||||
}
|
||||
|
||||
// A BufferPool holds a pool of buffers for holding sstable blocks. An initial
|
||||
// size of the pool is provided on Init, but a BufferPool will grow to meet the
|
||||
// largest working set size. It'll never shrink. When a buffer is released, the
|
||||
// BufferPool recycles the buffer for future allocations.
|
||||
//
|
||||
// A BufferPool should only be used for short-lived allocations with
|
||||
// well-understood working set sizes to avoid excessive memory consumption.
|
||||
//
|
||||
// BufferPool is not thread-safe.
|
||||
type BufferPool struct {
|
||||
// pool contains all the buffers held by the pool, including buffers that
|
||||
// are in-use. For every i < len(pool): pool[i].v is non-nil.
|
||||
pool []allocedBuffer
|
||||
}
|
||||
|
||||
type allocedBuffer struct {
|
||||
v *cache.Value
|
||||
// b holds the current byte slice. It's backed by v, but may be a subslice
|
||||
// of v's memory while the buffer is in-use [ len(b) ≤ len(v.Buf()) ].
|
||||
//
|
||||
// If the buffer is not currently in-use, b is nil. When being recycled, the
|
||||
// BufferPool.Alloc will reset b to be a subslice of v.Buf().
|
||||
b []byte
|
||||
}
|
||||
|
||||
// Init initializes the pool with an initial working set buffer size of
|
||||
// `initialSize`.
|
||||
func (p *BufferPool) Init(initialSize int) {
|
||||
*p = BufferPool{
|
||||
pool: make([]allocedBuffer, 0, initialSize),
|
||||
}
|
||||
}
|
||||
|
||||
// initPreallocated is like Init but for internal sstable package use in
|
||||
// instances where a pre-allocated slice of []allocedBuffer already exists. It's
|
||||
// used to avoid an extra allocation initializing BufferPool.pool.
|
||||
func (p *BufferPool) initPreallocated(pool []allocedBuffer) {
|
||||
*p = BufferPool{
|
||||
pool: pool[:0],
|
||||
}
|
||||
}
|
||||
|
||||
// Release releases all buffers held by the pool and resets the pool to an
|
||||
// uninitialized state.
|
||||
func (p *BufferPool) Release() {
|
||||
for i := range p.pool {
|
||||
if p.pool[i].b != nil {
|
||||
panic(errors.AssertionFailedf("Release called on a BufferPool with in-use buffers"))
|
||||
}
|
||||
cache.Free(p.pool[i].v)
|
||||
}
|
||||
*p = BufferPool{}
|
||||
}
|
||||
|
||||
// Alloc allocates a new buffer of size n. If the pool already holds a buffer at
|
||||
// least as large as n, the pooled buffer is used instead.
|
||||
//
|
||||
// Alloc is O(MAX(N,M)) where N is the largest number of concurrently in-use
|
||||
// buffers allocated and M is the initialSize passed to Init.
|
||||
func (p *BufferPool) Alloc(n int) Buf {
|
||||
unusableBufferIdx := -1
|
||||
for i := 0; i < len(p.pool); i++ {
|
||||
if p.pool[i].b == nil {
|
||||
if len(p.pool[i].v.Buf()) >= n {
|
||||
p.pool[i].b = p.pool[i].v.Buf()[:n]
|
||||
return Buf{p: p, i: i}
|
||||
}
|
||||
unusableBufferIdx = i
|
||||
}
|
||||
}
|
||||
|
||||
// If we would need to grow the size of the pool to allocate another buffer,
|
||||
// but there was a slot available occupied by a buffer that's just too
|
||||
// small, replace the too-small buffer.
|
||||
if len(p.pool) == cap(p.pool) && unusableBufferIdx >= 0 {
|
||||
i := unusableBufferIdx
|
||||
cache.Free(p.pool[i].v)
|
||||
p.pool[i].v = cache.Alloc(n)
|
||||
p.pool[i].b = p.pool[i].v.Buf()
|
||||
return Buf{p: p, i: i}
|
||||
}
|
||||
|
||||
// Allocate a new buffer.
|
||||
v := cache.Alloc(n)
|
||||
p.pool = append(p.pool, allocedBuffer{v: v, b: v.Buf()[:n]})
|
||||
return Buf{p: p, i: len(p.pool) - 1}
|
||||
}
|
||||
|
||||
// A Buf holds a reference to a manually-managed, pooled byte buffer.
|
||||
type Buf struct {
|
||||
p *BufferPool
|
||||
// i holds the index into p.pool where the buffer may be found. This scheme
|
||||
// avoids needing to allocate the handle to the buffer on the heap at the
|
||||
// cost of copying two words instead of one.
|
||||
i int
|
||||
}
|
||||
|
||||
// Valid returns true if the buf holds a valid buffer.
|
||||
func (b Buf) Valid() bool {
|
||||
return b.p != nil
|
||||
}
|
||||
|
||||
// Release releases the buffer back to the pool.
|
||||
func (b *Buf) Release() {
|
||||
if b.p == nil {
|
||||
return
|
||||
}
|
||||
// Clear the allocedBuffer's byte slice. This signals the allocated buffer
|
||||
// is no longer in use and a future call to BufferPool.Alloc may reuse this
|
||||
// buffer.
|
||||
b.p.pool[b.i].b = nil
|
||||
b.p = nil
|
||||
}
|
||||
11
vendor/github.com/cockroachdb/pebble/sstable/compression.go
generated
vendored
11
vendor/github.com/cockroachdb/pebble/sstable/compression.go
generated
vendored
@@ -52,8 +52,10 @@ func decompressInto(blockType blockType, compressed []byte, buf []byte) ([]byte,
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// decompressBlock decompresses an SST block, with space allocated from a cache.
|
||||
func decompressBlock(cache *cache.Cache, blockType blockType, b []byte) (*cache.Value, error) {
|
||||
// decompressBlock decompresses an SST block, with manually-allocated space.
|
||||
// NB: If decompressBlock returns (nil, nil), no decompression was necessary and
|
||||
// the caller may use `b` directly.
|
||||
func decompressBlock(blockType blockType, b []byte) (*cache.Value, error) {
|
||||
if blockType == noCompressionBlockType {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -62,14 +64,13 @@ func decompressBlock(cache *cache.Cache, blockType blockType, b []byte) (*cache.
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if prefixLen != 0 {
|
||||
b = b[prefixLen:]
|
||||
}
|
||||
b = b[prefixLen:]
|
||||
// Allocate sufficient space from the cache.
|
||||
decoded := cache.Alloc(decodedLen)
|
||||
decodedBuf := decoded.Buf()
|
||||
if _, err := decompressInto(blockType, b, decodedBuf); err != nil {
|
||||
cache.Free(decoded)
|
||||
return nil, err
|
||||
}
|
||||
return decoded, nil
|
||||
}
|
||||
|
||||
156
vendor/github.com/cockroachdb/pebble/sstable/format.go
generated
vendored
156
vendor/github.com/cockroachdb/pebble/sstable/format.go
generated
vendored
@@ -24,11 +24,163 @@ const (
|
||||
TableFormatPebblev1 // Block properties.
|
||||
TableFormatPebblev2 // Range keys.
|
||||
TableFormatPebblev3 // Value blocks.
|
||||
TableFormatPebblev4 // Reserved.
|
||||
TableFormatPebblev4 // DELSIZED tombstones.
|
||||
NumTableFormats
|
||||
|
||||
TableFormatMax = TableFormatPebblev4
|
||||
TableFormatMax = NumTableFormats - 1
|
||||
)
|
||||
|
||||
// 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/DEL*. 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
|
||||
|
||||
306
vendor/github.com/cockroachdb/pebble/sstable/layout.go
generated
vendored
Normal file
306
vendor/github.com/cockroachdb/pebble/sstable/layout.go
generated
vendored
Normal file
@@ -0,0 +1,306 @@
|
||||
// Copyright 2011 The LevelDB-Go and Pebble Authors. All rights reserved. Use
|
||||
// of this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
|
||||
package sstable
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"unsafe"
|
||||
|
||||
"github.com/cockroachdb/pebble/internal/base"
|
||||
)
|
||||
|
||||
// Layout describes the block organization of an sstable.
|
||||
type Layout struct {
|
||||
// NOTE: changes to fields in this struct should also be reflected in
|
||||
// ValidateBlockChecksums, which validates a static list of BlockHandles
|
||||
// referenced in this struct.
|
||||
|
||||
Data []BlockHandleWithProperties
|
||||
Index []BlockHandle
|
||||
TopIndex BlockHandle
|
||||
Filter BlockHandle
|
||||
RangeDel BlockHandle
|
||||
RangeKey BlockHandle
|
||||
ValueBlock []BlockHandle
|
||||
ValueIndex BlockHandle
|
||||
Properties BlockHandle
|
||||
MetaIndex BlockHandle
|
||||
Footer BlockHandle
|
||||
Format TableFormat
|
||||
}
|
||||
|
||||
// Describe returns a description of the layout. If the verbose parameter is
|
||||
// true, details of the structure of each block are returned as well.
|
||||
func (l *Layout) Describe(
|
||||
w io.Writer, verbose bool, r *Reader, fmtRecord func(key *base.InternalKey, value []byte),
|
||||
) {
|
||||
ctx := context.TODO()
|
||||
type block struct {
|
||||
BlockHandle
|
||||
name string
|
||||
}
|
||||
var blocks []block
|
||||
|
||||
for i := range l.Data {
|
||||
blocks = append(blocks, block{l.Data[i].BlockHandle, "data"})
|
||||
}
|
||||
for i := range l.Index {
|
||||
blocks = append(blocks, block{l.Index[i], "index"})
|
||||
}
|
||||
if l.TopIndex.Length != 0 {
|
||||
blocks = append(blocks, block{l.TopIndex, "top-index"})
|
||||
}
|
||||
if l.Filter.Length != 0 {
|
||||
blocks = append(blocks, block{l.Filter, "filter"})
|
||||
}
|
||||
if l.RangeDel.Length != 0 {
|
||||
blocks = append(blocks, block{l.RangeDel, "range-del"})
|
||||
}
|
||||
if l.RangeKey.Length != 0 {
|
||||
blocks = append(blocks, block{l.RangeKey, "range-key"})
|
||||
}
|
||||
for i := range l.ValueBlock {
|
||||
blocks = append(blocks, block{l.ValueBlock[i], "value-block"})
|
||||
}
|
||||
if l.ValueIndex.Length != 0 {
|
||||
blocks = append(blocks, block{l.ValueIndex, "value-index"})
|
||||
}
|
||||
if l.Properties.Length != 0 {
|
||||
blocks = append(blocks, block{l.Properties, "properties"})
|
||||
}
|
||||
if l.MetaIndex.Length != 0 {
|
||||
blocks = append(blocks, block{l.MetaIndex, "meta-index"})
|
||||
}
|
||||
if l.Footer.Length != 0 {
|
||||
if l.Footer.Length == levelDBFooterLen {
|
||||
blocks = append(blocks, block{l.Footer, "leveldb-footer"})
|
||||
} else {
|
||||
blocks = append(blocks, block{l.Footer, "footer"})
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(blocks, func(i, j int) bool {
|
||||
return blocks[i].Offset < blocks[j].Offset
|
||||
})
|
||||
|
||||
for i := range blocks {
|
||||
b := &blocks[i]
|
||||
fmt.Fprintf(w, "%10d %s (%d)\n", b.Offset, b.name, b.Length)
|
||||
|
||||
if !verbose {
|
||||
continue
|
||||
}
|
||||
if b.name == "filter" {
|
||||
continue
|
||||
}
|
||||
|
||||
if b.name == "footer" || b.name == "leveldb-footer" {
|
||||
trailer, offset := make([]byte, b.Length), b.Offset
|
||||
_ = r.readable.ReadAt(ctx, trailer, int64(offset))
|
||||
|
||||
if b.name == "footer" {
|
||||
checksumType := ChecksumType(trailer[0])
|
||||
fmt.Fprintf(w, "%10d checksum type: %s\n", offset, checksumType)
|
||||
trailer, offset = trailer[1:], offset+1
|
||||
}
|
||||
|
||||
metaHandle, n := binary.Uvarint(trailer)
|
||||
metaLen, m := binary.Uvarint(trailer[n:])
|
||||
fmt.Fprintf(w, "%10d meta: offset=%d, length=%d\n", offset, metaHandle, metaLen)
|
||||
trailer, offset = trailer[n+m:], offset+uint64(n+m)
|
||||
|
||||
indexHandle, n := binary.Uvarint(trailer)
|
||||
indexLen, m := binary.Uvarint(trailer[n:])
|
||||
fmt.Fprintf(w, "%10d index: offset=%d, length=%d\n", offset, indexHandle, indexLen)
|
||||
trailer, offset = trailer[n+m:], offset+uint64(n+m)
|
||||
|
||||
fmt.Fprintf(w, "%10d [padding]\n", offset)
|
||||
|
||||
trailing := 12
|
||||
if b.name == "leveldb-footer" {
|
||||
trailing = 8
|
||||
}
|
||||
|
||||
offset += uint64(len(trailer) - trailing)
|
||||
trailer = trailer[len(trailer)-trailing:]
|
||||
|
||||
if b.name == "footer" {
|
||||
version := trailer[:4]
|
||||
fmt.Fprintf(w, "%10d version: %d\n", offset, binary.LittleEndian.Uint32(version))
|
||||
trailer, offset = trailer[4:], offset+4
|
||||
}
|
||||
|
||||
magicNumber := trailer
|
||||
fmt.Fprintf(w, "%10d magic number: 0x%x\n", offset, magicNumber)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
h, err := r.readBlock(
|
||||
context.Background(), b.BlockHandle, nil /* transform */, nil /* readHandle */, nil /* stats */, nil /* buffer pool */)
|
||||
if err != nil {
|
||||
fmt.Fprintf(w, " [err: %s]\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
getRestart := func(data []byte, restarts, i int32) int32 {
|
||||
return decodeRestart(data[restarts+4*i:])
|
||||
}
|
||||
|
||||
formatIsRestart := func(data []byte, restarts, numRestarts, offset int32) {
|
||||
i := sort.Search(int(numRestarts), func(i int) bool {
|
||||
return getRestart(data, restarts, int32(i)) >= offset
|
||||
})
|
||||
if i < int(numRestarts) && getRestart(data, restarts, int32(i)) == offset {
|
||||
fmt.Fprintf(w, " [restart]\n")
|
||||
} else {
|
||||
fmt.Fprintf(w, "\n")
|
||||
}
|
||||
}
|
||||
|
||||
formatRestarts := func(data []byte, restarts, numRestarts int32) {
|
||||
for i := int32(0); i < numRestarts; i++ {
|
||||
offset := getRestart(data, restarts, i)
|
||||
fmt.Fprintf(w, "%10d [restart %d]\n",
|
||||
b.Offset+uint64(restarts+4*i), b.Offset+uint64(offset))
|
||||
}
|
||||
}
|
||||
|
||||
formatTrailer := func() {
|
||||
trailer := make([]byte, blockTrailerLen)
|
||||
offset := int64(b.Offset + b.Length)
|
||||
_ = r.readable.ReadAt(ctx, trailer, offset)
|
||||
bt := blockType(trailer[0])
|
||||
checksum := binary.LittleEndian.Uint32(trailer[1:])
|
||||
fmt.Fprintf(w, "%10d [trailer compression=%s checksum=0x%04x]\n", offset, bt, checksum)
|
||||
}
|
||||
|
||||
var lastKey InternalKey
|
||||
switch b.name {
|
||||
case "data", "range-del", "range-key":
|
||||
iter, _ := newBlockIter(r.Compare, h.Get())
|
||||
for key, value := iter.First(); key != nil; key, value = iter.Next() {
|
||||
ptr := unsafe.Pointer(uintptr(iter.ptr) + uintptr(iter.offset))
|
||||
shared, ptr := decodeVarint(ptr)
|
||||
unshared, ptr := decodeVarint(ptr)
|
||||
value2, _ := decodeVarint(ptr)
|
||||
|
||||
total := iter.nextOffset - iter.offset
|
||||
// The format of the numbers in the record line is:
|
||||
//
|
||||
// (<total> = <length> [<shared>] + <unshared> + <value>)
|
||||
//
|
||||
// <total> is the total number of bytes for the record.
|
||||
// <length> is the size of the 3 varint encoded integers for <shared>,
|
||||
// <unshared>, and <value>.
|
||||
// <shared> is the number of key bytes shared with the previous key.
|
||||
// <unshared> is the number of unshared key bytes.
|
||||
// <value> is the number of value bytes.
|
||||
fmt.Fprintf(w, "%10d record (%d = %d [%d] + %d + %d)",
|
||||
b.Offset+uint64(iter.offset), total,
|
||||
total-int32(unshared+value2), shared, unshared, value2)
|
||||
formatIsRestart(iter.data, iter.restarts, iter.numRestarts, iter.offset)
|
||||
if fmtRecord != nil {
|
||||
fmt.Fprintf(w, " ")
|
||||
if l.Format < TableFormatPebblev3 {
|
||||
fmtRecord(key, value.InPlaceValue())
|
||||
} else {
|
||||
// InPlaceValue() will succeed even for data blocks where the
|
||||
// actual value is in a different location, since this value was
|
||||
// fetched from a blockIter which does not know about value
|
||||
// blocks.
|
||||
v := value.InPlaceValue()
|
||||
if base.TrailerKind(key.Trailer) != InternalKeyKindSet {
|
||||
fmtRecord(key, v)
|
||||
} else if !isValueHandle(valuePrefix(v[0])) {
|
||||
fmtRecord(key, v[1:])
|
||||
} else {
|
||||
vh := decodeValueHandle(v[1:])
|
||||
fmtRecord(key, []byte(fmt.Sprintf("value handle %+v", vh)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if base.InternalCompare(r.Compare, lastKey, *key) >= 0 {
|
||||
fmt.Fprintf(w, " WARNING: OUT OF ORDER KEYS!\n")
|
||||
}
|
||||
lastKey.Trailer = key.Trailer
|
||||
lastKey.UserKey = append(lastKey.UserKey[:0], key.UserKey...)
|
||||
}
|
||||
formatRestarts(iter.data, iter.restarts, iter.numRestarts)
|
||||
formatTrailer()
|
||||
case "index", "top-index":
|
||||
iter, _ := newBlockIter(r.Compare, h.Get())
|
||||
for key, value := iter.First(); key != nil; key, value = iter.Next() {
|
||||
bh, err := decodeBlockHandleWithProperties(value.InPlaceValue())
|
||||
if err != nil {
|
||||
fmt.Fprintf(w, "%10d [err: %s]\n", b.Offset+uint64(iter.offset), err)
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(w, "%10d block:%d/%d",
|
||||
b.Offset+uint64(iter.offset), bh.Offset, bh.Length)
|
||||
formatIsRestart(iter.data, iter.restarts, iter.numRestarts, iter.offset)
|
||||
}
|
||||
formatRestarts(iter.data, iter.restarts, iter.numRestarts)
|
||||
formatTrailer()
|
||||
case "properties":
|
||||
iter, _ := newRawBlockIter(r.Compare, h.Get())
|
||||
for valid := iter.First(); valid; valid = iter.Next() {
|
||||
fmt.Fprintf(w, "%10d %s (%d)",
|
||||
b.Offset+uint64(iter.offset), iter.Key().UserKey, iter.nextOffset-iter.offset)
|
||||
formatIsRestart(iter.data, iter.restarts, iter.numRestarts, iter.offset)
|
||||
}
|
||||
formatRestarts(iter.data, iter.restarts, iter.numRestarts)
|
||||
formatTrailer()
|
||||
case "meta-index":
|
||||
iter, _ := newRawBlockIter(r.Compare, h.Get())
|
||||
for valid := iter.First(); valid; valid = iter.Next() {
|
||||
value := iter.Value()
|
||||
var bh BlockHandle
|
||||
var n int
|
||||
var vbih valueBlocksIndexHandle
|
||||
isValueBlocksIndexHandle := false
|
||||
if bytes.Equal(iter.Key().UserKey, []byte(metaValueIndexName)) {
|
||||
vbih, n, err = decodeValueBlocksIndexHandle(value)
|
||||
bh = vbih.h
|
||||
isValueBlocksIndexHandle = true
|
||||
} else {
|
||||
bh, n = decodeBlockHandle(value)
|
||||
}
|
||||
if n == 0 || n != len(value) {
|
||||
fmt.Fprintf(w, "%10d [err: %s]\n", b.Offset+uint64(iter.offset), err)
|
||||
continue
|
||||
}
|
||||
var vbihStr string
|
||||
if isValueBlocksIndexHandle {
|
||||
vbihStr = fmt.Sprintf(" value-blocks-index-lengths: %d(num), %d(offset), %d(length)",
|
||||
vbih.blockNumByteLength, vbih.blockOffsetByteLength, vbih.blockLengthByteLength)
|
||||
}
|
||||
fmt.Fprintf(w, "%10d %s block:%d/%d%s",
|
||||
b.Offset+uint64(iter.offset), iter.Key().UserKey,
|
||||
bh.Offset, bh.Length, vbihStr)
|
||||
formatIsRestart(iter.data, iter.restarts, iter.numRestarts, iter.offset)
|
||||
}
|
||||
formatRestarts(iter.data, iter.restarts, iter.numRestarts)
|
||||
formatTrailer()
|
||||
case "value-block":
|
||||
// We don't peer into the value-block since it can't be interpreted
|
||||
// without the valueHandles.
|
||||
case "value-index":
|
||||
// We have already read the value-index to construct the list of
|
||||
// value-blocks, so no need to do it again.
|
||||
}
|
||||
|
||||
h.Release()
|
||||
}
|
||||
|
||||
last := blocks[len(blocks)-1]
|
||||
fmt.Fprintf(w, "%10d EOF\n", last.Offset+last.Length)
|
||||
}
|
||||
40
vendor/github.com/cockroachdb/pebble/sstable/options.go
generated
vendored
40
vendor/github.com/cockroachdb/pebble/sstable/options.go
generated
vendored
@@ -5,6 +5,7 @@
|
||||
package sstable
|
||||
|
||||
import (
|
||||
"github.com/cockroachdb/fifo"
|
||||
"github.com/cockroachdb/pebble/internal/base"
|
||||
"github.com/cockroachdb/pebble/internal/cache"
|
||||
)
|
||||
@@ -21,6 +22,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 +112,14 @@ type ReaderOptions struct {
|
||||
// The default cache size is a zero-size cache.
|
||||
Cache *cache.Cache
|
||||
|
||||
// LoadBlockSema, if set, is used to limit the number of blocks that can be
|
||||
// loaded (i.e. read from the filesystem) in parallel. Each load acquires one
|
||||
// unit from the semaphore for the duration of the read.
|
||||
LoadBlockSema *fifo.Semaphore
|
||||
|
||||
// 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 +127,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 +149,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 +239,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.
|
||||
|
||||
275
vendor/github.com/cockroachdb/pebble/sstable/properties.go
generated
vendored
275
vendor/github.com/cockroachdb/pebble/sstable/properties.go
generated
vendored
@@ -23,20 +23,22 @@ 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() {
|
||||
t := reflect.TypeOf(Properties{})
|
||||
func generateTagMaps(t reflect.Type, indexPrefix []int) {
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
f := t.Field(i)
|
||||
if f.Type.Kind() == reflect.Struct {
|
||||
if tag := f.Tag.Get("prop"); i == 0 && tag == "pebble.embbeded_common_properties" {
|
||||
// CommonProperties struct embedded in Properties. Note that since
|
||||
// CommonProperties is placed at the top of properties we can use
|
||||
// the offsets of the fields within CommonProperties to determine
|
||||
// the offsets of those fields within Properties.
|
||||
generateTagMaps(f.Type, []int{i})
|
||||
continue
|
||||
}
|
||||
panic("pebble: unknown struct type in Properties")
|
||||
}
|
||||
if tag := f.Tag.Get("prop"); tag != "" {
|
||||
switch f.Type.Kind() {
|
||||
case reflect.Bool:
|
||||
@@ -46,86 +48,125 @@ func init() {
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported property field type: %s %s", f.Name, f.Type))
|
||||
}
|
||||
if len(indexPrefix) > 0 {
|
||||
// Prepend the index prefix so that we can use FieldByIndex on the top-level struct.
|
||||
f.Index = append(indexPrefix[:len(indexPrefix):len(indexPrefix)], f.Index...)
|
||||
}
|
||||
propTagMap[tag] = f
|
||||
propOffsetTagMap[f.Offset] = tag
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Properties holds the sstable property values. The properties are
|
||||
// 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"`
|
||||
// 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.
|
||||
NumDataBlocks uint64 `prop:"rocksdb.num.data.blocks"`
|
||||
func init() {
|
||||
generateTagMaps(reflect.TypeOf(Properties{}), nil)
|
||||
}
|
||||
|
||||
// CommonProperties holds properties for either a virtual or a physical sstable. This
|
||||
// can be used by code which doesn't care to make the distinction between physical
|
||||
// and virtual sstables properties.
|
||||
//
|
||||
// For virtual sstables, fields are constructed through extrapolation upon virtual
|
||||
// reader construction. See MakeVirtualReader for implementation details.
|
||||
//
|
||||
// NB: The values of these properties can affect correctness. For example,
|
||||
// if NumRangeKeySets == 0, but the sstable actually contains range keys, then
|
||||
// the iterators will behave incorrectly.
|
||||
type CommonProperties struct {
|
||||
// The number of entries in this table.
|
||||
NumEntries uint64 `prop:"rocksdb.num.entries"`
|
||||
// Total raw key size.
|
||||
RawKeySize uint64 `prop:"rocksdb.raw.key.size"`
|
||||
// Total raw value size.
|
||||
RawValueSize uint64 `prop:"rocksdb.raw.value.size"`
|
||||
// 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"`
|
||||
// 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 deletion entries in this table, including both point and
|
||||
// range deletions.
|
||||
NumDeletions uint64 `prop:"rocksdb.deleted.keys"`
|
||||
// The number of entries in this table.
|
||||
NumEntries uint64 `prop:"rocksdb.num.entries"`
|
||||
// The number of merge operands in the table.
|
||||
NumMergeOperands uint64 `prop:"rocksdb.merge.operands"`
|
||||
// The number of range deletions in this table.
|
||||
NumRangeDeletions uint64 `prop:"rocksdb.num.range-deletions"`
|
||||
// The number of RANGEKEYDELs in this table.
|
||||
NumRangeKeyDels uint64 `prop:"pebble.num.range-key-dels"`
|
||||
// The number of RANGEKEYSETs in this table.
|
||||
NumRangeKeySets uint64 `prop:"pebble.num.range-key-sets"`
|
||||
// Total size of value blocks and value index block. Only serialized if > 0.
|
||||
ValueBlocksSize uint64 `prop:"pebble.value-blocks.size"`
|
||||
}
|
||||
|
||||
// String is only used for testing purposes.
|
||||
func (c *CommonProperties) String() string {
|
||||
var buf bytes.Buffer
|
||||
v := reflect.ValueOf(*c)
|
||||
loaded := make(map[uintptr]struct{})
|
||||
writeProperties(loaded, v, &buf)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// NumPointDeletions is the number of point deletions in the sstable. For virtual
|
||||
// sstables, this is an estimate.
|
||||
func (c *CommonProperties) NumPointDeletions() uint64 {
|
||||
return c.NumDeletions - c.NumRangeDeletions
|
||||
}
|
||||
|
||||
// Properties holds the sstable property values. The properties are
|
||||
// automatically populated during sstable creation and load from the properties
|
||||
// meta block when an sstable is opened.
|
||||
type Properties struct {
|
||||
// CommonProperties needs to be at the top of the Properties struct so that the
|
||||
// offsets of the fields in CommonProperties match the offsets of the embedded
|
||||
// fields of CommonProperties in Properties.
|
||||
CommonProperties `prop:"pebble.embbeded_common_properties"`
|
||||
|
||||
// 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 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"`
|
||||
// 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"`
|
||||
// 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"`
|
||||
// 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"`
|
||||
// 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.
|
||||
NumDataBlocks uint64 `prop:"rocksdb.num.data.blocks"`
|
||||
// The number of merge operands in the table.
|
||||
NumMergeOperands uint64 `prop:"rocksdb.merge.operands"`
|
||||
// The number of RANGEKEYUNSETs in this table.
|
||||
NumRangeKeyUnsets uint64 `prop:"pebble.num.range-key-unsets"`
|
||||
// The number of value blocks in this table. Only serialized if > 0.
|
||||
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"`
|
||||
@@ -134,17 +175,10 @@ type Properties struct {
|
||||
// A comma separated list of names of the property collectors used in this
|
||||
// table.
|
||||
PropertyCollectorNames string `prop:"rocksdb.property.collectors"`
|
||||
// Total raw key size.
|
||||
RawKeySize uint64 `prop:"rocksdb.raw.key.size"`
|
||||
// Total raw key size of point deletion tombstones. This value is comparable
|
||||
// to RawKeySize.
|
||||
RawPointTombstoneKeySize uint64 `prop:"pebble.raw.point-tombstone.key.size"`
|
||||
// Total raw rangekey key size.
|
||||
RawRangeKeyKeySize uint64 `prop:"pebble.raw.range-key.key.size"`
|
||||
// Total raw rangekey value size.
|
||||
RawRangeKeyValueSize uint64 `prop:"pebble.raw.range-key.value.size"`
|
||||
// Total raw value size.
|
||||
RawValueSize uint64 `prop:"rocksdb.raw.value.size"`
|
||||
// The total number of keys in this table that were pinned by open snapshots.
|
||||
SnapshotPinnedKeys uint64 `prop:"pebble.num.snapshot-pinned-keys"`
|
||||
// The cumulative bytes of keys in this table that were pinned by
|
||||
@@ -157,8 +191,6 @@ type Properties struct {
|
||||
TopLevelIndexSize uint64 `prop:"rocksdb.top-level.index.size"`
|
||||
// User collected properties.
|
||||
UserProperties map[string]string
|
||||
// Total size of value blocks and value index block. Only serialized if > 0.
|
||||
ValueBlocksSize uint64 `prop:"pebble.value-blocks.size"`
|
||||
// If filtering is enabled, was the filter created on the whole key.
|
||||
WholeKeyFiltering bool `prop:"rocksdb.block.based.table.whole.key.filtering"`
|
||||
|
||||
@@ -179,12 +211,15 @@ func (p *Properties) NumRangeKeys() uint64 {
|
||||
return p.NumRangeKeyDels + p.NumRangeKeySets + p.NumRangeKeyUnsets
|
||||
}
|
||||
|
||||
func (p *Properties) String() string {
|
||||
var buf bytes.Buffer
|
||||
v := reflect.ValueOf(*p)
|
||||
func writeProperties(loaded map[uintptr]struct{}, v reflect.Value, buf *bytes.Buffer) {
|
||||
vt := v.Type()
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
ft := vt.Field(i)
|
||||
if ft.Type.Kind() == reflect.Struct {
|
||||
// Embedded struct within the properties.
|
||||
writeProperties(loaded, v.Field(i), buf)
|
||||
continue
|
||||
}
|
||||
tag := ft.Tag.Get("prop")
|
||||
if tag == "" {
|
||||
continue
|
||||
@@ -194,30 +229,33 @@ func (p *Properties) String() string {
|
||||
// TODO(peter): Use f.IsZero() when we can rely on go1.13.
|
||||
if zero := reflect.Zero(f.Type()); zero.Interface() == f.Interface() {
|
||||
// Skip printing of zero values which were not loaded from disk.
|
||||
if _, ok := p.Loaded[ft.Offset]; !ok {
|
||||
if _, ok := loaded[ft.Offset]; !ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(&buf, "%s: ", tag)
|
||||
fmt.Fprintf(buf, "%s: ", tag)
|
||||
switch ft.Type.Kind() {
|
||||
case reflect.Bool:
|
||||
fmt.Fprintf(&buf, "%t\n", f.Bool())
|
||||
fmt.Fprintf(buf, "%t\n", f.Bool())
|
||||
case reflect.Uint32:
|
||||
fmt.Fprintf(&buf, "%d\n", f.Uint())
|
||||
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())
|
||||
fmt.Fprintf(buf, "%s\n", f.String())
|
||||
default:
|
||||
panic("not reached")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Properties) String() string {
|
||||
var buf bytes.Buffer
|
||||
v := reflect.ValueOf(*p)
|
||||
writeProperties(p.Loaded, v, &buf)
|
||||
|
||||
// Write the UserProperties.
|
||||
keys := make([]string, 0, len(p.UserProperties))
|
||||
for key := range p.UserProperties {
|
||||
keys = append(keys, key)
|
||||
@@ -229,16 +267,18 @@ 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
|
||||
}
|
||||
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 +288,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 +304,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 +349,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 +358,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 +410,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 +429,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)
|
||||
|
||||
3407
vendor/github.com/cockroachdb/pebble/sstable/reader.go
generated
vendored
3407
vendor/github.com/cockroachdb/pebble/sstable/reader.go
generated
vendored
File diff suppressed because it is too large
Load Diff
291
vendor/github.com/cockroachdb/pebble/sstable/reader_iter.go
generated
vendored
Normal file
291
vendor/github.com/cockroachdb/pebble/sstable/reader_iter.go
generated
vendored
Normal file
@@ -0,0 +1,291 @@
|
||||
// Copyright 2011 The LevelDB-Go and Pebble Authors. All rights reserved. Use
|
||||
// of this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
|
||||
package sstable
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/cockroachdb/pebble/internal/base"
|
||||
"github.com/cockroachdb/pebble/internal/invariants"
|
||||
)
|
||||
|
||||
// Iterator iterates over an entire table of data.
|
||||
type Iterator interface {
|
||||
base.InternalIterator
|
||||
|
||||
// NextPrefix implements (base.InternalIterator).NextPrefix.
|
||||
NextPrefix(succKey []byte) (*InternalKey, base.LazyValue)
|
||||
|
||||
// MaybeFilteredKeys may be called when an iterator is exhausted to indicate
|
||||
// whether or not the last positioning method may have skipped any keys due
|
||||
// to block-property filters. This is used by the Pebble levelIter to
|
||||
// control when an iterator steps to the next sstable.
|
||||
//
|
||||
// MaybeFilteredKeys may always return false positives, that is it may
|
||||
// return true when no keys were filtered. It should only be called when the
|
||||
// iterator is exhausted. It must never return false negatives when the
|
||||
// iterator is exhausted.
|
||||
MaybeFilteredKeys() bool
|
||||
|
||||
SetCloseHook(fn func(i Iterator) error)
|
||||
}
|
||||
|
||||
// Iterator positioning optimizations and singleLevelIterator and
|
||||
// twoLevelIterator:
|
||||
//
|
||||
// An iterator is absolute positioned using one of the Seek or First or Last
|
||||
// calls. After absolute positioning, there can be relative positioning done
|
||||
// by stepping using Prev or Next.
|
||||
//
|
||||
// We implement optimizations below where an absolute positioning call can in
|
||||
// some cases use the current position to do less work. To understand these,
|
||||
// we first define some terms. An iterator is bounds-exhausted if the bounds
|
||||
// (upper of lower) have been reached. An iterator is data-exhausted if it has
|
||||
// the reached the end of the data (forward or reverse) in the sstable. A
|
||||
// singleLevelIterator only knows a local-data-exhausted property since when
|
||||
// it is used as part of a twoLevelIterator, the twoLevelIterator can step to
|
||||
// the next lower-level index block.
|
||||
//
|
||||
// The bounds-exhausted property is tracked by
|
||||
// singleLevelIterator.exhaustedBounds being +1 (upper bound reached) or -1
|
||||
// (lower bound reached). The same field is reused by twoLevelIterator. Either
|
||||
// may notice the exhaustion of the bound and set it. Note that if
|
||||
// singleLevelIterator sets this property, it is not a local property (since
|
||||
// the bound has been reached regardless of whether this is in the context of
|
||||
// the twoLevelIterator or not).
|
||||
//
|
||||
// The data-exhausted property is tracked in a more subtle manner. We define
|
||||
// two predicates:
|
||||
// - partial-local-data-exhausted (PLDE):
|
||||
// i.data.isDataInvalidated() || !i.data.valid()
|
||||
// - partial-global-data-exhausted (PGDE):
|
||||
// i.index.isDataInvalidated() || !i.index.valid() || i.data.isDataInvalidated() ||
|
||||
// !i.data.valid()
|
||||
//
|
||||
// PLDE is defined for a singleLevelIterator. PGDE is defined for a
|
||||
// twoLevelIterator. Oddly, in our code below the singleLevelIterator does not
|
||||
// know when it is part of a twoLevelIterator so it does not know when its
|
||||
// property is local or global.
|
||||
//
|
||||
// Now to define data-exhausted:
|
||||
// - Prerequisite: we must know that the iterator has been positioned and
|
||||
// i.err is nil.
|
||||
// - bounds-exhausted must not be true:
|
||||
// If bounds-exhausted is true, we have incomplete knowledge of
|
||||
// data-exhausted since PLDE or PGDE could be true because we could have
|
||||
// chosen not to load index block or data block and figured out that the
|
||||
// bound is exhausted (due to block property filters filtering out index and
|
||||
// data blocks and going past the bound on the top level index block). Note
|
||||
// that if we tried to separate out the BPF case from others we could
|
||||
// develop more knowledge here.
|
||||
// - PGDE is true for twoLevelIterator. PLDE is true if it is a standalone
|
||||
// singleLevelIterator. !PLDE or !PGDE of course imply that data-exhausted
|
||||
// is not true.
|
||||
//
|
||||
// An implication of the above is that if we are going to somehow utilize
|
||||
// knowledge of data-exhausted in an optimization, we must not forget the
|
||||
// existing value of bounds-exhausted since by forgetting the latter we can
|
||||
// erroneously think that data-exhausted is true. Bug #2036 was due to this
|
||||
// forgetting.
|
||||
//
|
||||
// Now to the two categories of optimizations we currently have:
|
||||
// - Monotonic bounds optimization that reuse prior iterator position when
|
||||
// doing seek: These only work with !data-exhausted. We could choose to make
|
||||
// these work with data-exhausted but have not bothered because in the
|
||||
// context of a DB if data-exhausted were true, the DB would move to the
|
||||
// next file in the level. Note that this behavior of moving to the next
|
||||
// file is not necessarily true for L0 files, so there could be some benefit
|
||||
// in the future in this optimization. See the WARNING-data-exhausted
|
||||
// comments if trying to optimize this in the future.
|
||||
// - TrySeekUsingNext optimizations: these work regardless of exhaustion
|
||||
// state.
|
||||
//
|
||||
// Implementation detail: In the code PLDE only checks that
|
||||
// i.data.isDataInvalidated(). This narrower check is safe, since this is a
|
||||
// subset of the set expressed by the OR expression. Also, it is not a
|
||||
// de-optimization since whenever we exhaust the iterator we explicitly call
|
||||
// i.data.invalidate(). PGDE checks i.index.isDataInvalidated() &&
|
||||
// i.data.isDataInvalidated(). Again, this narrower check is safe, and not a
|
||||
// de-optimization since whenever we exhaust the iterator we explicitly call
|
||||
// i.index.invalidate() and i.data.invalidate(). The && is questionable -- for
|
||||
// now this is a bit of defensive code. We should seriously consider removing
|
||||
// it, since defensive code suggests we are not confident about our invariants
|
||||
// (and if we are not confident, we need more invariant assertions, not
|
||||
// defensive code).
|
||||
//
|
||||
// TODO(sumeer): remove the aforementioned defensive code.
|
||||
|
||||
var singleLevelIterPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
i := &singleLevelIterator{}
|
||||
// Note: this is a no-op if invariants are disabled or race is enabled.
|
||||
invariants.SetFinalizer(i, checkSingleLevelIterator)
|
||||
return i
|
||||
},
|
||||
}
|
||||
|
||||
var twoLevelIterPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
i := &twoLevelIterator{}
|
||||
// Note: this is a no-op if invariants are disabled or race is enabled.
|
||||
invariants.SetFinalizer(i, checkTwoLevelIterator)
|
||||
return i
|
||||
},
|
||||
}
|
||||
|
||||
// TODO(jackson): rangedel fragmentBlockIters can't be pooled because of some
|
||||
// code paths that double Close the iters. Fix the double close and pool the
|
||||
// *fragmentBlockIter type directly.
|
||||
|
||||
var rangeKeyFragmentBlockIterPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
i := &rangeKeyFragmentBlockIter{}
|
||||
// Note: this is a no-op if invariants are disabled or race is enabled.
|
||||
invariants.SetFinalizer(i, checkRangeKeyFragmentBlockIterator)
|
||||
return i
|
||||
},
|
||||
}
|
||||
|
||||
func checkSingleLevelIterator(obj interface{}) {
|
||||
i := obj.(*singleLevelIterator)
|
||||
if p := i.data.handle.Get(); p != nil {
|
||||
fmt.Fprintf(os.Stderr, "singleLevelIterator.data.handle is not nil: %p\n", p)
|
||||
os.Exit(1)
|
||||
}
|
||||
if p := i.index.handle.Get(); p != nil {
|
||||
fmt.Fprintf(os.Stderr, "singleLevelIterator.index.handle is not nil: %p\n", p)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func checkTwoLevelIterator(obj interface{}) {
|
||||
i := obj.(*twoLevelIterator)
|
||||
if p := i.data.handle.Get(); p != nil {
|
||||
fmt.Fprintf(os.Stderr, "singleLevelIterator.data.handle is not nil: %p\n", p)
|
||||
os.Exit(1)
|
||||
}
|
||||
if p := i.index.handle.Get(); p != nil {
|
||||
fmt.Fprintf(os.Stderr, "singleLevelIterator.index.handle is not nil: %p\n", p)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func checkRangeKeyFragmentBlockIterator(obj interface{}) {
|
||||
i := obj.(*rangeKeyFragmentBlockIter)
|
||||
if p := i.blockIter.handle.Get(); p != nil {
|
||||
fmt.Fprintf(os.Stderr, "fragmentBlockIter.blockIter.handle is not nil: %p\n", p)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// compactionIterator is similar to Iterator but it increments the number of
|
||||
// bytes that have been iterated through.
|
||||
type compactionIterator struct {
|
||||
*singleLevelIterator
|
||||
bytesIterated *uint64
|
||||
prevOffset uint64
|
||||
}
|
||||
|
||||
// compactionIterator implements the base.InternalIterator interface.
|
||||
var _ base.InternalIterator = (*compactionIterator)(nil)
|
||||
|
||||
func (i *compactionIterator) String() string {
|
||||
if i.vState != nil {
|
||||
return i.vState.fileNum.String()
|
||||
}
|
||||
return i.reader.fileNum.String()
|
||||
}
|
||||
|
||||
func (i *compactionIterator) SeekGE(
|
||||
key []byte, flags base.SeekGEFlags,
|
||||
) (*InternalKey, base.LazyValue) {
|
||||
panic("pebble: SeekGE unimplemented")
|
||||
}
|
||||
|
||||
func (i *compactionIterator) SeekPrefixGE(
|
||||
prefix, key []byte, flags base.SeekGEFlags,
|
||||
) (*base.InternalKey, base.LazyValue) {
|
||||
panic("pebble: SeekPrefixGE unimplemented")
|
||||
}
|
||||
|
||||
func (i *compactionIterator) SeekLT(
|
||||
key []byte, flags base.SeekLTFlags,
|
||||
) (*InternalKey, base.LazyValue) {
|
||||
panic("pebble: SeekLT unimplemented")
|
||||
}
|
||||
|
||||
func (i *compactionIterator) First() (*InternalKey, base.LazyValue) {
|
||||
i.err = nil // clear cached iteration error
|
||||
return i.skipForward(i.singleLevelIterator.First())
|
||||
}
|
||||
|
||||
func (i *compactionIterator) Last() (*InternalKey, base.LazyValue) {
|
||||
panic("pebble: Last unimplemented")
|
||||
}
|
||||
|
||||
// Note: compactionIterator.Next mirrors the implementation of Iterator.Next
|
||||
// due to performance. Keep the two in sync.
|
||||
func (i *compactionIterator) Next() (*InternalKey, base.LazyValue) {
|
||||
if i.err != nil {
|
||||
return nil, base.LazyValue{}
|
||||
}
|
||||
return i.skipForward(i.data.Next())
|
||||
}
|
||||
|
||||
func (i *compactionIterator) NextPrefix(succKey []byte) (*InternalKey, base.LazyValue) {
|
||||
panic("pebble: NextPrefix unimplemented")
|
||||
}
|
||||
|
||||
func (i *compactionIterator) Prev() (*InternalKey, base.LazyValue) {
|
||||
panic("pebble: Prev unimplemented")
|
||||
}
|
||||
|
||||
func (i *compactionIterator) skipForward(
|
||||
key *InternalKey, val base.LazyValue,
|
||||
) (*InternalKey, base.LazyValue) {
|
||||
if key == nil {
|
||||
for {
|
||||
if key, _ := i.index.Next(); key == nil {
|
||||
break
|
||||
}
|
||||
result := i.loadBlock(+1)
|
||||
if result != loadBlockOK {
|
||||
if i.err != nil {
|
||||
break
|
||||
}
|
||||
switch result {
|
||||
case loadBlockFailed:
|
||||
// We checked that i.index was at a valid entry, so
|
||||
// loadBlockFailed could not have happened due to to i.index
|
||||
// being exhausted, and must be due to an error.
|
||||
panic("loadBlock should not have failed with no error")
|
||||
case loadBlockIrrelevant:
|
||||
panic("compactionIter should not be using block intervals for skipping")
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected case %d", result))
|
||||
}
|
||||
}
|
||||
// result == loadBlockOK
|
||||
if key, val = i.data.First(); key != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
curOffset := i.recordOffset()
|
||||
*i.bytesIterated += uint64(curOffset - i.prevOffset)
|
||||
i.prevOffset = curOffset
|
||||
|
||||
if i.vState != nil && key != nil {
|
||||
cmp := i.cmp(key.UserKey, i.vState.upper.UserKey)
|
||||
if cmp > 0 || (i.vState.upper.IsExclusiveSentinel() && cmp == 0) {
|
||||
return nil, base.LazyValue{}
|
||||
}
|
||||
}
|
||||
|
||||
return key, val
|
||||
}
|
||||
1391
vendor/github.com/cockroachdb/pebble/sstable/reader_iter_single_lvl.go
generated
vendored
Normal file
1391
vendor/github.com/cockroachdb/pebble/sstable/reader_iter_single_lvl.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1085
vendor/github.com/cockroachdb/pebble/sstable/reader_iter_two_lvl.go
generated
vendored
Normal file
1085
vendor/github.com/cockroachdb/pebble/sstable/reader_iter_two_lvl.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
206
vendor/github.com/cockroachdb/pebble/sstable/reader_virtual.go
generated
vendored
Normal file
206
vendor/github.com/cockroachdb/pebble/sstable/reader_virtual.go
generated
vendored
Normal file
@@ -0,0 +1,206 @@
|
||||
// Copyright 2011 The LevelDB-Go and Pebble Authors. All rights reserved. Use
|
||||
// of this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
|
||||
package sstable
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cockroachdb/pebble/internal/base"
|
||||
"github.com/cockroachdb/pebble/internal/keyspan"
|
||||
"github.com/cockroachdb/pebble/internal/manifest"
|
||||
)
|
||||
|
||||
// VirtualReader wraps Reader. Its purpose is to restrict functionality of the
|
||||
// Reader which should be inaccessible to virtual sstables, and enforce bounds
|
||||
// invariants associated with virtual sstables. All reads on virtual sstables
|
||||
// should go through a VirtualReader.
|
||||
//
|
||||
// INVARIANT: Any iterators created through a virtual reader will guarantee that
|
||||
// they don't expose keys outside the virtual sstable bounds.
|
||||
type VirtualReader struct {
|
||||
vState virtualState
|
||||
reader *Reader
|
||||
Properties CommonProperties
|
||||
}
|
||||
|
||||
// Lightweight virtual sstable state which can be passed to sstable iterators.
|
||||
type virtualState struct {
|
||||
lower InternalKey
|
||||
upper InternalKey
|
||||
fileNum base.FileNum
|
||||
Compare Compare
|
||||
isForeign bool
|
||||
}
|
||||
|
||||
func ceilDiv(a, b uint64) uint64 {
|
||||
return (a + b - 1) / b
|
||||
}
|
||||
|
||||
// MakeVirtualReader is used to contruct a reader which can read from virtual
|
||||
// sstables.
|
||||
func MakeVirtualReader(
|
||||
reader *Reader, meta manifest.VirtualFileMeta, isForeign bool,
|
||||
) VirtualReader {
|
||||
if reader.fileNum != meta.FileBacking.DiskFileNum {
|
||||
panic("pebble: invalid call to MakeVirtualReader")
|
||||
}
|
||||
|
||||
vState := virtualState{
|
||||
lower: meta.Smallest,
|
||||
upper: meta.Largest,
|
||||
fileNum: meta.FileNum,
|
||||
Compare: reader.Compare,
|
||||
isForeign: isForeign,
|
||||
}
|
||||
v := VirtualReader{
|
||||
vState: vState,
|
||||
reader: reader,
|
||||
}
|
||||
|
||||
v.Properties.RawKeySize = ceilDiv(reader.Properties.RawKeySize*meta.Size, meta.FileBacking.Size)
|
||||
v.Properties.RawValueSize = ceilDiv(reader.Properties.RawValueSize*meta.Size, meta.FileBacking.Size)
|
||||
v.Properties.NumEntries = ceilDiv(reader.Properties.NumEntries*meta.Size, meta.FileBacking.Size)
|
||||
v.Properties.NumDeletions = ceilDiv(reader.Properties.NumDeletions*meta.Size, meta.FileBacking.Size)
|
||||
v.Properties.NumRangeDeletions = ceilDiv(reader.Properties.NumRangeDeletions*meta.Size, meta.FileBacking.Size)
|
||||
v.Properties.NumRangeKeyDels = ceilDiv(reader.Properties.NumRangeKeyDels*meta.Size, meta.FileBacking.Size)
|
||||
|
||||
// Note that we rely on NumRangeKeySets for correctness. If the sstable may
|
||||
// contain range keys, then NumRangeKeySets must be > 0. ceilDiv works because
|
||||
// meta.Size will not be 0 for virtual sstables.
|
||||
v.Properties.NumRangeKeySets = ceilDiv(reader.Properties.NumRangeKeySets*meta.Size, meta.FileBacking.Size)
|
||||
v.Properties.ValueBlocksSize = ceilDiv(reader.Properties.ValueBlocksSize*meta.Size, meta.FileBacking.Size)
|
||||
v.Properties.NumSizedDeletions = ceilDiv(reader.Properties.NumSizedDeletions*meta.Size, meta.FileBacking.Size)
|
||||
v.Properties.RawPointTombstoneKeySize = ceilDiv(reader.Properties.RawPointTombstoneKeySize*meta.Size, meta.FileBacking.Size)
|
||||
v.Properties.RawPointTombstoneValueSize = ceilDiv(reader.Properties.RawPointTombstoneValueSize*meta.Size, meta.FileBacking.Size)
|
||||
return v
|
||||
}
|
||||
|
||||
// NewCompactionIter is the compaction iterator function for virtual readers.
|
||||
func (v *VirtualReader) NewCompactionIter(
|
||||
bytesIterated *uint64, rp ReaderProvider, bufferPool *BufferPool,
|
||||
) (Iterator, error) {
|
||||
return v.reader.newCompactionIter(bytesIterated, rp, &v.vState, bufferPool)
|
||||
}
|
||||
|
||||
// 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) NewIterWithBlockPropertyFiltersAndContextEtc(
|
||||
ctx context.Context,
|
||||
lower, upper []byte,
|
||||
filterer *BlockPropertiesFilterer,
|
||||
hideObsoletePoints, useFilterBlock bool,
|
||||
stats *base.InternalIteratorStats,
|
||||
rp ReaderProvider,
|
||||
) (Iterator, error) {
|
||||
return v.reader.newIterWithBlockPropertyFiltersAndContext(
|
||||
ctx, lower, upper, filterer, hideObsoletePoints, useFilterBlock, stats, rp, &v.vState,
|
||||
)
|
||||
}
|
||||
|
||||
// ValidateBlockChecksumsOnBacking will call ValidateBlockChecksumsOnBacking on the underlying reader.
|
||||
// Note that block checksum validation is NOT restricted to virtual sstable bounds.
|
||||
func (v *VirtualReader) ValidateBlockChecksumsOnBacking() error {
|
||||
return v.reader.ValidateBlockChecksums()
|
||||
}
|
||||
|
||||
// NewRawRangeDelIter wraps Reader.NewRawRangeDelIter.
|
||||
func (v *VirtualReader) NewRawRangeDelIter() (keyspan.FragmentIterator, error) {
|
||||
iter, err := v.reader.NewRawRangeDelIter()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if iter == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Truncation of spans isn't allowed at a user key that also contains points
|
||||
// in the same virtual sstable, as it would lead to covered points getting
|
||||
// uncovered. Set panicOnUpperTruncate to true if the file's upper bound
|
||||
// is not an exclusive sentinel.
|
||||
//
|
||||
// As an example, if an sstable contains a rangedel a-c and point keys at
|
||||
// a.SET.2 and b.SET.3, the file bounds [a#2,SET-b#RANGEDELSENTINEL] are
|
||||
// allowed (as they exclude b.SET.3), or [a#2,SET-c#RANGEDELSENTINEL] (as it
|
||||
// includes both point keys), but not [a#2,SET-b#3,SET] (as it would truncate
|
||||
// the rangedel at b and lead to the point being uncovered).
|
||||
return keyspan.Truncate(
|
||||
v.reader.Compare, iter, v.vState.lower.UserKey, v.vState.upper.UserKey,
|
||||
&v.vState.lower, &v.vState.upper, !v.vState.upper.IsExclusiveSentinel(), /* panicOnUpperTruncate */
|
||||
), nil
|
||||
}
|
||||
|
||||
// NewRawRangeKeyIter wraps Reader.NewRawRangeKeyIter.
|
||||
func (v *VirtualReader) NewRawRangeKeyIter() (keyspan.FragmentIterator, error) {
|
||||
iter, err := v.reader.NewRawRangeKeyIter()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if iter == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Truncation of spans isn't allowed at a user key that also contains points
|
||||
// in the same virtual sstable, as it would lead to covered points getting
|
||||
// uncovered. Set panicOnUpperTruncate to true if the file's upper bound
|
||||
// is not an exclusive sentinel.
|
||||
//
|
||||
// As an example, if an sstable contains a range key a-c and point keys at
|
||||
// a.SET.2 and b.SET.3, the file bounds [a#2,SET-b#RANGEKEYSENTINEL] are
|
||||
// allowed (as they exclude b.SET.3), or [a#2,SET-c#RANGEKEYSENTINEL] (as it
|
||||
// includes both point keys), but not [a#2,SET-b#3,SET] (as it would truncate
|
||||
// the range key at b and lead to the point being uncovered).
|
||||
return keyspan.Truncate(
|
||||
v.reader.Compare, iter, v.vState.lower.UserKey, v.vState.upper.UserKey,
|
||||
&v.vState.lower, &v.vState.upper, !v.vState.upper.IsExclusiveSentinel(), /* panicOnUpperTruncate */
|
||||
), nil
|
||||
}
|
||||
|
||||
// Constrain bounds will narrow the start, end bounds if they do not fit within
|
||||
// the virtual sstable. The function will return if the new end key is
|
||||
// inclusive.
|
||||
func (v *virtualState) constrainBounds(
|
||||
start, end []byte, endInclusive bool,
|
||||
) (lastKeyInclusive bool, first []byte, last []byte) {
|
||||
first = start
|
||||
if start == nil || v.Compare(start, v.lower.UserKey) < 0 {
|
||||
first = v.lower.UserKey
|
||||
}
|
||||
|
||||
// Note that we assume that start, end has some overlap with the virtual
|
||||
// sstable bounds.
|
||||
last = v.upper.UserKey
|
||||
lastKeyInclusive = !v.upper.IsExclusiveSentinel()
|
||||
if end != nil {
|
||||
cmp := v.Compare(end, v.upper.UserKey)
|
||||
switch {
|
||||
case cmp == 0:
|
||||
lastKeyInclusive = !v.upper.IsExclusiveSentinel() && endInclusive
|
||||
last = v.upper.UserKey
|
||||
case cmp > 0:
|
||||
lastKeyInclusive = !v.upper.IsExclusiveSentinel()
|
||||
last = v.upper.UserKey
|
||||
default:
|
||||
lastKeyInclusive = endInclusive
|
||||
last = end
|
||||
}
|
||||
}
|
||||
// TODO(bananabrick): What if someone passes in bounds completely outside of
|
||||
// virtual sstable bounds?
|
||||
return lastKeyInclusive, first, last
|
||||
}
|
||||
|
||||
// EstimateDiskUsage just calls VirtualReader.reader.EstimateDiskUsage after
|
||||
// enforcing the virtual sstable bounds.
|
||||
func (v *VirtualReader) EstimateDiskUsage(start, end []byte) (uint64, error) {
|
||||
_, f, l := v.vState.constrainBounds(start, end, true /* endInclusive */)
|
||||
return v.reader.EstimateDiskUsage(f, l)
|
||||
}
|
||||
|
||||
// CommonProperties implements the CommonReader interface.
|
||||
func (v *VirtualReader) CommonProperties() *CommonProperties {
|
||||
return &v.Properties
|
||||
}
|
||||
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 (
|
||||
|
||||
20
vendor/github.com/cockroachdb/pebble/sstable/value_block.go
generated
vendored
20
vendor/github.com/cockroachdb/pebble/sstable/value_block.go
generated
vendored
@@ -13,7 +13,6 @@ import (
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/cockroachdb/pebble/internal/base"
|
||||
"github.com/cockroachdb/pebble/internal/cache"
|
||||
"github.com/cockroachdb/pebble/internal/invariants"
|
||||
"github.com/cockroachdb/pebble/objstorage/objstorageprovider/objiotracing"
|
||||
"golang.org/x/exp/rand"
|
||||
@@ -728,7 +727,7 @@ func (ukb *UserKeyPrefixBound) IsEmpty() bool {
|
||||
type blockProviderWhenOpen interface {
|
||||
readBlockForVBR(
|
||||
ctx context.Context, h BlockHandle, stats *base.InternalIteratorStats,
|
||||
) (cache.Handle, error)
|
||||
) (bufferHandle, error)
|
||||
}
|
||||
|
||||
type blockProviderWhenClosed struct {
|
||||
@@ -749,9 +748,12 @@ func (bpwc *blockProviderWhenClosed) close() {
|
||||
|
||||
func (bpwc blockProviderWhenClosed) readBlockForVBR(
|
||||
ctx context.Context, h BlockHandle, stats *base.InternalIteratorStats,
|
||||
) (cache.Handle, error) {
|
||||
) (bufferHandle, error) {
|
||||
ctx = objiotracing.WithBlockType(ctx, objiotracing.ValueBlock)
|
||||
return bpwc.r.readBlock(ctx, h, nil, nil, stats)
|
||||
// TODO(jackson,sumeer): Consider whether to use a buffer pool in this case.
|
||||
// The bpwc is not allowed to outlive the iterator tree, so it cannot
|
||||
// outlive the buffer pool.
|
||||
return bpwc.r.readBlock(ctx, h, nil, nil, stats, nil /* buffer pool */)
|
||||
}
|
||||
|
||||
// ReaderProvider supports the implementation of blockProviderWhenClosed.
|
||||
@@ -790,16 +792,16 @@ type valueBlockReader struct {
|
||||
// The value blocks index is lazily retrieved the first time the reader
|
||||
// needs to read a value that resides in a value block.
|
||||
vbiBlock []byte
|
||||
vbiCache cache.Handle
|
||||
vbiCache bufferHandle
|
||||
// When sequentially iterating through all key-value pairs, the cost of
|
||||
// repeatedly getting a block that is already in the cache and releasing the
|
||||
// cache.Handle can be ~40% of the cpu overhead. So the reader remembers the
|
||||
// bufferHandle can be ~40% of the cpu overhead. So the reader remembers the
|
||||
// last value block it retrieved, in case there is locality of access, and
|
||||
// this value block can be used for the next value retrieval.
|
||||
valueBlockNum uint32
|
||||
valueBlock []byte
|
||||
valueBlockPtr unsafe.Pointer
|
||||
valueCache cache.Handle
|
||||
valueCache bufferHandle
|
||||
lazyFetcher base.LazyFetcher
|
||||
closed bool
|
||||
bufToMangle []byte
|
||||
@@ -833,12 +835,12 @@ func (r *valueBlockReader) close() {
|
||||
// we were to reopen this valueBlockReader and retrieve the same
|
||||
// Handle.value from the cache, we don't want to accidentally unref it when
|
||||
// attempting to unref the old handle.
|
||||
r.vbiCache = cache.Handle{}
|
||||
r.vbiCache = bufferHandle{}
|
||||
r.valueBlock = nil
|
||||
r.valueBlockPtr = nil
|
||||
r.valueCache.Release()
|
||||
// See comment above.
|
||||
r.valueCache = cache.Handle{}
|
||||
r.valueCache = bufferHandle{}
|
||||
r.closed = true
|
||||
// rp, vbih, stats remain valid, so that LazyFetcher.ValueFetcher can be
|
||||
// implemented.
|
||||
|
||||
311
vendor/github.com/cockroachdb/pebble/sstable/writer.go
generated
vendored
311
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,63 @@ func (w *Writer) makeAddPointDecisionV3(
|
||||
} else {
|
||||
cmpUser = w.compare(prevPointUserKey, key.UserKey)
|
||||
}
|
||||
// Ensure that no one adds a point key kind without considering the obsolete
|
||||
// handling for that kind.
|
||||
switch keyKind {
|
||||
case InternalKeyKindSet, InternalKeyKindSetWithDelete, InternalKeyKindMerge,
|
||||
InternalKeyKindDelete, InternalKeyKindSingleDelete, InternalKeyKindDeleteSized:
|
||||
default:
|
||||
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. When the previous key is a MERGE we must
|
||||
// preserve SET* and MERGE since their values will be merged into the
|
||||
// previous key. We also must preserve DEL* since there may be an older
|
||||
// SET*/MERGE in a lower level that must not be merged with the MERGE --
|
||||
// if we omit the DEL* that lower SET*/MERGE will become visible.
|
||||
//
|
||||
// Regardless of whether it is the same user key or not
|
||||
// C3 The current key is some kind of point delete, and we are writing to
|
||||
// the lowest level, then it is also obsolete. The correctness of this
|
||||
// relies on the same user key not spanning multiple sstables in a level.
|
||||
//
|
||||
// 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 C3. forceObsolete must only be set due a RANGEDEL, and that RANGEDEL
|
||||
// must also delete all the lower seqnums for the same user key. C3 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 non-MERGE key.
|
||||
isObsoleteC1AndC2 := cmpUser == 0 &&
|
||||
(prevPointKeyInfo.isObsolete || prevKeyKind != InternalKeyKindMerge)
|
||||
isObsoleteC3 := w.writingToLowestLevel &&
|
||||
(keyKind == InternalKeyKindDelete || keyKind == InternalKeyKindSingleDelete ||
|
||||
keyKind == InternalKeyKindDeleteSized)
|
||||
isObsolete = isObsoleteC1AndC2 || isObsoleteC3
|
||||
// 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 +921,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 +946,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 +1003,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 +1031,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 +1067,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 +1263,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 +1409,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 +1699,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 +1843,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 +1903,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 +1917,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 +1996,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 +2185,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 +2214,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 +2282,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 +2302,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 +2326,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 +2366,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