From fa3d3d06d34a48c9a0e791e33ac4a8a25b1e7a55 Mon Sep 17 00:00:00 2001 From: Radu Berinde Date: Wed, 21 Feb 2024 18:48:02 -0800 Subject: [PATCH] rangekey: move iteration stack to rangekeystack --- internal/rangekey/coalesce.go | 234 +---------- internal/rangekey/coalesce_test.go | 380 ----------------- .../testdata/defragmenting_iter | 0 .../{rangekey => rangekeystack}/testdata/iter | 0 internal/rangekeystack/user_iterator.go | 239 +++++++++++ internal/rangekeystack/user_iterator_test.go | 396 ++++++++++++++++++ iterator.go | 6 +- 7 files changed, 643 insertions(+), 612 deletions(-) rename internal/{rangekey => rangekeystack}/testdata/defragmenting_iter (100%) rename internal/{rangekey => rangekeystack}/testdata/iter (100%) create mode 100644 internal/rangekeystack/user_iterator.go create mode 100644 internal/rangekeystack/user_iterator_test.go diff --git a/internal/rangekey/coalesce.go b/internal/rangekey/coalesce.go index c6462a95f4..84aa7029f2 100644 --- a/internal/rangekey/coalesce.go +++ b/internal/rangekey/coalesce.go @@ -5,240 +5,14 @@ package rangekey import ( - "bytes" "math" "sort" "github.com/cockroachdb/pebble/internal/base" "github.com/cockroachdb/pebble/internal/invariants" "github.com/cockroachdb/pebble/internal/keyspan" - "github.com/cockroachdb/pebble/internal/keyspan/keyspanimpl" - "github.com/cockroachdb/pebble/internal/manifest" ) -// UserIteratorConfig holds state for constructing the range key iterator stack -// for user iteration. The range key iterator must merge range key spans across -// the levels of the LSM. This merging is performed by a keyspanimpl.MergingIter -// on-the-fly. The UserIteratorConfig implements keyspan.Transformer, evaluating -// range-key semantics and shadowing, so the spans returned by a MergingIter are -// fully resolved. -// -// The MergingIter is wrapped by a BoundedIter, which elides spans that are -// outside the iterator bounds (or the current prefix's bounds, during prefix -// iteration mode). -// -// To provide determinisim during iteration, the BoundedIter is wrapped by a -// DefragmentingIter that defragments abutting spans with identical -// user-observable state. -// -// At the top-level an InterleavingIter interleaves range keys with point keys -// and performs truncation to iterator bounds. -// -// Below is an abbreviated diagram illustrating the mechanics of a SeekGE. -// -// InterleavingIter.SeekGE -// │ -// DefragmentingIter.SeekGE -// │ -// BoundedIter.SeekGE -// │ -// ╭────────────────┴───────────────╮ -// │ ├── defragmentBwd* -// MergingIter.SeekGE │ -// │ ╰── defragmentFwd -// ╰─╶╶ per level╶╶ ─╮ -// │ -// │ -// ├── .SeekLT -// │ -// ╰── .Next -type UserIteratorConfig struct { - snapshot uint64 - comparer *base.Comparer - miter keyspanimpl.MergingIter - biter keyspan.BoundedIter - diter keyspan.DefragmentingIter - liters [manifest.NumLevels]keyspanimpl.LevelIter - litersUsed int - internalKeys bool - bufs *Buffers -} - -// Buffers holds various buffers used for range key iteration. They're exposed -// so that they may be pooled and reused between iterators. -type Buffers struct { - merging keyspanimpl.MergingBuffers - defragmenting keyspan.DefragmentingBuffers - sortBuf keyspan.KeysBySuffix -} - -// PrepareForReuse discards any excessively large buffers. -func (bufs *Buffers) PrepareForReuse() { - bufs.merging.PrepareForReuse() - bufs.defragmenting.PrepareForReuse() -} - -// Init initializes the range key iterator stack for user iteration. The -// resulting fragment iterator applies range key semantics, defragments spans -// according to their user-observable state and, if !internalKeys, removes all -// Keys other than RangeKeySets describing the current state of range keys. The -// resulting spans contain Keys sorted by suffix (unless internalKeys is true, -// in which case they remain sorted by trailer descending). -// -// The snapshot sequence number parameter determines which keys are visible. Any -// keys not visible at the provided snapshot are ignored. -func (ui *UserIteratorConfig) Init( - comparer *base.Comparer, - snapshot uint64, - lower, upper []byte, - hasPrefix *bool, - prefix *[]byte, - internalKeys bool, - bufs *Buffers, - iters ...keyspan.FragmentIterator, -) keyspan.FragmentIterator { - ui.snapshot = snapshot - ui.comparer = comparer - ui.internalKeys = internalKeys - ui.miter.Init(comparer.Compare, ui, &bufs.merging, iters...) - ui.biter.Init(comparer.Compare, comparer.Split, &ui.miter, lower, upper, hasPrefix, prefix) - if internalKeys { - ui.diter.Init(comparer, &ui.biter, keyspan.DefragmentInternal, keyspan.StaticDefragmentReducer, &bufs.defragmenting) - } else { - ui.diter.Init(comparer, &ui.biter, ui, keyspan.StaticDefragmentReducer, &bufs.defragmenting) - } - ui.litersUsed = 0 - ui.bufs = bufs - return &ui.diter -} - -// AddLevel adds a new level to the bottom of the iterator stack. AddLevel -// must be called after Init and before any other method on the iterator. -func (ui *UserIteratorConfig) AddLevel(iter keyspan.FragmentIterator) { - ui.miter.AddLevel(iter) -} - -// NewLevelIter returns a pointer to a newly allocated or reused -// keyspanimpl.LevelIter. The caller is responsible for calling Init() on this -// instance. -func (ui *UserIteratorConfig) NewLevelIter() *keyspanimpl.LevelIter { - if ui.litersUsed >= len(ui.liters) { - return &keyspanimpl.LevelIter{} - } - ui.litersUsed++ - return &ui.liters[ui.litersUsed-1] -} - -// SetBounds propagates bounds to the iterator stack. The fragment iterator -// interface ordinarily doesn't enforce bounds, so this is exposed as an -// explicit method on the user iterator config. -func (ui *UserIteratorConfig) SetBounds(lower, upper []byte) { - ui.biter.SetBounds(lower, upper) -} - -// Transform implements the keyspan.Transformer interface for use with a -// keyspanimpl.MergingIter. It transforms spans by resolving range keys at the -// provided snapshot sequence number. Shadowing of keys is resolved (eg, removal -// of unset keys, removal of keys overwritten by a set at the same suffix, etc) -// and then non-RangeKeySet keys are removed. The resulting transformed spans -// only contain RangeKeySets describing the state visible at the provided -// sequence number, and hold their Keys sorted by Suffix (except if internalKeys -// is true, then keys remain sorted by trailer. -func (ui *UserIteratorConfig) Transform(cmp base.Compare, s keyspan.Span, dst *keyspan.Span) error { - // Apply shadowing of keys. - dst.Start = s.Start - dst.End = s.End - ui.bufs.sortBuf = keyspan.KeysBySuffix{ - Cmp: cmp, - Keys: ui.bufs.sortBuf.Keys[:0], - } - coalesce(ui.comparer.Equal, &ui.bufs.sortBuf, ui.snapshot, s.Keys) - if ui.internalKeys { - if s.KeysOrder != keyspan.ByTrailerDesc { - panic("unexpected key ordering in UserIteratorTransform with internalKeys = true") - } - dst.Keys = ui.bufs.sortBuf.Keys - keyspan.SortKeysByTrailer(&dst.Keys) - return nil - } - // During user iteration over range keys, unsets and deletes don't matter. This - // step helps logical defragmentation during iteration. - keys := ui.bufs.sortBuf.Keys - dst.Keys = dst.Keys[:0] - for i := range keys { - switch keys[i].Kind() { - case base.InternalKeyKindRangeKeySet: - if invariants.Enabled && len(dst.Keys) > 0 && cmp(dst.Keys[len(dst.Keys)-1].Suffix, keys[i].Suffix) > 0 { - panic("pebble: keys unexpectedly not in ascending suffix order") - } - dst.Keys = append(dst.Keys, keys[i]) - case base.InternalKeyKindRangeKeyUnset: - if invariants.Enabled && len(dst.Keys) > 0 && cmp(dst.Keys[len(dst.Keys)-1].Suffix, keys[i].Suffix) > 0 { - panic("pebble: keys unexpectedly not in ascending suffix order") - } - // Skip. - continue - case base.InternalKeyKindRangeKeyDelete: - // Skip. - continue - default: - return base.CorruptionErrorf("pebble: unrecognized range key kind %s", keys[i].Kind()) - } - } - // coalesce results in dst.Keys being sorted by Suffix. - dst.KeysOrder = keyspan.BySuffixAsc - return nil -} - -// ShouldDefragment implements the DefragmentMethod interface and configures a -// DefragmentingIter to defragment spans of range keys if their user-visible -// state is identical. This defragmenting method assumes the provided spans have -// already been transformed through (UserIterationConfig).Transform, so all -// RangeKeySets are user-visible sets and are already in Suffix order. This -// defragmenter checks for equality between set suffixes and values (ignoring -// sequence numbers). It's intended for use during user iteration, when the -// wrapped keyspan iterator is merging spans across all levels of the LSM. -func (ui *UserIteratorConfig) ShouldDefragment(equal base.Equal, a, b *keyspan.Span) bool { - // This method is not called with internalKeys = true. - if ui.internalKeys { - panic("unexpected call to ShouldDefragment with internalKeys = true") - } - // This implementation must only be used on spans that have transformed by - // ui.Transform. The transform applies shadowing, removes all keys besides - // the resulting Sets and sorts the keys by suffix. Since shadowing has been - // applied, each Set must set a unique suffix. If the two spans are - // equivalent, they must have the same number of range key sets. - if len(a.Keys) != len(b.Keys) || len(a.Keys) == 0 { - return false - } - if a.KeysOrder != keyspan.BySuffixAsc || b.KeysOrder != keyspan.BySuffixAsc { - panic("pebble: range key span's keys unexpectedly not in ascending suffix order") - } - - ret := true - for i := range a.Keys { - if invariants.Enabled { - if a.Keys[i].Kind() != base.InternalKeyKindRangeKeySet || - b.Keys[i].Kind() != base.InternalKeyKindRangeKeySet { - panic("pebble: unexpected non-RangeKeySet during defragmentation") - } - if i > 0 && (ui.comparer.Compare(a.Keys[i].Suffix, a.Keys[i-1].Suffix) < 0 || - ui.comparer.Compare(b.Keys[i].Suffix, b.Keys[i-1].Suffix) < 0) { - panic("pebble: range keys not ordered by suffix during defragmentation") - } - } - if !equal(a.Keys[i].Suffix, b.Keys[i].Suffix) { - ret = false - break - } - if !bytes.Equal(a.Keys[i].Value, b.Keys[i].Value) { - ret = false - break - } - } - return ret -} - // Coalesce imposes range key semantics and coalesces range keys with the same // bounds. Coalesce drops any keys shadowed by more recent sets, unsets or // deletes. Coalesce modifies the provided span's Keys slice, reslicing the @@ -282,14 +56,16 @@ func Coalesce(cmp base.Compare, eq base.Equal, keys []keyspan.Key, dst *[]keyspa Cmp: cmp, Keys: (*dst)[:0], } - coalesce(eq, &keysBySuffix, math.MaxUint64, keys) + CoalesceIntoKeysBySuffix(eq, &keysBySuffix, math.MaxUint64, keys) // Update the span with the (potentially reduced) keys slice. coalesce left // the keys in *dst sorted by suffix. Re-sort them by trailer. *dst = keysBySuffix.Keys keyspan.SortKeysByTrailer(dst) } -func coalesce( +// CoalesceIntoKeysBySuffix is a variant of Coalesce which outputs the results into +// keyspan.KeysBySuffix without sorting them. +func CoalesceIntoKeysBySuffix( equal base.Equal, keysBySuffix *keyspan.KeysBySuffix, snapshot uint64, keys []keyspan.Key, ) { // First, enforce visibility and RangeKeyDelete mechanics. We only need to @@ -399,7 +175,7 @@ func (f *ForeignSSTTransformer) Transform( Cmp: cmp, Keys: f.sortBuf.Keys[:0], } - coalesce(f.Equal, &f.sortBuf, math.MaxUint64, s.Keys) + CoalesceIntoKeysBySuffix(f.Equal, &f.sortBuf, math.MaxUint64, s.Keys) keys := f.sortBuf.Keys dst.Keys = dst.Keys[:0] for i := range keys { diff --git a/internal/rangekey/coalesce_test.go b/internal/rangekey/coalesce_test.go index 6213f4f4a2..177969dc29 100644 --- a/internal/rangekey/coalesce_test.go +++ b/internal/rangekey/coalesce_test.go @@ -7,21 +7,11 @@ package rangekey import ( "bytes" "fmt" - "io" - "math" - "math/rand" - "strconv" - "strings" "testing" - "time" "github.com/cockroachdb/datadriven" - "github.com/cockroachdb/pebble/internal/base" "github.com/cockroachdb/pebble/internal/keyspan" - "github.com/cockroachdb/pebble/internal/keyspan/keyspanimpl" "github.com/cockroachdb/pebble/internal/testkeys" - "github.com/pmezard/go-difflib/difflib" - "github.com/stretchr/testify/require" ) func TestCoalesce(t *testing.T) { @@ -46,373 +36,3 @@ func TestCoalesce(t *testing.T) { } }) } - -func TestIter(t *testing.T) { - eq := testkeys.Comparer.Equal - cmp := testkeys.Comparer.Compare - var iter keyspanimpl.MergingIter - var buf bytes.Buffer - - datadriven.RunTest(t, "testdata/iter", func(t *testing.T, td *datadriven.TestData) string { - buf.Reset() - switch td.Cmd { - case "define": - visibleSeqNum := base.InternalKeySeqNumMax - for _, arg := range td.CmdArgs { - if arg.Key == "visible-seq-num" { - var err error - visibleSeqNum, err = strconv.ParseUint(arg.Vals[0], 10, 64) - require.NoError(t, err) - } - } - - var spans []keyspan.Span - lines := strings.Split(strings.TrimSpace(td.Input), "\n") - for _, line := range lines { - spans = append(spans, keyspan.ParseSpan(line)) - } - transform := keyspan.TransformerFunc(func(cmp base.Compare, s keyspan.Span, dst *keyspan.Span) error { - keysBySuffix := keyspan.KeysBySuffix{ - Cmp: cmp, - Keys: dst.Keys[:0], - } - coalesce(eq, &keysBySuffix, visibleSeqNum, s.Keys) - // Update the span with the (potentially reduced) keys slice. coalesce left - // the keys in *dst sorted by suffix. Re-sort them by trailer. - dst.Keys = keysBySuffix.Keys - keyspan.SortKeysByTrailer(&dst.Keys) - dst.Start = s.Start - dst.End = s.End - return nil - }) - iter.Init(cmp, transform, new(keyspanimpl.MergingBuffers), keyspan.NewIter(cmp, spans)) - return "OK" - case "iter": - buf.Reset() - lines := strings.Split(strings.TrimSpace(td.Input), "\n") - for _, line := range lines { - line = strings.TrimSpace(line) - i := strings.IndexByte(line, ' ') - iterCmd := line - if i > 0 { - iterCmd = string(line[:i]) - } - var s *keyspan.Span - var err error - switch iterCmd { - case "first": - s, err = iter.First() - case "last": - s, err = iter.Last() - case "next": - s, err = iter.Next() - case "prev": - s, err = iter.Prev() - case "seek-ge": - s, err = iter.SeekGE([]byte(strings.TrimSpace(line[i:]))) - case "seek-lt": - s, err = iter.SeekLT([]byte(strings.TrimSpace(line[i:]))) - default: - return fmt.Sprintf("unrecognized iter command %q", iterCmd) - } - require.NoError(t, err) - fmt.Fprint(&buf, s) - if buf.Len() > 0 { - fmt.Fprintln(&buf) - } - } - return buf.String() - default: - return fmt.Sprintf("unrecognized command %q", td.Cmd) - } - }) -} - -func TestDefragmenting(t *testing.T) { - cmp := testkeys.Comparer.Compare - - var buf bytes.Buffer - var spans []keyspan.Span - var hasPrefix bool - var prefix []byte - datadriven.RunTest(t, "testdata/defragmenting_iter", func(t *testing.T, td *datadriven.TestData) string { - buf.Reset() - switch td.Cmd { - case "define": - spans = spans[:0] - lines := strings.Split(strings.TrimSpace(td.Input), "\n") - for _, line := range lines { - spans = append(spans, keyspan.ParseSpan(line)) - } - return "" - case "iter": - var userIterCfg UserIteratorConfig - iter := userIterCfg.Init(testkeys.Comparer, base.InternalKeySeqNumMax, - nil /* lower */, nil, /* upper */ - &hasPrefix, &prefix, false /* internalKeys */, new(Buffers), - keyspan.NewIter(cmp, spans)) - for _, line := range strings.Split(td.Input, "\n") { - runIterOp(&buf, iter, line) - } - return strings.TrimSpace(buf.String()) - default: - return fmt.Sprintf("unrecognized command %q", td.Cmd) - } - }) -} - -func TestDefragmentingIter_Randomized(t *testing.T) { - seed := time.Now().UnixNano() - for i := int64(0); i < 100; i++ { - testDefragmentingIteRandomizedOnce(t, seed+i) - } -} - -func TestDefragmentingIter_RandomizedFixedSeed(t *testing.T) { - const seed = 1648173101214881000 - testDefragmentingIteRandomizedOnce(t, seed) -} - -func testDefragmentingIteRandomizedOnce(t *testing.T, seed int64) { - cmp := testkeys.Comparer.Compare - formatKey := testkeys.Comparer.FormatKey - - rng := rand.New(rand.NewSource(seed)) - t.Logf("seed = %d", seed) - - // Use a key space of alphanumeric strings, with a random max length between - // 1-2. Repeat keys are more common at the lower max lengths. - ks := testkeys.Alpha(rng.Intn(2) + 1) - - // Generate between 1-15 range keys. - const maxRangeKeys = 15 - var original, fragmented []keyspan.Span - numRangeKeys := 1 + rng.Intn(maxRangeKeys) - for i := 0; i < numRangeKeys; i++ { - startIdx := rng.Int63n(ks.Count()) - endIdx := rng.Int63n(ks.Count()) - for startIdx == endIdx { - endIdx = rng.Int63n(ks.Count()) - } - if startIdx > endIdx { - startIdx, endIdx = endIdx, startIdx - } - - key := keyspan.Key{ - Trailer: base.MakeTrailer(uint64(i), base.InternalKeyKindRangeKeySet), - Value: []byte(fmt.Sprintf("v%d", rng.Intn(3))), - } - // Generate suffixes 0, 1, 2, or 3 with 0 indicating none. - if suffix := rng.Int63n(4); suffix > 0 { - key.Suffix = testkeys.Suffix(suffix) - } - original = append(original, keyspan.Span{ - Start: testkeys.Key(ks, startIdx), - End: testkeys.Key(ks, endIdx), - Keys: []keyspan.Key{key}, - }) - - for startIdx < endIdx { - width := rng.Int63n(endIdx-startIdx) + 1 - fragmented = append(fragmented, keyspan.Span{ - Start: testkeys.Key(ks, startIdx), - End: testkeys.Key(ks, startIdx+width), - Keys: []keyspan.Key{key}, - }) - startIdx += width - } - } - - // Both the original and the deliberately fragmented spans may contain - // overlaps, so we need to sort and fragment them. - original = fragment(cmp, formatKey, original) - fragmented = fragment(cmp, formatKey, fragmented) - - var referenceCfg, fragmentedCfg UserIteratorConfig - referenceIter := referenceCfg.Init(testkeys.Comparer, base.InternalKeySeqNumMax, - nil /* lower */, nil, /* upper */ - new(bool), new([]byte), false /* internalKeys */, new(Buffers), - keyspan.NewIter(cmp, original)) - fragmentedIter := fragmentedCfg.Init(testkeys.Comparer, base.InternalKeySeqNumMax, - nil /* lower */, nil, /* upper */ - new(bool), new([]byte), false /* internalKeys */, new(Buffers), - keyspan.NewIter(cmp, fragmented)) - - // Generate 100 random operations and run them against both iterators. - const numIterOps = 100 - type opKind struct { - weight int - fn func() string - } - ops := []opKind{ - {weight: 2, fn: func() string { return "first" }}, - {weight: 2, fn: func() string { return "last" }}, - {weight: 50, fn: func() string { return "next" }}, - {weight: 50, fn: func() string { return "prev" }}, - {weight: 5, fn: func() string { - k := testkeys.Key(ks, rng.Int63n(ks.Count())) - return fmt.Sprintf("seekge(%s)", k) - }}, - {weight: 5, fn: func() string { - k := testkeys.Key(ks, rng.Int63n(ks.Count())) - return fmt.Sprintf("seeklt(%s)", k) - }}, - } - var totalWeight int - for _, op := range ops { - totalWeight += op.weight - } - var referenceHistory, fragmentedHistory bytes.Buffer - for i := 0; i < numIterOps; i++ { - p := rng.Intn(totalWeight) - opIndex := 0 - if i == 0 { - // First op is always a First(). - } else { - for i, op := range ops { - if p < op.weight { - opIndex = i - break - } - p -= op.weight - } - } - op := ops[opIndex].fn() - runIterOp(&referenceHistory, referenceIter, op) - runIterOp(&fragmentedHistory, fragmentedIter, op) - if !bytes.Equal(referenceHistory.Bytes(), fragmentedHistory.Bytes()) { - t.Fatal(debugContext(cmp, formatKey, original, fragmented, - referenceHistory.String(), fragmentedHistory.String())) - } - } -} - -func fragment(cmp base.Compare, formatKey base.FormatKey, spans []keyspan.Span) []keyspan.Span { - keyspan.Sort(cmp, spans) - var fragments []keyspan.Span - f := keyspan.Fragmenter{ - Cmp: cmp, - Format: formatKey, - Emit: func(f keyspan.Span) { - fragments = append(fragments, f) - }, - } - for _, s := range spans { - f.Add(s) - } - f.Finish() - return fragments -} - -func debugContext( - cmp base.Compare, - formatKey base.FormatKey, - original, fragmented []keyspan.Span, - refHistory, fragHistory string, -) string { - var buf bytes.Buffer - fmt.Fprintln(&buf, "Reference:") - for _, s := range original { - fmt.Fprintln(&buf, s) - } - fmt.Fprintln(&buf) - fmt.Fprintln(&buf, "Fragmented:") - for _, s := range fragmented { - fmt.Fprintln(&buf, s) - } - fmt.Fprintln(&buf) - fmt.Fprintln(&buf, "\nOperations diff:") - diff, err := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{ - A: difflib.SplitLines(refHistory), - B: difflib.SplitLines(fragHistory), - Context: 5, - }) - if err != nil { - panic(err) - } - fmt.Fprintln(&buf, diff) - return buf.String() -} - -var iterDelim = map[rune]bool{',': true, ' ': true, '(': true, ')': true, '"': true} - -func runIterOp(w io.Writer, it keyspan.FragmentIterator, op string) { - fields := strings.FieldsFunc(op, func(r rune) bool { return iterDelim[r] }) - var s *keyspan.Span - var err error - switch strings.ToLower(fields[0]) { - case "first": - s, err = it.First() - case "last": - s, err = it.Last() - case "seekge": - s, err = it.SeekGE([]byte(fields[1])) - case "seeklt": - s, err = it.SeekLT([]byte(fields[1])) - case "next": - s, err = it.Next() - case "prev": - s, err = it.Prev() - default: - panic(fmt.Sprintf("unrecognized iter op %q", fields[0])) - } - fmt.Fprintf(w, "%-10s", op) - switch { - case err != nil: - fmt.Fprintf(w, "", err) - case s == nil: - fmt.Fprintln(w, ".") - default: - fmt.Fprintln(w, s) - } -} - -func BenchmarkTransform(b *testing.B) { - var bufs Buffers - var ui UserIteratorConfig - reinit := func() { - bufs.PrepareForReuse() - _ = ui.Init(testkeys.Comparer, math.MaxUint64, nil, nil, new(bool), nil, true /* internalKeys */, &bufs) - } - - for _, shadowing := range []bool{false, true} { - b.Run(fmt.Sprintf("shadowing=%t", shadowing), func(b *testing.B) { - for n := 1; n <= 128; n *= 2 { - b.Run(fmt.Sprintf("keys=%d", n), func(b *testing.B) { - rng := rand.New(rand.NewSource(233473048763)) - reinit() - - suffixes := make([][]byte, n) - for s := range suffixes { - if shadowing { - suffixes[s] = testkeys.Suffix(int64(rng.Intn(n))) - } else { - suffixes[s] = testkeys.Suffix(int64(s)) - } - } - rng.Shuffle(len(suffixes), func(i, j int) { - suffixes[i], suffixes[j] = suffixes[j], suffixes[i] - }) - - var keys []keyspan.Key - for k := 0; k < n; k++ { - keys = append(keys, keyspan.Key{ - Trailer: base.MakeTrailer(uint64(n-k), base.InternalKeyKindRangeKeySet), - Suffix: suffixes[k], - }) - } - dst := keyspan.Span{Keys: make([]keyspan.Key, 0, len(keys))} - b.ResetTimer() - - for i := 0; i < b.N; i++ { - err := ui.Transform(testkeys.Comparer.Compare, keyspan.Span{Keys: keys}, &dst) - if err != nil { - b.Fatal(err) - } - dst.Keys = dst.Keys[:0] - } - }) - } - }) - } -} diff --git a/internal/rangekey/testdata/defragmenting_iter b/internal/rangekeystack/testdata/defragmenting_iter similarity index 100% rename from internal/rangekey/testdata/defragmenting_iter rename to internal/rangekeystack/testdata/defragmenting_iter diff --git a/internal/rangekey/testdata/iter b/internal/rangekeystack/testdata/iter similarity index 100% rename from internal/rangekey/testdata/iter rename to internal/rangekeystack/testdata/iter diff --git a/internal/rangekeystack/user_iterator.go b/internal/rangekeystack/user_iterator.go new file mode 100644 index 0000000000..2f52466336 --- /dev/null +++ b/internal/rangekeystack/user_iterator.go @@ -0,0 +1,239 @@ +// Copyright 2024 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 rangekeystack + +import ( + "bytes" + + "github.com/cockroachdb/pebble/internal/base" + "github.com/cockroachdb/pebble/internal/invariants" + "github.com/cockroachdb/pebble/internal/keyspan" + "github.com/cockroachdb/pebble/internal/keyspan/keyspanimpl" + "github.com/cockroachdb/pebble/internal/manifest" + "github.com/cockroachdb/pebble/internal/rangekey" +) + +// UserIteratorConfig holds state for constructing the range key iterator stack +// for user iteration. The range key iterator must merge range key spans across +// the levels of the LSM. This merging is performed by a keyspanimpl.MergingIter +// on-the-fly. The UserIteratorConfig implements keyspan.Transformer, evaluating +// range-key semantics and shadowing, so the spans returned by a MergingIter are +// fully resolved. +// +// The MergingIter is wrapped by a BoundedIter, which elides spans that are +// outside the iterator bounds (or the current prefix's bounds, during prefix +// iteration mode). +// +// To provide determinisim during iteration, the BoundedIter is wrapped by a +// DefragmentingIter that defragments abutting spans with identical +// user-observable state. +// +// At the top-level an InterleavingIter interleaves range keys with point keys +// and performs truncation to iterator bounds. +// +// Below is an abbreviated diagram illustrating the mechanics of a SeekGE. +// +// InterleavingIter.SeekGE +// │ +// DefragmentingIter.SeekGE +// │ +// BoundedIter.SeekGE +// │ +// ╭────────────────┴───────────────╮ +// │ ├── defragmentBwd* +// MergingIter.SeekGE │ +// │ ╰── defragmentFwd +// ╰─╶╶ per level╶╶ ─╮ +// │ +// │ +// ├── .SeekLT +// │ +// ╰── .Next +type UserIteratorConfig struct { + snapshot uint64 + comparer *base.Comparer + miter keyspanimpl.MergingIter + biter keyspan.BoundedIter + diter keyspan.DefragmentingIter + liters [manifest.NumLevels]keyspanimpl.LevelIter + litersUsed int + internalKeys bool + bufs *Buffers +} + +// Buffers holds various buffers used for range key iteration. They're exposed +// so that they may be pooled and reused between iterators. +type Buffers struct { + merging keyspanimpl.MergingBuffers + defragmenting keyspan.DefragmentingBuffers + sortBuf keyspan.KeysBySuffix +} + +// PrepareForReuse discards any excessively large buffers. +func (bufs *Buffers) PrepareForReuse() { + bufs.merging.PrepareForReuse() + bufs.defragmenting.PrepareForReuse() +} + +// Init initializes the range key iterator stack for user iteration. The +// resulting fragment iterator applies range key semantics, defragments spans +// according to their user-observable state and, if !internalKeys, removes all +// Keys other than RangeKeySets describing the current state of range keys. The +// resulting spans contain Keys sorted by suffix (unless internalKeys is true, +// in which case they remain sorted by trailer descending). +// +// The snapshot sequence number parameter determines which keys are visible. Any +// keys not visible at the provided snapshot are ignored. +func (ui *UserIteratorConfig) Init( + comparer *base.Comparer, + snapshot uint64, + lower, upper []byte, + hasPrefix *bool, + prefix *[]byte, + internalKeys bool, + bufs *Buffers, + iters ...keyspan.FragmentIterator, +) keyspan.FragmentIterator { + ui.snapshot = snapshot + ui.comparer = comparer + ui.internalKeys = internalKeys + ui.miter.Init(comparer.Compare, ui, &bufs.merging, iters...) + ui.biter.Init(comparer.Compare, comparer.Split, &ui.miter, lower, upper, hasPrefix, prefix) + if internalKeys { + ui.diter.Init(comparer, &ui.biter, keyspan.DefragmentInternal, keyspan.StaticDefragmentReducer, &bufs.defragmenting) + } else { + ui.diter.Init(comparer, &ui.biter, ui, keyspan.StaticDefragmentReducer, &bufs.defragmenting) + } + ui.litersUsed = 0 + ui.bufs = bufs + return &ui.diter +} + +// AddLevel adds a new level to the bottom of the iterator stack. AddLevel +// must be called after Init and before any other method on the iterator. +func (ui *UserIteratorConfig) AddLevel(iter keyspan.FragmentIterator) { + ui.miter.AddLevel(iter) +} + +// NewLevelIter returns a pointer to a newly allocated or reused +// keyspanimpl.LevelIter. The caller is responsible for calling Init() on this +// instance. +func (ui *UserIteratorConfig) NewLevelIter() *keyspanimpl.LevelIter { + if ui.litersUsed >= len(ui.liters) { + return &keyspanimpl.LevelIter{} + } + ui.litersUsed++ + return &ui.liters[ui.litersUsed-1] +} + +// SetBounds propagates bounds to the iterator stack. The fragment iterator +// interface ordinarily doesn't enforce bounds, so this is exposed as an +// explicit method on the user iterator config. +func (ui *UserIteratorConfig) SetBounds(lower, upper []byte) { + ui.biter.SetBounds(lower, upper) +} + +// Transform implements the keyspan.Transformer interface for use with a +// keyspanimpl.MergingIter. It transforms spans by resolving range keys at the +// provided snapshot sequence number. Shadowing of keys is resolved (eg, removal +// of unset keys, removal of keys overwritten by a set at the same suffix, etc) +// and then non-RangeKeySet keys are removed. The resulting transformed spans +// only contain RangeKeySets describing the state visible at the provided +// sequence number, and hold their Keys sorted by Suffix (except if internalKeys +// is true, then keys remain sorted by trailer. +func (ui *UserIteratorConfig) Transform(cmp base.Compare, s keyspan.Span, dst *keyspan.Span) error { + // Apply shadowing of keys. + dst.Start = s.Start + dst.End = s.End + ui.bufs.sortBuf = keyspan.KeysBySuffix{ + Cmp: cmp, + Keys: ui.bufs.sortBuf.Keys[:0], + } + rangekey.CoalesceIntoKeysBySuffix(ui.comparer.Equal, &ui.bufs.sortBuf, ui.snapshot, s.Keys) + if ui.internalKeys { + if s.KeysOrder != keyspan.ByTrailerDesc { + panic("unexpected key ordering in UserIteratorTransform with internalKeys = true") + } + dst.Keys = ui.bufs.sortBuf.Keys + keyspan.SortKeysByTrailer(&dst.Keys) + return nil + } + // During user iteration over range keys, unsets and deletes don't matter. This + // step helps logical defragmentation during iteration. + keys := ui.bufs.sortBuf.Keys + dst.Keys = dst.Keys[:0] + for i := range keys { + switch keys[i].Kind() { + case base.InternalKeyKindRangeKeySet: + if invariants.Enabled && len(dst.Keys) > 0 && cmp(dst.Keys[len(dst.Keys)-1].Suffix, keys[i].Suffix) > 0 { + panic("pebble: keys unexpectedly not in ascending suffix order") + } + dst.Keys = append(dst.Keys, keys[i]) + case base.InternalKeyKindRangeKeyUnset: + if invariants.Enabled && len(dst.Keys) > 0 && cmp(dst.Keys[len(dst.Keys)-1].Suffix, keys[i].Suffix) > 0 { + panic("pebble: keys unexpectedly not in ascending suffix order") + } + // Skip. + continue + case base.InternalKeyKindRangeKeyDelete: + // Skip. + continue + default: + return base.CorruptionErrorf("pebble: unrecognized range key kind %s", keys[i].Kind()) + } + } + // coalesce results in dst.Keys being sorted by Suffix. + dst.KeysOrder = keyspan.BySuffixAsc + return nil +} + +// ShouldDefragment implements the DefragmentMethod interface and configures a +// DefragmentingIter to defragment spans of range keys if their user-visible +// state is identical. This defragmenting method assumes the provided spans have +// already been transformed through (UserIterationConfig).Transform, so all +// RangeKeySets are user-visible sets and are already in Suffix order. This +// defragmenter checks for equality between set suffixes and values (ignoring +// sequence numbers). It's intended for use during user iteration, when the +// wrapped keyspan iterator is merging spans across all levels of the LSM. +func (ui *UserIteratorConfig) ShouldDefragment(equal base.Equal, a, b *keyspan.Span) bool { + // This method is not called with internalKeys = true. + if ui.internalKeys { + panic("unexpected call to ShouldDefragment with internalKeys = true") + } + // This implementation must only be used on spans that have transformed by + // ui.Transform. The transform applies shadowing, removes all keys besides + // the resulting Sets and sorts the keys by suffix. Since shadowing has been + // applied, each Set must set a unique suffix. If the two spans are + // equivalent, they must have the same number of range key sets. + if len(a.Keys) != len(b.Keys) || len(a.Keys) == 0 { + return false + } + if a.KeysOrder != keyspan.BySuffixAsc || b.KeysOrder != keyspan.BySuffixAsc { + panic("pebble: range key span's keys unexpectedly not in ascending suffix order") + } + + ret := true + for i := range a.Keys { + if invariants.Enabled { + if a.Keys[i].Kind() != base.InternalKeyKindRangeKeySet || + b.Keys[i].Kind() != base.InternalKeyKindRangeKeySet { + panic("pebble: unexpected non-RangeKeySet during defragmentation") + } + if i > 0 && (ui.comparer.Compare(a.Keys[i].Suffix, a.Keys[i-1].Suffix) < 0 || + ui.comparer.Compare(b.Keys[i].Suffix, b.Keys[i-1].Suffix) < 0) { + panic("pebble: range keys not ordered by suffix during defragmentation") + } + } + if !equal(a.Keys[i].Suffix, b.Keys[i].Suffix) { + ret = false + break + } + if !bytes.Equal(a.Keys[i].Value, b.Keys[i].Value) { + ret = false + break + } + } + return ret +} diff --git a/internal/rangekeystack/user_iterator_test.go b/internal/rangekeystack/user_iterator_test.go new file mode 100644 index 0000000000..6c7fb9c6ea --- /dev/null +++ b/internal/rangekeystack/user_iterator_test.go @@ -0,0 +1,396 @@ +// Copyright 2024 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 rangekeystack + +import ( + "bytes" + "fmt" + "io" + "math" + "math/rand" + "strconv" + "strings" + "testing" + "time" + + "github.com/cockroachdb/datadriven" + "github.com/cockroachdb/pebble/internal/base" + "github.com/cockroachdb/pebble/internal/keyspan" + "github.com/cockroachdb/pebble/internal/keyspan/keyspanimpl" + "github.com/cockroachdb/pebble/internal/rangekey" + "github.com/cockroachdb/pebble/internal/testkeys" + "github.com/pmezard/go-difflib/difflib" + "github.com/stretchr/testify/require" +) + +func TestIter(t *testing.T) { + eq := testkeys.Comparer.Equal + cmp := testkeys.Comparer.Compare + var iter keyspanimpl.MergingIter + var buf bytes.Buffer + + datadriven.RunTest(t, "testdata/iter", func(t *testing.T, td *datadriven.TestData) string { + buf.Reset() + switch td.Cmd { + case "define": + visibleSeqNum := base.InternalKeySeqNumMax + for _, arg := range td.CmdArgs { + if arg.Key == "visible-seq-num" { + var err error + visibleSeqNum, err = strconv.ParseUint(arg.Vals[0], 10, 64) + require.NoError(t, err) + } + } + + var spans []keyspan.Span + lines := strings.Split(strings.TrimSpace(td.Input), "\n") + for _, line := range lines { + spans = append(spans, keyspan.ParseSpan(line)) + } + transform := keyspan.TransformerFunc(func(cmp base.Compare, s keyspan.Span, dst *keyspan.Span) error { + keysBySuffix := keyspan.KeysBySuffix{ + Cmp: cmp, + Keys: dst.Keys[:0], + } + rangekey.CoalesceIntoKeysBySuffix(eq, &keysBySuffix, visibleSeqNum, s.Keys) + // Update the span with the (potentially reduced) keys slice. coalesce left + // the keys in *dst sorted by suffix. Re-sort them by trailer. + dst.Keys = keysBySuffix.Keys + keyspan.SortKeysByTrailer(&dst.Keys) + dst.Start = s.Start + dst.End = s.End + return nil + }) + iter.Init(cmp, transform, new(keyspanimpl.MergingBuffers), keyspan.NewIter(cmp, spans)) + return "OK" + case "iter": + buf.Reset() + lines := strings.Split(strings.TrimSpace(td.Input), "\n") + for _, line := range lines { + line = strings.TrimSpace(line) + i := strings.IndexByte(line, ' ') + iterCmd := line + if i > 0 { + iterCmd = string(line[:i]) + } + var s *keyspan.Span + var err error + switch iterCmd { + case "first": + s, err = iter.First() + case "last": + s, err = iter.Last() + case "next": + s, err = iter.Next() + case "prev": + s, err = iter.Prev() + case "seek-ge": + s, err = iter.SeekGE([]byte(strings.TrimSpace(line[i:]))) + case "seek-lt": + s, err = iter.SeekLT([]byte(strings.TrimSpace(line[i:]))) + default: + return fmt.Sprintf("unrecognized iter command %q", iterCmd) + } + require.NoError(t, err) + fmt.Fprint(&buf, s) + if buf.Len() > 0 { + fmt.Fprintln(&buf) + } + } + return buf.String() + default: + return fmt.Sprintf("unrecognized command %q", td.Cmd) + } + }) +} + +func TestDefragmenting(t *testing.T) { + cmp := testkeys.Comparer.Compare + + var buf bytes.Buffer + var spans []keyspan.Span + var hasPrefix bool + var prefix []byte + datadriven.RunTest(t, "testdata/defragmenting_iter", func(t *testing.T, td *datadriven.TestData) string { + buf.Reset() + switch td.Cmd { + case "define": + spans = spans[:0] + lines := strings.Split(strings.TrimSpace(td.Input), "\n") + for _, line := range lines { + spans = append(spans, keyspan.ParseSpan(line)) + } + return "" + case "iter": + var userIterCfg UserIteratorConfig + iter := userIterCfg.Init(testkeys.Comparer, base.InternalKeySeqNumMax, + nil /* lower */, nil, /* upper */ + &hasPrefix, &prefix, false /* internalKeys */, new(Buffers), + keyspan.NewIter(cmp, spans)) + for _, line := range strings.Split(td.Input, "\n") { + runIterOp(&buf, iter, line) + } + return strings.TrimSpace(buf.String()) + default: + return fmt.Sprintf("unrecognized command %q", td.Cmd) + } + }) +} + +func TestDefragmentingIter_Randomized(t *testing.T) { + seed := time.Now().UnixNano() + for i := int64(0); i < 100; i++ { + testDefragmentingIteRandomizedOnce(t, seed+i) + } +} + +func TestDefragmentingIter_RandomizedFixedSeed(t *testing.T) { + const seed = 1648173101214881000 + testDefragmentingIteRandomizedOnce(t, seed) +} + +func testDefragmentingIteRandomizedOnce(t *testing.T, seed int64) { + cmp := testkeys.Comparer.Compare + formatKey := testkeys.Comparer.FormatKey + + rng := rand.New(rand.NewSource(seed)) + t.Logf("seed = %d", seed) + + // Use a key space of alphanumeric strings, with a random max length between + // 1-2. Repeat keys are more common at the lower max lengths. + ks := testkeys.Alpha(rng.Intn(2) + 1) + + // Generate between 1-15 range keys. + const maxRangeKeys = 15 + var original, fragmented []keyspan.Span + numRangeKeys := 1 + rng.Intn(maxRangeKeys) + for i := 0; i < numRangeKeys; i++ { + startIdx := rng.Int63n(ks.Count()) + endIdx := rng.Int63n(ks.Count()) + for startIdx == endIdx { + endIdx = rng.Int63n(ks.Count()) + } + if startIdx > endIdx { + startIdx, endIdx = endIdx, startIdx + } + + key := keyspan.Key{ + Trailer: base.MakeTrailer(uint64(i), base.InternalKeyKindRangeKeySet), + Value: []byte(fmt.Sprintf("v%d", rng.Intn(3))), + } + // Generate suffixes 0, 1, 2, or 3 with 0 indicating none. + if suffix := rng.Int63n(4); suffix > 0 { + key.Suffix = testkeys.Suffix(suffix) + } + original = append(original, keyspan.Span{ + Start: testkeys.Key(ks, startIdx), + End: testkeys.Key(ks, endIdx), + Keys: []keyspan.Key{key}, + }) + + for startIdx < endIdx { + width := rng.Int63n(endIdx-startIdx) + 1 + fragmented = append(fragmented, keyspan.Span{ + Start: testkeys.Key(ks, startIdx), + End: testkeys.Key(ks, startIdx+width), + Keys: []keyspan.Key{key}, + }) + startIdx += width + } + } + + // Both the original and the deliberately fragmented spans may contain + // overlaps, so we need to sort and fragment them. + original = fragment(cmp, formatKey, original) + fragmented = fragment(cmp, formatKey, fragmented) + + var referenceCfg, fragmentedCfg UserIteratorConfig + referenceIter := referenceCfg.Init(testkeys.Comparer, base.InternalKeySeqNumMax, + nil /* lower */, nil, /* upper */ + new(bool), new([]byte), false /* internalKeys */, new(Buffers), + keyspan.NewIter(cmp, original)) + fragmentedIter := fragmentedCfg.Init(testkeys.Comparer, base.InternalKeySeqNumMax, + nil /* lower */, nil, /* upper */ + new(bool), new([]byte), false /* internalKeys */, new(Buffers), + keyspan.NewIter(cmp, fragmented)) + + // Generate 100 random operations and run them against both iterators. + const numIterOps = 100 + type opKind struct { + weight int + fn func() string + } + ops := []opKind{ + {weight: 2, fn: func() string { return "first" }}, + {weight: 2, fn: func() string { return "last" }}, + {weight: 50, fn: func() string { return "next" }}, + {weight: 50, fn: func() string { return "prev" }}, + {weight: 5, fn: func() string { + k := testkeys.Key(ks, rng.Int63n(ks.Count())) + return fmt.Sprintf("seekge(%s)", k) + }}, + {weight: 5, fn: func() string { + k := testkeys.Key(ks, rng.Int63n(ks.Count())) + return fmt.Sprintf("seeklt(%s)", k) + }}, + } + var totalWeight int + for _, op := range ops { + totalWeight += op.weight + } + var referenceHistory, fragmentedHistory bytes.Buffer + for i := 0; i < numIterOps; i++ { + p := rng.Intn(totalWeight) + opIndex := 0 + if i == 0 { + // First op is always a First(). + } else { + for i, op := range ops { + if p < op.weight { + opIndex = i + break + } + p -= op.weight + } + } + op := ops[opIndex].fn() + runIterOp(&referenceHistory, referenceIter, op) + runIterOp(&fragmentedHistory, fragmentedIter, op) + if !bytes.Equal(referenceHistory.Bytes(), fragmentedHistory.Bytes()) { + t.Fatal(debugContext(cmp, formatKey, original, fragmented, + referenceHistory.String(), fragmentedHistory.String())) + } + } +} + +func fragment(cmp base.Compare, formatKey base.FormatKey, spans []keyspan.Span) []keyspan.Span { + keyspan.Sort(cmp, spans) + var fragments []keyspan.Span + f := keyspan.Fragmenter{ + Cmp: cmp, + Format: formatKey, + Emit: func(f keyspan.Span) { + fragments = append(fragments, f) + }, + } + for _, s := range spans { + f.Add(s) + } + f.Finish() + return fragments +} + +func debugContext( + cmp base.Compare, + formatKey base.FormatKey, + original, fragmented []keyspan.Span, + refHistory, fragHistory string, +) string { + var buf bytes.Buffer + fmt.Fprintln(&buf, "Reference:") + for _, s := range original { + fmt.Fprintln(&buf, s) + } + fmt.Fprintln(&buf) + fmt.Fprintln(&buf, "Fragmented:") + for _, s := range fragmented { + fmt.Fprintln(&buf, s) + } + fmt.Fprintln(&buf) + fmt.Fprintln(&buf, "\nOperations diff:") + diff, err := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{ + A: difflib.SplitLines(refHistory), + B: difflib.SplitLines(fragHistory), + Context: 5, + }) + if err != nil { + panic(err) + } + fmt.Fprintln(&buf, diff) + return buf.String() +} + +var iterDelim = map[rune]bool{',': true, ' ': true, '(': true, ')': true, '"': true} + +func runIterOp(w io.Writer, it keyspan.FragmentIterator, op string) { + fields := strings.FieldsFunc(op, func(r rune) bool { return iterDelim[r] }) + var s *keyspan.Span + var err error + switch strings.ToLower(fields[0]) { + case "first": + s, err = it.First() + case "last": + s, err = it.Last() + case "seekge": + s, err = it.SeekGE([]byte(fields[1])) + case "seeklt": + s, err = it.SeekLT([]byte(fields[1])) + case "next": + s, err = it.Next() + case "prev": + s, err = it.Prev() + default: + panic(fmt.Sprintf("unrecognized iter op %q", fields[0])) + } + fmt.Fprintf(w, "%-10s", op) + switch { + case err != nil: + fmt.Fprintf(w, "", err) + case s == nil: + fmt.Fprintln(w, ".") + default: + fmt.Fprintln(w, s) + } +} + +func BenchmarkTransform(b *testing.B) { + var bufs Buffers + var ui UserIteratorConfig + reinit := func() { + bufs.PrepareForReuse() + _ = ui.Init(testkeys.Comparer, math.MaxUint64, nil, nil, new(bool), nil, true /* internalKeys */, &bufs) + } + + for _, shadowing := range []bool{false, true} { + b.Run(fmt.Sprintf("shadowing=%t", shadowing), func(b *testing.B) { + for n := 1; n <= 128; n *= 2 { + b.Run(fmt.Sprintf("keys=%d", n), func(b *testing.B) { + rng := rand.New(rand.NewSource(233473048763)) + reinit() + + suffixes := make([][]byte, n) + for s := range suffixes { + if shadowing { + suffixes[s] = testkeys.Suffix(int64(rng.Intn(n))) + } else { + suffixes[s] = testkeys.Suffix(int64(s)) + } + } + rng.Shuffle(len(suffixes), func(i, j int) { + suffixes[i], suffixes[j] = suffixes[j], suffixes[i] + }) + + var keys []keyspan.Key + for k := 0; k < n; k++ { + keys = append(keys, keyspan.Key{ + Trailer: base.MakeTrailer(uint64(n-k), base.InternalKeyKindRangeKeySet), + Suffix: suffixes[k], + }) + } + dst := keyspan.Span{Keys: make([]keyspan.Key, 0, len(keys))} + b.ResetTimer() + + for i := 0; i < b.N; i++ { + err := ui.Transform(testkeys.Comparer.Compare, keyspan.Span{Keys: keys}, &dst) + if err != nil { + b.Fatal(err) + } + dst.Keys = dst.Keys[:0] + } + }) + } + }) + } +} diff --git a/iterator.go b/iterator.go index 02bdc1681e..7de668dede 100644 --- a/iterator.go +++ b/iterator.go @@ -20,7 +20,7 @@ import ( "github.com/cockroachdb/pebble/internal/keyspan" "github.com/cockroachdb/pebble/internal/keyspan/keyspanimpl" "github.com/cockroachdb/pebble/internal/manifest" - "github.com/cockroachdb/pebble/internal/rangekey" + "github.com/cockroachdb/pebble/internal/rangekeystack" "github.com/cockroachdb/pebble/sstable" "github.com/cockroachdb/redact" ) @@ -389,7 +389,7 @@ type iteratorRangeKeyState struct { // iterator stack, but do not need to be directly accessed during iteration. // This struct is bundled within the iteratorRangeKeyState struct to reduce // allocations. - iterConfig rangekey.UserIteratorConfig + iterConfig rangekeystack.UserIteratorConfig } type rangeKeyBuffers struct { @@ -399,7 +399,7 @@ type rangeKeyBuffers struct { // Start and end boundaries, suffixes and values are all copied into buf. buf bytealloc.A // internal holds buffers used by the range key internal iterators. - internal rangekey.Buffers + internal rangekeystack.Buffers } func (b *rangeKeyBuffers) PrepareForReuse() {