Upgrade versions of vendored libs

This commit is contained in:
eleksir
2023-06-14 01:06:17 +03:00
parent 37898d3013
commit 747ff1454e
120 changed files with 5126 additions and 1245 deletions

View File

@@ -47,7 +47,10 @@ type blockWriter struct {
// ensuring that any of the keys with the same prefix can be used to
// assemble the full key when the prefix does change.
restarts []uint32
curKey []byte
// Do not read curKey directly from outside blockWriter since it can have
// the InternalKeyKindSSTableInternalObsoleteBit set. Use getCurKey() or
// getCurUserKey() instead.
curKey []byte
// curValue excludes the optional prefix provided to
// storeWithOptionalValuePrefix.
curValue []byte
@@ -79,6 +82,20 @@ const setHasSameKeyPrefixRestartMask uint32 = 1 << 31
const restartMaskLittleEndianHighByteWithoutSetHasSamePrefix byte = 0b0111_1111
const restartMaskLittleEndianHighByteOnlySetHasSamePrefix byte = 0b1000_0000
func (w *blockWriter) getCurKey() InternalKey {
k := base.DecodeInternalKey(w.curKey)
k.Trailer = k.Trailer & trailerObsoleteMask
return k
}
func (w *blockWriter) getCurUserKey() []byte {
n := len(w.curKey) - base.InternalTrailerLen
if n < 0 {
panic(errors.AssertionFailedf("corrupt key in blockWriter buffer"))
}
return w.curKey[:n:n]
}
// If !addValuePrefix, the valuePrefix is ignored.
func (w *blockWriter) storeWithOptionalValuePrefix(
keySize int,
@@ -191,12 +208,21 @@ func (w *blockWriter) storeWithOptionalValuePrefix(
func (w *blockWriter) add(key InternalKey, value []byte) {
w.addWithOptionalValuePrefix(
key, value, len(key.UserKey), false, 0, false)
key, false, value, len(key.UserKey), false, 0, false)
}
// Callers that always set addValuePrefix to false should use add() instead.
//
// isObsolete indicates whether this key-value pair is obsolete in this
// sstable (only applicable when writing data blocks) -- see the comment in
// table.go and the longer one in format.go. addValuePrefix adds a 1 byte
// prefix to the value, specified in valuePrefix -- this is used for data
// blocks in TableFormatPebblev3 onwards for SETs (see the comment in
// format.go, with more details in value_block.go). setHasSameKeyPrefix is
// also used in TableFormatPebblev3 onwards for SETs.
func (w *blockWriter) addWithOptionalValuePrefix(
key InternalKey,
isObsolete bool,
value []byte,
maxSharedKeyLen int,
addValuePrefix bool,
@@ -210,6 +236,9 @@ func (w *blockWriter) addWithOptionalValuePrefix(
w.curKey = make([]byte, 0, size*2)
}
w.curKey = w.curKey[:size]
if isObsolete {
key.Trailer = key.Trailer | trailerObsoleteBit
}
key.Encode(w.curKey)
w.storeWithOptionalValuePrefix(
@@ -374,13 +403,14 @@ type blockIter struct {
cached []blockEntry
cachedBuf []byte
cacheHandle cache.Handle
// The first key in the block. This is used by the caller to set bounds
// The first user key in the block. This is used by the caller to set bounds
// for block iteration for already loaded blocks.
firstKey InternalKey
firstUserKey []byte
lazyValueHandling struct {
vbr *valueBlockReader
hasValuePrefix bool
}
hideObsoletePoints bool
}
// blockIter implements the base.InternalIterator interface.
@@ -388,14 +418,16 @@ var _ base.InternalIterator = (*blockIter)(nil)
func newBlockIter(cmp Compare, block block) (*blockIter, error) {
i := &blockIter{}
return i, i.init(cmp, block, 0)
return i, i.init(cmp, block, 0, false)
}
func (i *blockIter) String() string {
return "block"
}
func (i *blockIter) init(cmp Compare, block block, globalSeqNum uint64) error {
func (i *blockIter) init(
cmp Compare, block block, globalSeqNum uint64, hideObsoletePoints bool,
) error {
numRestarts := int32(binary.LittleEndian.Uint32(block[len(block)-4:]))
if numRestarts == 0 {
return base.CorruptionErrorf("pebble/table: invalid table (block has no restart points)")
@@ -408,6 +440,7 @@ func (i *blockIter) init(cmp Compare, block block, globalSeqNum uint64) error {
i.data = block
i.fullKey = i.fullKey[:0]
i.val = nil
i.hideObsoletePoints = hideObsoletePoints
i.clearCache()
if i.restarts > 0 {
if err := i.readFirstKey(); err != nil {
@@ -415,15 +448,21 @@ func (i *blockIter) init(cmp Compare, block block, globalSeqNum uint64) error {
}
} else {
// Block is empty.
i.firstKey = InternalKey{}
i.firstUserKey = nil
}
return nil
}
func (i *blockIter) initHandle(cmp Compare, block cache.Handle, globalSeqNum uint64) error {
// NB: two cases of hideObsoletePoints:
// - Local sstable iteration: globalSeqNum will be set iff the sstable was
// ingested.
// - Foreign sstable iteration: globalSeqNum is always set.
func (i *blockIter) initHandle(
cmp Compare, block cache.Handle, globalSeqNum uint64, hideObsoletePoints bool,
) error {
i.cacheHandle.Release()
i.cacheHandle = block
return i.init(cmp, block.Get(), globalSeqNum)
return i.init(cmp, block.Get(), globalSeqNum, hideObsoletePoints)
}
func (i *blockIter) invalidate() {
@@ -590,24 +629,27 @@ func (i *blockIter) readFirstKey() error {
// Manually inlining base.DecodeInternalKey provides a 5-10% speedup on
// BlockIter benchmarks.
if n := len(firstKey) - 8; n >= 0 {
i.firstKey.Trailer = binary.LittleEndian.Uint64(firstKey[n:])
i.firstKey.UserKey = firstKey[:n:n]
if i.globalSeqNum != 0 {
i.firstKey.SetSeqNum(i.globalSeqNum)
}
i.firstUserKey = firstKey[:n:n]
} else {
i.firstKey.Trailer = uint64(InternalKeyKindInvalid)
i.firstKey.UserKey = nil
i.firstUserKey = nil
return base.CorruptionErrorf("pebble/table: invalid firstKey in block")
}
return nil
}
func (i *blockIter) decodeInternalKey(key []byte) {
// The sstable internal obsolete bit is set when writing a block and unset by
// blockIter, so no code outside block writing/reading code ever sees it.
const trailerObsoleteBit = uint64(base.InternalKeyKindSSTableInternalObsoleteBit)
const trailerObsoleteMask = (InternalKeySeqNumMax << 8) | uint64(base.InternalKeyKindSSTableInternalObsoleteMask)
func (i *blockIter) decodeInternalKey(key []byte) (hiddenPoint bool) {
// Manually inlining base.DecodeInternalKey provides a 5-10% speedup on
// BlockIter benchmarks.
if n := len(key) - 8; n >= 0 {
i.ikey.Trailer = binary.LittleEndian.Uint64(key[n:])
trailer := binary.LittleEndian.Uint64(key[n:])
hiddenPoint = i.hideObsoletePoints &&
(trailer&trailerObsoleteBit != 0)
i.ikey.Trailer = trailer & trailerObsoleteMask
i.ikey.UserKey = key[:n:n]
if i.globalSeqNum != 0 {
i.ikey.SetSeqNum(i.globalSeqNum)
@@ -616,6 +658,7 @@ func (i *blockIter) decodeInternalKey(key []byte) {
i.ikey.Trailer = uint64(InternalKeyKindInvalid)
i.ikey.UserKey = nil
}
return hiddenPoint
}
func (i *blockIter) clearCache() {
@@ -640,13 +683,15 @@ func (i *blockIter) cacheEntry() {
i.cachedBuf = append(i.cachedBuf, i.key...)
}
func (i *blockIter) getFirstUserKey() []byte {
return i.firstUserKey
}
// SeekGE implements internalIterator.SeekGE, as documented in the pebble
// package.
func (i *blockIter) SeekGE(key []byte, flags base.SeekGEFlags) (*InternalKey, base.LazyValue) {
i.clearCache()
ikey := base.MakeSearchKey(key)
// Find the index of the smallest restart point whose key is > the key
// sought; index will be numRestarts if there is no such restart point.
i.offset = 0
@@ -700,23 +745,23 @@ func (i *blockIter) SeekGE(key []byte, flags base.SeekGEFlags) (*InternalKey, ba
ptr = unsafe.Pointer(uintptr(ptr) + 5)
}
// Manually inlining base.DecodeInternalKey provides a 5-10% speedup on
// BlockIter benchmarks.
// Manually inlining part of base.DecodeInternalKey provides a 5-10%
// speedup on BlockIter benchmarks.
s := getBytes(ptr, int(v1))
var k InternalKey
var k []byte
if n := len(s) - 8; n >= 0 {
k.Trailer = binary.LittleEndian.Uint64(s[n:])
k.UserKey = s[:n:n]
// NB: We can't have duplicate keys if the globalSeqNum != 0, so we
// leave the seqnum on this key as 0 as it won't affect our search
// since ikey has the maximum seqnum.
} else {
k.Trailer = uint64(InternalKeyKindInvalid)
k = s[:n:n]
}
// Else k is invalid, and left as nil
if base.InternalCompare(i.cmp, ikey, k) >= 0 {
if i.cmp(key, k) > 0 {
// The search key is greater than the user key at this restart point.
// Search beyond this restart point, since we are trying to find the
// first restart point with a user key >= the search key.
index = h + 1 // preserves f(i-1) == false
} else {
// k >= search key, so prune everything after index (since index
// satisfies the property we are looking for).
upper = h // preserves f(j) == true
}
}
@@ -724,21 +769,25 @@ func (i *blockIter) SeekGE(key []byte, flags base.SeekGEFlags) (*InternalKey, ba
// => answer is index.
}
// Since keys are strictly increasing, if index > 0 then the restart point at
// index-1 will be the largest whose key is <= the key sought. If index ==
// 0, then all keys in this block are larger than the key sought, and offset
// remains at zero.
// index is the first restart point with key >= search key. Define the keys
// between a restart point and the next restart point as belonging to that
// restart point.
//
// Since keys are strictly increasing, if index > 0 then the restart point
// at index-1 will be the first one that has some keys belonging to it that
// could be equal to the search key. If index == 0, then all keys in this
// block are larger than the key sought, and offset remains at zero.
if index > 0 {
i.offset = decodeRestart(i.data[i.restarts+4*(index-1):])
}
i.readEntry()
i.decodeInternalKey(i.key)
hiddenPoint := i.decodeInternalKey(i.key)
// Iterate from that restart point to somewhere >= the key sought.
if !i.valid() {
return nil, base.LazyValue{}
}
if base.InternalCompare(i.cmp, i.ikey, ikey) >= 0 {
if !hiddenPoint && i.cmp(i.ikey.UserKey, key) >= 0 {
// Initialize i.lazyValue
if !i.lazyValueHandling.hasValuePrefix ||
base.TrailerKind(i.ikey.Trailer) != InternalKeyKindSet {
@@ -751,7 +800,7 @@ func (i *blockIter) SeekGE(key []byte, flags base.SeekGEFlags) (*InternalKey, ba
return &i.ikey, i.lazyValue
}
for i.Next(); i.valid(); i.Next() {
if base.InternalCompare(i.cmp, i.ikey, ikey) >= 0 {
if i.cmp(i.ikey.UserKey, key) >= 0 {
// i.Next() has already initialized i.lazyValue.
return &i.ikey, i.lazyValue
}
@@ -773,8 +822,6 @@ func (i *blockIter) SeekPrefixGE(
func (i *blockIter) SeekLT(key []byte, flags base.SeekLTFlags) (*InternalKey, base.LazyValue) {
i.clearCache()
ikey := base.MakeSearchKey(key)
// Find the index of the smallest restart point whose key is >= the key
// sought; index will be numRestarts if there is no such restart point.
i.offset = 0
@@ -828,23 +875,23 @@ func (i *blockIter) SeekLT(key []byte, flags base.SeekLTFlags) (*InternalKey, ba
ptr = unsafe.Pointer(uintptr(ptr) + 5)
}
// Manually inlining base.DecodeInternalKey provides a 5-10% speedup on
// BlockIter benchmarks.
// Manually inlining part of base.DecodeInternalKey provides a 5-10%
// speedup on BlockIter benchmarks.
s := getBytes(ptr, int(v1))
var k InternalKey
var k []byte
if n := len(s) - 8; n >= 0 {
k.Trailer = binary.LittleEndian.Uint64(s[n:])
k.UserKey = s[:n:n]
// NB: We can't have duplicate keys if the globalSeqNum != 0, so we
// leave the seqnum on this key as 0 as it won't affect our search
// since ikey has the maximum seqnum.
} else {
k.Trailer = uint64(InternalKeyKindInvalid)
k = s[:n:n]
}
// Else k is invalid, and left as nil
if base.InternalCompare(i.cmp, ikey, k) > 0 {
if i.cmp(key, k) > 0 {
// The search key is greater than the user key at this restart point.
// Search beyond this restart point, since we are trying to find the
// first restart point with a user key >= the search key.
index = h + 1 // preserves f(i-1) == false
} else {
// k >= search key, so prune everything after index (since index
// satisfies the property we are looking for).
upper = h // preserves f(j) == true
}
}
@@ -852,8 +899,15 @@ func (i *blockIter) SeekLT(key []byte, flags base.SeekLTFlags) (*InternalKey, ba
// => answer is index.
}
// Since keys are strictly increasing, if index > 0 then the restart point at
// index-1 will be the largest whose key is < the key sought.
// index is the first restart point with key >= search key. Define the keys
// between a restart point and the next restart point as belonging to that
// restart point. Note that index could be equal to i.numRestarts, i.e., we
// are past the last restart.
//
// Since keys are strictly increasing, if index > 0 then the restart point
// at index-1 will be the first one that has some keys belonging to it that
// are less than the search key. If index == 0, then all keys in this block
// are larger than the search key, so there is no match.
targetOffset := i.restarts
if index > 0 {
i.offset = decodeRestart(i.data[i.restarts+4*(index-1):])
@@ -876,24 +930,41 @@ func (i *blockIter) SeekLT(key []byte, flags base.SeekLTFlags) (*InternalKey, ba
for {
i.offset = i.nextOffset
i.readEntry()
i.decodeInternalKey(i.key)
// When hidden keys are common, there is additional optimization possible
// by not caching entries that are hidden (note that some calls to
// cacheEntry don't decode the internal key before caching, but checking
// whether a key is hidden does not require full decoding). However, we do
// need to use the blockEntry.offset in the cache for the first entry at
// the reset point to do the binary search when the cache is empty -- so
// we would need to cache that first entry (though not the key) even if
// was hidden. Our current assumption is that if there are large numbers
// of hidden keys we will be able to skip whole blocks (using block
// property filters) so we don't bother optimizing.
hiddenPoint := i.decodeInternalKey(i.key)
if i.cmp(i.ikey.UserKey, ikey.UserKey) >= 0 {
// NB: we don't use the hiddenPoint return value of decodeInternalKey
// since we want to stop as soon as we reach a key >= ikey.UserKey, so
// that we can reverse.
if i.cmp(i.ikey.UserKey, key) >= 0 {
// The current key is greater than or equal to our search key. Back up to
// the previous key which was less than our search key. Note that this for
// loop will execute at least once with this if-block not being true, so
// the key we are backing up to is the last one this loop cached.
i.Prev()
// i.Prev() has already initialized i.lazyValue.
return &i.ikey, i.lazyValue
return i.Prev()
}
if i.nextOffset >= targetOffset {
// We've reached the end of the current restart block. Return the current
// key. When the restart interval is 1, the first iteration of the for
// loop will bring us here. In that case ikey is backed by the block so
// we get the desired key stability guarantee for the lifetime of the
// blockIter.
// We've reached the end of the current restart block. Return the
// current key if not hidden, else call Prev().
//
// When the restart interval is 1, the first iteration of the for loop
// will bring us here. In that case ikey is backed by the block so we
// get the desired key stability guarantee for the lifetime of the
// blockIter. That is, we never cache anything and therefore never
// return a key backed by cachedBuf.
if hiddenPoint {
return i.Prev()
}
break
}
@@ -923,7 +994,10 @@ func (i *blockIter) First() (*InternalKey, base.LazyValue) {
}
i.clearCache()
i.readEntry()
i.decodeInternalKey(i.key)
hiddenPoint := i.decodeInternalKey(i.key)
if hiddenPoint {
return i.Next()
}
if !i.lazyValueHandling.hasValuePrefix ||
base.TrailerKind(i.ikey.Trailer) != InternalKeyKindSet {
i.lazyValue = base.MakeInPlaceValue(i.val)
@@ -958,7 +1032,10 @@ func (i *blockIter) Last() (*InternalKey, base.LazyValue) {
i.readEntry()
}
i.decodeInternalKey(i.key)
hiddenPoint := i.decodeInternalKey(i.key)
if hiddenPoint {
return i.Prev()
}
if !i.lazyValueHandling.hasValuePrefix ||
base.TrailerKind(i.ikey.Trailer) != InternalKeyKindSet {
i.lazyValue = base.MakeInPlaceValue(i.val)
@@ -988,6 +1065,7 @@ func (i *blockIter) Next() (*InternalKey, base.LazyValue) {
i.clearCache()
}
start:
i.offset = i.nextOffset
if !i.valid() {
return nil, base.LazyValue{}
@@ -995,11 +1073,17 @@ func (i *blockIter) Next() (*InternalKey, base.LazyValue) {
i.readEntry()
// Manually inlined version of i.decodeInternalKey(i.key).
if n := len(i.key) - 8; n >= 0 {
i.ikey.Trailer = binary.LittleEndian.Uint64(i.key[n:])
trailer := binary.LittleEndian.Uint64(i.key[n:])
hiddenPoint := i.hideObsoletePoints &&
(trailer&trailerObsoleteBit != 0)
i.ikey.Trailer = trailer & trailerObsoleteMask
i.ikey.UserKey = i.key[:n:n]
if i.globalSeqNum != 0 {
i.ikey.SetSeqNum(i.globalSeqNum)
}
if hiddenPoint {
goto start
}
} else {
i.ikey.Trailer = uint64(InternalKeyKindInvalid)
i.ikey.UserKey = nil
@@ -1015,8 +1099,8 @@ func (i *blockIter) Next() (*InternalKey, base.LazyValue) {
return &i.ikey, i.lazyValue
}
// nextPrefix is used for implementing NPrefix.
func (i *blockIter) nextPrefix(succKey []byte) (*InternalKey, base.LazyValue) {
// NextPrefix implements (base.InternalIterator).NextPrefix.
func (i *blockIter) NextPrefix(succKey []byte) (*InternalKey, base.LazyValue) {
if i.lazyValueHandling.hasValuePrefix {
return i.nextPrefixV3(succKey)
}
@@ -1141,6 +1225,7 @@ func (i *blockIter) nextPrefixV3(succKey []byte) (*InternalKey, base.LazyValue)
// The trailer is written in little endian, so the key kind is the first
// byte in the trailer that is encoded in the slice [unshared-8:unshared].
keyKind := InternalKeyKind((*[manual.MaxArrayLen]byte)(ptr)[unshared-8])
keyKind = keyKind & base.InternalKeyKindSSTableInternalObsoleteMask
prefixChanged := false
if keyKind == InternalKeyKindSet {
if invariants.Enabled && value == 0 {
@@ -1256,8 +1341,12 @@ func (i *blockIter) nextPrefixV3(succKey []byte) (*InternalKey, base.LazyValue)
i.key = i.fullKey
}
// Manually inlined version of i.decodeInternalKey(i.key).
hiddenPoint := false
if n := len(i.key) - 8; n >= 0 {
i.ikey.Trailer = binary.LittleEndian.Uint64(i.key[n:])
trailer := binary.LittleEndian.Uint64(i.key[n:])
hiddenPoint = i.hideObsoletePoints &&
(trailer&trailerObsoleteBit != 0)
i.ikey.Trailer = trailer & trailerObsoleteMask
i.ikey.UserKey = i.key[:n:n]
if i.globalSeqNum != 0 {
i.ikey.SetSeqNum(i.globalSeqNum)
@@ -1273,6 +1362,9 @@ func (i *blockIter) nextPrefixV3(succKey []byte) (*InternalKey, base.LazyValue)
}
if prefixChanged || i.cmp(i.ikey.UserKey, succKey) >= 0 {
// Prefix has changed.
if hiddenPoint {
return i.Next()
}
if invariants.Enabled && !i.lazyValueHandling.hasValuePrefix {
panic(errors.AssertionFailedf("nextPrefixV3 being run for non-v3 sstable"))
}
@@ -1294,23 +1386,11 @@ func (i *blockIter) nextPrefixV3(succKey []byte) (*InternalKey, base.LazyValue)
return i.SeekGE(succKey, base.SeekGEFlagsNone)
}
// NextPrefix implements (base.InternalIterator).NextPrefix
func (i *blockIter) NextPrefix(succKey []byte) (*InternalKey, base.LazyValue) {
const nextsBeforeSeek = 3
k, v := i.Next()
for j := 1; k != nil && i.cmp(k.UserKey, succKey) < 0; j++ {
if j >= nextsBeforeSeek {
return i.SeekGE(succKey, base.SeekGEFlagsNone)
}
k, v = i.Next()
}
return k, v
}
// Prev implements internalIterator.Prev, as documented in the pebble
// package.
func (i *blockIter) Prev() (*InternalKey, base.LazyValue) {
if n := len(i.cached) - 1; n >= 0 {
start:
for n := len(i.cached) - 1; n >= 0; n-- {
i.nextOffset = i.offset
e := &i.cached[n]
i.offset = e.offset
@@ -1318,7 +1398,13 @@ func (i *blockIter) Prev() (*InternalKey, base.LazyValue) {
// Manually inlined version of i.decodeInternalKey(i.key).
i.key = i.cachedBuf[e.keyStart:e.keyEnd]
if n := len(i.key) - 8; n >= 0 {
i.ikey.Trailer = binary.LittleEndian.Uint64(i.key[n:])
trailer := binary.LittleEndian.Uint64(i.key[n:])
hiddenPoint := i.hideObsoletePoints &&
(trailer&trailerObsoleteBit != 0)
if hiddenPoint {
continue
}
i.ikey.Trailer = trailer & trailerObsoleteMask
i.ikey.UserKey = i.key[:n:n]
if i.globalSeqNum != 0 {
i.ikey.SetSeqNum(i.globalSeqNum)
@@ -1360,6 +1446,8 @@ func (i *blockIter) Prev() (*InternalKey, base.LazyValue) {
// index ≤ h < upper
offset := decodeRestart(i.data[i.restarts+4*h:])
if offset < targetOffset {
// Looking for the first restart that has offset >= targetOffset, so
// ignore h and earlier.
index = h + 1 // preserves f(i-1) == false
} else {
upper = h // preserves f(j) == true
@@ -1369,20 +1457,35 @@ func (i *blockIter) Prev() (*InternalKey, base.LazyValue) {
// => answer is index.
}
// index is first restart with offset >= targetOffset. Note that
// targetOffset may not be at a restart point since one can call Prev()
// after Next() (so the cache was not populated) and targetOffset refers to
// the current entry. index-1 must have an offset < targetOffset (it can't
// be equal to targetOffset since the binary search would have selected that
// as the index).
i.offset = 0
if index > 0 {
i.offset = decodeRestart(i.data[i.restarts+4*(index-1):])
}
// TODO(sumeer): why is the else case not an error given targetOffset is a
// valid offset.
i.readEntry()
// We stop when i.nextOffset == targetOffset since the targetOffset is the
// entry we are stepping back from, and we don't need to cache the entry
// before it, since it is the candidate to return.
for i.nextOffset < targetOffset {
i.cacheEntry()
i.offset = i.nextOffset
i.readEntry()
}
i.decodeInternalKey(i.key)
hiddenPoint := i.decodeInternalKey(i.key)
if hiddenPoint {
// Use the cache.
goto start
}
if !i.lazyValueHandling.hasValuePrefix ||
base.TrailerKind(i.ikey.Trailer) != InternalKeyKindSet {
i.lazyValue = base.MakeInPlaceValue(i.val)
@@ -1455,6 +1558,10 @@ type fragmentBlockIter struct {
err error
dir int8
closeHook func(i keyspan.FragmentIterator) error
// elideSameSeqnum, if true, returns only the first-occurring (in forward
// order) Key for each sequence number.
elideSameSeqnum bool
}
func (i *fragmentBlockIter) resetForReuse() fragmentBlockIter {
@@ -1483,6 +1590,24 @@ func (i *fragmentBlockIter) decodeSpanKeys(k *InternalKey, internalValue []byte)
}
}
func (i *fragmentBlockIter) elideKeysOfSameSeqNum() {
if invariants.Enabled {
if !i.elideSameSeqnum || len(i.span.Keys) == 0 {
panic("elideKeysOfSameSeqNum called when it should not be")
}
}
lastSeqNum := i.span.Keys[0].SeqNum()
k := 1
for j := 1; j < len(i.span.Keys); j++ {
if lastSeqNum != i.span.Keys[j].SeqNum() {
lastSeqNum = i.span.Keys[j].SeqNum()
i.span.Keys[k] = i.span.Keys[j]
k++
}
}
i.span.Keys = i.span.Keys[:k]
}
// gatherForward gathers internal keys with identical bounds. Keys defined over
// spans of the keyspace are fragmented such that any overlapping key spans have
// identical bounds. When these spans are persisted to a range deletion or range
@@ -1532,6 +1657,9 @@ func (i *fragmentBlockIter) gatherForward(k *InternalKey, lazyValue base.LazyVal
k, lazyValue = i.blockIter.Next()
internalValue = lazyValue.InPlaceValue()
}
if i.elideSameSeqnum && len(i.span.Keys) > 0 {
i.elideKeysOfSameSeqNum()
}
// i.blockIter is positioned over the first internal key for the next span.
return &i.span
}
@@ -1591,6 +1719,9 @@ func (i *fragmentBlockIter) gatherBackward(k *InternalKey, lazyValue base.LazyVa
// Backwards iteration encounters internal keys in the wrong order.
keyspan.SortKeysByTrailer(&i.span.Keys)
if i.elideSameSeqnum && len(i.span.Keys) > 0 {
i.elideKeysOfSameSeqNum()
}
return &i.span
}

View File

@@ -117,7 +117,7 @@ type BlockPropertyCollector interface {
}
// SuffixReplaceableBlockCollector is an extension to the BlockPropertyCollector
// interface that allows a block property collector to indicate the it supports
// interface that allows a block property collector to indicate that it supports
// being *updated* during suffix replacement, i.e. when an existing SST in which
// all keys have the same key suffix is updated to have a new suffix.
//

View File

@@ -24,11 +24,161 @@ const (
TableFormatPebblev1 // Block properties.
TableFormatPebblev2 // Range keys.
TableFormatPebblev3 // Value blocks.
TableFormatPebblev4 // Reserved.
TableFormatPebblev4 // DELSIZED tombstones.
TableFormatMax = TableFormatPebblev4
)
// TableFormatPebblev4, in addition to DELSIZED, introduces the use of
// InternalKeyKindSSTableInternalObsoleteBit.
//
// 1. Motivation
//
// We have various related problems caused by Pebble snapshots:
//
// - P1: RANGEDELs that delete points in the same sstable, but the points
// happen to not get deleted during compactions because of an open snapshot.
// This causes very expensive iteration, that has been observed in
// production deployments
//
// - P2: When iterating over a foreign sstable (in disaggregated storage), we
// need to do (a) point collapsing to expose at most one point per user key,
// (b) apply RANGEDELs in the sstable to hide deleted points in the same
// sstable. This per-sstable point collapsing iteration needs to be very
// efficient (ideally as efficient from a CPU perspective as iteration over
// regular sstables) since foreign sstables can be very long-lived -- one of
// the goals of disaggregated storage is to scale compute and disk bandwidth
// resources as a function of the hot (from a write perspective) data and
// not the whole data, so we don't want to have to rewrite foreign sstables
// solely to improve read performance.
//
// The ideal solution for P2 would allow user-facing reads to utilize the
// existing SST iterators (with slight modifications) and with no loss of
// efficiency. And for P1 and P2 we would like to skip whole blocks of
// overwritten/deleted points. Even when we can't skip whole blocks, avoiding
// key comparisons at iteration time to discover what points are deleted is
// very desirable, since keys can be long.
//
// We observe that:
//
// - Reads:
// - All user-facing reads in CockroachDB use iterators over the DB, hence
// have a higher read seqnum than all sstables (there are some rare cases
// that can violate this, but those are not important from a performance
// optimization perspective).
//
// - Certain internal-facing reads in CockroachDB use snapshots, but the
// snapshots are shortlived enough that most L5 and L6 sstables will have
// all seqnums lower than the snapshot seqnum.
//
// - Writes:
// - We already do key comparisons between points when writing the sstable
// to ensure that the sstable invariant (monotonically increasing internal
// keys) is not violated. So we know which points share the same userkey,
// and thereby which points are obsolete because there is a more recent
// point in the same sstable.
//
// - The compactionIter knows which point id deleted by a RANGEDEL even if
// the point does need to be written because of a snapshot.
//
// So this known information can be encoded in the sstable at write time and
// utilized for optimized reading.
//
// 2. Solution
//
// We primarily scope the solution to the following point kinds: SET,
// SETWITHDEL, DEL, DELSIZED, SINGLEDEL. These are the ones marked locally
// obsolete, i.e., obsolete within the sstable, and we can guarantee that at
// most one point will be exposed per user key. MERGE keys create more
// complexity: MERGE followed by MERGE causes multiple keys to not be
// obsolete. Same applies for MERGE followed by SET/SETWITHDEL. Note that:
//
// - For regular sst iteration, the obsolete marking is a performance
// optimization, and multiple keys for the same userkey can be handled by
// higher layers in the iterator tree (specifically pebble.Iterator).
//
// - For foreign sst iteration, we disallow MERGEs to be written to such
// shared ssts (details below).
//
// The key kinds are marked with an obsolete bit
// (InternalKeyKindSSTableInternalObsoleteBit) when the key-value pair is
// obsolete. This marking is done within blockWriter, based on information
// passed to it by Writer. In turn, Writer uses a combination of key
// comparisons, and information provided by compactionIter to decide whether a
// key-value pair is obsolete. Additionally, a Pebble-internal
// BlockPropertyCollector (obsoleteKeyBlockPropertyCollector) is used to mark
// blocks where all key-value pairs are obsolete. Since the common case is
// non-obsolete blocks, this block property collector uses the empty byte
// slice to represent a non-obsolete block, which consumes no space in
// BlockHandleWithProperties.Props.
//
// At read time, the obsolete bit is only visible to the blockIter, which can
// be optionally configured to hide obsolete points. This hiding is only
// configured for data block iterators for sstables being read by user-facing
// iterators at a seqnum greater than the max seqnum in the sstable.
// Additionally, when this hiding is configured, a Pebble-internal block
// property filter (obsoleteKeyBlockPropertyFilter), is used to skip whole
// blocks that are obsolete.
//
// 2.1 Correctness
//
// Due to the level invariant, the sequence of seqnums for a user key in a
// sstable represents a contiguous subsequence of the seqnums for the userkey
// across the whole LSM, and is more recent than the seqnums in a sstable in a
// lower level. So exposing exactly one point from a sstable for a userkey
// will also mask the points for the userkey in lower levels. If we expose no
// point, because of RANGEDELs, that RANGEDEL will also mask the points in
// lower levels.
//
// Note that we do not need to do anything special at write time for
// SETWITHDEL and SINGLEDEL. This is because these key kinds are treated
// specially only by compactions, which do not hide obsolete points. For
// regular reads, SETWITHDEL behaves the same as SET and SINGLEDEL behaves the
// same as DEL.
//
// 2.2 Strictness and MERGE
//
// Setting the obsolete bit on point keys is advanced usage, so we support two
// modes, both of which must be truthful when setting the obsolete bit, but
// vary in when they don't set the obsolete bit.
//
// - Non-strict: In this mode, the bit does not need to be set for keys that
// are obsolete. Additionally, any sstable containing MERGE keys can only
// use this mode. An iterator over such an sstable, when configured to
// hideObsoletePoints, can expose multiple internal keys per user key, and
// can expose keys that are deleted by rangedels in the same sstable. This
// is the mode that non-advanced users should use. Pebble without
// disaggregated storage will also use this mode and will best-effort set
// the obsolete bit, to optimize iteration when snapshots have retained many
// obsolete keys.
//
// - Strict: In this mode, every obsolete key must have the obsolete bit set,
// and no MERGE keys are permitted. An iterator over such an sstable, when
// configured to hideObsoletePoints satisfies two properties:
// - S1: will expose at most one internal key per user key, which is the
// most recent one.
// - S2: will never expose keys that are deleted by rangedels in the same
// sstable.
//
// This is the mode for two use cases in disaggregated storage (which will
// exclude parts of the key space that has MERGEs), for levels that contain
// sstables that can become foreign sstables:
// - Pebble compaction output to these levels that can become foreign
// sstables.
//
// - CockroachDB ingest operations that can ingest into the levels that can
// become foreign sstables. Note, these are not sstables corresponding to
// copied data for CockroachDB range snapshots. This case occurs for
// operations like index backfills: these trivially satisfy the strictness
// criteria since they only write one key per userkey.
//
// TODO(sumeer): this latter case is not currently supported, since only
// Writer.AddWithForceObsolete calls are permitted for writing strict
// obsolete sstables. This is done to reduce the likelihood of bugs. One
// simple way to lift this limitation would be to disallow adding any
// RANGEDELs when a Pebble-external writer is trying to construct a strict
// obsolete sstable.
// ParseTableFormat parses the given magic bytes and version into its
// corresponding internal TableFormat.
func ParseTableFormat(magic []byte, version uint32) (TableFormat, error) {

View File

@@ -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

View File

@@ -21,6 +21,17 @@ const (
NCompression
)
var ignoredInternalProperties = map[string]struct{}{
"rocksdb.column.family.id": {},
"rocksdb.fixed.key.length": {},
"rocksdb.index.key.is.user.key": {},
"rocksdb.index.value.is.delta.encoded": {},
"rocksdb.oldest.key.time": {},
"rocksdb.creation.time": {},
"rocksdb.file.creation.time": {},
"rocksdb.format.version": {},
}
func (c Compression) String() string {
switch c {
case DefaultCompression:
@@ -100,6 +111,9 @@ type ReaderOptions struct {
// The default cache size is a zero-size cache.
Cache *cache.Cache
// User properties specified in this map will not be added to sst.Properties.UserProperties.
DeniedUserProperties map[string]struct{}
// Comparer defines a total ordering over the space of []byte keys: a 'less
// than' relationship. The same comparison algorithm must be used for reads
// and writes over the lifetime of the DB.
@@ -107,6 +121,9 @@ type ReaderOptions struct {
// The default value uses the same ordering as bytes.Compare.
Comparer *Comparer
// Merge defines the Merge function in use for this keyspace.
Merge base.Merge
// Filters is a map from filter policy name to filter policy. It is used for
// debugging tools which may be used on multiple databases configured with
// different filter policies. It is not necessary to populate this filters
@@ -126,12 +143,18 @@ func (o ReaderOptions) ensureDefaults() ReaderOptions {
if o.Comparer == nil {
o.Comparer = base.DefaultComparer
}
if o.Merge == nil {
o.Merge = base.DefaultMerger.Merge
}
if o.MergerName == "" {
o.MergerName = base.DefaultMerger.Name
}
if o.LoggerAndTracer == nil {
o.LoggerAndTracer = base.NoopLoggerAndTracer{}
}
if o.DeniedUserProperties == nil {
o.DeniedUserProperties = ignoredInternalProperties
}
return o
}
@@ -210,6 +233,17 @@ type WriterOptions struct {
// by a wider range of tools and libraries.
TableFormat TableFormat
// IsStrictObsolete is only relevant for >= TableFormatPebblev4. See comment
// in format.go. Must be false if format < TableFormatPebblev4.
//
// TODO(bilal): set this when writing shared ssts.
IsStrictObsolete bool
// WritingToLowestLevel is only relevant for >= TableFormatPebblev4. It is
// used to set the obsolete bit on DEL/DELSIZED/SINGLEDEL if they are the
// youngest for a userkey.
WritingToLowestLevel bool
// TablePropertyCollectors is a list of TablePropertyCollector creation
// functions. A new TablePropertyCollector is created for each sstable built
// and lives for the lifetime of the table.

View File

@@ -23,14 +23,6 @@ var propTagMap = make(map[string]reflect.StructField)
var propBoolTrue = []byte{'1'}
var propBoolFalse = []byte{'0'}
var columnFamilyIDField = func() reflect.StructField {
f, ok := reflect.TypeOf(Properties{}).FieldByName("ColumnFamilyID")
if !ok {
panic("Properties.ColumnFamilyID field not found")
}
return f
}()
var propOffsetTagMap = make(map[uintptr]string)
func init() {
@@ -56,51 +48,35 @@ func init() {
// automatically populated during sstable creation and load from the properties
// meta block when an sstable is opened.
type Properties struct {
// ID of column family for this SST file, corresponding to the CF identified
// by column_family_name.
ColumnFamilyID uint64 `prop:"rocksdb.column.family.id"`
// Name of the column family with which this SST file is associated. Empty if
// the column family is unknown.
ColumnFamilyName string `prop:"rocksdb.column.family.name"`
// The name of the comparer used in this table.
ComparerName string `prop:"rocksdb.comparator"`
// The compression algorithm used to compress blocks.
CompressionName string `prop:"rocksdb.compression"`
// The compression options used to compress blocks.
CompressionOptions string `prop:"rocksdb.compression_options"`
// The time when the SST file was created. Since SST files are immutable,
// this is equivalent to last modified time.
CreationTime uint64 `prop:"rocksdb.creation.time"`
// The total size of all data blocks.
DataSize uint64 `prop:"rocksdb.data.size"`
// The external sstable version format. Version 2 is the one RocksDB has been
// using since 5.13. RocksDB only uses the global sequence number for an
// sstable if this property has been set.
ExternalFormatVersion uint32 `prop:"rocksdb.external_sst_file.version"`
// Actual SST file creation time. 0 means unknown.
FileCreationTime uint64 `prop:"rocksdb.file.creation.time"`
// The name of the filter policy used in this table. Empty if no filter
// policy is used.
FilterPolicyName string `prop:"rocksdb.filter.policy"`
// The size of filter block.
FilterSize uint64 `prop:"rocksdb.filter.size"`
// If 0, key is variable length. Otherwise number of bytes for each key.
FixedKeyLen uint64 `prop:"rocksdb.fixed.key.length"`
// Format version, reserved for backward compatibility.
FormatVersion uint64 `prop:"rocksdb.format.version"`
// The global sequence number to use for all entries in the table. Present if
// the table was created externally and ingested whole.
GlobalSeqNum uint64 `prop:"rocksdb.external_sst_file.global_seqno"`
// Whether the index key is user key or an internal key.
IndexKeyIsUserKey uint64 `prop:"rocksdb.index.key.is.user.key"`
// Total number of index partitions if kTwoLevelIndexSearch is used.
IndexPartitions uint64 `prop:"rocksdb.index.partitions"`
// The size of index block.
IndexSize uint64 `prop:"rocksdb.index.size"`
// The index type. TODO(peter): add a more detailed description.
IndexType uint32 `prop:"rocksdb.block.based.table.index.type"`
// Whether delta encoding is used to encode the index values.
IndexValueIsDeltaEncoded uint64 `prop:"rocksdb.index.value.is.delta.encoded"`
// For formats >= TableFormatPebblev4, this is set to true if the obsolete
// bit is strict for all the point keys.
IsStrictObsolete bool `prop:"pebble.obsolete.is_strict"`
// The name of the merger used in this table. Empty if no merger is used.
MergerName string `prop:"rocksdb.merge.operator"`
// The number of blocks in this table.
@@ -108,6 +84,9 @@ type Properties struct {
// The number of deletion entries in this table, including both point and
// range deletions.
NumDeletions uint64 `prop:"rocksdb.deleted.keys"`
// The number of point deletion entries ("tombstones") in this table that
// carry a size hint indicating the size of the value the tombstone deletes.
NumSizedDeletions uint64 `prop:"pebble.num.deletions.sized"`
// The number of entries in this table.
NumEntries uint64 `prop:"rocksdb.num.entries"`
// The number of merge operands in the table.
@@ -124,8 +103,6 @@ type Properties struct {
NumValueBlocks uint64 `prop:"pebble.num.value-blocks"`
// The number of values stored in value blocks. Only serialized if > 0.
NumValuesInValueBlocks uint64 `prop:"pebble.num.values.in.value-blocks"`
// Timestamp of the earliest key. 0 if unknown.
OldestKeyTime uint64 `prop:"rocksdb.oldest.key.time"`
// The name of the prefix extractor used in this table. Empty if no prefix
// extractor is used.
PrefixExtractorName string `prop:"rocksdb.prefix.extractor.name"`
@@ -139,6 +116,10 @@ type Properties struct {
// Total raw key size of point deletion tombstones. This value is comparable
// to RawKeySize.
RawPointTombstoneKeySize uint64 `prop:"pebble.raw.point-tombstone.key.size"`
// Sum of the raw value sizes carried by point deletion tombstones
// containing size estimates. See the DeleteSized key kind. This value is
// comparable to Raw{Key,Value}Size.
RawPointTombstoneValueSize uint64 `prop:"pebble.raw.point-tombstone.value.size"`
// Total raw rangekey key size.
RawRangeKeyKeySize uint64 `prop:"pebble.raw.range-key.key.size"`
// Total raw rangekey value size.
@@ -206,12 +187,7 @@ func (p *Properties) String() string {
case reflect.Uint32:
fmt.Fprintf(&buf, "%d\n", f.Uint())
case reflect.Uint64:
u := f.Uint()
if ft.Offset == columnFamilyIDField.Offset && u == math.MaxInt32 {
fmt.Fprintf(&buf, "-\n")
} else {
fmt.Fprintf(&buf, "%d\n", f.Uint())
}
fmt.Fprintf(&buf, "%d\n", f.Uint())
case reflect.String:
fmt.Fprintf(&buf, "%s\n", f.String())
default:
@@ -229,7 +205,9 @@ func (p *Properties) String() string {
return buf.String()
}
func (p *Properties) load(b block, blockOffset uint64) error {
func (p *Properties) load(
b block, blockOffset uint64, deniedUserProperties map[string]struct{},
) error {
i, err := newRawBlockIter(bytes.Compare, b)
if err != nil {
return err
@@ -237,8 +215,7 @@ func (p *Properties) load(b block, blockOffset uint64) error {
p.Loaded = make(map[uintptr]struct{})
v := reflect.ValueOf(p).Elem()
for valid := i.First(); valid; valid = i.Next() {
tag := intern.Bytes(i.Key().UserKey)
if f, ok := propTagMap[tag]; ok {
if f, ok := propTagMap[string(i.Key().UserKey)]; ok {
p.Loaded[f.Offset] = struct{}{}
field := v.FieldByIndex(f.Index)
switch f.Type.Kind() {
@@ -248,7 +225,7 @@ func (p *Properties) load(b block, blockOffset uint64) error {
field.SetUint(uint64(binary.LittleEndian.Uint32(i.Value())))
case reflect.Uint64:
var n uint64
if tag == propGlobalSeqnumName {
if string(i.Key().UserKey) == propGlobalSeqnumName {
n = binary.LittleEndian.Uint64(i.Value())
} else {
n, _ = binary.Uvarint(i.Value())
@@ -264,7 +241,10 @@ func (p *Properties) load(b block, blockOffset uint64) error {
if p.UserProperties == nil {
p.UserProperties = make(map[string]string)
}
p.UserProperties[tag] = string(i.Value())
if _, denied := deniedUserProperties[string(i.Key().UserKey)]; !denied {
p.UserProperties[intern.Bytes(i.Key().UserKey)] = string(i.Value())
}
}
return nil
}
@@ -306,10 +286,6 @@ func (p *Properties) save(tblFormat TableFormat, w *rawBlockWriter) {
m[k] = []byte(v)
}
p.saveUvarint(m, unsafe.Offsetof(p.ColumnFamilyID), p.ColumnFamilyID)
if p.ColumnFamilyName != "" {
p.saveString(m, unsafe.Offsetof(p.ColumnFamilyName), p.ColumnFamilyName)
}
if p.ComparerName != "" {
p.saveString(m, unsafe.Offsetof(p.ComparerName), p.ComparerName)
}
@@ -319,44 +295,45 @@ func (p *Properties) save(tblFormat TableFormat, w *rawBlockWriter) {
if p.CompressionOptions != "" {
p.saveString(m, unsafe.Offsetof(p.CompressionOptions), p.CompressionOptions)
}
p.saveUvarint(m, unsafe.Offsetof(p.CreationTime), p.CreationTime)
p.saveUvarint(m, unsafe.Offsetof(p.DataSize), p.DataSize)
if p.ExternalFormatVersion != 0 {
p.saveUint32(m, unsafe.Offsetof(p.ExternalFormatVersion), p.ExternalFormatVersion)
p.saveUint64(m, unsafe.Offsetof(p.GlobalSeqNum), p.GlobalSeqNum)
}
if p.FileCreationTime > 0 {
p.saveUvarint(m, unsafe.Offsetof(p.FileCreationTime), p.FileCreationTime)
}
if p.FilterPolicyName != "" {
p.saveString(m, unsafe.Offsetof(p.FilterPolicyName), p.FilterPolicyName)
}
p.saveUvarint(m, unsafe.Offsetof(p.FilterSize), p.FilterSize)
p.saveUvarint(m, unsafe.Offsetof(p.FixedKeyLen), p.FixedKeyLen)
p.saveUvarint(m, unsafe.Offsetof(p.FormatVersion), p.FormatVersion)
p.saveUvarint(m, unsafe.Offsetof(p.IndexKeyIsUserKey), p.IndexKeyIsUserKey)
if p.IndexPartitions != 0 {
p.saveUvarint(m, unsafe.Offsetof(p.IndexPartitions), p.IndexPartitions)
p.saveUvarint(m, unsafe.Offsetof(p.TopLevelIndexSize), p.TopLevelIndexSize)
}
p.saveUvarint(m, unsafe.Offsetof(p.IndexSize), p.IndexSize)
p.saveUint32(m, unsafe.Offsetof(p.IndexType), p.IndexType)
p.saveUvarint(m, unsafe.Offsetof(p.IndexValueIsDeltaEncoded), p.IndexValueIsDeltaEncoded)
if p.IsStrictObsolete {
p.saveBool(m, unsafe.Offsetof(p.IsStrictObsolete), p.IsStrictObsolete)
}
if p.MergerName != "" {
p.saveString(m, unsafe.Offsetof(p.MergerName), p.MergerName)
}
p.saveUvarint(m, unsafe.Offsetof(p.NumDataBlocks), p.NumDataBlocks)
p.saveUvarint(m, unsafe.Offsetof(p.NumEntries), p.NumEntries)
p.saveUvarint(m, unsafe.Offsetof(p.NumDeletions), p.NumDeletions)
if p.NumSizedDeletions > 0 {
p.saveUvarint(m, unsafe.Offsetof(p.NumSizedDeletions), p.NumSizedDeletions)
}
p.saveUvarint(m, unsafe.Offsetof(p.NumMergeOperands), p.NumMergeOperands)
p.saveUvarint(m, unsafe.Offsetof(p.NumRangeDeletions), p.NumRangeDeletions)
// NB: We only write out the RawTombstoneKeySize for Pebble formats. This
// isn't strictly necessary because unrecognized properties are interpreted
// as user-defined properties, however writing them prevents byte-for-byte
// NB: We only write out some properties for Pebble formats. This isn't
// strictly necessary because unrecognized properties are interpreted as
// user-defined properties, however writing them prevents byte-for-byte
// equivalence with RocksDB files that some of our testing requires.
if p.RawPointTombstoneKeySize > 0 && tblFormat >= TableFormatPebblev1 {
p.saveUvarint(m, unsafe.Offsetof(p.RawPointTombstoneKeySize), p.RawPointTombstoneKeySize)
}
if p.RawPointTombstoneValueSize > 0 {
p.saveUvarint(m, unsafe.Offsetof(p.RawPointTombstoneValueSize), p.RawPointTombstoneValueSize)
}
if p.NumRangeKeys() > 0 {
p.saveUvarint(m, unsafe.Offsetof(p.NumRangeKeyDels), p.NumRangeKeyDels)
p.saveUvarint(m, unsafe.Offsetof(p.NumRangeKeySets), p.NumRangeKeySets)
@@ -370,7 +347,6 @@ func (p *Properties) save(tblFormat TableFormat, w *rawBlockWriter) {
if p.NumValuesInValueBlocks > 0 {
p.saveUvarint(m, unsafe.Offsetof(p.NumValuesInValueBlocks), p.NumValuesInValueBlocks)
}
p.saveUvarint(m, unsafe.Offsetof(p.OldestKeyTime), p.OldestKeyTime)
if p.PrefixExtractorName != "" {
p.saveString(m, unsafe.Offsetof(p.PrefixExtractorName), p.PrefixExtractorName)
}
@@ -390,6 +366,16 @@ func (p *Properties) save(tblFormat TableFormat, w *rawBlockWriter) {
}
p.saveBool(m, unsafe.Offsetof(p.WholeKeyFiltering), p.WholeKeyFiltering)
if tblFormat < TableFormatPebblev1 {
m["rocksdb.column.family.id"] = binary.AppendUvarint([]byte(nil), math.MaxInt32)
m["rocksdb.fixed.key.length"] = []byte{0x00}
m["rocksdb.index.key.is.user.key"] = []byte{0x00}
m["rocksdb.index.value.is.delta.encoded"] = []byte{0x00}
m["rocksdb.oldest.key.time"] = []byte{0x00}
m["rocksdb.creation.time"] = []byte{0x00}
m["rocksdb.format.version"] = []byte{0x00}
}
keys := make([]string, 0, len(m))
for key := range m {
keys = append(keys, key)

View File

@@ -323,6 +323,8 @@ type singleLevelIterator struct {
// is high).
useFilter bool
lastBloomFilterMatched bool
hideObsoletePoints bool
}
// singleLevelIterator implements the base.InternalIterator interface.
@@ -404,7 +406,7 @@ func (i *singleLevelIterator) init(
v *virtualState,
lower, upper []byte,
filterer *BlockPropertiesFilterer,
useFilter bool,
useFilter, hideObsoletePoints bool,
stats *base.InternalIteratorStats,
rp ReaderProvider,
) error {
@@ -428,7 +430,8 @@ func (i *singleLevelIterator) init(
i.reader = r
i.cmp = r.Compare
i.stats = stats
err = i.index.initHandle(i.cmp, indexH, r.Properties.GlobalSeqNum)
i.hideObsoletePoints = hideObsoletePoints
err = i.index.initHandle(i.cmp, indexH, r.Properties.GlobalSeqNum, false)
if err != nil {
// blockIter.Close releases indexH and always returns a nil error
_ = i.index.Close()
@@ -464,9 +467,9 @@ func (i *singleLevelIterator) init(
// setupForCompaction sets up the singleLevelIterator for use with compactionIter.
// Currently, it skips readahead ramp-up. It should be called after init is called.
func (i *singleLevelIterator) setupForCompaction() {
i.dataRH.MaxReadahead()
i.dataRH.SetupForCompaction()
if i.vbRH != nil {
i.vbRH.MaxReadahead()
i.vbRH.SetupForCompaction()
}
}
@@ -566,7 +569,7 @@ func (i *singleLevelIterator) loadBlock(dir int8) loadBlockResult {
i.err = err
return loadBlockFailed
}
i.err = i.data.initHandle(i.cmp, block, i.reader.Properties.GlobalSeqNum)
i.err = i.data.initHandle(i.cmp, block, i.reader.Properties.GlobalSeqNum, i.hideObsoletePoints)
if i.err != nil {
// The block is partially loaded, and we don't want it to appear valid.
i.data.invalidate()
@@ -653,12 +656,13 @@ func (i *singleLevelIterator) resolveMaybeExcluded(dir int8) intersectsResult {
}
func (i *singleLevelIterator) initBoundsForAlreadyLoadedBlock() {
if i.data.firstKey.UserKey == nil {
if i.data.getFirstUserKey() == nil {
panic("initBoundsForAlreadyLoadedBlock must not be called on empty or corrupted block")
}
i.blockLower = i.lower
if i.blockLower != nil {
if i.data.firstKey.UserKey != nil && i.cmp(i.blockLower, i.data.firstKey.UserKey) < 0 {
firstUserKey := i.data.getFirstUserKey()
if firstUserKey != nil && i.cmp(i.blockLower, firstUserKey) < 0 {
// The lower-bound is less than the first key in the block. No need
// to check the lower-bound again for this block.
i.blockLower = nil
@@ -1066,7 +1070,7 @@ func (i *singleLevelIterator) SeekLT(
var dontSeekWithinBlock bool
if !i.data.isDataInvalidated() && !i.index.isDataInvalidated() && i.data.valid() && i.index.valid() &&
boundsCmp < 0 && i.cmp(i.data.firstKey.UserKey, key) < 0 {
boundsCmp < 0 && i.cmp(i.data.getFirstUserKey(), key) < 0 {
// Fast-path: The bounds have moved backward, and this SeekLT is
// respecting the upper bound (guaranteed by Iterator). We know that
// the iterator must already be positioned within or just outside the
@@ -1314,7 +1318,7 @@ func (i *singleLevelIterator) NextPrefix(succKey []byte) (*InternalKey, base.Laz
if i.err != nil {
return nil, base.LazyValue{}
}
if key, val := i.data.nextPrefix(succKey); key != nil {
if key, val := i.data.NextPrefix(succKey); key != nil {
if i.blockUpper != nil {
cmp := i.cmp(key.UserKey, i.blockUpper)
if (!i.endKeyInclusive && cmp >= 0) || cmp > 0 {
@@ -1763,8 +1767,7 @@ func (i *twoLevelIterator) loadIndex(dir int8) loadBlockResult {
i.err = err
return loadBlockFailed
}
if i.err = i.index.initHandle(
i.cmp, indexBlock, i.reader.Properties.GlobalSeqNum); i.err == nil {
if i.err = i.index.initHandle(i.cmp, indexBlock, i.reader.Properties.GlobalSeqNum, false); i.err == nil {
return loadBlockOK
}
return loadBlockFailed
@@ -1842,7 +1845,7 @@ func (i *twoLevelIterator) init(
v *virtualState,
lower, upper []byte,
filterer *BlockPropertiesFilterer,
useFilter bool,
useFilter, hideObsoletePoints bool,
stats *base.InternalIteratorStats,
rp ReaderProvider,
) error {
@@ -1867,7 +1870,8 @@ func (i *twoLevelIterator) init(
i.reader = r
i.cmp = r.Compare
i.stats = stats
err = i.topLevelIndex.initHandle(i.cmp, topLevelIndexH, r.Properties.GlobalSeqNum)
i.hideObsoletePoints = hideObsoletePoints
err = i.topLevelIndex.initHandle(i.cmp, topLevelIndexH, r.Properties.GlobalSeqNum, false)
if err != nil {
// blockIter.Close releases topLevelIndexH and always returns a nil error
_ = i.topLevelIndex.Close()
@@ -2907,21 +2911,20 @@ func (v *VirtualReader) NewCompactionIter(
return v.reader.newCompactionIter(bytesIterated, rp, &v.vState)
}
// NewIterWithBlockPropertyFiltersAndContext wraps
// NewIterWithBlockPropertyFiltersAndContextEtc wraps
// Reader.NewIterWithBlockPropertyFiltersAndContext. We assume that the passed
// in [lower, upper) bounds will have at least some overlap with the virtual
// sstable bounds. No overlap is not currently supported in the iterator.
func (v *VirtualReader) NewIterWithBlockPropertyFiltersAndContext(
func (v *VirtualReader) NewIterWithBlockPropertyFiltersAndContextEtc(
ctx context.Context,
lower, upper []byte,
filterer *BlockPropertiesFilterer,
useFilterBlock bool,
hideObsoletePoints, useFilterBlock bool,
stats *base.InternalIteratorStats,
rp ReaderProvider,
) (Iterator, error) {
return v.reader.newIterWithBlockPropertyFiltersAndContext(
ctx,
lower, upper, filterer, useFilterBlock, stats, rp, &v.vState,
ctx, lower, upper, filterer, hideObsoletePoints, useFilterBlock, stats, rp, &v.vState,
)
}
@@ -3060,31 +3063,51 @@ func (r *Reader) NewIterWithBlockPropertyFilters(
) (Iterator, error) {
return r.newIterWithBlockPropertyFiltersAndContext(
context.Background(),
lower, upper, filterer, useFilterBlock, stats, rp, nil,
lower, upper, filterer, false, useFilterBlock, stats, rp, nil,
)
}
// NewIterWithBlockPropertyFiltersAndContext is similar to
// NewIterWithBlockPropertyFiltersAndContextEtc is similar to
// NewIterWithBlockPropertyFilters and additionally accepts a context for
// tracing.
func (r *Reader) NewIterWithBlockPropertyFiltersAndContext(
//
// If hideObsoletePoints, the callee assumes that filterer already includes
// obsoleteKeyBlockPropertyFilter. The caller can satisfy this contract by
// first calling TryAddBlockPropertyFilterForHideObsoletePoints.
func (r *Reader) NewIterWithBlockPropertyFiltersAndContextEtc(
ctx context.Context,
lower, upper []byte,
filterer *BlockPropertiesFilterer,
useFilterBlock bool,
hideObsoletePoints, useFilterBlock bool,
stats *base.InternalIteratorStats,
rp ReaderProvider,
) (Iterator, error) {
return r.newIterWithBlockPropertyFiltersAndContext(
ctx,
lower, upper, filterer, useFilterBlock, stats, rp, nil,
ctx, lower, upper, filterer, hideObsoletePoints, useFilterBlock, stats, rp, nil,
)
}
// TryAddBlockPropertyFilterForHideObsoletePoints is expected to be called
// before the call to NewIterWithBlockPropertyFiltersAndContextEtc, to get the
// value of hideObsoletePoints and potentially add a block property filter.
func (r *Reader) TryAddBlockPropertyFilterForHideObsoletePoints(
snapshotForHideObsoletePoints uint64,
fileLargestSeqNum uint64,
pointKeyFilters []BlockPropertyFilter,
) (hideObsoletePoints bool, filters []BlockPropertyFilter) {
hideObsoletePoints = r.tableFormat >= TableFormatPebblev4 &&
snapshotForHideObsoletePoints > fileLargestSeqNum
if hideObsoletePoints {
pointKeyFilters = append(pointKeyFilters, obsoleteKeyBlockPropertyFilter{})
}
return hideObsoletePoints, pointKeyFilters
}
func (r *Reader) newIterWithBlockPropertyFiltersAndContext(
ctx context.Context,
lower, upper []byte,
filterer *BlockPropertiesFilterer,
hideObsoletePoints bool,
useFilterBlock bool,
stats *base.InternalIteratorStats,
rp ReaderProvider,
@@ -3095,7 +3118,7 @@ func (r *Reader) newIterWithBlockPropertyFiltersAndContext(
// until the final iterator closes.
if r.Properties.IndexType == twoLevelIndex {
i := twoLevelIterPool.Get().(*twoLevelIterator)
err := i.init(ctx, r, v, lower, upper, filterer, useFilterBlock, stats, rp)
err := i.init(ctx, r, v, lower, upper, filterer, useFilterBlock, hideObsoletePoints, stats, rp)
if err != nil {
return nil, err
}
@@ -3103,7 +3126,7 @@ func (r *Reader) newIterWithBlockPropertyFiltersAndContext(
}
i := singleLevelIterPool.Get().(*singleLevelIterator)
err := i.init(ctx, r, v, lower, upper, filterer, useFilterBlock, stats, rp)
err := i.init(ctx, r, v, lower, upper, filterer, useFilterBlock, hideObsoletePoints, stats, rp)
if err != nil {
return nil, err
}
@@ -3135,7 +3158,8 @@ func (r *Reader) newCompactionIter(
err := i.init(
context.Background(),
r, v, nil /* lower */, nil /* upper */, nil,
false /* useFilter */, nil /* stats */, rp,
false /* useFilter */, false, /* hideObsoletePoints */
nil /* stats */, rp,
)
if err != nil {
return nil, err
@@ -3149,7 +3173,8 @@ func (r *Reader) newCompactionIter(
i := singleLevelIterPool.Get().(*singleLevelIterator)
err := i.init(
context.Background(), r, v, nil /* lower */, nil, /* upper */
nil, false /* useFilter */, nil /* stats */, rp,
nil, false /* useFilter */, false, /* hideObsoletePoints */
nil /* stats */, rp,
)
if err != nil {
return nil, err
@@ -3175,8 +3200,8 @@ func (r *Reader) NewRawRangeDelIter() (keyspan.FragmentIterator, error) {
if err != nil {
return nil, err
}
i := &fragmentBlockIter{}
if err := i.blockIter.initHandle(r.Compare, h, r.Properties.GlobalSeqNum); err != nil {
i := &fragmentBlockIter{elideSameSeqnum: true}
if err := i.blockIter.initHandle(r.Compare, h, r.Properties.GlobalSeqNum, false); err != nil {
return nil, err
}
return i, nil
@@ -3197,7 +3222,7 @@ func (r *Reader) NewRawRangeKeyIter() (keyspan.FragmentIterator, error) {
return nil, err
}
i := rangeKeyFragmentBlockIterPool.Get().(*rangeKeyFragmentBlockIter)
if err := i.blockIter.initHandle(r.Compare, h, r.Properties.GlobalSeqNum); err != nil {
if err := i.blockIter.initHandle(r.Compare, h, r.Properties.GlobalSeqNum, false); err != nil {
return nil, err
}
return i, nil
@@ -3357,7 +3382,7 @@ func (r *Reader) transformRangeDelV1(b []byte) ([]byte, error) {
// tombstones. We need properly fragmented and sorted range tombstones in
// order to serve from them directly.
iter := &blockIter{}
if err := iter.init(r.Compare, b, r.Properties.GlobalSeqNum); err != nil {
if err := iter.init(r.Compare, b, r.Properties.GlobalSeqNum, false); err != nil {
return nil, err
}
var tombstones []keyspan.Span
@@ -3444,7 +3469,7 @@ func (r *Reader) readMetaindex(metaindexBH BlockHandle) error {
return err
}
r.propertiesBH = bh
err := r.Properties.load(b.Get(), bh.Offset)
err := r.Properties.load(b.Get(), bh.Offset, r.opts.DeniedUserProperties)
b.Release()
if err != nil {
return err
@@ -3549,7 +3574,8 @@ func (r *Reader) Layout() (*Layout, error) {
if err != nil {
return nil, err
}
if err := iter.init(r.Compare, subIndex.Get(), 0 /* globalSeqNum */); err != nil {
if err := iter.init(r.Compare, subIndex.Get(), 0, /* globalSeqNum */
false /* hideObsoletePoints */); err != nil {
return nil, err
}
for key, value := iter.First(); key != nil; key, value = iter.Next() {
@@ -3867,6 +3893,7 @@ func NewReader(f objstorage.Readable, o ReaderOptions, extraOpts ...ReaderOption
if r.err != nil {
return nil, r.Close()
}
return r, nil
}

View File

@@ -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()

View File

@@ -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 (

View File

@@ -132,6 +132,8 @@ type Writer struct {
separator Separator
successor Successor
tableFormat TableFormat
isStrictObsolete bool
writingToLowestLevel bool
cache *cache.Cache
restartInterval int
checksumType ChecksumType
@@ -166,6 +168,7 @@ type Writer struct {
props Properties
propCollectors []TablePropertyCollector
blockPropCollectors []BlockPropertyCollector
obsoleteCollector obsoleteKeyBlockPropertyCollector
blockPropsEncoder blockPropertiesEncoder
// filter accumulates the filter block. If populated, the filter ingests
// either the output of w.split (i.e. a prefix extractor) if w.split is not
@@ -215,6 +218,8 @@ type pointKeyInfo struct {
// prefixLen uses w.split, if not nil. Only computed when w.valueBlockWriter
// is not nil.
prefixLen int
// True iff the point was marked obsolete.
isObsolete bool
}
type coordinationState struct {
@@ -673,7 +678,12 @@ func (w *Writer) Set(key, value []byte) error {
if w.err != nil {
return w.err
}
return w.addPoint(base.MakeInternalKey(key, 0, InternalKeyKindSet), value)
if w.isStrictObsolete {
return errors.Errorf("use AddWithForceObsolete")
}
// forceObsolete is false based on the assumption that no RANGEDELs in the
// sstable delete the added points.
return w.addPoint(base.MakeInternalKey(key, 0, InternalKeyKindSet), value, false)
}
// Delete deletes the value for the given key. The sequence number is set to
@@ -685,7 +695,12 @@ func (w *Writer) Delete(key []byte) error {
if w.err != nil {
return w.err
}
return w.addPoint(base.MakeInternalKey(key, 0, InternalKeyKindDelete), nil)
if w.isStrictObsolete {
return errors.Errorf("use AddWithForceObsolete")
}
// forceObsolete is false based on the assumption that no RANGEDELs in the
// sstable delete the added points.
return w.addPoint(base.MakeInternalKey(key, 0, InternalKeyKindDelete), nil, false)
}
// DeleteRange deletes all of the keys (and values) in the range [start,end)
@@ -711,7 +726,13 @@ func (w *Writer) Merge(key, value []byte) error {
if w.err != nil {
return w.err
}
return w.addPoint(base.MakeInternalKey(key, 0, InternalKeyKindMerge), value)
if w.isStrictObsolete {
return errors.Errorf("use AddWithForceObsolete")
}
// forceObsolete is false based on the assumption that no RANGEDELs in the
// sstable that delete the added points. If the user configured this writer
// to be strict-obsolete, addPoint will reject the addition of this MERGE.
return w.addPoint(base.MakeInternalKey(key, 0, InternalKeyKindMerge), value, false)
}
// Add adds a key/value pair to the table being written. For a given Writer,
@@ -721,6 +742,24 @@ func (w *Writer) Merge(key, value []byte) error {
// point entries. Additionally, range deletion tombstones must be fragmented
// (i.e. by keyspan.Fragmenter).
func (w *Writer) Add(key InternalKey, value []byte) error {
if w.isStrictObsolete {
return errors.Errorf("use AddWithForceObsolete")
}
return w.AddWithForceObsolete(key, value, false)
}
// AddWithForceObsolete must be used when writing a strict-obsolete sstable.
//
// forceObsolete indicates whether the caller has determined that this key is
// obsolete even though it may be the latest point key for this userkey. This
// should be set to true for keys obsoleted by RANGEDELs, and is required for
// strict-obsolete sstables.
//
// Note that there are two properties, S1 and S2 (see comment in format.go)
// that strict-obsolete ssts must satisfy. S2, due to RANGEDELs, is solely the
// responsibility of the caller. S1 is solely the responsibility of the
// callee.
func (w *Writer) AddWithForceObsolete(key InternalKey, value []byte, forceObsolete bool) error {
if w.err != nil {
return w.err
}
@@ -735,7 +774,7 @@ func (w *Writer) Add(key InternalKey, value []byte) error {
"pebble: range keys must be added via one of the RangeKey* functions")
return w.err
}
return w.addPoint(key, value)
return w.addPoint(key, value, forceObsolete)
}
func (w *Writer) makeAddPointDecisionV2(key InternalKey) error {
@@ -745,12 +784,7 @@ func (w *Writer) makeAddPointDecisionV2(key InternalKey) error {
return nil
}
if !w.disableKeyOrderChecks {
prevPointUserKey := w.dataBlockBuf.dataBlock.curKey
n := len(prevPointUserKey) - base.InternalTrailerLen
if n < 0 {
panic("corrupt key in blockWriter buffer")
}
prevPointUserKey = prevPointUserKey[:n:n]
prevPointUserKey := w.dataBlockBuf.dataBlock.getCurUserKey()
cmpUser := w.compare(prevPointUserKey, key.UserKey)
if cmpUser > 0 || (cmpUser == 0 && prevTrailer <= key.Trailer) {
return errors.Errorf(
@@ -762,9 +796,17 @@ func (w *Writer) makeAddPointDecisionV2(key InternalKey) error {
return nil
}
// REQUIRES: at least one point has been written to the Writer.
func (w *Writer) getLastPointUserKey() []byte {
if w.dataBlockBuf.dataBlock.nEntries == 0 {
panic(errors.AssertionFailedf("no point keys added to writer"))
}
return w.dataBlockBuf.dataBlock.getCurUserKey()
}
func (w *Writer) makeAddPointDecisionV3(
key InternalKey, valueLen int,
) (setHasSamePrefix bool, writeToValueBlock bool, err error) {
) (setHasSamePrefix bool, writeToValueBlock bool, isObsolete bool, err error) {
prevPointKeyInfo := w.lastPointKeyInfo
w.lastPointKeyInfo.userKeyLen = len(key.UserKey)
w.lastPointKeyInfo.prefixLen = w.lastPointKeyInfo.userKeyLen
@@ -772,18 +814,16 @@ func (w *Writer) makeAddPointDecisionV3(
w.lastPointKeyInfo.prefixLen = w.split(key.UserKey)
}
w.lastPointKeyInfo.trailer = key.Trailer
if w.dataBlockBuf.dataBlock.nEntries == 0 {
return false, false, nil
w.lastPointKeyInfo.isObsolete = false
if !w.meta.HasPointKeys {
return false, false, false, nil
}
prevPointUserKey := w.dataBlockBuf.dataBlock.curKey
n := len(prevPointUserKey) - base.InternalTrailerLen
if n < 0 {
panic("corrupt key in blockWriter buffer")
}
prevPointUserKey = prevPointUserKey[:n:n]
keyKind := base.TrailerKind(key.Trailer)
prevPointUserKey := w.getLastPointUserKey()
prevPointKey := InternalKey{UserKey: prevPointUserKey, Trailer: prevPointKeyInfo.trailer}
considerWriteToValueBlock := base.TrailerKind(prevPointKeyInfo.trailer) == InternalKeyKindSet &&
base.TrailerKind(key.Trailer) == InternalKeyKindSet
prevKeyKind := base.TrailerKind(prevPointKeyInfo.trailer)
considerWriteToValueBlock := prevKeyKind == InternalKeyKindSet &&
keyKind == InternalKeyKindSet
if considerWriteToValueBlock && !w.requiredInPlaceValueBound.IsEmpty() {
keyPrefix := key.UserKey[:w.lastPointKeyInfo.prefixLen]
cmpUpper := w.compare(
@@ -812,14 +852,59 @@ func (w *Writer) makeAddPointDecisionV3(
} else {
cmpUser = w.compare(prevPointUserKey, key.UserKey)
}
keyKindIsSetOrMerge := keyKind == InternalKeyKindSet || keyKind == InternalKeyKindSetWithDelete ||
keyKind == InternalKeyKindMerge
keyKindIsPointDelete := keyKind == InternalKeyKindDelete ||
keyKind == InternalKeyKindSingleDelete || keyKind == InternalKeyKindDeleteSized
if !keyKindIsSetOrMerge && !keyKindIsPointDelete {
panic(errors.AssertionFailedf("unexpected key kind %s", keyKind.String()))
}
// If same user key, then the current key is obsolete if any of the
// following is true:
// C1 The prev key was obsolete.
// C2 The prev key was not a MERGE
// C3 The current key is not a SET or SETWITHDELETE or MERGE, then it is
// obsolete. This rule excludes SET, SETWITHDELETE, MERGE, since they
// will be merged with the previous key.
//
// Regardless of whether it is the same user key or not
// C4 The current key is some kind of point delete, and we are writing to
// the lowest level, then it is also obsolete.
//
// C1 ensures that for a user key there is at most one transition from
// !obsolete to obsolete. Consider a user key k, for which the first n keys
// are not obsolete. We consider the various value of n:
//
// n = 0: This happens due to forceObsolete being set by the caller, or due
// to C4. forceObsolete must only be set due a RANGEDEL, and that RANGEDEL
// must also delete all the lower seqnums for the same user key. C4 triggers
// due to a point delete and that deletes all the lower seqnums for the same
// user key.
//
// n = 1: This is the common case. It happens when the first key is not a
// MERGE, or the current key is some kind of point delete.
//
// n > 1: This is due to a sequence of MERGE keys, potentially followed by a
// single SET or SETWITHDELETE.
isObsolete = (cmpUser == 0 &&
(prevKeyKind != InternalKeyKindMerge || prevPointKeyInfo.isObsolete || !keyKindIsSetOrMerge)) ||
(w.writingToLowestLevel &&
(keyKind == InternalKeyKindDelete || keyKind == InternalKeyKindSingleDelete ||
keyKind == InternalKeyKindDeleteSized))
// TODO(sumeer): storing isObsolete SET and SETWITHDEL in value blocks is
// possible, but requires some care in documenting and checking invariants.
// There is code that assumes nothing in value blocks because of single MVCC
// version (those should be ok). We have to ensure setHasSamePrefix is
// correctly initialized here etc.
if !w.disableKeyOrderChecks &&
(cmpUser > 0 || (cmpUser == 0 && prevPointKeyInfo.trailer <= key.Trailer)) {
return false, false, errors.Errorf(
return false, false, false, errors.Errorf(
"pebble: keys must be added in strictly increasing order: %s, %s",
prevPointKey.Pretty(w.formatKey), key.Pretty(w.formatKey))
}
if !considerWriteToValueBlock {
return false, false, nil
return false, false, isObsolete, nil
}
// NB: it is possible that cmpUser == 0, i.e., these two SETs have identical
// user keys (because of an open snapshot). This should be the rare case.
@@ -832,19 +917,24 @@ func (w *Writer) makeAddPointDecisionV3(
if considerWriteToValueBlock && valueLen <= tinyValueThreshold {
considerWriteToValueBlock = false
}
return setHasSamePrefix, considerWriteToValueBlock, nil
return setHasSamePrefix, considerWriteToValueBlock, isObsolete, nil
}
func (w *Writer) addPoint(key InternalKey, value []byte) error {
func (w *Writer) addPoint(key InternalKey, value []byte, forceObsolete bool) error {
if w.isStrictObsolete && key.Kind() == InternalKeyKindMerge {
return errors.Errorf("MERGE not supported in a strict-obsolete sstable")
}
var err error
var setHasSameKeyPrefix, writeToValueBlock, addPrefixToValueStoredWithKey bool
var isObsolete bool
maxSharedKeyLen := len(key.UserKey)
if w.valueBlockWriter != nil {
// maxSharedKeyLen is limited to the prefix of the preceding key. If the
// preceding key was in a different block, then the blockWriter will
// ignore this maxSharedKeyLen.
maxSharedKeyLen = w.lastPointKeyInfo.prefixLen
setHasSameKeyPrefix, writeToValueBlock, err = w.makeAddPointDecisionV3(key, len(value))
setHasSameKeyPrefix, writeToValueBlock, isObsolete, err =
w.makeAddPointDecisionV3(key, len(value))
addPrefixToValueStoredWithKey = base.TrailerKind(key.Trailer) == InternalKeyKindSet
} else {
err = w.makeAddPointDecisionV2(key)
@@ -852,6 +942,8 @@ func (w *Writer) addPoint(key InternalKey, value []byte) error {
if err != nil {
return err
}
isObsolete = w.tableFormat >= TableFormatPebblev4 && (isObsolete || forceObsolete)
w.lastPointKeyInfo.isObsolete = isObsolete
var valueStoredWithKey []byte
var prefix valuePrefix
var valueStoredWithKeyLen int
@@ -907,16 +999,19 @@ func (w *Writer) addPoint(key InternalKey, value []byte) error {
return err
}
}
if w.tableFormat >= TableFormatPebblev4 {
w.obsoleteCollector.AddPoint(isObsolete)
}
w.maybeAddToFilter(key.UserKey)
w.dataBlockBuf.dataBlock.addWithOptionalValuePrefix(
key, valueStoredWithKey, maxSharedKeyLen, addPrefixToValueStoredWithKey, prefix,
key, isObsolete, valueStoredWithKey, maxSharedKeyLen, addPrefixToValueStoredWithKey, prefix,
setHasSameKeyPrefix)
w.meta.updateSeqNum(key.SeqNum())
if !w.meta.HasPointKeys {
k := base.DecodeInternalKey(w.dataBlockBuf.dataBlock.curKey)
k := w.dataBlockBuf.dataBlock.getCurKey()
// NB: We need to ensure that SmallestPoint.UserKey is set, so we create
// an InternalKey which is semantically identical to the key, but won't
// have a nil UserKey. We do this, because key.UserKey could be nil, and
@@ -932,7 +1027,22 @@ func (w *Writer) addPoint(key InternalKey, value []byte) error {
switch key.Kind() {
case InternalKeyKindDelete, InternalKeyKindSingleDelete:
w.props.NumDeletions++
w.props.RawPointTombstoneKeySize += uint64(key.Size())
w.props.RawPointTombstoneKeySize += uint64(len(key.UserKey))
case InternalKeyKindDeleteSized:
var size uint64
if len(value) > 0 {
var n int
size, n = binary.Uvarint(value)
if n <= 0 {
w.err = errors.Newf("%s key's value (%x) does not parse as uvarint",
errors.Safe(key.Kind().String()), value)
return w.err
}
}
w.props.NumDeletions++
w.props.NumSizedDeletions++
w.props.RawPointTombstoneKeySize += uint64(len(key.UserKey))
w.props.RawPointTombstoneValueSize += size
case InternalKeyKindMerge:
w.props.NumMergeOperands++
}
@@ -953,7 +1063,7 @@ func (w *Writer) addTombstone(key InternalKey, value []byte) error {
if !w.disableKeyOrderChecks && !w.rangeDelV1Format && w.rangeDelBlock.nEntries > 0 {
// Check that tombstones are being added in fragmented order. If the two
// tombstones overlap, their start and end keys must be identical.
prevKey := base.DecodeInternalKey(w.rangeDelBlock.curKey)
prevKey := w.rangeDelBlock.getCurKey()
switch c := w.compare(prevKey.UserKey, key.UserKey); {
case c > 0:
w.err = errors.Errorf("pebble: keys must be added in order: %s, %s",
@@ -1149,7 +1259,7 @@ func (w *Writer) encodeRangeKeySpan(span keyspan.Span) {
func (w *Writer) addRangeKey(key InternalKey, value []byte) error {
if !w.disableKeyOrderChecks && w.rangeKeyBlock.nEntries > 0 {
prevStartKey := base.DecodeInternalKey(w.rangeKeyBlock.curKey)
prevStartKey := w.rangeKeyBlock.getCurKey()
prevEndKey, _, ok := rangekey.DecodeEndKey(prevStartKey.Kind(), w.rangeKeyBlock.curValue)
if !ok {
// We panic here as we should have previously decoded and validated this
@@ -1295,7 +1405,7 @@ func (w *Writer) flush(key InternalKey) error {
// dataBlockBuf is added back to a sync.Pool. In this particular case, the
// byte slice which supports "sep" will eventually be copied when "sep" is
// added to the index block.
prevKey := base.DecodeInternalKey(w.dataBlockBuf.dataBlock.curKey)
prevKey := w.dataBlockBuf.dataBlock.getCurKey()
sep := w.indexEntrySep(prevKey, key, w.dataBlockBuf)
// We determine that we should flush an index block from the Writer client
// goroutine, but we actually finish the index block from the writeQueue.
@@ -1585,7 +1695,7 @@ func (w *Writer) finishIndexBlock(indexBuf *indexBlockBuf, props []byte) error {
nEntries: indexBuf.block.nEntries, properties: props,
}
w.indexSepAlloc, part.sep = cloneKeyWithBuf(
base.DecodeInternalKey(indexBuf.block.curKey), w.indexSepAlloc,
indexBuf.block.getCurKey(), w.indexSepAlloc,
)
bk := indexBuf.finish()
if len(w.indexBlockAlloc) < len(bk) {
@@ -1729,12 +1839,20 @@ func (w *Writer) assertFormatCompatibility() error {
)
}
// PebbleDBv3: value blocks.
if (w.props.NumValueBlocks > 0 || w.props.NumValuesInValueBlocks > 0 ||
w.props.ValueBlocksSize > 0) && w.tableFormat < TableFormatPebblev3 {
return errors.Newf(
"table format version %s is less than the minimum required version %s for value blocks",
w.tableFormat, TableFormatPebblev3)
}
// PebbleDBv4: DELSIZED tombstones.
if w.props.NumSizedDeletions > 0 && w.tableFormat < TableFormatPebblev4 {
return errors.Newf(
"table format version %s is less than the minimum required version %s for sized deletion tombstones",
w.tableFormat, TableFormatPebblev4)
}
return nil
}
@@ -1781,7 +1899,7 @@ func (w *Writer) Close() (err error) {
// however, if a dataBlock is flushed, then we add a key to the new w.dataBlockBuf in the
// addPoint function after the flush occurs.
if w.dataBlockBuf.dataBlock.nEntries >= 1 {
w.meta.SetLargestPointKey(base.DecodeInternalKey(w.dataBlockBuf.dataBlock.curKey).Clone())
w.meta.SetLargestPointKey(w.dataBlockBuf.dataBlock.getCurKey().Clone())
}
// Finish the last data block, or force an empty data block if there
@@ -1795,7 +1913,7 @@ func (w *Writer) Close() (err error) {
if err != nil {
return err
}
prevKey := base.DecodeInternalKey(w.dataBlockBuf.dataBlock.curKey)
prevKey := w.dataBlockBuf.dataBlock.getCurKey()
if err := w.addIndexEntrySync(prevKey, InternalKey{}, bhp, w.dataBlockBuf.tmp[:]); err != nil {
return err
}
@@ -1874,7 +1992,7 @@ func (w *Writer) Close() (err error) {
var rangeKeyBH BlockHandle
if w.props.NumRangeKeys() > 0 {
key := base.DecodeInternalKey(w.rangeKeyBlock.curKey)
key := w.rangeKeyBlock.getCurKey()
kind := key.Kind()
endKey, _, ok := rangekey.DecodeEndKey(kind, w.rangeKeyBlock.curValue)
if !ok {
@@ -2063,7 +2181,7 @@ func (o PreviousPointKeyOpt) UnsafeKey() base.InternalKey {
if o.w.dataBlockBuf.dataBlock.nEntries >= 1 {
// o.w.dataBlockBuf.dataBlock.curKey is guaranteed to point to the last point key
// which was added to the Writer.
return base.DecodeInternalKey(o.w.dataBlockBuf.dataBlock.curKey)
return o.w.dataBlockBuf.dataBlock.getCurKey()
}
return base.InternalKey{}
}
@@ -2092,6 +2210,8 @@ func NewWriter(writable objstorage.Writable, o WriterOptions, extraOpts ...Write
separator: o.Comparer.Separator,
successor: o.Comparer.Successor,
tableFormat: o.TableFormat,
isStrictObsolete: o.IsStrictObsolete,
writingToLowestLevel: o.WritingToLowestLevel,
cache: o.Cache,
restartInterval: o.BlockRestartInterval,
checksumType: o.Checksum,
@@ -2158,14 +2278,14 @@ func NewWriter(writable objstorage.Writable, o WriterOptions, extraOpts ...Write
}
}
w.props.ColumnFamilyID = math.MaxInt32
w.props.ComparerName = o.Comparer.Name
w.props.CompressionName = o.Compression.String()
w.props.MergerName = o.MergerName
w.props.PropertyCollectorNames = "[]"
w.props.ExternalFormatVersion = rocksDBExternalFormatVersion
if len(o.TablePropertyCollectors) > 0 || len(o.BlockPropertyCollectors) > 0 {
if len(o.TablePropertyCollectors) > 0 || len(o.BlockPropertyCollectors) > 0 ||
w.tableFormat >= TableFormatPebblev4 {
var buf bytes.Buffer
buf.WriteString("[")
if len(o.TablePropertyCollectors) > 0 {
@@ -2178,16 +2298,22 @@ func NewWriter(writable objstorage.Writable, o WriterOptions, extraOpts ...Write
buf.WriteString(w.propCollectors[i].Name())
}
}
numBlockPropertyCollectors := len(o.BlockPropertyCollectors)
if w.tableFormat >= TableFormatPebblev4 {
numBlockPropertyCollectors++
}
// shortID is a uint8, so we cannot exceed that number of block
// property collectors.
if numBlockPropertyCollectors > math.MaxUint8 {
w.err = errors.New("pebble: too many block property collectors")
return w
}
if numBlockPropertyCollectors > 0 {
w.blockPropCollectors = make([]BlockPropertyCollector, numBlockPropertyCollectors)
}
if len(o.BlockPropertyCollectors) > 0 {
// shortID is a uint8, so we cannot exceed that number of block
// property collectors.
if len(o.BlockPropertyCollectors) > math.MaxUint8 {
w.err = errors.New("pebble: too many block property collectors")
return w
}
// The shortID assigned to a collector is the same as its index in
// this slice.
w.blockPropCollectors = make([]BlockPropertyCollector, len(o.BlockPropertyCollectors))
for i := range o.BlockPropertyCollectors {
w.blockPropCollectors[i] = o.BlockPropertyCollectors[i]()
if i > 0 || len(o.TablePropertyCollectors) > 0 {
@@ -2196,6 +2322,13 @@ func NewWriter(writable objstorage.Writable, o WriterOptions, extraOpts ...Write
buf.WriteString(w.blockPropCollectors[i].Name())
}
}
if w.tableFormat >= TableFormatPebblev4 {
if numBlockPropertyCollectors > 1 || len(o.TablePropertyCollectors) > 0 {
buf.WriteString(",")
}
w.blockPropCollectors[numBlockPropertyCollectors-1] = &w.obsoleteCollector
buf.WriteString(w.obsoleteCollector.Name())
}
buf.WriteString("]")
w.props.PropertyCollectorNames = buf.String()
}
@@ -2229,3 +2362,85 @@ func init() {
}
private.SSTableInternalProperties = internalGetProperties
}
type obsoleteKeyBlockPropertyCollector struct {
blockIsNonObsolete bool
indexIsNonObsolete bool
tableIsNonObsolete bool
}
func encodeNonObsolete(isNonObsolete bool, buf []byte) []byte {
if isNonObsolete {
return buf
}
return append(buf, 't')
}
func (o *obsoleteKeyBlockPropertyCollector) Name() string {
return "obsolete-key"
}
func (o *obsoleteKeyBlockPropertyCollector) Add(key InternalKey, value []byte) error {
// Ignore.
return nil
}
func (o *obsoleteKeyBlockPropertyCollector) AddPoint(isObsolete bool) {
o.blockIsNonObsolete = o.blockIsNonObsolete || !isObsolete
}
func (o *obsoleteKeyBlockPropertyCollector) FinishDataBlock(buf []byte) ([]byte, error) {
o.tableIsNonObsolete = o.tableIsNonObsolete || o.blockIsNonObsolete
return encodeNonObsolete(o.blockIsNonObsolete, buf), nil
}
func (o *obsoleteKeyBlockPropertyCollector) AddPrevDataBlockToIndexBlock() {
o.indexIsNonObsolete = o.indexIsNonObsolete || o.blockIsNonObsolete
o.blockIsNonObsolete = false
}
func (o *obsoleteKeyBlockPropertyCollector) FinishIndexBlock(buf []byte) ([]byte, error) {
indexIsNonObsolete := o.indexIsNonObsolete
o.indexIsNonObsolete = false
return encodeNonObsolete(indexIsNonObsolete, buf), nil
}
func (o *obsoleteKeyBlockPropertyCollector) FinishTable(buf []byte) ([]byte, error) {
return encodeNonObsolete(o.tableIsNonObsolete, buf), nil
}
func (o *obsoleteKeyBlockPropertyCollector) UpdateKeySuffixes(
oldProp []byte, oldSuffix, newSuffix []byte,
) error {
_, err := propToIsObsolete(oldProp)
if err != nil {
return err
}
// Suffix rewriting currently loses the obsolete bit.
o.blockIsNonObsolete = true
return nil
}
// NB: obsoleteKeyBlockPropertyFilter is stateless. This aspect of the filter
// is used in table_cache.go for in-place modification of a filters slice.
type obsoleteKeyBlockPropertyFilter struct{}
func (o obsoleteKeyBlockPropertyFilter) Name() string {
return "obsolete-key"
}
// Intersects returns true if the set represented by prop intersects with
// the set in the filter.
func (o obsoleteKeyBlockPropertyFilter) Intersects(prop []byte) (bool, error) {
return propToIsObsolete(prop)
}
func propToIsObsolete(prop []byte) (bool, error) {
if len(prop) == 0 {
return true, nil
}
if len(prop) > 1 || prop[0] != 't' {
return false, errors.Errorf("unexpected property %x", prop)
}
return false, nil
}