Upgrade versions of vendored libs
This commit is contained in:
493
vendor/github.com/cockroachdb/pebble/merging_iter.go
generated
vendored
493
vendor/github.com/cockroachdb/pebble/merging_iter.go
generated
vendored
@@ -8,6 +8,7 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"runtime/debug"
|
||||
"unsafe"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/cockroachdb/pebble/internal/base"
|
||||
@@ -16,7 +17,8 @@ import (
|
||||
)
|
||||
|
||||
type mergingIterLevel struct {
|
||||
iter internalIterator
|
||||
index int
|
||||
iter internalIterator
|
||||
// rangeDelIter is set to the range-deletion iterator for the level. When
|
||||
// configured with a levelIter, this pointer changes as sstable boundaries
|
||||
// are crossed. See levelIter.initRangeDel and the Range Deletions comment
|
||||
@@ -44,20 +46,25 @@ type levelIterBoundaryContext struct {
|
||||
// smallestUserKey and largestUserKey are populated with the smallest and
|
||||
// largest boundaries of the current file.
|
||||
smallestUserKey, largestUserKey []byte
|
||||
// isLargestUserKeyRangeDelSentinel is set to true when a file's largest
|
||||
// boundary is an exclusive range deletion sentinel. If true, the file does
|
||||
// not contain any keys with the provided user key, and the largestUserKey
|
||||
// bound is exclusive.
|
||||
isLargestUserKeyRangeDelSentinel bool
|
||||
// isLargestUserKeyExclusive is set to true when a file's largest boundary
|
||||
// is an exclusive key, (eg, a range deletion sentinel). If true, the file
|
||||
// does not contain any keys with the provided user key, and the
|
||||
// largestUserKey bound is exclusive.
|
||||
isLargestUserKeyExclusive bool
|
||||
// isSyntheticIterBoundsKey is set to true iff the key returned by the level
|
||||
// iterator is a synthetic key derived from the iterator bounds. This is
|
||||
// used to prevent the mergingIter from being stuck at such a synthetic key
|
||||
// if it becomes the top element of the heap.
|
||||
// iterator is a synthetic key derived from the iterator bounds. This is used
|
||||
// to prevent the mergingIter from being stuck at such a synthetic key if it
|
||||
// becomes the top element of the heap. When used with a user-facing Iterator,
|
||||
// the only range deletions exposed by this mergingIter should be those with
|
||||
// `isSyntheticIterBoundsKey || isIgnorableBoundaryKey`.
|
||||
isSyntheticIterBoundsKey bool
|
||||
// isIgnorableBoundaryKey is set to true iff the key returned by the level
|
||||
// iterator is a file boundary key that should be ignored. This is used to
|
||||
// iterator is a file boundary key that should be ignored when returning to
|
||||
// the parent iterator. File boundary keys are used by the level iter to
|
||||
// keep a levelIter file's range deletion iterator open as long as other
|
||||
// levels within the merging iterator require it.
|
||||
// levels within the merging iterator require it. When used with a user-facing
|
||||
// Iterator, the only range deletions exposed by this mergingIter should be
|
||||
// those with `isSyntheticIterBoundsKey || isIgnorableBoundaryKey`.
|
||||
isIgnorableBoundaryKey bool
|
||||
}
|
||||
|
||||
@@ -73,7 +80,7 @@ type levelIterBoundaryContext struct {
|
||||
// property. When one of the child iterators is exhausted during Next/Prev
|
||||
// iteration, it is removed from the heap.
|
||||
//
|
||||
// Range Deletions
|
||||
// # Range Deletions
|
||||
//
|
||||
// A mergingIter can optionally be configured with a slice of range deletion
|
||||
// iterators. The range deletion iterator slice must exactly parallel the point
|
||||
@@ -143,11 +150,11 @@ type levelIterBoundaryContext struct {
|
||||
// to skip processing entries in that shadow. For example, consider the
|
||||
// scenario:
|
||||
//
|
||||
// r0: a---e
|
||||
// r1: d---h
|
||||
// r2: g---k
|
||||
// r3: j---n
|
||||
// r4: m---q
|
||||
// r0: a---e
|
||||
// r1: d---h
|
||||
// r2: g---k
|
||||
// r3: j---n
|
||||
// r4: m---q
|
||||
//
|
||||
// This is showing 5 levels of range deletions. Consider what happens upon
|
||||
// SeekGE("b"). We first seek the point iterator for level 0 (the point values
|
||||
@@ -189,17 +196,17 @@ type levelIterBoundaryContext struct {
|
||||
//
|
||||
// For a full example, consider the following setup:
|
||||
//
|
||||
// p0: o
|
||||
// r0: m---q
|
||||
// p0: o
|
||||
// r0: m---q
|
||||
//
|
||||
// p1: n p
|
||||
// r1: g---k
|
||||
// p1: n p
|
||||
// r1: g---k
|
||||
//
|
||||
// p2: b d i
|
||||
// r2: a---e q----v
|
||||
// p2: b d i
|
||||
// r2: a---e q----v
|
||||
//
|
||||
// p3: e
|
||||
// r3:
|
||||
// p3: e
|
||||
// r3:
|
||||
//
|
||||
// If we start iterating from the beginning, the first key we encounter is "b"
|
||||
// in p2. When the mergingIter is pointing at a valid entry, the range deletion
|
||||
@@ -243,12 +250,15 @@ type mergingIter struct {
|
||||
upper []byte
|
||||
stats *InternalIteratorStats
|
||||
|
||||
// levelsPositioned, if non-nil, is a slice of the same length as levels.
|
||||
// It's used by NextPrefix to record which levels have already been
|
||||
// repositioned. It's created lazily by the first call to NextPrefix.
|
||||
levelsPositioned []bool
|
||||
|
||||
combinedIterState *combinedIterState
|
||||
|
||||
// Elide range tombstones from being returned during iteration. Set to true
|
||||
// when mergingIter is a child of Iterator and the mergingIter is processing
|
||||
// range tombstones.
|
||||
elideRangeTombstones bool
|
||||
// Used in some tests to disable the random disabling of seek optimizations.
|
||||
forceEnableSeekOpt bool
|
||||
}
|
||||
|
||||
// mergingIter implements the base.InternalIterator interface.
|
||||
@@ -300,21 +310,20 @@ func (m *mergingIter) init(
|
||||
m.split = split
|
||||
m.stats = stats
|
||||
if cap(m.heap.items) < len(levels) {
|
||||
m.heap.items = make([]mergingIterItem, 0, len(levels))
|
||||
m.heap.items = make([]*mergingIterLevel, 0, len(levels))
|
||||
} else {
|
||||
m.heap.items = m.heap.items[:0]
|
||||
}
|
||||
for l := range m.levels {
|
||||
m.levels[l].index = l
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mergingIter) initHeap() {
|
||||
m.heap.items = m.heap.items[:0]
|
||||
for i := range m.levels {
|
||||
if l := &m.levels[i]; l.iterKey != nil {
|
||||
m.heap.items = append(m.heap.items, mergingIterItem{
|
||||
index: i,
|
||||
key: *l.iterKey,
|
||||
value: l.iterValue,
|
||||
})
|
||||
m.heap.items = append(m.heap.items, l)
|
||||
} else {
|
||||
m.err = firstError(m.err, l.iter.Error())
|
||||
if m.err != nil {
|
||||
@@ -343,13 +352,13 @@ func (m *mergingIter) initMinRangeDelIters(oldTopLevel int) {
|
||||
}
|
||||
|
||||
// Position the range-del iterators at levels <= m.heap.items[0].index.
|
||||
item := &m.heap.items[0]
|
||||
item := m.heap.items[0]
|
||||
for level := oldTopLevel + 1; level <= item.index; level++ {
|
||||
l := &m.levels[level]
|
||||
if l.rangeDelIter == nil {
|
||||
continue
|
||||
}
|
||||
l.tombstone = keyspan.SeekGE(m.heap.cmp, l.rangeDelIter, item.key.UserKey)
|
||||
l.tombstone = l.rangeDelIter.SeekGE(item.iterKey.UserKey)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -370,13 +379,13 @@ func (m *mergingIter) initMaxRangeDelIters(oldTopLevel int) {
|
||||
return
|
||||
}
|
||||
// Position the range-del iterators at levels <= m.heap.items[0].index.
|
||||
item := &m.heap.items[0]
|
||||
item := m.heap.items[0]
|
||||
for level := oldTopLevel + 1; level <= item.index; level++ {
|
||||
l := &m.levels[level]
|
||||
if l.rangeDelIter == nil {
|
||||
continue
|
||||
}
|
||||
l.tombstone = keyspan.SeekLE(m.heap.cmp, l.rangeDelIter, item.key.UserKey)
|
||||
l.tombstone = keyspan.SeekLE(m.heap.cmp, l.rangeDelIter, item.iterKey.UserKey)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -400,8 +409,8 @@ func (m *mergingIter) switchToMinHeap() {
|
||||
// The current key is a:2 and i2 is pointed at a:1. When we switch to forward
|
||||
// iteration, we want to return a key that is greater than a:2.
|
||||
|
||||
key := m.heap.items[0].key
|
||||
cur := &m.levels[m.heap.items[0].index]
|
||||
key := m.heap.items[0].iterKey
|
||||
cur := m.heap.items[0]
|
||||
|
||||
for i := range m.levels {
|
||||
l := &m.levels[i]
|
||||
@@ -442,7 +451,7 @@ func (m *mergingIter) switchToMinHeap() {
|
||||
}
|
||||
}
|
||||
for ; l.iterKey != nil; l.iterKey, l.iterValue = l.iter.Next() {
|
||||
if base.InternalCompare(m.heap.cmp, key, *l.iterKey) < 0 {
|
||||
if base.InternalCompare(m.heap.cmp, *key, *l.iterKey) < 0 {
|
||||
// key < iter-key
|
||||
break
|
||||
}
|
||||
@@ -483,8 +492,8 @@ func (m *mergingIter) switchToMaxHeap() {
|
||||
//
|
||||
// The current key is b:2 and i2 is pointing at b:1. When we switch to
|
||||
// reverse iteration, we want to return a key that is less than b:2.
|
||||
key := m.heap.items[0].key
|
||||
cur := &m.levels[m.heap.items[0].index]
|
||||
key := m.heap.items[0].iterKey
|
||||
cur := m.heap.items[0]
|
||||
|
||||
for i := range m.levels {
|
||||
l := &m.levels[i]
|
||||
@@ -524,7 +533,7 @@ func (m *mergingIter) switchToMaxHeap() {
|
||||
}
|
||||
}
|
||||
for ; l.iterKey != nil; l.iterKey, l.iterValue = l.iter.Prev() {
|
||||
if base.InternalCompare(m.heap.cmp, key, *l.iterKey) > 0 {
|
||||
if base.InternalCompare(m.heap.cmp, *key, *l.iterKey) > 0 {
|
||||
// key > iter-key
|
||||
break
|
||||
}
|
||||
@@ -547,13 +556,69 @@ func (m *mergingIter) switchToMaxHeap() {
|
||||
m.initMaxHeap()
|
||||
}
|
||||
|
||||
// Steps to the next entry. item is the current top item in the heap.
|
||||
func (m *mergingIter) nextEntry(item *mergingIterItem) {
|
||||
l := &m.levels[item.index]
|
||||
oldTopLevel := item.index
|
||||
// maybeNextEntryWithinPrefix steps to the next entry, as long as the iteration
|
||||
// prefix has not already been exceeded. If it has, it exhausts the iterator by
|
||||
// resetting the heap to empty.
|
||||
func (m *mergingIter) maybeNextEntryWithinPrefix(l *mergingIterLevel) {
|
||||
if s := m.split(l.iterKey.UserKey); !bytes.Equal(m.prefix, l.iterKey.UserKey[:s]) {
|
||||
// The item at the root of the heap already exceeds the iteration
|
||||
// prefix. We should not advance any more. Clear the heap to reflect
|
||||
// that the iterator is now exhausted (within this prefix, at
|
||||
// least).
|
||||
m.heap.items = m.heap.items[:0]
|
||||
return
|
||||
}
|
||||
m.nextEntry(l, nil /* succKey */)
|
||||
}
|
||||
|
||||
// nextEntry unconditionally steps to the next entry. item is the current top
|
||||
// item in the heap.
|
||||
//
|
||||
// nextEntry should be called directly when not in prefix-iteration mode, or by
|
||||
// Next. During prefix iteration mode, all other callers should use
|
||||
// maybeNextEntryWithinPrefix which will avoid advancing the iterator if the
|
||||
// current iteration prefix has been exhausted. See the comment within
|
||||
// nextEntry's body for an explanation of why other callers should call
|
||||
// maybeNextEntryWithinPrefix, which will ensure the documented invariant is
|
||||
// preserved.
|
||||
func (m *mergingIter) nextEntry(l *mergingIterLevel, succKey []byte) {
|
||||
// INVARIANT: If in prefix iteration mode, item.iterKey must have a prefix equal
|
||||
// to m.prefix. This invariant is important for ensuring TrySeekUsingNext
|
||||
// optimizations behave correctly.
|
||||
//
|
||||
// During prefix iteration, the iterator does not have a full view of the
|
||||
// LSM. Some level iterators may omit keys that are known to fall outside
|
||||
// the seek prefix (eg, due to sstable bloom filter exclusion). It's
|
||||
// important that in such cases we don't position any iterators beyond
|
||||
// m.prefix, because doing so may interfere with future seeks.
|
||||
//
|
||||
// Let prefixes P1 < P2 < P3. Imagine a SeekPrefixGE to prefix P1, followed
|
||||
// by a SeekPrefixGE to prefix P2. Imagine there exist live keys at prefix
|
||||
// P2, but they're not visible to the SeekPrefixGE(P1) (because of
|
||||
// bloom-filter exclusion or a range tombstone that deletes prefix P1 but
|
||||
// not P2). If the SeekPrefixGE(P1) is allowed to move any level iterators
|
||||
// to P3, the SeekPrefixGE(P2, TrySeekUsingNext=true) may mistakenly think
|
||||
// the level contains no point keys or range tombstones within the prefix
|
||||
// P2. Care is taken to avoid ever advancing the iterator beyond the current
|
||||
// prefix. If nextEntry is ever invoked while we're already beyond the
|
||||
// current prefix, we're violating the invariant.
|
||||
if invariants.Enabled && m.prefix != nil {
|
||||
if s := m.split(l.iterKey.UserKey); !bytes.Equal(m.prefix, l.iterKey.UserKey[:s]) {
|
||||
m.logger.Fatalf("mergingIter: prefix violation: nexting beyond prefix %q; existing heap root %q\n%s",
|
||||
m.prefix, l.iterKey, debug.Stack())
|
||||
}
|
||||
}
|
||||
|
||||
oldTopLevel := l.index
|
||||
oldRangeDelIter := l.rangeDelIter
|
||||
if l.iterKey, l.iterValue = l.iter.Next(); l.iterKey != nil {
|
||||
item.key, item.value = *l.iterKey, l.iterValue
|
||||
|
||||
if succKey == nil {
|
||||
l.iterKey, l.iterValue = l.iter.Next()
|
||||
} else {
|
||||
l.iterKey, l.iterValue = l.iter.NextPrefix(succKey)
|
||||
}
|
||||
|
||||
if l.iterKey != nil {
|
||||
if m.heap.len() > 1 {
|
||||
m.heap.fix(0)
|
||||
}
|
||||
@@ -575,11 +640,15 @@ func (m *mergingIter) nextEntry(item *mergingIterItem) {
|
||||
m.initMinRangeDelIters(oldTopLevel)
|
||||
}
|
||||
|
||||
// isNextEntryDeleted() starts from the current entry (as the next entry) and if it is deleted,
|
||||
// moves the iterators forward as needed and returns true, else it returns false. item is the top
|
||||
// item in the heap.
|
||||
func (m *mergingIter) isNextEntryDeleted(item *mergingIterItem) bool {
|
||||
// Look for a range deletion tombstone containing item.key at higher
|
||||
// isNextEntryDeleted starts from the current entry (as the next entry) and if
|
||||
// it is deleted, moves the iterators forward as needed and returns true, else
|
||||
// it returns false. item is the top item in the heap.
|
||||
//
|
||||
// During prefix iteration mode, isNextEntryDeleted will exhaust the iterator by
|
||||
// clearing the heap if the deleted key(s) extend beyond the iteration prefix
|
||||
// during prefix-iteration mode.
|
||||
func (m *mergingIter) isNextEntryDeleted(item *mergingIterLevel) bool {
|
||||
// Look for a range deletion tombstone containing item.iterKey at higher
|
||||
// levels (level < item.index). If we find such a range tombstone we know
|
||||
// it deletes the key in the current level. Also look for a range
|
||||
// deletion at the current level (level == item.index). If we find such a
|
||||
@@ -593,16 +662,16 @@ func (m *mergingIter) isNextEntryDeleted(item *mergingIterItem) bool {
|
||||
// direction.
|
||||
continue
|
||||
}
|
||||
if m.heap.cmp(l.tombstone.End, item.key.UserKey) <= 0 {
|
||||
if m.heap.cmp(l.tombstone.End, item.iterKey.UserKey) <= 0 {
|
||||
// The current key is at or past the tombstone end key.
|
||||
//
|
||||
// NB: for the case that this l.rangeDelIter is provided by a levelIter we know that
|
||||
// the levelIter must be positioned at a key >= item.key. So it is sufficient to seek the
|
||||
// the levelIter must be positioned at a key >= item.iterKey. So it is sufficient to seek the
|
||||
// current l.rangeDelIter (since any range del iterators that will be provided by the
|
||||
// levelIter in the future cannot contain item.key). Also, it is possible that we
|
||||
// levelIter in the future cannot contain item.iterKey). Also, it is possible that we
|
||||
// will encounter parts of the range delete that should be ignored -- we handle that
|
||||
// below.
|
||||
l.tombstone = keyspan.SeekGE(m.heap.cmp, l.rangeDelIter, item.key.UserKey)
|
||||
l.tombstone = l.rangeDelIter.SeekGE(item.iterKey.UserKey)
|
||||
}
|
||||
if l.tombstone == nil {
|
||||
continue
|
||||
@@ -611,9 +680,9 @@ func (m *mergingIter) isNextEntryDeleted(item *mergingIterItem) bool {
|
||||
// Reasoning for correctness of untruncated tombstone handling when the untruncated
|
||||
// tombstone is at a higher level:
|
||||
// The iterator corresponding to this tombstone is still in the heap so it must be
|
||||
// positioned >= item.key. Which means the Largest key bound of the sstable containing this
|
||||
// tombstone is >= item.key. So the upper limit of this tombstone cannot be file-bounds-constrained
|
||||
// to < item.key. But it is possible that item.key < smallestUserKey, in which
|
||||
// positioned >= item.iterKey. Which means the Largest key bound of the sstable containing this
|
||||
// tombstone is >= item.iterKey. So the upper limit of this tombstone cannot be file-bounds-constrained
|
||||
// to < item.iterKey. But it is possible that item.key < smallestUserKey, in which
|
||||
// case this tombstone should be ignored.
|
||||
//
|
||||
// Example 1:
|
||||
@@ -626,11 +695,11 @@ func (m *mergingIter) isNextEntryDeleted(item *mergingIterItem) bool {
|
||||
// does not cover this key.
|
||||
//
|
||||
// For a tombstone at the same level as the key, the file bounds are trivially satisfied.
|
||||
if (l.smallestUserKey == nil || m.heap.cmp(l.smallestUserKey, item.key.UserKey) <= 0) &&
|
||||
l.tombstone.VisibleAt(m.snapshot) && l.tombstone.Contains(m.heap.cmp, item.key.UserKey) {
|
||||
if (l.smallestUserKey == nil || m.heap.cmp(l.smallestUserKey, item.iterKey.UserKey) <= 0) &&
|
||||
l.tombstone.VisibleAt(m.snapshot) && l.tombstone.Contains(m.heap.cmp, item.iterKey.UserKey) {
|
||||
if level < item.index {
|
||||
// We could also do m.seekGE(..., level + 1). The levels from
|
||||
// [level + 1, item.index) are already after item.key so seeking them may be
|
||||
// [level + 1, item.index) are already after item.iterKey so seeking them may be
|
||||
// wasteful.
|
||||
|
||||
// We can seek up to the min of largestUserKey and tombstone.End.
|
||||
@@ -642,33 +711,70 @@ func (m *mergingIter) isNextEntryDeleted(item *mergingIterItem) bool {
|
||||
// the exclusive bound of the tombstone. We do not need to look at
|
||||
// isLargestKeyRangeDelSentinel.
|
||||
//
|
||||
// Progress argument: Since this file is at a higher level than item.key we know
|
||||
// Progress argument: Since this file is at a higher level than item.iterKey we know
|
||||
// that the iterator in this file must be positioned within its bounds and at a key
|
||||
// X > item.key (otherwise it would be the min of the heap). It is not
|
||||
// possible for X.UserKey == item.key.UserKey, since it is incompatible with
|
||||
// X > item.key (a lower version cannot be in a higher sstable), so it must be that
|
||||
// X.UserKey > item.key.UserKey. Which means l.largestUserKey > item.key.UserKey.
|
||||
// We also know that l.tombstone.End > item.key.UserKey. So the min of these,
|
||||
// seekKey, computed below, is > item.key.UserKey, so the call to seekGE() will
|
||||
// X > item.iterKey (otherwise it would be the min of the heap). It is not
|
||||
// possible for X.UserKey == item.iterKey.UserKey, since it is incompatible with
|
||||
// X > item.iterKey (a lower version cannot be in a higher sstable), so it must be that
|
||||
// X.UserKey > item.iterKey.UserKey. Which means l.largestUserKey > item.key.UserKey.
|
||||
// We also know that l.tombstone.End > item.iterKey.UserKey. So the min of these,
|
||||
// seekKey, computed below, is > item.iterKey.UserKey, so the call to seekGE() will
|
||||
// make forward progress.
|
||||
seekKey := l.tombstone.End
|
||||
if l.largestUserKey != nil && m.heap.cmp(l.largestUserKey, seekKey) < 0 {
|
||||
seekKey = l.largestUserKey
|
||||
}
|
||||
// This seek is not directly due to a SeekGE call, so we don't
|
||||
// know enough about the underlying iterator positions, and so
|
||||
// we keep the try-seek-using-next optimization disabled.
|
||||
// This seek is not directly due to a SeekGE call, so we don't know
|
||||
// enough about the underlying iterator positions, and so we keep the
|
||||
// try-seek-using-next optimization disabled. Additionally, if we're in
|
||||
// prefix-seek mode and a re-seek would have moved us past the original
|
||||
// prefix, we can remove all merging iter levels below the rangedel
|
||||
// tombstone's level and return immediately instead of re-seeking. This
|
||||
// is correct since those levels cannot provide a key that matches the
|
||||
// prefix, and is also visible. Additionally, this is important to make
|
||||
// subsequent `TrySeekUsingNext` work correctly, as a re-seek on a
|
||||
// different prefix could have resulted in this iterator skipping visible
|
||||
// keys at prefixes in between m.prefix and seekKey, that are currently
|
||||
// not in the heap due to a bloom filter mismatch.
|
||||
//
|
||||
// Additionally, we set the relative-seek flag. This is
|
||||
// important when iterating with lazy combined iteration. If
|
||||
// there's a range key between this level's current file and the
|
||||
// file the seek will land on, we need to detect it in order to
|
||||
// trigger construction of the combined iterator.
|
||||
if m.prefix != nil {
|
||||
if n := m.split(seekKey); !bytes.Equal(m.prefix, seekKey[:n]) {
|
||||
for i := item.index; i < len(m.levels); i++ {
|
||||
// Remove this level from the heap. Setting iterKey and iterValue
|
||||
// to their zero values should be sufficient for initMinHeap to not
|
||||
// re-initialize the heap with them in it. Other fields in
|
||||
// mergingIterLevel can remain as-is; the iter/rangeDelIter needs
|
||||
// to stay intact for future trySeekUsingNexts to work, the level
|
||||
// iter boundary context is owned by the levelIter which is not
|
||||
// being repositioned, and any tombstones in these levels will be
|
||||
// irrelevant for us anyway.
|
||||
m.levels[i].iterKey = nil
|
||||
m.levels[i].iterValue = base.LazyValue{}
|
||||
}
|
||||
// TODO(bilal): Consider a more efficient way of removing levels from
|
||||
// the heap without reinitializing all of it. This would likely
|
||||
// necessitate tracking the heap positions of each mergingIterHeap
|
||||
// item in the mergingIterLevel, and then swapping that item in the
|
||||
// heap with the last-positioned heap item, and shrinking the heap by
|
||||
// one.
|
||||
m.initMinHeap()
|
||||
return true
|
||||
}
|
||||
}
|
||||
m.seekGE(seekKey, item.index, base.SeekGEFlagsNone.EnableRelativeSeek())
|
||||
return true
|
||||
}
|
||||
if l.tombstone.CoversAt(m.snapshot, item.key.SeqNum()) {
|
||||
m.nextEntry(item)
|
||||
if l.tombstone.CoversAt(m.snapshot, item.iterKey.SeqNum()) {
|
||||
if m.prefix == nil {
|
||||
m.nextEntry(item, nil /* succKey */)
|
||||
} else {
|
||||
m.maybeNextEntryWithinPrefix(item)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -678,73 +784,56 @@ func (m *mergingIter) isNextEntryDeleted(item *mergingIterItem) bool {
|
||||
|
||||
// Starting from the current entry, finds the first (next) entry that can be returned.
|
||||
func (m *mergingIter) findNextEntry() (*InternalKey, base.LazyValue) {
|
||||
var reseeked bool
|
||||
for m.heap.len() > 0 && m.err == nil {
|
||||
item := &m.heap.items[0]
|
||||
item := m.heap.items[0]
|
||||
if m.levels[item.index].isSyntheticIterBoundsKey {
|
||||
break
|
||||
}
|
||||
// For prefix iteration, stop if we've already exceeded the iterator's
|
||||
// current prefix. There are two cases where we perform this check.
|
||||
//
|
||||
// 1. We already re-seeked the iterator due to a range tombstone. We
|
||||
// could amortize the cost of this comparison, by doing it only after
|
||||
// we have iterated in this for loop a few times. But unless we find
|
||||
// a performance benefit to that, we do the simple thing and compare
|
||||
// each time. Note that isNextEntryDeleted already did at least 4 key
|
||||
// comparisons in order to return true, and additionally at least one
|
||||
// heap comparison to step to the next entry.
|
||||
//
|
||||
// Note that we cannot move this comparison into the
|
||||
// isNextEntryDeleted branch. Once isNextEntryDeleted determines a
|
||||
// key is deleted and seeks the level's iterator, item.key's memory
|
||||
// is potentially invalid. If the iterator is now exhausted, item.key
|
||||
// may be garbage.
|
||||
//
|
||||
// 2. If the heap root is an ignorable boundary key, Next-ing the
|
||||
// iterator will close the associated file and open the next file in
|
||||
// the level. We don't want to do this unnecessarily because the
|
||||
// 'exhausted' file may only appear exhausted because it failed a
|
||||
// bloom filter check. Avoiding Next-ing the file has a performance
|
||||
// benefit of unnecessarily advancing to the next file, but it's also
|
||||
// necessary for correctness of some TrySeekUsingNext optimizations
|
||||
// deeper in the iterator stack:
|
||||
//
|
||||
// There may exist valid keys between our current prefix (m.prefix)
|
||||
// and the ignorable boundary key (item.key) that were not returned
|
||||
// due to the bloom filter test. A subsequent SeekPrefixGE call with
|
||||
// a seek key in the range (m.prefix - item.key) will have the
|
||||
// TrySeekUsingNext flag enabled. The levelIter relies on having not
|
||||
// skipped these keys in order to avoid more expensive full seeks.
|
||||
if m.prefix != nil && (reseeked || m.levels[item.index].isIgnorableBoundaryKey) {
|
||||
if n := m.split(item.key.UserKey); !bytes.Equal(m.prefix, item.key.UserKey[:n]) {
|
||||
return nil, base.LazyValue{}
|
||||
}
|
||||
}
|
||||
|
||||
m.addItemStats(item)
|
||||
if m.isNextEntryDeleted(item) {
|
||||
m.stats.PointsCoveredByRangeTombstones++
|
||||
reseeked = true
|
||||
|
||||
// Skip ignorable boundary keys. These are not real keys and exist to
|
||||
// keep sstables open until we've surpassed their end boundaries so that
|
||||
// their range deletions are visible.
|
||||
if m.levels[item.index].isIgnorableBoundaryKey {
|
||||
if m.prefix == nil {
|
||||
m.nextEntry(item, nil /* succKey */)
|
||||
} else {
|
||||
m.maybeNextEntryWithinPrefix(item)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if item.key.Visible(m.snapshot, m.batchSnapshot) &&
|
||||
(!m.levels[item.index].isIgnorableBoundaryKey) &&
|
||||
(item.key.Kind() != InternalKeyKindRangeDelete || !m.elideRangeTombstones) {
|
||||
return &item.key, item.value
|
||||
|
||||
// Check if the heap root key is deleted by a range tombstone in a
|
||||
// higher level. If it is, isNextEntryDeleted will advance the iterator
|
||||
// to a later key (through seeking or nexting).
|
||||
if m.isNextEntryDeleted(item) {
|
||||
m.stats.PointsCoveredByRangeTombstones++
|
||||
continue
|
||||
}
|
||||
m.nextEntry(item)
|
||||
|
||||
// Check if the key is visible at the iterator sequence numbers.
|
||||
if !item.iterKey.Visible(m.snapshot, m.batchSnapshot) {
|
||||
if m.prefix == nil {
|
||||
m.nextEntry(item, nil /* succKey */)
|
||||
} else {
|
||||
m.maybeNextEntryWithinPrefix(item)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// The heap root is visible and not deleted by any range tombstones.
|
||||
// Return it.
|
||||
return item.iterKey, item.iterValue
|
||||
}
|
||||
return nil, base.LazyValue{}
|
||||
}
|
||||
|
||||
// Steps to the prev entry. item is the current top item in the heap.
|
||||
func (m *mergingIter) prevEntry(item *mergingIterItem) {
|
||||
l := &m.levels[item.index]
|
||||
oldTopLevel := item.index
|
||||
func (m *mergingIter) prevEntry(l *mergingIterLevel) {
|
||||
oldTopLevel := l.index
|
||||
oldRangeDelIter := l.rangeDelIter
|
||||
if l.iterKey, l.iterValue = l.iter.Prev(); l.iterKey != nil {
|
||||
item.key, item.value = *l.iterKey, l.iterValue
|
||||
if m.heap.len() > 1 {
|
||||
m.heap.fix(0)
|
||||
}
|
||||
@@ -770,8 +859,8 @@ func (m *mergingIter) prevEntry(item *mergingIterItem) {
|
||||
// isPrevEntryDeleted() starts from the current entry (as the prev entry) and if it is deleted,
|
||||
// moves the iterators backward as needed and returns true, else it returns false. item is the top
|
||||
// item in the heap.
|
||||
func (m *mergingIter) isPrevEntryDeleted(item *mergingIterItem) bool {
|
||||
// Look for a range deletion tombstone containing item.key at higher
|
||||
func (m *mergingIter) isPrevEntryDeleted(item *mergingIterLevel) bool {
|
||||
// Look for a range deletion tombstone containing item.iterKey at higher
|
||||
// levels (level < item.index). If we find such a range tombstone we know
|
||||
// it deletes the key in the current level. Also look for a range
|
||||
// deletion at the current level (level == item.index). If we find such a
|
||||
@@ -785,16 +874,16 @@ func (m *mergingIter) isPrevEntryDeleted(item *mergingIterItem) bool {
|
||||
// direction.
|
||||
continue
|
||||
}
|
||||
if m.heap.cmp(item.key.UserKey, l.tombstone.Start) < 0 {
|
||||
if m.heap.cmp(item.iterKey.UserKey, l.tombstone.Start) < 0 {
|
||||
// The current key is before the tombstone start key.
|
||||
//
|
||||
// NB: for the case that this l.rangeDelIter is provided by a levelIter we know that
|
||||
// the levelIter must be positioned at a key < item.key. So it is sufficient to seek the
|
||||
// the levelIter must be positioned at a key < item.iterKey. So it is sufficient to seek the
|
||||
// current l.rangeDelIter (since any range del iterators that will be provided by the
|
||||
// levelIter in the future cannot contain item.key). Also, it is it is possible that we
|
||||
// levelIter in the future cannot contain item.iterKey). Also, it is it is possible that we
|
||||
// will encounter parts of the range delete that should be ignored -- we handle that
|
||||
// below.
|
||||
l.tombstone = keyspan.SeekLE(m.heap.cmp, l.rangeDelIter, item.key.UserKey)
|
||||
l.tombstone = keyspan.SeekLE(m.heap.cmp, l.rangeDelIter, item.iterKey.UserKey)
|
||||
}
|
||||
if l.tombstone == nil {
|
||||
continue
|
||||
@@ -804,9 +893,9 @@ func (m *mergingIter) isPrevEntryDeleted(item *mergingIterItem) bool {
|
||||
// tombstone is at a higher level:
|
||||
//
|
||||
// The iterator corresponding to this tombstone is still in the heap so it must be
|
||||
// positioned <= item.key. Which means the Smallest key bound of the sstable containing this
|
||||
// tombstone is <= item.key. So the lower limit of this tombstone cannot have been
|
||||
// file-bounds-constrained to > item.key. But it is possible that item.key >= Largest
|
||||
// positioned <= item.iterKey. Which means the Smallest key bound of the sstable containing this
|
||||
// tombstone is <= item.iterKey. So the lower limit of this tombstone cannot have been
|
||||
// file-bounds-constrained to > item.iterKey. But it is possible that item.key >= Largest
|
||||
// key bound of this sstable, in which case this tombstone should be ignored.
|
||||
//
|
||||
// Example 1:
|
||||
@@ -823,20 +912,20 @@ func (m *mergingIter) isPrevEntryDeleted(item *mergingIterItem) bool {
|
||||
// Example 3:
|
||||
// sstable bounds [c#8, g#RangeDelSentinel] containing [b, i)#7 and the key is g#10.
|
||||
// This key is not deleted by this tombstone. We need to look at
|
||||
// isLargestUserKeyRangeDelSentinel.
|
||||
// isLargestUserKeyExclusive.
|
||||
//
|
||||
// For a tombstone at the same level as the key, the file bounds are trivially satisfied.
|
||||
|
||||
// Default to within bounds.
|
||||
withinLargestSSTableBound := true
|
||||
if l.largestUserKey != nil {
|
||||
cmpResult := m.heap.cmp(l.largestUserKey, item.key.UserKey)
|
||||
withinLargestSSTableBound = cmpResult > 0 || (cmpResult == 0 && !l.isLargestUserKeyRangeDelSentinel)
|
||||
cmpResult := m.heap.cmp(l.largestUserKey, item.iterKey.UserKey)
|
||||
withinLargestSSTableBound = cmpResult > 0 || (cmpResult == 0 && !l.isLargestUserKeyExclusive)
|
||||
}
|
||||
if withinLargestSSTableBound && l.tombstone.Contains(m.heap.cmp, item.key.UserKey) && l.tombstone.VisibleAt(m.snapshot) {
|
||||
if withinLargestSSTableBound && l.tombstone.Contains(m.heap.cmp, item.iterKey.UserKey) && l.tombstone.VisibleAt(m.snapshot) {
|
||||
if level < item.index {
|
||||
// We could also do m.seekLT(..., level + 1). The levels from
|
||||
// [level + 1, item.index) are already before item.key so seeking them may be
|
||||
// [level + 1, item.index) are already before item.iterKey so seeking them may be
|
||||
// wasteful.
|
||||
|
||||
// We can seek up to the max of smallestUserKey and tombstone.Start.UserKey.
|
||||
@@ -844,10 +933,10 @@ func (m *mergingIter) isPrevEntryDeleted(item *mergingIterItem) bool {
|
||||
// Using example 1 above, we can seek to the larger of c and b, which is c.
|
||||
//
|
||||
// Progress argument: We know that the iterator in this file is positioned within
|
||||
// its bounds and at a key X < item.key (otherwise it would be the max of the heap).
|
||||
// So smallestUserKey <= item.key.UserKey and we already know that
|
||||
// l.tombstone.Start.UserKey <= item.key.UserKey. So the seekKey computed below
|
||||
// is <= item.key.UserKey, and since we do a seekLT() we will make backwards
|
||||
// its bounds and at a key X < item.iterKey (otherwise it would be the max of the heap).
|
||||
// So smallestUserKey <= item.iterKey.UserKey and we already know that
|
||||
// l.tombstone.Start.UserKey <= item.iterKey.UserKey. So the seekKey computed below
|
||||
// is <= item.iterKey.UserKey, and since we do a seekLT() we will make backwards
|
||||
// progress.
|
||||
seekKey := l.tombstone.Start
|
||||
if l.smallestUserKey != nil && m.heap.cmp(l.smallestUserKey, seekKey) > 0 {
|
||||
@@ -861,7 +950,7 @@ func (m *mergingIter) isPrevEntryDeleted(item *mergingIterItem) bool {
|
||||
m.seekLT(seekKey, item.index, base.SeekLTFlagsNone.EnableRelativeSeek())
|
||||
return true
|
||||
}
|
||||
if l.tombstone.CoversAt(m.snapshot, item.key.SeqNum()) {
|
||||
if l.tombstone.CoversAt(m.snapshot, item.iterKey.SeqNum()) {
|
||||
m.prevEntry(item)
|
||||
return true
|
||||
}
|
||||
@@ -873,7 +962,7 @@ func (m *mergingIter) isPrevEntryDeleted(item *mergingIterItem) bool {
|
||||
// Starting from the current entry, finds the first (prev) entry that can be returned.
|
||||
func (m *mergingIter) findPrevEntry() (*InternalKey, base.LazyValue) {
|
||||
for m.heap.len() > 0 && m.err == nil {
|
||||
item := &m.heap.items[0]
|
||||
item := m.heap.items[0]
|
||||
if m.levels[item.index].isSyntheticIterBoundsKey {
|
||||
break
|
||||
}
|
||||
@@ -882,10 +971,9 @@ func (m *mergingIter) findPrevEntry() (*InternalKey, base.LazyValue) {
|
||||
m.stats.PointsCoveredByRangeTombstones++
|
||||
continue
|
||||
}
|
||||
if item.key.Visible(m.snapshot, m.batchSnapshot) &&
|
||||
(!m.levels[item.index].isIgnorableBoundaryKey) &&
|
||||
(item.key.Kind() != InternalKeyKindRangeDelete || !m.elideRangeTombstones) {
|
||||
return &item.key, item.value
|
||||
if item.iterKey.Visible(m.snapshot, m.batchSnapshot) &&
|
||||
(!m.levels[item.index].isIgnorableBoundaryKey) {
|
||||
return item.iterKey, item.iterValue
|
||||
}
|
||||
m.prevEntry(item)
|
||||
}
|
||||
@@ -915,6 +1003,37 @@ func (m *mergingIter) seekGE(key []byte, level int, flags base.SeekGEFlags) {
|
||||
// level (and any lower levels) when the current iterator position is
|
||||
// contained within a range tombstone at a higher level.
|
||||
|
||||
// Deterministically disable the TrySeekUsingNext optimizations sometimes in
|
||||
// invariant builds to encourage the metamorphic tests to surface bugs. Note
|
||||
// that we cannot disable the optimization within individual levels. It must
|
||||
// be disabled for all levels or none. If one lower-level iterator performs
|
||||
// a fresh seek whereas another takes advantage of its current iterator
|
||||
// position, the heap can become inconsistent. Consider the following
|
||||
// example:
|
||||
//
|
||||
// L5: [ [b-c) ] [ d ]*
|
||||
// L6: [ b ] [e]*
|
||||
//
|
||||
// Imagine a SeekGE(a). The [b-c) range tombstone deletes the L6 point key
|
||||
// 'b', resulting in the iterator positioned at d with the heap:
|
||||
//
|
||||
// {L5: d, L6: e}
|
||||
//
|
||||
// A subsequent SeekGE(b) is seeking to a larger key, so the caller may set
|
||||
// TrySeekUsingNext()=true. If the L5 iterator used the TrySeekUsingNext
|
||||
// optimization but the L6 iterator did not, the iterator would have the
|
||||
// heap:
|
||||
//
|
||||
// {L6: b, L5: d}
|
||||
//
|
||||
// Because the L5 iterator has already advanced to the next sstable, the
|
||||
// merging iterator cannot observe the [b-c) range tombstone and will
|
||||
// mistakenly return L6's deleted point key 'b'.
|
||||
if invariants.Enabled && flags.TrySeekUsingNext() && !m.forceEnableSeekOpt &&
|
||||
disableSeekOpt(key, uintptr(unsafe.Pointer(m))) {
|
||||
flags = flags.DisableTrySeekUsingNext()
|
||||
}
|
||||
|
||||
for ; level < len(m.levels); level++ {
|
||||
if invariants.Enabled && m.lower != nil && m.heap.cmp(key, m.lower) < 0 {
|
||||
m.logger.Fatalf("mergingIter: lower bound violation: %s < %s\n%s", key, m.lower, debug.Stack())
|
||||
@@ -950,7 +1069,7 @@ func (m *mergingIter) seekGE(key []byte, level int, flags base.SeekGEFlags) {
|
||||
// so we can have a sstable with bounds [c#8, i#InternalRangeDelSentinel], and the
|
||||
// tombstone is [b, k)#8 and the seek key is i: levelIter.SeekGE(i) will move past
|
||||
// this sstable since it realizes the largest key is a InternalRangeDelSentinel.
|
||||
l.tombstone = keyspan.SeekGE(m.heap.cmp, rangeDelIter, key)
|
||||
l.tombstone = rangeDelIter.SeekGE(key)
|
||||
if l.tombstone != nil && l.tombstone.VisibleAt(m.snapshot) && l.tombstone.Contains(m.heap.cmp, key) &&
|
||||
(l.smallestUserKey == nil || m.heap.cmp(l.smallestUserKey, key) <= 0) {
|
||||
// NB: Based on the comment above l.largestUserKey >= key, and based on the
|
||||
@@ -1040,7 +1159,7 @@ func (m *mergingIter) seekLT(key []byte, level int, flags base.SeekLTFlags) {
|
||||
withinLargestSSTableBound := true
|
||||
if l.largestUserKey != nil {
|
||||
cmpResult := m.heap.cmp(l.largestUserKey, key)
|
||||
withinLargestSSTableBound = cmpResult > 0 || (cmpResult == 0 && !l.isLargestUserKeyRangeDelSentinel)
|
||||
withinLargestSSTableBound = cmpResult > 0 || (cmpResult == 0 && !l.isLargestUserKeyExclusive)
|
||||
}
|
||||
|
||||
l.tombstone = keyspan.SeekLE(m.heap.cmp, rangeDelIter, key)
|
||||
@@ -1123,7 +1242,59 @@ func (m *mergingIter) Next() (*InternalKey, base.LazyValue) {
|
||||
return nil, base.LazyValue{}
|
||||
}
|
||||
|
||||
m.nextEntry(&m.heap.items[0])
|
||||
// NB: It's okay to call nextEntry directly even during prefix iteration
|
||||
// mode (as opposed to indirectly through maybeNextEntryWithinPrefix).
|
||||
// During prefix iteration mode, we rely on the caller to not call Next if
|
||||
// the iterator has already advanced beyond the iteration prefix. See the
|
||||
// comment above the base.InternalIterator interface.
|
||||
m.nextEntry(m.heap.items[0], nil /* succKey */)
|
||||
return m.findNextEntry()
|
||||
}
|
||||
|
||||
func (m *mergingIter) NextPrefix(succKey []byte) (*InternalKey, LazyValue) {
|
||||
if m.dir != 1 {
|
||||
panic("pebble: cannot switch directions with NextPrefix")
|
||||
}
|
||||
if m.err != nil || m.heap.len() == 0 {
|
||||
return nil, LazyValue{}
|
||||
}
|
||||
if m.levelsPositioned == nil {
|
||||
m.levelsPositioned = make([]bool, len(m.levels))
|
||||
} else {
|
||||
for i := range m.levelsPositioned {
|
||||
m.levelsPositioned[i] = false
|
||||
}
|
||||
}
|
||||
|
||||
// The heap root necessarily must be positioned at a key < succKey, because
|
||||
// NextPrefix was invoked.
|
||||
root := &m.heap.items[0]
|
||||
m.levelsPositioned[(*root).index] = true
|
||||
if invariants.Enabled && m.heap.cmp((*root).iterKey.UserKey, succKey) >= 0 {
|
||||
m.logger.Fatalf("pebble: invariant violation: NextPrefix(%q) called on merging iterator already positioned at %q",
|
||||
succKey, (*root).iterKey)
|
||||
}
|
||||
m.nextEntry(*root, succKey)
|
||||
// NB: root is a pointer to the heap root. nextEntry may have changed
|
||||
// the heap root, so we must not expect root to still point to the same
|
||||
// level (or to even be valid, if the heap is now exhaused).
|
||||
|
||||
for m.heap.len() > 0 {
|
||||
if m.levelsPositioned[(*root).index] {
|
||||
// A level we've previously positioned is at the top of the heap, so
|
||||
// there are no other levels positioned at keys < succKey. We've
|
||||
// advanced as far as we need to.
|
||||
break
|
||||
}
|
||||
// Since this level was not the original heap root when NextPrefix was
|
||||
// called, we don't know whether this level's current key has the
|
||||
// previous prefix or a new one.
|
||||
if m.heap.cmp((*root).iterKey.UserKey, succKey) >= 0 {
|
||||
break
|
||||
}
|
||||
m.levelsPositioned[(*root).index] = true
|
||||
m.nextEntry(*root, succKey)
|
||||
}
|
||||
return m.findNextEntry()
|
||||
}
|
||||
|
||||
@@ -1145,7 +1316,7 @@ func (m *mergingIter) Prev() (*InternalKey, base.LazyValue) {
|
||||
return nil, base.LazyValue{}
|
||||
}
|
||||
|
||||
m.prevEntry(&m.heap.items[0])
|
||||
m.prevEntry(m.heap.items[0])
|
||||
return m.findPrevEntry()
|
||||
}
|
||||
|
||||
@@ -1188,7 +1359,7 @@ func (m *mergingIter) DebugString() string {
|
||||
sep := ""
|
||||
for m.heap.len() > 0 {
|
||||
item := m.heap.pop()
|
||||
fmt.Fprintf(&buf, "%s%s", sep, item.key)
|
||||
fmt.Fprintf(&buf, "%s%s", sep, item.iterKey)
|
||||
sep = " "
|
||||
}
|
||||
if m.dir == 1 {
|
||||
@@ -1209,10 +1380,10 @@ func (m *mergingIter) ForEachLevelIter(fn func(li *levelIter) bool) {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mergingIter) addItemStats(item *mergingIterItem) {
|
||||
func (m *mergingIter) addItemStats(l *mergingIterLevel) {
|
||||
m.stats.PointCount++
|
||||
m.stats.KeyBytes += uint64(len(item.key.UserKey))
|
||||
m.stats.ValueBytes += uint64(len(item.value.ValueOrHandle))
|
||||
m.stats.KeyBytes += uint64(len(l.iterKey.UserKey))
|
||||
m.stats.ValueBytes += uint64(len(l.iterValue.ValueOrHandle))
|
||||
}
|
||||
|
||||
var _ internalIterator = &mergingIter{}
|
||||
|
||||
Reference in New Issue
Block a user