From 97285aea3b00d6c44da2758b17177529fc273ce2 Mon Sep 17 00:00:00 2001 From: Jackson Owens Date: Mon, 14 Aug 2023 11:09:55 -0400 Subject: [PATCH] sstable: fix two-level monotonic bounds bug This commit fixes a sstable iterator bug surfaced by the metamorphic test in the issue #2816. In the circumstances of this bug, a two-level sstable iterator with monotonically advanced bounds could reuse the wrong index block if a previous block was excluded due to a block-property filter. An accompanying regression unit test is added too. Additionally, it refactors some additional sstable tests to run against more table formats and automatically against newly introduced table formats. While this commit is sufficient to fix the bug, in subsequent work I anticipate refactoring and tightening invariants. This refactoring will not be suitable for a backport, so this commit first resolves the bug in a backportable way. --- sstable/block.go | 18 ++- sstable/data_test.go | 41 +++++ sstable/format.go | 1 + sstable/reader_iter_single_lvl.go | 12 +- sstable/reader_iter_two_lvl.go | 21 ++- sstable/reader_test.go | 111 ++++++++----- .../testdata/reader_bpf/{ => Pebblev2}/iter | 2 +- sstable/testdata/reader_bpf/Pebblev3/iter | 146 ++++++++++++++++++ sstable/testdata/reader_bpf_v3/iter | 56 ------- .../{readerstats => readerstats_LevelDB}/iter | 2 +- .../iter | 2 +- 11 files changed, 306 insertions(+), 106 deletions(-) rename sstable/testdata/reader_bpf/{ => Pebblev2}/iter (94%) create mode 100644 sstable/testdata/reader_bpf/Pebblev3/iter delete mode 100644 sstable/testdata/reader_bpf_v3/iter rename sstable/testdata/{readerstats => readerstats_LevelDB}/iter (97%) rename sstable/testdata/{readerstats_v3 => readerstats_Pebblev3}/iter (99%) diff --git a/sstable/block.go b/sstable/block.go index cbd2e5ce6f..c6345ea00e 100644 --- a/sstable/block.go +++ b/sstable/block.go @@ -688,8 +688,11 @@ func (i *blockIter) getFirstUserKey() []byte { // SeekGE implements internalIterator.SeekGE, as documented in the pebble // package. func (i *blockIter) SeekGE(key []byte, flags base.SeekGEFlags) (*InternalKey, base.LazyValue) { - i.clearCache() + if invariants.Enabled && i.isDataInvalidated() { + panic(errors.AssertionFailedf("invalidated blockIter used")) + } + i.clearCache() // Find the index of the smallest restart point whose key is > the key // sought; index will be numRestarts if there is no such restart point. i.offset = 0 @@ -818,8 +821,11 @@ func (i *blockIter) SeekPrefixGE( // SeekLT implements internalIterator.SeekLT, as documented in the pebble // package. func (i *blockIter) SeekLT(key []byte, flags base.SeekLTFlags) (*InternalKey, base.LazyValue) { - i.clearCache() + if invariants.Enabled && i.isDataInvalidated() { + panic(errors.AssertionFailedf("invalidated blockIter used")) + } + i.clearCache() // Find the index of the smallest restart point whose key is >= the key // sought; index will be numRestarts if there is no such restart point. i.offset = 0 @@ -986,6 +992,10 @@ func (i *blockIter) SeekLT(key []byte, flags base.SeekLTFlags) (*InternalKey, ba // First implements internalIterator.First, as documented in the pebble // package. func (i *blockIter) First() (*InternalKey, base.LazyValue) { + if invariants.Enabled && i.isDataInvalidated() { + panic(errors.AssertionFailedf("invalidated blockIter used")) + } + i.offset = 0 if !i.valid() { return nil, base.LazyValue{} @@ -1015,6 +1025,10 @@ func decodeRestart(b []byte) int32 { // Last implements internalIterator.Last, as documented in the pebble package. func (i *blockIter) Last() (*InternalKey, base.LazyValue) { + if invariants.Enabled && i.isDataInvalidated() { + panic(errors.AssertionFailedf("invalidated blockIter used")) + } + // Seek forward from the last restart point. i.offset = decodeRestart(i.data[i.restarts+4*(i.numRestarts-1):]) if !i.valid() { diff --git a/sstable/data_test.go b/sstable/data_test.go index 9912bd070f..2b1926a355 100644 --- a/sstable/data_test.go +++ b/sstable/data_test.go @@ -394,6 +394,47 @@ func runIterCmd( continue case "reset-stats": *opts.stats = base.InternalIteratorStats{} + continue + case "internal-iter-state": + fmt.Fprintf(&b, "| %T:\n", origIter) + si, _ := origIter.(*singleLevelIterator) + if twoLevelIter, ok := origIter.(*twoLevelIterator); ok { + si = &twoLevelIter.singleLevelIterator + if twoLevelIter.topLevelIndex.valid() { + fmt.Fprintf(&b, "| topLevelIndex.Key() = %q\n", twoLevelIter.topLevelIndex.Key()) + v := twoLevelIter.topLevelIndex.value() + bhp, err := decodeBlockHandleWithProperties(v.InPlaceValue()) + if err != nil { + fmt.Fprintf(&b, "| topLevelIndex.InPlaceValue() failed to decode as BHP: %s\n", err) + } else { + fmt.Fprintf(&b, "| topLevelIndex.InPlaceValue() = (Offset: %d, Length: %d, Props: %x)\n", + bhp.Offset, bhp.Length, bhp.Props) + } + } else { + fmt.Fprintf(&b, "| topLevelIndex iter invalid\n") + } + fmt.Fprintf(&b, "| topLevelIndex.isDataInvalidated()=%t\n", twoLevelIter.topLevelIndex.isDataInvalidated()) + } + if si.index.valid() { + fmt.Fprintf(&b, "| index.Key() = %q\n", si.index.Key()) + v := si.index.value() + bhp, err := decodeBlockHandleWithProperties(v.InPlaceValue()) + if err != nil { + fmt.Fprintf(&b, "| index.InPlaceValue() failed to decode as BHP: %s\n", err) + } else { + fmt.Fprintf(&b, "| index.InPlaceValue() = (Offset: %d, Length: %d, Props: %x)\n", + bhp.Offset, bhp.Length, bhp.Props) + } + } else { + fmt.Fprintf(&b, "| index iter invalid\n") + } + fmt.Fprintf(&b, "| index.isDataInvalidated()=%t\n", si.index.isDataInvalidated()) + fmt.Fprintf(&b, "| data.isDataInvalidated()=%t\n", si.data.isDataInvalidated()) + fmt.Fprintf(&b, "| hideObsoletePoints = %t\n", si.hideObsoletePoints) + fmt.Fprintf(&b, "| dataBH = (Offset: %d, Length: %d)\n", si.dataBH.Offset, si.dataBH.Length) + fmt.Fprintf(&b, "| (boundsCmp,positionedUsingLatestBounds) = (%d,%t)\n", si.boundsCmp, si.positionedUsingLatestBounds) + fmt.Fprintf(&b, "| exhaustedBounds = %d\n", si.exhaustedBounds) + continue } if opts.everyOp != nil { diff --git a/sstable/format.go b/sstable/format.go index a42ed7c393..92a3095dc1 100644 --- a/sstable/format.go +++ b/sstable/format.go @@ -25,6 +25,7 @@ const ( TableFormatPebblev2 // Range keys. TableFormatPebblev3 // Value blocks. TableFormatPebblev4 // DELSIZED tombstones. + NumTableFormats TableFormatMax = TableFormatPebblev4 ) diff --git a/sstable/reader_iter_single_lvl.go b/sstable/reader_iter_single_lvl.go index 4fd22bad66..2e23d6aabc 100644 --- a/sstable/reader_iter_single_lvl.go +++ b/sstable/reader_iter_single_lvl.go @@ -302,6 +302,12 @@ func disableBoundsOpt(bound []byte, ptr uintptr) bool { return bound[len(bound)-1]&byte(1) == 0 && simpleHash == 0 } +// ensureBoundsOptDeterminism provides a facility for disabling of the bounds +// optimizations performed by disableBoundsOpt for tests that require +// deterministic iterator behavior. Some unit tests examine internal iterator +// state and require this behavior to be deterministic. +var ensureBoundsOptDeterminism bool + // SetBounds implements internalIterator.SetBounds, as documented in the pebble // package. Note that the upper field is exclusive. func (i *singleLevelIterator) SetBounds(lower, upper []byte) { @@ -320,12 +326,14 @@ func (i *singleLevelIterator) SetBounds(lower, upper []byte) { if i.positionedUsingLatestBounds { if i.upper != nil && lower != nil && i.cmp(i.upper, lower) <= 0 { i.boundsCmp = +1 - if invariants.Enabled && disableBoundsOpt(lower, uintptr(unsafe.Pointer(i))) { + if invariants.Enabled && !ensureBoundsOptDeterminism && + disableBoundsOpt(lower, uintptr(unsafe.Pointer(i))) { i.boundsCmp = 0 } } else if i.lower != nil && upper != nil && i.cmp(upper, i.lower) <= 0 { i.boundsCmp = -1 - if invariants.Enabled && disableBoundsOpt(upper, uintptr(unsafe.Pointer(i))) { + if invariants.Enabled && !ensureBoundsOptDeterminism && + disableBoundsOpt(upper, uintptr(unsafe.Pointer(i))) { i.boundsCmp = 0 } } diff --git a/sstable/reader_iter_two_lvl.go b/sstable/reader_iter_two_lvl.go index ce98fe8e3a..b23c603cee 100644 --- a/sstable/reader_iter_two_lvl.go +++ b/sstable/reader_iter_two_lvl.go @@ -32,6 +32,7 @@ func (i *twoLevelIterator) loadIndex(dir int8) loadBlockResult { // Ensure the index data block iterators are invalidated even if loading of // the index fails. i.data.invalidate() + i.index.invalidate() if !i.topLevelIndex.valid() { i.index.offset = 0 i.index.restarts = 0 @@ -259,8 +260,16 @@ func (i *twoLevelIterator) SeekGE( // the position of the two-level index iterator without remembering the // previous value of maybeFilteredKeys. + // We fall into the slow path if i.index.isDataInvalidated() even if the + // top-level iterator is already positioned correctly and all other + // conditions are met. An alternative structure could reuse topLevelIndex's + // current position and reload the index block to which it points. Arguably, + // an index block load is expensive and the index block may still be earlier + // than the index block containing the sought key, resulting in a wasteful + // block load. + var dontSeekWithinSingleLevelIter bool - if i.topLevelIndex.isDataInvalidated() || !i.topLevelIndex.valid() || err != nil || + if i.topLevelIndex.isDataInvalidated() || !i.topLevelIndex.valid() || i.index.isDataInvalidated() || err != nil || (i.boundsCmp <= 0 && !flags.TrySeekUsingNext()) || i.cmp(key, i.topLevelIndex.Key().UserKey) > 0 { // Slow-path: need to position the topLevelIndex. @@ -447,8 +456,16 @@ func (i *twoLevelIterator) SeekPrefixGE( // not reuse the position of the two-level index iterator without // remembering the previous value of maybeFilteredKeysTwoLevel. + // We fall into the slow path if i.index.isDataInvalidated() even if the + // top-level iterator is already positioned correctly and all other + // conditions are met. An alternative structure could reuse topLevelIndex's + // current position and reload the index block to which it points. Arguably, + // an index block load is expensive and the index block may still be earlier + // than the index block containing the sought key, resulting in a wasteful + // block load. + var dontSeekWithinSingleLevelIter bool - if i.topLevelIndex.isDataInvalidated() || !i.topLevelIndex.valid() || err != nil || + if i.topLevelIndex.isDataInvalidated() || !i.topLevelIndex.valid() || i.index.isDataInvalidated() || err != nil || (i.boundsCmp <= 0 && !flags.TrySeekUsingNext()) || i.cmp(key, i.topLevelIndex.Key().UserKey) > 0 { // Slow-path: need to position the topLevelIndex. diff --git a/sstable/reader_test.go b/sstable/reader_test.go index e2390c38dc..bfb14d6ab9 100644 --- a/sstable/reader_test.go +++ b/sstable/reader_test.go @@ -471,7 +471,7 @@ func TestReader(t *testing.T) { "prefixFilter": "testdata/prefixreader", } - for _, format := range []TableFormat{TableFormatPebblev2, TableFormatPebblev3, TableFormatPebblev4} { + for format := TableFormatPebblev2; format <= TableFormatMax; format++ { for dName, blockSize := range blockSizes { for iName, indexBlockSize := range blockSizes { for lName, tableOpt := range writerOpts { @@ -486,7 +486,7 @@ func TestReader(t *testing.T) { format, oName, lName, dName, iName), func(t *testing.T) { runTestReader( - t, tableOpt, testDirs[oName], nil /* Reader */, 0, false, true) + t, tableOpt, testDirs[oName], nil /* Reader */, true) }) } } @@ -513,7 +513,7 @@ func TestReaderHideObsolete(t *testing.T) { t.Run(fmt.Sprintf("blockSize=%s", dName), func(t *testing.T) { runTestReader( t, opts, "testdata/reader_hide_obsolete", - nil /* Reader */, 0, false, true) + nil /* Reader */, true) }) } } @@ -539,46 +539,76 @@ func TestHamletReader(t *testing.T) { t.Run( fmt.Sprintf("sst=%s", prebuiltSST), func(t *testing.T) { - runTestReader(t, WriterOptions{}, "testdata/hamletreader", r, 0, false, false) + runTestReader(t, WriterOptions{}, "testdata/hamletreader", r, false) }, ) } } -func TestReaderStats(t *testing.T) { - tableOpt := WriterOptions{ - BlockSize: 30, - IndexBlockSize: 30, +func forEveryTableFormat[I any]( + t *testing.T, formatTable [NumTableFormats]I, runTest func(*testing.T, TableFormat, I), +) { + t.Helper() + for tf := TableFormatUnspecified + 1; tf < TableFormatMax; tf++ { + t.Run(tf.String(), func(t *testing.T) { + runTest(t, tf, formatTable[tf]) + }) } - runTestReader(t, tableOpt, "testdata/readerstats", nil, 10000, false, false) } -func TestReaderStatsV3(t *testing.T) { - writerOpt := WriterOptions{ - BlockSize: 32 << 10, - IndexBlockSize: 32 << 10, - Comparer: testkeys.Comparer, - TableFormat: TableFormatPebblev3, - } - tdFile := "testdata/readerstats_v3" - runTestReader(t, writerOpt, tdFile, nil /* Reader */, 0, true, false) +func TestReaderStats(t *testing.T) { + forEveryTableFormat[string](t, + [NumTableFormats]string{ + TableFormatUnspecified: "", + TableFormatLevelDB: "testdata/readerstats_LevelDB", + TableFormatRocksDBv2: "testdata/readerstats_LevelDB", + TableFormatPebblev1: "testdata/readerstats_LevelDB", + TableFormatPebblev2: "testdata/readerstats_LevelDB", + TableFormatPebblev3: "testdata/readerstats_Pebblev3", + TableFormatPebblev4: "testdata/readerstats_Pebblev3", + }, func(t *testing.T, format TableFormat, dir string) { + if dir == "" { + t.Skip() + } + writerOpt := WriterOptions{ + BlockSize: 32 << 10, + IndexBlockSize: 32 << 10, + Comparer: testkeys.Comparer, + TableFormat: format, + } + runTestReader(t, writerOpt, dir, nil /* Reader */, false /* printValue */) + }) } func TestReaderWithBlockPropertyFilter(t *testing.T) { - for _, format := range []TableFormat{TableFormatPebblev2, TableFormatPebblev3} { - writerOpt := WriterOptions{ - BlockSize: 1, - IndexBlockSize: 40, - Comparer: testkeys.Comparer, - TableFormat: format, - BlockPropertyCollectors: []func() BlockPropertyCollector{NewTestKeysBlockPropertyCollector}, - } - tdFile := "testdata/reader_bpf" - if format == TableFormatPebblev3 { - tdFile = "testdata/reader_bpf_v3" - } - runTestReader(t, writerOpt, tdFile, nil /* Reader */, 0, true, false) - } + // Some of these tests examine internal iterator state, so they require + // determinism. When the invariants tag is set, disableBoundsOpt may disable + // the bounds optimization depending on the iterator pointer address. This + // can add nondeterminism to the internal iterator statae. Disable this + // nondeterminism for the duration of this test. + ensureBoundsOptDeterminism = true + defer func() { ensureBoundsOptDeterminism = false }() + + forEveryTableFormat[string](t, + [NumTableFormats]string{ + TableFormatUnspecified: "", // Block properties unsupported + TableFormatLevelDB: "", // Block properties unsupported + TableFormatRocksDBv2: "", // Block properties unsupported + TableFormatPebblev1: "", // Block properties unsupported + TableFormatPebblev2: "testdata/reader_bpf/Pebblev2", + TableFormatPebblev3: "testdata/reader_bpf/Pebblev3", + TableFormatPebblev4: "testdata/reader_bpf/Pebblev3", + }, func(t *testing.T, format TableFormat, dir string) { + if dir == "" { + t.Skip("Block-properties unsupported") + } + writerOpt := WriterOptions{ + Comparer: testkeys.Comparer, + TableFormat: format, + BlockPropertyCollectors: []func() BlockPropertyCollector{NewTestKeysBlockPropertyCollector}, + } + runTestReader(t, writerOpt, dir, nil /* Reader */, false) + }) } func TestInjectedErrors(t *testing.T) { @@ -702,15 +732,7 @@ func indexLayoutString(t *testing.T, r *Reader) string { return buf.String() } -func runTestReader( - t *testing.T, - o WriterOptions, - dir string, - r *Reader, - cacheSize int, - printLayout bool, - printValue bool, -) { +func runTestReader(t *testing.T, o WriterOptions, dir string, r *Reader, printValue bool) { datadriven.Walk(t, dir, func(t *testing.T, path string) { defer func() { if r != nil { @@ -726,6 +748,13 @@ func runTestReader( r.Close() r = nil } + var cacheSize int + var printLayout bool + d.MaybeScanArgs(t, "cache-size", &cacheSize) + d.MaybeScanArgs(t, "print-layout", &printLayout) + d.MaybeScanArgs(t, "block-size", &o.BlockSize) + d.MaybeScanArgs(t, "index-block-size", &o.IndexBlockSize) + var err error _, r, err = runBuildCmd(d, &o, cacheSize) if err != nil { diff --git a/sstable/testdata/reader_bpf/iter b/sstable/testdata/reader_bpf/Pebblev2/iter similarity index 94% rename from sstable/testdata/reader_bpf/iter rename to sstable/testdata/reader_bpf/Pebblev2/iter index e527895de9..407eb0d91a 100644 --- a/sstable/testdata/reader_bpf/iter +++ b/sstable/testdata/reader_bpf/Pebblev2/iter @@ -1,7 +1,7 @@ # Test case for bug https://github.com/cockroachdb/pebble/issues/2036 Build # sstable with two-level index, with two data blocks in each lower-level index # block. -build +build block-size=1 index-block-size=40 print-layout=true c@10.SET.10:cAT10 d@7.SET.9:dAT7 e@15.SET.8:eAT15 diff --git a/sstable/testdata/reader_bpf/Pebblev3/iter b/sstable/testdata/reader_bpf/Pebblev3/iter new file mode 100644 index 0000000000..8a37664e27 --- /dev/null +++ b/sstable/testdata/reader_bpf/Pebblev3/iter @@ -0,0 +1,146 @@ +# Test case for bug https://github.com/cockroachdb/pebble/issues/2036 Build +# sstable with two-level index, with two data blocks in each lower-level index +# block. +build block-size=1 index-block-size=40 print-layout=true +c@10.SET.10:cAT10 +d@7.SET.9:dAT7 +e@15.SET.8:eAT15 +f@7.SET.5:fAT7 +---- +index entries: + d@7: size 53 + c@10: size 29 + d@7: size 27 + g: size 51 + e@15: size 29 + g: size 27 + +iter +first +next +next +next +---- + + + + + + +# The block property filter matches data block 2 and 4. +iter block-property-filter=(7,8) +first +next +---- + + + +# Use the same block property filter, but use seeks to find these entries. +# With the bug the second seek-ge below would step to the second lower-level +# index block and only see the entry in the data block 4. +iter block-property-filter=(7,8) +set-bounds lower=a upper=c +seek-ge a +seek-ge b true +set-bounds lower=c upper=g +seek-ge c +next +next +---- +. +. +. +. + + +. + +# Regression test for #2816 +# +# This unit test tests a scenario where the two-level index iterator's position +# could diverge from the currently loaded index block. When taking advantage of +# the monotonic bounds optimization at the two-level index level, the iterator +# would mistakenly seek within the wrong index block. +# +# This allowed the final `seek-ge wc` and `next` to both return wz@8. + +build block-size=1 index-block-size=1 print-layout=true +eu@2.SET.2:eu +wb@2.SET.2:wb +wz@8.SET.8:wzAT8 +ye@1.SET.1:yeAT1 +---- +index entries: + f: size 26 + f: size 26 + wc: size 27 + wc: size 26 + x: size 26 + x: size 29 + z: size 26 + z: size 29 + +iter block-property-filter=(8,9) +set-bounds lower=v upper=v +seek-ge wz@8 +internal-iter-state +seek-ge wb@2 +internal-iter-state +set-bounds lower=v upper=z +internal-iter-state +seek-ge wc +internal-iter-state +next +---- +. +. +| *sstable.twoLevelIterator: +| topLevelIndex.Key() = "x#72057594037927935,17" +| topLevelIndex.InPlaceValue() = (Offset: 193, Length: 26, Props: 00020801) +| topLevelIndex.isDataInvalidated()=false +| index.Key() = "x#72057594037927935,17" +| index.InPlaceValue() = (Offset: 62, Length: 29, Props: 00020801) +| index.isDataInvalidated()=false +| data.isDataInvalidated()=false +| hideObsoletePoints = false +| dataBH = (Offset: 62, Length: 29) +| (boundsCmp,positionedUsingLatestBounds) = (0,true) +| exhaustedBounds = 1 +. +| *sstable.twoLevelIterator: +| topLevelIndex.Key() = "wc#72057594037927935,17" +| topLevelIndex.InPlaceValue() = (Offset: 161, Length: 27, Props: 00020201) +| topLevelIndex.isDataInvalidated()=false +| index iter invalid +| index.isDataInvalidated()=true +| data.isDataInvalidated()=true +| hideObsoletePoints = false +| dataBH = (Offset: 62, Length: 29) +| (boundsCmp,positionedUsingLatestBounds) = (0,true) +| exhaustedBounds = 1 +. +| *sstable.twoLevelIterator: +| topLevelIndex.Key() = "wc#72057594037927935,17" +| topLevelIndex.InPlaceValue() = (Offset: 161, Length: 27, Props: 00020201) +| topLevelIndex.isDataInvalidated()=false +| index iter invalid +| index.isDataInvalidated()=true +| data.isDataInvalidated()=true +| hideObsoletePoints = false +| dataBH = (Offset: 62, Length: 29) +| (boundsCmp,positionedUsingLatestBounds) = (1,false) +| exhaustedBounds = 1 + +| *sstable.twoLevelIterator: +| topLevelIndex.Key() = "x#72057594037927935,17" +| topLevelIndex.InPlaceValue() = (Offset: 193, Length: 26, Props: 00020801) +| topLevelIndex.isDataInvalidated()=false +| index.Key() = "x#72057594037927935,17" +| index.InPlaceValue() = (Offset: 62, Length: 29, Props: 00020801) +| index.isDataInvalidated()=false +| data.isDataInvalidated()=false +| hideObsoletePoints = false +| dataBH = (Offset: 62, Length: 29) +| (boundsCmp,positionedUsingLatestBounds) = (0,false) +| exhaustedBounds = 0 +. diff --git a/sstable/testdata/reader_bpf_v3/iter b/sstable/testdata/reader_bpf_v3/iter deleted file mode 100644 index 14dabbed45..0000000000 --- a/sstable/testdata/reader_bpf_v3/iter +++ /dev/null @@ -1,56 +0,0 @@ -# Test case for bug https://github.com/cockroachdb/pebble/issues/2036 Build -# sstable with two-level index, with two data blocks in each lower-level index -# block. -build -c@10.SET.10:cAT10 -d@7.SET.9:dAT7 -e@15.SET.8:eAT15 -f@7.SET.5:fAT7 ----- -index entries: - d@7: size 53 - c@10: size 29 - d@7: size 27 - g: size 51 - e@15: size 29 - g: size 27 - -iter -first -next -next -next ----- - - - - - - -# The block property filter matches data block 2 and 4. -iter block-property-filter=(7,8) -first -next ----- - - - -# Use the same block property filter, but use seeks to find these entries. -# With the bug the second seek-ge below would step to the second lower-level -# index block and only see the entry in the data block 4. -iter block-property-filter=(7,8) -set-bounds lower=a upper=c -seek-ge a -seek-ge b true -set-bounds lower=c upper=g -seek-ge c -next -next ----- -. -. -. -. - - -. diff --git a/sstable/testdata/readerstats/iter b/sstable/testdata/readerstats_LevelDB/iter similarity index 97% rename from sstable/testdata/readerstats/iter rename to sstable/testdata/readerstats_LevelDB/iter index 2afe3db843..1ba247d13a 100644 --- a/sstable/testdata/readerstats/iter +++ b/sstable/testdata/readerstats_LevelDB/iter @@ -1,5 +1,5 @@ # Two keys in each data block. -build +build block-size=30 index-block-size=30 cache-size=10000 a.SET.1:A b.SET.2:B c.SET.3:C diff --git a/sstable/testdata/readerstats_v3/iter b/sstable/testdata/readerstats_Pebblev3/iter similarity index 99% rename from sstable/testdata/readerstats_v3/iter rename to sstable/testdata/readerstats_Pebblev3/iter index 8531ba8a1e..b20a4b212b 100644 --- a/sstable/testdata/readerstats_v3/iter +++ b/sstable/testdata/readerstats_Pebblev3/iter @@ -1,4 +1,4 @@ -build +build print-layout=true c@10.SET.10:cAT10 c@9.SET.9:cAT9 c@8.SET.8:cAT8