mirror of
https://source.quilibrium.com/quilibrium/ceremonyclient.git
synced 2024-12-27 09:05:18 +00:00
714 lines
29 KiB
Go
714 lines
29 KiB
Go
|
// Copyright 2021 The LevelDB-Go and Pebble Authors. All rights reserved. Use
|
||
|
// of this source code is governed by a BSD-style license that can be found in
|
||
|
// the LICENSE file.
|
||
|
|
||
|
package pebble
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
|
||
|
"github.com/cockroachdb/errors"
|
||
|
"github.com/cockroachdb/pebble/internal/base"
|
||
|
"github.com/cockroachdb/pebble/internal/invariants"
|
||
|
"github.com/cockroachdb/pebble/internal/keyspan"
|
||
|
"github.com/cockroachdb/pebble/internal/manifest"
|
||
|
"github.com/cockroachdb/pebble/sstable"
|
||
|
)
|
||
|
|
||
|
// constructRangeKeyIter constructs the range-key iterator stack, populating
|
||
|
// i.rangeKey.rangeKeyIter with the resulting iterator.
|
||
|
func (i *Iterator) constructRangeKeyIter() {
|
||
|
i.rangeKey.rangeKeyIter = i.rangeKey.iterConfig.Init(
|
||
|
&i.comparer, i.seqNum, i.opts.LowerBound, i.opts.UpperBound,
|
||
|
&i.hasPrefix, &i.prefixOrFullSeekKey, false /* internalKeys */, &i.rangeKey.rangeKeyBuffers.internal)
|
||
|
|
||
|
// If there's an indexed batch with range keys, include it.
|
||
|
if i.batch != nil {
|
||
|
if i.batch.index == nil {
|
||
|
// This isn't an indexed batch. We shouldn't have gotten this far.
|
||
|
panic(errors.AssertionFailedf("creating an iterator over an unindexed batch"))
|
||
|
} else {
|
||
|
// Only include the batch's range key iterator if it has any keys.
|
||
|
// NB: This can force reconstruction of the rangekey iterator stack
|
||
|
// in SetOptions if subsequently range keys are added. See
|
||
|
// SetOptions.
|
||
|
if i.batch.countRangeKeys > 0 {
|
||
|
i.batch.initRangeKeyIter(&i.opts, &i.batchRangeKeyIter, i.batchSeqNum)
|
||
|
i.rangeKey.iterConfig.AddLevel(&i.batchRangeKeyIter)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if !i.batchOnlyIter {
|
||
|
// Next are the flushables: memtables and large batches.
|
||
|
if i.readState != nil {
|
||
|
for j := len(i.readState.memtables) - 1; j >= 0; j-- {
|
||
|
mem := i.readState.memtables[j]
|
||
|
// We only need to read from memtables which contain sequence numbers older
|
||
|
// than seqNum.
|
||
|
if logSeqNum := mem.logSeqNum; logSeqNum >= i.seqNum {
|
||
|
continue
|
||
|
}
|
||
|
if rki := mem.newRangeKeyIter(&i.opts); rki != nil {
|
||
|
i.rangeKey.iterConfig.AddLevel(rki)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
current := i.version
|
||
|
if current == nil {
|
||
|
current = i.readState.current
|
||
|
}
|
||
|
// Next are the file levels: L0 sub-levels followed by lower levels.
|
||
|
|
||
|
// Add file-specific iterators for L0 files containing range keys. We
|
||
|
// maintain a separate manifest.LevelMetadata for each level containing only
|
||
|
// files that contain range keys, however we don't compute a separate
|
||
|
// L0Sublevels data structure too.
|
||
|
//
|
||
|
// We first use L0's LevelMetadata to peek and see whether L0 contains any
|
||
|
// range keys at all. If it does, we create a range key level iterator per
|
||
|
// level that contains range keys using the information from L0Sublevels.
|
||
|
// Some sublevels may not contain any range keys, and we need to iterate
|
||
|
// through the fileMetadata to determine that. Since L0's file count should
|
||
|
// not significantly exceed ~1000 files (see L0CompactionFileThreshold),
|
||
|
// this should be okay.
|
||
|
if !current.RangeKeyLevels[0].Empty() {
|
||
|
// L0 contains at least 1 file containing range keys.
|
||
|
// Add level iterators for the L0 sublevels, iterating from newest to
|
||
|
// oldest.
|
||
|
for j := len(current.L0SublevelFiles) - 1; j >= 0; j-- {
|
||
|
iter := current.L0SublevelFiles[j].Iter()
|
||
|
if !containsAnyRangeKeys(iter) {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
li := i.rangeKey.iterConfig.NewLevelIter()
|
||
|
li.Init(
|
||
|
i.opts.SpanIterOptions(),
|
||
|
i.cmp,
|
||
|
i.newIterRangeKey,
|
||
|
iter.Filter(manifest.KeyTypeRange),
|
||
|
manifest.L0Sublevel(j),
|
||
|
manifest.KeyTypeRange,
|
||
|
)
|
||
|
i.rangeKey.iterConfig.AddLevel(li)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Add level iterators for the non-empty non-L0 levels.
|
||
|
for level := 1; level < len(current.RangeKeyLevels); level++ {
|
||
|
if current.RangeKeyLevels[level].Empty() {
|
||
|
continue
|
||
|
}
|
||
|
li := i.rangeKey.iterConfig.NewLevelIter()
|
||
|
spanIterOpts := i.opts.SpanIterOptions()
|
||
|
li.Init(spanIterOpts, i.cmp, i.newIterRangeKey, current.RangeKeyLevels[level].Iter(),
|
||
|
manifest.Level(level), manifest.KeyTypeRange)
|
||
|
i.rangeKey.iterConfig.AddLevel(li)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func containsAnyRangeKeys(iter manifest.LevelIterator) bool {
|
||
|
for f := iter.First(); f != nil; f = iter.Next() {
|
||
|
if f.HasRangeKeys {
|
||
|
return true
|
||
|
}
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
// Range key masking
|
||
|
//
|
||
|
// Pebble iterators may be configured such that range keys with suffixes mask
|
||
|
// point keys with lower suffixes. The intended use is implementing a MVCC
|
||
|
// delete range operation using range keys, when suffixes are MVCC timestamps.
|
||
|
//
|
||
|
// To enable masking, the user populates the IterOptions's RangeKeyMasking
|
||
|
// field. The Suffix field configures which range keys act as masks. The
|
||
|
// intended use is to hold a MVCC read timestamp. When implementing a MVCC
|
||
|
// delete range operation, only range keys that are visible at the read
|
||
|
// timestamp should be visible. If a range key has a suffix ≤
|
||
|
// RangeKeyMasking.Suffix, it acts as a mask.
|
||
|
//
|
||
|
// Range key masking is facilitated by the keyspan.InterleavingIter. The
|
||
|
// interleaving iterator interleaves range keys and point keys during combined
|
||
|
// iteration. During user iteration, the interleaving iterator is configured
|
||
|
// with a keyspan.SpanMask, implemented by the rangeKeyMasking struct below.
|
||
|
// The SpanMask interface defines two methods: SpanChanged and SkipPoint.
|
||
|
//
|
||
|
// SpanChanged is used to keep the current mask up-to-date. Whenever the point
|
||
|
// iterator has stepped into or out of the bounds of a range key, the
|
||
|
// interleaving iterator invokes SpanChanged passing the current covering range
|
||
|
// key. The below rangeKeyMasking implementation scans the range keys looking
|
||
|
// for the range key with the largest suffix that's still ≤ the suffix supplied
|
||
|
// to IterOptions.RangeKeyMasking.Suffix (the "read timestamp"). If it finds a
|
||
|
// range key that meets the condition, the range key should act as a mask. The
|
||
|
// span and the relevant range key's suffix are saved.
|
||
|
//
|
||
|
// The above ensures that `rangeKeyMasking.maskActiveSuffix` always contains the
|
||
|
// current masking suffix such that any point keys with lower suffixes should be
|
||
|
// skipped.
|
||
|
//
|
||
|
// There are two ways in which masked point keys are skipped.
|
||
|
//
|
||
|
// 1. Interleaving iterator SkipPoint
|
||
|
//
|
||
|
// Whenever the interleaving iterator encounters a point key that falls within
|
||
|
// the bounds of a range key, it invokes SkipPoint. The interleaving iterator
|
||
|
// guarantees that the SpanChanged method described above has already been
|
||
|
// invoked with the covering range key. The below rangeKeyMasking implementation
|
||
|
// of SkipPoint splits the key into prefix and suffix, compares the suffix to
|
||
|
// the `maskActiveSuffix` updated by SpanChanged and returns true if
|
||
|
// suffix(point) < maskActiveSuffix.
|
||
|
//
|
||
|
// The SkipPoint logic is sufficient to ensure that the Pebble iterator filters
|
||
|
// out all masked point keys. However, it requires the iterator read each masked
|
||
|
// point key. For broad range keys that mask many points, this may be expensive.
|
||
|
//
|
||
|
// 2. Block property filter
|
||
|
//
|
||
|
// For more efficient handling of braad range keys that mask many points, the
|
||
|
// IterOptions.RangeKeyMasking field has an optional Filter option. This Filter
|
||
|
// field takes a superset of the block-property filter interface, adding a
|
||
|
// method to dynamically configure the filter's filtering criteria.
|
||
|
//
|
||
|
// To make use of the Filter option, the user is required to define and
|
||
|
// configure a block-property collector that collects a property containing at
|
||
|
// least the maximum suffix of a key within a block.
|
||
|
//
|
||
|
// When the SpanChanged method described above is invoked, rangeKeyMasking also
|
||
|
// reconfigures the user-provided filter. It invokes a SetSuffix method,
|
||
|
// providing the `maskActiveSuffix`, requesting that from now on the
|
||
|
// block-property filter return Intersects()=false for any properties indicating
|
||
|
// that a block contains exclusively keys with suffixes greater than the
|
||
|
// provided suffix.
|
||
|
//
|
||
|
// Note that unlike other block-property filters, the filter used for masking
|
||
|
// must not apply across the entire keyspace. It must only filter blocks that
|
||
|
// lie within the bounds of the range key that set the mask suffix. To
|
||
|
// accommodate this, rangeKeyMasking implements a special interface:
|
||
|
// sstable.BoundLimitedBlockPropertyFilter. This interface extends the block
|
||
|
// property filter interface with two new methods: KeyIsWithinLowerBound and
|
||
|
// KeyIsWithinUpperBound. The rangeKeyMasking type wraps the user-provided block
|
||
|
// property filter, implementing these two methods and overriding Intersects to
|
||
|
// always return true if there is no active mask.
|
||
|
//
|
||
|
// The logic to ensure that a mask block-property filter is only applied within
|
||
|
// the bounds of the masking range key is subtle. The interleaving iterator
|
||
|
// guarantees that it never invokes SpanChanged until the point iterator is
|
||
|
// positioned within the range key. During forward iteration, this guarantees
|
||
|
// that any block that a sstable reader might attempt to load contains only keys
|
||
|
// greater than or equal to the range key's lower bound. During backward
|
||
|
// iteration, it provides the analagous guarantee on the range key's upper
|
||
|
// bound.
|
||
|
//
|
||
|
// The above ensures that an sstable reader only needs to verify that a block
|
||
|
// that it skips meets the opposite bound. This is where the
|
||
|
// KeyIsWithinLowerBound and KeyIsWithinUpperBound methods are used. When an
|
||
|
// sstable iterator is configured with a BoundLimitedBlockPropertyFilter, it
|
||
|
// checks for intersection with the block-property filter before every block
|
||
|
// load, like ordinary block-property filters. However, if the bound-limited
|
||
|
// block property filter indicates that it does NOT intersect, the filter's
|
||
|
// relevant KeyIsWithin{Lower,Upper}Bound method is queried, using a block
|
||
|
// index separator as the bound. If the method indicates that the provided index
|
||
|
// separator does not fall within the range key bounds, the no-intersection
|
||
|
// result is ignored, and the block is read.
|
||
|
|
||
|
type rangeKeyMasking struct {
|
||
|
cmp base.Compare
|
||
|
split base.Split
|
||
|
filter BlockPropertyFilterMask
|
||
|
// maskActiveSuffix holds the suffix of a range key currently acting as a
|
||
|
// mask, hiding point keys with suffixes greater than it. maskActiveSuffix
|
||
|
// is only ever non-nil if IterOptions.RangeKeyMasking.Suffix is non-nil.
|
||
|
// maskActiveSuffix is updated whenever the iterator passes over a new range
|
||
|
// key. The maskActiveSuffix should only be used if maskSpan is non-nil.
|
||
|
//
|
||
|
// See SpanChanged.
|
||
|
maskActiveSuffix []byte
|
||
|
// maskSpan holds the span from which the active mask suffix was extracted.
|
||
|
// The span is used for bounds comparisons, to ensure that a range-key mask
|
||
|
// is not applied beyond the bounds of the range key.
|
||
|
maskSpan *keyspan.Span
|
||
|
parent *Iterator
|
||
|
}
|
||
|
|
||
|
func (m *rangeKeyMasking) init(parent *Iterator, cmp base.Compare, split base.Split) {
|
||
|
m.cmp = cmp
|
||
|
m.split = split
|
||
|
if parent.opts.RangeKeyMasking.Filter != nil {
|
||
|
m.filter = parent.opts.RangeKeyMasking.Filter()
|
||
|
}
|
||
|
m.parent = parent
|
||
|
}
|
||
|
|
||
|
// SpanChanged implements the keyspan.SpanMask interface, used during range key
|
||
|
// iteration.
|
||
|
func (m *rangeKeyMasking) SpanChanged(s *keyspan.Span) {
|
||
|
if s == nil && m.maskSpan == nil {
|
||
|
return
|
||
|
}
|
||
|
m.maskSpan = nil
|
||
|
m.maskActiveSuffix = m.maskActiveSuffix[:0]
|
||
|
|
||
|
// Find the smallest suffix of a range key contained within the Span,
|
||
|
// excluding suffixes less than m.opts.RangeKeyMasking.Suffix.
|
||
|
if s != nil {
|
||
|
m.parent.rangeKey.stale = true
|
||
|
if m.parent.opts.RangeKeyMasking.Suffix != nil {
|
||
|
for j := range s.Keys {
|
||
|
if s.Keys[j].Suffix == nil {
|
||
|
continue
|
||
|
}
|
||
|
if m.cmp(s.Keys[j].Suffix, m.parent.opts.RangeKeyMasking.Suffix) < 0 {
|
||
|
continue
|
||
|
}
|
||
|
if len(m.maskActiveSuffix) == 0 || m.cmp(m.maskActiveSuffix, s.Keys[j].Suffix) > 0 {
|
||
|
m.maskSpan = s
|
||
|
m.maskActiveSuffix = append(m.maskActiveSuffix[:0], s.Keys[j].Suffix...)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if m.maskSpan != nil && m.parent.opts.RangeKeyMasking.Filter != nil {
|
||
|
// Update the block-property filter to filter point keys with suffixes
|
||
|
// greater than m.maskActiveSuffix.
|
||
|
err := m.filter.SetSuffix(m.maskActiveSuffix)
|
||
|
if err != nil {
|
||
|
m.parent.err = err
|
||
|
}
|
||
|
}
|
||
|
// If no span is active, we leave the inner block-property filter configured
|
||
|
// with its existing suffix. That's okay, because Intersects calls are first
|
||
|
// evaluated by iteratorRangeKeyState.Intersects, which considers all blocks
|
||
|
// as intersecting if there's no active mask.
|
||
|
}
|
||
|
|
||
|
// SkipPoint implements the keyspan.SpanMask interface, used during range key
|
||
|
// iteration. Whenever a point key is covered by a non-empty Span, the
|
||
|
// interleaving iterator invokes SkipPoint. This function is responsible for
|
||
|
// performing range key masking.
|
||
|
//
|
||
|
// If a non-nil IterOptions.RangeKeyMasking.Suffix is set, range key masking is
|
||
|
// enabled. Masking hides point keys, transparently skipping over the keys.
|
||
|
// Whether or not a point key is masked is determined by comparing the point
|
||
|
// key's suffix, the overlapping span's keys' suffixes, and the user-configured
|
||
|
// IterOption's RangeKeyMasking.Suffix. When configured with a masking threshold
|
||
|
// _t_, and there exists a span with suffix _r_ covering a point key with suffix
|
||
|
// _p_, and
|
||
|
//
|
||
|
// _t_ ≤ _r_ < _p_
|
||
|
//
|
||
|
// then the point key is elided. Consider the following rendering, where using
|
||
|
// integer suffixes with higher integers sort before suffixes with lower
|
||
|
// integers, (for example @7 ≤ @6 < @5):
|
||
|
//
|
||
|
// ^
|
||
|
// @9 | •―――――――――――――――○ [e,m)@9
|
||
|
// s 8 | • l@8
|
||
|
// u 7 |------------------------------------ @7 RangeKeyMasking.Suffix
|
||
|
// f 6 | [h,q)@6 •―――――――――――――――――○ (threshold)
|
||
|
// f 5 | • h@5
|
||
|
// f 4 | • n@4
|
||
|
// i 3 | •―――――――――――○ [f,l)@3
|
||
|
// x 2 | • b@2
|
||
|
// 1 |
|
||
|
// 0 |___________________________________
|
||
|
// a b c d e f g h i j k l m n o p q
|
||
|
//
|
||
|
// An iterator scanning the entire keyspace with the masking threshold set to @7
|
||
|
// will observe point keys b@2 and l@8. The span keys [h,q)@6 and [f,l)@3 serve
|
||
|
// as masks, because cmp(@6,@7) ≥ 0 and cmp(@3,@7) ≥ 0. The span key [e,m)@9
|
||
|
// does not serve as a mask, because cmp(@9,@7) < 0.
|
||
|
//
|
||
|
// Although point l@8 falls within the user key bounds of [e,m)@9, [e,m)@9 is
|
||
|
// non-masking due to its suffix. The point key l@8 also falls within the user
|
||
|
// key bounds of [h,q)@6, but since cmp(@6,@8) ≥ 0, l@8 is unmasked.
|
||
|
//
|
||
|
// Invariant: The userKey is within the user key bounds of the span most
|
||
|
// recently provided to `SpanChanged`.
|
||
|
func (m *rangeKeyMasking) SkipPoint(userKey []byte) bool {
|
||
|
m.parent.stats.RangeKeyStats.ContainedPoints++
|
||
|
if m.maskSpan == nil {
|
||
|
// No range key is currently acting as a mask, so don't skip.
|
||
|
return false
|
||
|
}
|
||
|
// Range key masking is enabled and the current span includes a range key
|
||
|
// that is being used as a mask. (NB: SpanChanged already verified that the
|
||
|
// range key's suffix is ≥ RangeKeyMasking.Suffix).
|
||
|
//
|
||
|
// This point key falls within the bounds of the range key (guaranteed by
|
||
|
// the InterleavingIter). Skip the point key if the range key's suffix is
|
||
|
// greater than the point key's suffix.
|
||
|
pointSuffix := userKey[m.split(userKey):]
|
||
|
if len(pointSuffix) > 0 && m.cmp(m.maskActiveSuffix, pointSuffix) < 0 {
|
||
|
m.parent.stats.RangeKeyStats.SkippedPoints++
|
||
|
return true
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
// The iteratorRangeKeyState type implements the sstable package's
|
||
|
// BoundLimitedBlockPropertyFilter interface in order to use block property
|
||
|
// filters for range key masking. The iteratorRangeKeyState implementation wraps
|
||
|
// the block-property filter provided in Options.RangeKeyMasking.Filter.
|
||
|
//
|
||
|
// Using a block-property filter for range-key masking requires limiting the
|
||
|
// filter's effect to the bounds of the range key currently acting as a mask.
|
||
|
// Consider the range key [a,m)@10, and an iterator positioned just before the
|
||
|
// below block, bounded by index separators `c` and `z`:
|
||
|
//
|
||
|
// c z
|
||
|
// x | c@9 c@5 c@1 d@7 e@4 y@4 | ...
|
||
|
// iter pos
|
||
|
//
|
||
|
// The next block cannot be skipped, despite the range key suffix @10 is greater
|
||
|
// than all the block's keys' suffixes, because it contains a key (y@4) outside
|
||
|
// the bounds of the range key.
|
||
|
//
|
||
|
// This extended BoundLimitedBlockPropertyFilter interface adds two new methods,
|
||
|
// KeyIsWithinLowerBound and KeyIsWithinUpperBound, for testing whether a
|
||
|
// particular block is within bounds.
|
||
|
//
|
||
|
// The iteratorRangeKeyState implements these new methods by first checking if
|
||
|
// the iterator is currently positioned within a range key. If not, the provided
|
||
|
// key is considered out-of-bounds. If the iterator is positioned within a range
|
||
|
// key, it compares the corresponding range key bound.
|
||
|
var _ sstable.BoundLimitedBlockPropertyFilter = (*rangeKeyMasking)(nil)
|
||
|
|
||
|
// Name implements the limitedBlockPropertyFilter interface defined in the
|
||
|
// sstable package by passing through to the user-defined block property filter.
|
||
|
func (m *rangeKeyMasking) Name() string {
|
||
|
return m.filter.Name()
|
||
|
}
|
||
|
|
||
|
// Intersects implements the limitedBlockPropertyFilter interface defined in the
|
||
|
// sstable package by passing the intersection decision to the user-provided
|
||
|
// block property filter only if a range key is covering the current iterator
|
||
|
// position.
|
||
|
func (m *rangeKeyMasking) Intersects(prop []byte) (bool, error) {
|
||
|
if m.maskSpan == nil {
|
||
|
// No span is actively masking.
|
||
|
return true, nil
|
||
|
}
|
||
|
return m.filter.Intersects(prop)
|
||
|
}
|
||
|
|
||
|
// KeyIsWithinLowerBound implements the limitedBlockPropertyFilter interface
|
||
|
// defined in the sstable package. It's used to restrict the masking block
|
||
|
// property filter to only applying within the bounds of the active range key.
|
||
|
func (m *rangeKeyMasking) KeyIsWithinLowerBound(key []byte) bool {
|
||
|
// Invariant: m.maskSpan != nil
|
||
|
//
|
||
|
// The provided `key` is an inclusive lower bound of the block we're
|
||
|
// considering skipping.
|
||
|
return m.cmp(m.maskSpan.Start, key) <= 0
|
||
|
}
|
||
|
|
||
|
// KeyIsWithinUpperBound implements the limitedBlockPropertyFilter interface
|
||
|
// defined in the sstable package. It's used to restrict the masking block
|
||
|
// property filter to only applying within the bounds of the active range key.
|
||
|
func (m *rangeKeyMasking) KeyIsWithinUpperBound(key []byte) bool {
|
||
|
// Invariant: m.maskSpan != nil
|
||
|
//
|
||
|
// The provided `key` is an *inclusive* upper bound of the block we're
|
||
|
// considering skipping, so the range key's end must be strictly greater
|
||
|
// than the block bound for the block to be within bounds.
|
||
|
return m.cmp(m.maskSpan.End, key) > 0
|
||
|
}
|
||
|
|
||
|
// lazyCombinedIter implements the internalIterator interface, wrapping a
|
||
|
// pointIter. It requires the pointIter's the levelIters be configured with
|
||
|
// pointers to its combinedIterState. When the levelIter observes a file
|
||
|
// containing a range key, the lazyCombinedIter constructs the combined
|
||
|
// range+point key iterator stack and switches to it.
|
||
|
type lazyCombinedIter struct {
|
||
|
// parent holds a pointer to the root *pebble.Iterator containing this
|
||
|
// iterator. It's used to mutate the internalIterator in use when switching
|
||
|
// to combined iteration.
|
||
|
parent *Iterator
|
||
|
pointIter internalIterator
|
||
|
combinedIterState combinedIterState
|
||
|
}
|
||
|
|
||
|
// combinedIterState encapsulates the current state of combined iteration.
|
||
|
// Various low-level iterators (mergingIter, leveliter) hold pointers to the
|
||
|
// *pebble.Iterator's combinedIterState. This allows them to check whether or
|
||
|
// not they must monitor for files containing range keys (!initialized), or not.
|
||
|
//
|
||
|
// When !initialized, low-level iterators watch for files containing range keys.
|
||
|
// When one is discovered, they set triggered=true and key to the smallest
|
||
|
// (forward direction) or largest (reverse direction) range key that's been
|
||
|
// observed.
|
||
|
type combinedIterState struct {
|
||
|
// key holds the smallest (forward direction) or largest (backward
|
||
|
// direction) user key from a range key bound discovered during the iterator
|
||
|
// operation that triggered the switch to combined iteration.
|
||
|
//
|
||
|
// Slices stored here must be stable. This is possible because callers pass
|
||
|
// a Smallest/Largest bound from a fileMetadata, which are immutable. A key
|
||
|
// slice's bytes must not be overwritten.
|
||
|
key []byte
|
||
|
triggered bool
|
||
|
initialized bool
|
||
|
}
|
||
|
|
||
|
// Assert that *lazyCombinedIter implements internalIterator.
|
||
|
var _ internalIterator = (*lazyCombinedIter)(nil)
|
||
|
|
||
|
// initCombinedIteration is invoked after a pointIter positioning operation
|
||
|
// resulted in i.combinedIterState.triggered=true.
|
||
|
//
|
||
|
// The `dir` parameter is `+1` or `-1` indicating forward iteration or backward
|
||
|
// iteration respectively.
|
||
|
//
|
||
|
// The `pointKey` and `pointValue` parameters provide the new point key-value
|
||
|
// pair that the iterator was just positioned to. The combined iterator should
|
||
|
// be seeded with this point key-value pair and return the smaller (forward
|
||
|
// iteration) or largest (backward iteration) of the two.
|
||
|
//
|
||
|
// The `seekKey` parameter is non-nil only if the iterator operation that
|
||
|
// triggered the switch to combined iteration was a SeekGE, SeekPrefixGE or
|
||
|
// SeekLT. It provides the seek key supplied and is used to seek the range-key
|
||
|
// iterator using the same key. This is necessary for SeekGE/SeekPrefixGE
|
||
|
// operations that land in the middle of a range key and must truncate to the
|
||
|
// user-provided seek key.
|
||
|
func (i *lazyCombinedIter) initCombinedIteration(
|
||
|
dir int8, pointKey *InternalKey, pointValue base.LazyValue, seekKey []byte,
|
||
|
) (*InternalKey, base.LazyValue) {
|
||
|
// Invariant: i.parent.rangeKey is nil.
|
||
|
// Invariant: !i.combinedIterState.initialized.
|
||
|
if invariants.Enabled {
|
||
|
if i.combinedIterState.initialized {
|
||
|
panic("pebble: combined iterator already initialized")
|
||
|
}
|
||
|
if i.parent.rangeKey != nil {
|
||
|
panic("pebble: iterator already has a range-key iterator stack")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// We need to determine the key to seek the range key iterator to. If
|
||
|
// seekKey is not nil, the user-initiated operation that triggered the
|
||
|
// switch to combined iteration was itself a seek, and we can use that key.
|
||
|
// Otherwise, a First/Last or relative positioning operation triggered the
|
||
|
// switch to combined iteration.
|
||
|
//
|
||
|
// The levelIter that observed a file containing range keys populated
|
||
|
// combinedIterState.key with the smallest (forward) or largest (backward)
|
||
|
// range key it observed. If multiple levelIters observed files with range
|
||
|
// keys during the same operation on the mergingIter, combinedIterState.key
|
||
|
// is the smallest [during forward iteration; largest in reverse iteration]
|
||
|
// such key.
|
||
|
if seekKey == nil {
|
||
|
// Use the levelIter-populated key.
|
||
|
seekKey = i.combinedIterState.key
|
||
|
|
||
|
// We may need to adjust the levelIter-populated seek key to the
|
||
|
// surfaced point key. If the key observed is beyond [in the iteration
|
||
|
// direction] the current point key, there may still exist a range key
|
||
|
// at an earlier key. Consider the following example:
|
||
|
//
|
||
|
// L5: 000003:[bar.DEL.5, foo.RANGEKEYSET.9]
|
||
|
// L6: 000001:[bar.SET.2] 000002:[bax.RANGEKEYSET.8]
|
||
|
//
|
||
|
// A call to First() seeks the levels to files L5.000003 and L6.000001.
|
||
|
// The L5 levelIter observes that L5.000003 contains the range key with
|
||
|
// start key `foo`, and triggers a switch to combined iteration, setting
|
||
|
// `combinedIterState.key` = `foo`.
|
||
|
//
|
||
|
// The L6 levelIter did not observe the true first range key
|
||
|
// (bax.RANGEKEYSET.8), because it appears in a later sstable. When the
|
||
|
// combined iterator is initialized, the range key iterator must be
|
||
|
// seeked to a key that will find `bax`. To accomplish this, we seek the
|
||
|
// key instead to `bar`. It is guaranteed that no range key exists
|
||
|
// earlier than `bar`, otherwise a levelIter would've observed it and
|
||
|
// set `combinedIterState.key` to its start key.
|
||
|
if pointKey != nil {
|
||
|
if dir == +1 && i.parent.cmp(i.combinedIterState.key, pointKey.UserKey) > 0 {
|
||
|
seekKey = pointKey.UserKey
|
||
|
} else if dir == -1 && i.parent.cmp(seekKey, pointKey.UserKey) < 0 {
|
||
|
seekKey = pointKey.UserKey
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// An operation on the point iterator observed a file containing range keys,
|
||
|
// so we must switch to combined interleaving iteration. First, construct
|
||
|
// the range key iterator stack. It must not exist, otherwise we'd already
|
||
|
// be performing combined iteration.
|
||
|
i.parent.rangeKey = iterRangeKeyStateAllocPool.Get().(*iteratorRangeKeyState)
|
||
|
i.parent.rangeKey.init(i.parent.comparer.Compare, i.parent.comparer.Split, &i.parent.opts)
|
||
|
i.parent.constructRangeKeyIter()
|
||
|
|
||
|
// Initialize the Iterator's interleaving iterator.
|
||
|
i.parent.rangeKey.iiter.Init(
|
||
|
&i.parent.comparer, i.parent.pointIter, i.parent.rangeKey.rangeKeyIter,
|
||
|
keyspan.InterleavingIterOpts{
|
||
|
Mask: &i.parent.rangeKeyMasking,
|
||
|
LowerBound: i.parent.opts.LowerBound,
|
||
|
UpperBound: i.parent.opts.UpperBound,
|
||
|
})
|
||
|
|
||
|
// Set the parent's primary iterator to point to the combined, interleaving
|
||
|
// iterator that's now initialized with our current state.
|
||
|
i.parent.iter = &i.parent.rangeKey.iiter
|
||
|
i.combinedIterState.initialized = true
|
||
|
i.combinedIterState.key = nil
|
||
|
|
||
|
// All future iterator operations will go directly through the combined
|
||
|
// iterator.
|
||
|
//
|
||
|
// Initialize the interleaving iterator. We pass the point key-value pair so
|
||
|
// that the interleaving iterator knows where the point iterator is
|
||
|
// positioned. Additionally, we pass the seek key to which the range-key
|
||
|
// iterator should be seeked in order to initialize its position.
|
||
|
//
|
||
|
// In the forward direction (invert for backwards), the seek key is a key
|
||
|
// guaranteed to find the smallest range key that's greater than the last
|
||
|
// key the iterator returned. The range key may be less than pointKey, in
|
||
|
// which case the range key will be interleaved next instead of the point
|
||
|
// key.
|
||
|
if dir == +1 {
|
||
|
var prefix []byte
|
||
|
if i.parent.hasPrefix {
|
||
|
prefix = i.parent.prefixOrFullSeekKey
|
||
|
}
|
||
|
return i.parent.rangeKey.iiter.InitSeekGE(prefix, seekKey, pointKey, pointValue)
|
||
|
}
|
||
|
return i.parent.rangeKey.iiter.InitSeekLT(seekKey, pointKey, pointValue)
|
||
|
}
|
||
|
|
||
|
func (i *lazyCombinedIter) SeekGE(
|
||
|
key []byte, flags base.SeekGEFlags,
|
||
|
) (*InternalKey, base.LazyValue) {
|
||
|
if i.combinedIterState.initialized {
|
||
|
return i.parent.rangeKey.iiter.SeekGE(key, flags)
|
||
|
}
|
||
|
k, v := i.pointIter.SeekGE(key, flags)
|
||
|
if i.combinedIterState.triggered {
|
||
|
return i.initCombinedIteration(+1, k, v, key)
|
||
|
}
|
||
|
return k, v
|
||
|
}
|
||
|
|
||
|
func (i *lazyCombinedIter) SeekPrefixGE(
|
||
|
prefix, key []byte, flags base.SeekGEFlags,
|
||
|
) (*InternalKey, base.LazyValue) {
|
||
|
if i.combinedIterState.initialized {
|
||
|
return i.parent.rangeKey.iiter.SeekPrefixGE(prefix, key, flags)
|
||
|
}
|
||
|
k, v := i.pointIter.SeekPrefixGE(prefix, key, flags)
|
||
|
if i.combinedIterState.triggered {
|
||
|
return i.initCombinedIteration(+1, k, v, key)
|
||
|
}
|
||
|
return k, v
|
||
|
}
|
||
|
|
||
|
func (i *lazyCombinedIter) SeekLT(
|
||
|
key []byte, flags base.SeekLTFlags,
|
||
|
) (*InternalKey, base.LazyValue) {
|
||
|
if i.combinedIterState.initialized {
|
||
|
return i.parent.rangeKey.iiter.SeekLT(key, flags)
|
||
|
}
|
||
|
k, v := i.pointIter.SeekLT(key, flags)
|
||
|
if i.combinedIterState.triggered {
|
||
|
return i.initCombinedIteration(-1, k, v, key)
|
||
|
}
|
||
|
return k, v
|
||
|
}
|
||
|
|
||
|
func (i *lazyCombinedIter) First() (*InternalKey, base.LazyValue) {
|
||
|
if i.combinedIterState.initialized {
|
||
|
return i.parent.rangeKey.iiter.First()
|
||
|
}
|
||
|
k, v := i.pointIter.First()
|
||
|
if i.combinedIterState.triggered {
|
||
|
return i.initCombinedIteration(+1, k, v, nil)
|
||
|
}
|
||
|
return k, v
|
||
|
}
|
||
|
|
||
|
func (i *lazyCombinedIter) Last() (*InternalKey, base.LazyValue) {
|
||
|
if i.combinedIterState.initialized {
|
||
|
return i.parent.rangeKey.iiter.Last()
|
||
|
}
|
||
|
k, v := i.pointIter.Last()
|
||
|
if i.combinedIterState.triggered {
|
||
|
return i.initCombinedIteration(-1, k, v, nil)
|
||
|
}
|
||
|
return k, v
|
||
|
}
|
||
|
|
||
|
func (i *lazyCombinedIter) Next() (*InternalKey, base.LazyValue) {
|
||
|
if i.combinedIterState.initialized {
|
||
|
return i.parent.rangeKey.iiter.Next()
|
||
|
}
|
||
|
k, v := i.pointIter.Next()
|
||
|
if i.combinedIterState.triggered {
|
||
|
return i.initCombinedIteration(+1, k, v, nil)
|
||
|
}
|
||
|
return k, v
|
||
|
}
|
||
|
|
||
|
func (i *lazyCombinedIter) NextPrefix(succKey []byte) (*InternalKey, base.LazyValue) {
|
||
|
if i.combinedIterState.initialized {
|
||
|
return i.parent.rangeKey.iiter.NextPrefix(succKey)
|
||
|
}
|
||
|
k, v := i.pointIter.NextPrefix(succKey)
|
||
|
if i.combinedIterState.triggered {
|
||
|
return i.initCombinedIteration(+1, k, v, nil)
|
||
|
}
|
||
|
return k, v
|
||
|
}
|
||
|
|
||
|
func (i *lazyCombinedIter) Prev() (*InternalKey, base.LazyValue) {
|
||
|
if i.combinedIterState.initialized {
|
||
|
return i.parent.rangeKey.iiter.Prev()
|
||
|
}
|
||
|
k, v := i.pointIter.Prev()
|
||
|
if i.combinedIterState.triggered {
|
||
|
return i.initCombinedIteration(-1, k, v, nil)
|
||
|
}
|
||
|
return k, v
|
||
|
}
|
||
|
|
||
|
func (i *lazyCombinedIter) Error() error {
|
||
|
if i.combinedIterState.initialized {
|
||
|
return i.parent.rangeKey.iiter.Error()
|
||
|
}
|
||
|
return i.pointIter.Error()
|
||
|
}
|
||
|
|
||
|
func (i *lazyCombinedIter) Close() error {
|
||
|
if i.combinedIterState.initialized {
|
||
|
return i.parent.rangeKey.iiter.Close()
|
||
|
}
|
||
|
return i.pointIter.Close()
|
||
|
}
|
||
|
|
||
|
func (i *lazyCombinedIter) SetBounds(lower, upper []byte) {
|
||
|
if i.combinedIterState.initialized {
|
||
|
i.parent.rangeKey.iiter.SetBounds(lower, upper)
|
||
|
return
|
||
|
}
|
||
|
i.pointIter.SetBounds(lower, upper)
|
||
|
}
|
||
|
|
||
|
func (i *lazyCombinedIter) SetContext(ctx context.Context) {
|
||
|
if i.combinedIterState.initialized {
|
||
|
i.parent.rangeKey.iiter.SetContext(ctx)
|
||
|
return
|
||
|
}
|
||
|
i.pointIter.SetContext(ctx)
|
||
|
}
|
||
|
|
||
|
func (i *lazyCombinedIter) String() string {
|
||
|
if i.combinedIterState.initialized {
|
||
|
return i.parent.rangeKey.iiter.String()
|
||
|
}
|
||
|
return i.pointIter.String()
|
||
|
}
|