github.com/cockroachdb/pebble@v1.1.1-0.20240513155919-3622ade60459/level_iter_test.go (about) 1 // Copyright 2018 The LevelDB-Go and Pebble Authors. All rights reserved. Use 2 // of this source code is governed by a BSD-style license that can be found in 3 // the LICENSE file. 4 5 package pebble 6 7 import ( 8 "bytes" 9 "context" 10 "fmt" 11 "strings" 12 "testing" 13 "time" 14 15 "github.com/cockroachdb/datadriven" 16 "github.com/cockroachdb/pebble/bloom" 17 "github.com/cockroachdb/pebble/internal/base" 18 "github.com/cockroachdb/pebble/internal/keyspan" 19 "github.com/cockroachdb/pebble/internal/manifest" 20 "github.com/cockroachdb/pebble/internal/rangedel" 21 "github.com/cockroachdb/pebble/internal/testkeys" 22 "github.com/cockroachdb/pebble/objstorage/objstorageprovider" 23 "github.com/cockroachdb/pebble/sstable" 24 "github.com/cockroachdb/pebble/vfs" 25 "github.com/stretchr/testify/require" 26 "golang.org/x/exp/rand" 27 ) 28 29 const ( 30 level = 1 31 ) 32 33 func TestLevelIter(t *testing.T) { 34 var iters []*fakeIter 35 var files manifest.LevelSlice 36 37 newIters := func( 38 _ context.Context, file *manifest.FileMetadata, opts *IterOptions, _ internalIterOpts, 39 ) (internalIterator, keyspan.FragmentIterator, error) { 40 f := *iters[file.FileNum] 41 f.lower = opts.GetLowerBound() 42 f.upper = opts.GetUpperBound() 43 return &f, nil, nil 44 } 45 46 datadriven.RunTest(t, "testdata/level_iter", func(t *testing.T, d *datadriven.TestData) string { 47 switch d.Cmd { 48 case "define": 49 iters = nil 50 var metas []*fileMetadata 51 for _, line := range strings.Split(d.Input, "\n") { 52 f := &fakeIter{} 53 for _, key := range strings.Fields(line) { 54 j := strings.Index(key, ":") 55 f.keys = append(f.keys, base.ParseInternalKey(key[:j])) 56 f.vals = append(f.vals, []byte(key[j+1:])) 57 } 58 iters = append(iters, f) 59 60 meta := (&fileMetadata{ 61 FileNum: FileNum(len(metas)), 62 }).ExtendPointKeyBounds( 63 DefaultComparer.Compare, 64 f.keys[0], 65 f.keys[len(f.keys)-1], 66 ) 67 meta.InitPhysicalBacking() 68 metas = append(metas, meta) 69 } 70 files = manifest.NewLevelSliceKeySorted(base.DefaultComparer.Compare, metas) 71 72 return "" 73 74 case "iter": 75 var opts IterOptions 76 for _, arg := range d.CmdArgs { 77 if len(arg.Vals) != 1 { 78 return fmt.Sprintf("%s: %s=<value>", d.Cmd, arg.Key) 79 } 80 switch arg.Key { 81 case "lower": 82 opts.LowerBound = []byte(arg.Vals[0]) 83 case "upper": 84 opts.UpperBound = []byte(arg.Vals[0]) 85 default: 86 return fmt.Sprintf("%s: unknown arg: %s", d.Cmd, arg.Key) 87 } 88 } 89 90 iter := newLevelIter(opts, testkeys.Comparer, newIters, files.Iter(), manifest.Level(level), 91 internalIterOpts{}) 92 defer iter.Close() 93 // Fake up the range deletion initialization. 94 iter.initRangeDel(new(keyspan.FragmentIterator)) 95 iter.disableInvariants = true 96 return runInternalIterCmd(t, d, iter, iterCmdVerboseKey) 97 98 case "load": 99 // The "load" command allows testing the iterator options passed to load 100 // sstables. 101 // 102 // load <key> [lower=<key>] [upper=<key>] 103 var opts IterOptions 104 var key string 105 for _, arg := range d.CmdArgs { 106 if len(arg.Vals) == 0 { 107 key = arg.Key 108 continue 109 } 110 if len(arg.Vals) != 1 { 111 return fmt.Sprintf("%s: %s=<value>", d.Cmd, arg.Key) 112 } 113 switch arg.Key { 114 case "lower": 115 opts.LowerBound = []byte(arg.Vals[0]) 116 case "upper": 117 opts.UpperBound = []byte(arg.Vals[0]) 118 default: 119 return fmt.Sprintf("%s: unknown arg: %s", d.Cmd, arg.Key) 120 } 121 } 122 123 var tableOpts *IterOptions 124 newIters2 := func( 125 ctx context.Context, file *manifest.FileMetadata, opts *IterOptions, 126 internalOpts internalIterOpts, 127 ) (internalIterator, keyspan.FragmentIterator, error) { 128 tableOpts = opts 129 return newIters(ctx, file, opts, internalOpts) 130 } 131 132 iter := newLevelIter(opts, testkeys.Comparer, newIters2, files.Iter(), 133 manifest.Level(level), internalIterOpts{}) 134 iter.SeekGE([]byte(key), base.SeekGEFlagsNone) 135 lower, upper := tableOpts.GetLowerBound(), tableOpts.GetUpperBound() 136 return fmt.Sprintf("[%s,%s]\n", lower, upper) 137 138 default: 139 return fmt.Sprintf("unknown command: %s", d.Cmd) 140 } 141 }) 142 } 143 144 type levelIterTest struct { 145 cmp base.Comparer 146 mem vfs.FS 147 readers []*sstable.Reader 148 metas []*fileMetadata 149 itersCreated int 150 } 151 152 func newLevelIterTest() *levelIterTest { 153 lt := &levelIterTest{ 154 cmp: *DefaultComparer, 155 mem: vfs.NewMem(), 156 } 157 lt.cmp.Split = func(a []byte) int { return len(a) } 158 return lt 159 } 160 161 func (lt *levelIterTest) newIters( 162 ctx context.Context, file *manifest.FileMetadata, opts *IterOptions, iio internalIterOpts, 163 ) (internalIterator, keyspan.FragmentIterator, error) { 164 lt.itersCreated++ 165 iter, err := lt.readers[file.FileNum].NewIterWithBlockPropertyFiltersAndContextEtc( 166 ctx, opts.LowerBound, opts.UpperBound, nil, false, true, iio.stats, 167 sstable.TrivialReaderProvider{Reader: lt.readers[file.FileNum]}) 168 if err != nil { 169 return nil, nil, err 170 } 171 rangeDelIter, err := lt.readers[file.FileNum].NewRawRangeDelIter() 172 if err != nil { 173 return nil, nil, err 174 } 175 return iter, rangeDelIter, nil 176 } 177 178 func (lt *levelIterTest) runClear(d *datadriven.TestData) string { 179 lt.mem = vfs.NewMem() 180 for _, r := range lt.readers { 181 r.Close() 182 } 183 lt.readers = nil 184 lt.metas = nil 185 lt.itersCreated = 0 186 return "" 187 } 188 189 func (lt *levelIterTest) runBuild(d *datadriven.TestData) string { 190 fileNum := FileNum(len(lt.readers)) 191 name := fmt.Sprint(fileNum) 192 f0, err := lt.mem.Create(name) 193 if err != nil { 194 return err.Error() 195 } 196 197 tableFormat := sstable.TableFormatRocksDBv2 198 for _, arg := range d.CmdArgs { 199 if arg.Key == "format" { 200 switch arg.Vals[0] { 201 case "rocksdbv2": 202 tableFormat = sstable.TableFormatRocksDBv2 203 case "pebblev2": 204 tableFormat = sstable.TableFormatPebblev2 205 } 206 } 207 } 208 fp := bloom.FilterPolicy(10) 209 w := sstable.NewWriter(objstorageprovider.NewFileWritable(f0), sstable.WriterOptions{ 210 Comparer: <.cmp, 211 FilterPolicy: fp, 212 TableFormat: tableFormat, 213 }) 214 var tombstones []keyspan.Span 215 f := keyspan.Fragmenter{ 216 Cmp: lt.cmp.Compare, 217 Format: lt.cmp.FormatKey, 218 Emit: func(fragmented keyspan.Span) { 219 tombstones = append(tombstones, fragmented) 220 }, 221 } 222 for _, key := range strings.Split(d.Input, "\n") { 223 j := strings.Index(key, ":") 224 ikey := base.ParseInternalKey(key[:j]) 225 value := []byte(key[j+1:]) 226 switch ikey.Kind() { 227 case InternalKeyKindRangeDelete: 228 f.Add(rangedel.Decode(ikey, value, nil)) 229 case InternalKeyKindRangeKeySet, InternalKeyKindRangeKeyUnset, InternalKeyKindRangeKeyDelete: 230 if err := w.AddRangeKey(ikey, value); err != nil { 231 return err.Error() 232 } 233 default: 234 if err := w.Add(ikey, value); err != nil { 235 return err.Error() 236 } 237 } 238 } 239 f.Finish() 240 for _, v := range tombstones { 241 if err := rangedel.Encode(&v, w.Add); err != nil { 242 return err.Error() 243 } 244 } 245 if err := w.Close(); err != nil { 246 return err.Error() 247 } 248 meta, err := w.Metadata() 249 if err != nil { 250 return err.Error() 251 } 252 253 f1, err := lt.mem.Open(name) 254 if err != nil { 255 return err.Error() 256 } 257 readable, err := sstable.NewSimpleReadable(f1) 258 if err != nil { 259 return err.Error() 260 } 261 r, err := sstable.NewReader(readable, sstable.ReaderOptions{ 262 Filters: map[string]FilterPolicy{ 263 fp.Name(): fp, 264 }, 265 }) 266 if err != nil { 267 return err.Error() 268 } 269 lt.readers = append(lt.readers, r) 270 m := &fileMetadata{FileNum: fileNum} 271 if meta.HasPointKeys { 272 m.ExtendPointKeyBounds(lt.cmp.Compare, meta.SmallestPoint, meta.LargestPoint) 273 } 274 if meta.HasRangeDelKeys { 275 m.ExtendPointKeyBounds(lt.cmp.Compare, meta.SmallestRangeDel, meta.LargestRangeDel) 276 } 277 if meta.HasRangeKeys { 278 m.ExtendRangeKeyBounds(lt.cmp.Compare, meta.SmallestRangeKey, meta.LargestRangeKey) 279 } 280 m.InitPhysicalBacking() 281 lt.metas = append(lt.metas, m) 282 283 var buf bytes.Buffer 284 for _, f := range lt.metas { 285 fmt.Fprintf(&buf, "%d: %s-%s\n", f.FileNum, f.Smallest, f.Largest) 286 } 287 return buf.String() 288 } 289 290 func TestLevelIterBoundaries(t *testing.T) { 291 lt := newLevelIterTest() 292 defer lt.runClear(nil) 293 294 var iter *levelIter 295 datadriven.RunTest(t, "testdata/level_iter_boundaries", func(t *testing.T, d *datadriven.TestData) string { 296 switch d.Cmd { 297 case "clear": 298 return lt.runClear(d) 299 300 case "build": 301 return lt.runBuild(d) 302 303 case "iter": 304 // The save and continue parameters allow us to save the iterator 305 // for later continued use. 306 save := false 307 cont := false 308 for _, arg := range d.CmdArgs { 309 switch arg.Key { 310 case "save": 311 save = true 312 case "continue": 313 cont = true 314 default: 315 return fmt.Sprintf("%s: unknown arg: %s", d.Cmd, arg.Key) 316 } 317 } 318 if !cont && iter != nil { 319 return "preceding iter was not closed" 320 } 321 if cont && iter == nil { 322 return "no existing iter" 323 } 324 if iter == nil { 325 slice := manifest.NewLevelSliceKeySorted(lt.cmp.Compare, lt.metas) 326 iter = newLevelIter(IterOptions{}, testkeys.Comparer, lt.newIters, slice.Iter(), 327 manifest.Level(level), internalIterOpts{}) 328 // Fake up the range deletion initialization. 329 iter.initRangeDel(new(keyspan.FragmentIterator)) 330 } 331 if !save { 332 defer func() { 333 iter.Close() 334 iter = nil 335 }() 336 } 337 return runInternalIterCmd(t, d, iter, iterCmdVerboseKey) 338 339 case "file-pos": 340 // Returns the FileNum at which the iterator is positioned. 341 if iter == nil { 342 return "nil levelIter" 343 } 344 if iter.iterFile == nil { 345 return "nil iterFile" 346 } 347 return fmt.Sprintf("file %d", iter.iterFile.FileNum) 348 349 default: 350 return fmt.Sprintf("unknown command: %s", d.Cmd) 351 } 352 }) 353 } 354 355 // levelIterTestIter allows a datadriven test to use runInternalIterCmd and 356 // perform parallel operations on both both a levelIter and rangeDelIter. 357 type levelIterTestIter struct { 358 *levelIter 359 rangeDelIter keyspan.FragmentIterator 360 } 361 362 func (i *levelIterTestIter) rangeDelSeek( 363 key []byte, ikey *InternalKey, val base.LazyValue, dir int, 364 ) (*InternalKey, base.LazyValue) { 365 var tombstone keyspan.Span 366 if i.rangeDelIter != nil { 367 var t *keyspan.Span 368 if dir < 0 { 369 t = keyspan.SeekLE(i.levelIter.cmp, i.rangeDelIter, key) 370 } else { 371 t = i.rangeDelIter.SeekGE(key) 372 } 373 if t != nil { 374 tombstone = t.Visible(1000) 375 } 376 } 377 if ikey == nil { 378 return &InternalKey{ 379 UserKey: []byte(fmt.Sprintf("./%s", tombstone)), 380 }, base.LazyValue{} 381 } 382 return &InternalKey{ 383 UserKey: []byte(fmt.Sprintf("%s/%s", ikey.UserKey, tombstone)), 384 Trailer: ikey.Trailer, 385 }, val 386 } 387 388 func (i *levelIterTestIter) String() string { 389 return "level-iter-test" 390 } 391 392 func (i *levelIterTestIter) SeekGE( 393 key []byte, flags base.SeekGEFlags, 394 ) (*InternalKey, base.LazyValue) { 395 ikey, val := i.levelIter.SeekGE(key, flags) 396 return i.rangeDelSeek(key, ikey, val, 1) 397 } 398 399 func (i *levelIterTestIter) SeekPrefixGE( 400 prefix, key []byte, flags base.SeekGEFlags, 401 ) (*base.InternalKey, base.LazyValue) { 402 ikey, val := i.levelIter.SeekPrefixGE(prefix, key, flags) 403 return i.rangeDelSeek(key, ikey, val, 1) 404 } 405 406 func (i *levelIterTestIter) SeekLT( 407 key []byte, flags base.SeekLTFlags, 408 ) (*InternalKey, base.LazyValue) { 409 ikey, val := i.levelIter.SeekLT(key, flags) 410 return i.rangeDelSeek(key, ikey, val, -1) 411 } 412 413 func TestLevelIterSeek(t *testing.T) { 414 lt := newLevelIterTest() 415 defer lt.runClear(nil) 416 417 datadriven.RunTest(t, "testdata/level_iter_seek", func(t *testing.T, d *datadriven.TestData) string { 418 switch d.Cmd { 419 case "clear": 420 return lt.runClear(d) 421 422 case "build": 423 return lt.runBuild(d) 424 425 case "iter": 426 var stats base.InternalIteratorStats 427 slice := manifest.NewLevelSliceKeySorted(lt.cmp.Compare, lt.metas) 428 iter := &levelIterTestIter{levelIter: &levelIter{}} 429 iter.init(context.Background(), IterOptions{}, testkeys.Comparer, lt.newIters, slice.Iter(), 430 manifest.Level(level), internalIterOpts{stats: &stats}) 431 defer iter.Close() 432 iter.initRangeDel(&iter.rangeDelIter) 433 return runInternalIterCmd(t, d, iter, iterCmdVerboseKey, iterCmdStats(&stats)) 434 435 case "iters-created": 436 return fmt.Sprintf("%d", lt.itersCreated) 437 default: 438 return fmt.Sprintf("unknown command: %s", d.Cmd) 439 } 440 }) 441 } 442 443 func buildLevelIterTables( 444 b *testing.B, blockSize, restartInterval, count int, 445 ) ([]*sstable.Reader, manifest.LevelSlice, [][]byte, func()) { 446 mem := vfs.NewMem() 447 files := make([]vfs.File, count) 448 for i := range files { 449 f, err := mem.Create(fmt.Sprintf("bench%d", i)) 450 if err != nil { 451 b.Fatal(err) 452 } 453 files[i] = f 454 } 455 456 writers := make([]*sstable.Writer, len(files)) 457 for i := range files { 458 writers[i] = sstable.NewWriter(objstorageprovider.NewFileWritable(files[i]), sstable.WriterOptions{ 459 BlockRestartInterval: restartInterval, 460 BlockSize: blockSize, 461 Compression: NoCompression, 462 }) 463 } 464 465 var keys [][]byte 466 var i int 467 const targetSize = 2 << 20 468 for _, w := range writers { 469 for ; w.EstimatedSize() < targetSize; i++ { 470 key := []byte(fmt.Sprintf("%08d", i)) 471 keys = append(keys, key) 472 ikey := base.MakeInternalKey(key, 0, InternalKeyKindSet) 473 w.Add(ikey, nil) 474 } 475 if err := w.Close(); err != nil { 476 b.Fatal(err) 477 } 478 } 479 480 opts := sstable.ReaderOptions{Cache: NewCache(128 << 20), Comparer: DefaultComparer} 481 defer opts.Cache.Unref() 482 readers := make([]*sstable.Reader, len(files)) 483 for i := range files { 484 f, err := mem.Open(fmt.Sprintf("bench%d", i)) 485 if err != nil { 486 b.Fatal(err) 487 } 488 readable, err := sstable.NewSimpleReadable(f) 489 if err != nil { 490 b.Fatal(err) 491 } 492 readers[i], err = sstable.NewReader(readable, opts) 493 if err != nil { 494 b.Fatal(err) 495 } 496 } 497 498 cleanup := func() { 499 for _, r := range readers { 500 require.NoError(b, r.Close()) 501 } 502 } 503 504 meta := make([]*fileMetadata, len(readers)) 505 for i := range readers { 506 iter, err := readers[i].NewIter(nil /* lower */, nil /* upper */) 507 require.NoError(b, err) 508 smallest, _ := iter.First() 509 meta[i] = &fileMetadata{} 510 meta[i].FileNum = FileNum(i) 511 largest, _ := iter.Last() 512 meta[i].ExtendPointKeyBounds(opts.Comparer.Compare, (*smallest).Clone(), (*largest).Clone()) 513 meta[i].InitPhysicalBacking() 514 } 515 slice := manifest.NewLevelSliceKeySorted(base.DefaultComparer.Compare, meta) 516 return readers, slice, keys, cleanup 517 } 518 519 func BenchmarkLevelIterSeekGE(b *testing.B) { 520 const blockSize = 32 << 10 521 522 for _, restartInterval := range []int{16} { 523 b.Run(fmt.Sprintf("restart=%d", restartInterval), 524 func(b *testing.B) { 525 for _, count := range []int{5} { 526 b.Run(fmt.Sprintf("count=%d", count), 527 func(b *testing.B) { 528 readers, metas, keys, cleanup := buildLevelIterTables(b, blockSize, restartInterval, count) 529 defer cleanup() 530 newIters := func( 531 _ context.Context, file *manifest.FileMetadata, _ *IterOptions, _ internalIterOpts, 532 ) (internalIterator, keyspan.FragmentIterator, error) { 533 iter, err := readers[file.FileNum].NewIter(nil /* lower */, nil /* upper */) 534 return iter, nil, err 535 } 536 l := newLevelIter(IterOptions{}, DefaultComparer, newIters, metas.Iter(), manifest.Level(level), internalIterOpts{}) 537 rng := rand.New(rand.NewSource(uint64(time.Now().UnixNano()))) 538 539 b.ResetTimer() 540 for i := 0; i < b.N; i++ { 541 l.SeekGE(keys[rng.Intn(len(keys))], base.SeekGEFlagsNone) 542 } 543 l.Close() 544 }) 545 } 546 }) 547 } 548 } 549 550 // A benchmark that simulates the behavior of a levelIter being used as part 551 // of a mergingIter where narrow bounds are repeatedly set and used to Seek 552 // and then iterate over the keys within the bounds. This resembles MVCC 553 // scanning by CockroachDB when doing a lookup/index join with a large number 554 // of left rows, that are batched and reuse the same iterator, and which can 555 // have good locality of access. This results in the successive bounds being 556 // in the same file. 557 func BenchmarkLevelIterSeqSeekGEWithBounds(b *testing.B) { 558 const blockSize = 32 << 10 559 560 for _, restartInterval := range []int{16} { 561 b.Run(fmt.Sprintf("restart=%d", restartInterval), 562 func(b *testing.B) { 563 for _, count := range []int{5} { 564 b.Run(fmt.Sprintf("count=%d", count), 565 func(b *testing.B) { 566 readers, metas, keys, cleanup := 567 buildLevelIterTables(b, blockSize, restartInterval, count) 568 defer cleanup() 569 // This newIters is cheaper than in practice since it does not do 570 // tableCacheShard.findNode. 571 newIters := func( 572 _ context.Context, file *manifest.FileMetadata, opts *IterOptions, _ internalIterOpts, 573 ) (internalIterator, keyspan.FragmentIterator, error) { 574 iter, err := readers[file.FileNum].NewIter( 575 opts.LowerBound, opts.UpperBound) 576 return iter, nil, err 577 } 578 l := newLevelIter(IterOptions{}, DefaultComparer, newIters, metas.Iter(), manifest.Level(level), internalIterOpts{}) 579 // Fake up the range deletion initialization, to resemble the usage 580 // in a mergingIter. 581 l.initRangeDel(new(keyspan.FragmentIterator)) 582 keyCount := len(keys) 583 b.ResetTimer() 584 for i := 0; i < b.N; i++ { 585 pos := i % (keyCount - 1) 586 l.SetBounds(keys[pos], keys[pos+1]) 587 // SeekGE will return keys[pos]. 588 k, _ := l.SeekGE(keys[pos], base.SeekGEFlagsNone) 589 // Next() will get called once and return nil. 590 for k != nil { 591 k, _ = l.Next() 592 } 593 } 594 l.Close() 595 }) 596 } 597 }) 598 } 599 } 600 601 // BenchmarkLevelIterSeqSeekPrefixGE simulates the behavior of a levelIter 602 // being used as part of a mergingIter where SeekPrefixGE is used to seek in a 603 // monotonically increasing manner. This resembles key-value lookups done by 604 // CockroachDB when evaluating Put operations. 605 func BenchmarkLevelIterSeqSeekPrefixGE(b *testing.B) { 606 const blockSize = 32 << 10 607 const restartInterval = 16 608 readers, metas, keys, cleanup := 609 buildLevelIterTables(b, blockSize, restartInterval, 5) 610 defer cleanup() 611 // This newIters is cheaper than in practice since it does not do 612 // tableCacheShard.findNode. 613 newIters := func( 614 _ context.Context, file *manifest.FileMetadata, opts *IterOptions, _ internalIterOpts, 615 ) (internalIterator, keyspan.FragmentIterator, error) { 616 iter, err := readers[file.FileNum].NewIter( 617 opts.LowerBound, opts.UpperBound) 618 return iter, nil, err 619 } 620 621 for _, skip := range []int{1, 2, 4, 8, 16} { 622 for _, useNext := range []bool{false, true} { 623 b.Run(fmt.Sprintf("skip=%d/use-next=%t", skip, useNext), 624 func(b *testing.B) { 625 l := newLevelIter(IterOptions{}, testkeys.Comparer, newIters, metas.Iter(), 626 manifest.Level(level), internalIterOpts{}) 627 // Fake up the range deletion initialization, to resemble the usage 628 // in a mergingIter. 629 l.initRangeDel(new(keyspan.FragmentIterator)) 630 keyCount := len(keys) 631 pos := 0 632 l.SeekPrefixGE(keys[pos], keys[pos], base.SeekGEFlagsNone) 633 b.ResetTimer() 634 for i := 0; i < b.N; i++ { 635 pos += skip 636 var flags base.SeekGEFlags 637 if useNext { 638 flags = flags.EnableTrySeekUsingNext() 639 } 640 if pos >= keyCount { 641 pos = 0 642 flags = flags.DisableTrySeekUsingNext() 643 } 644 // SeekPrefixGE will return keys[pos]. 645 l.SeekPrefixGE(keys[pos], keys[pos], flags) 646 } 647 b.StopTimer() 648 l.Close() 649 }) 650 } 651 } 652 } 653 654 func BenchmarkLevelIterNext(b *testing.B) { 655 const blockSize = 32 << 10 656 657 for _, restartInterval := range []int{16} { 658 b.Run(fmt.Sprintf("restart=%d", restartInterval), 659 func(b *testing.B) { 660 for _, count := range []int{5} { 661 b.Run(fmt.Sprintf("count=%d", count), 662 func(b *testing.B) { 663 readers, metas, _, cleanup := buildLevelIterTables(b, blockSize, restartInterval, count) 664 defer cleanup() 665 newIters := func( 666 _ context.Context, file *manifest.FileMetadata, _ *IterOptions, _ internalIterOpts, 667 ) (internalIterator, keyspan.FragmentIterator, error) { 668 iter, err := readers[file.FileNum].NewIter(nil /* lower */, nil /* upper */) 669 return iter, nil, err 670 } 671 l := newLevelIter(IterOptions{}, testkeys.Comparer, newIters, metas.Iter(), manifest.Level(level), internalIterOpts{}) 672 673 b.ResetTimer() 674 for i := 0; i < b.N; i++ { 675 key, _ := l.Next() 676 if key == nil { 677 key, _ = l.First() 678 } 679 _ = key 680 } 681 l.Close() 682 }) 683 } 684 }) 685 } 686 } 687 688 func BenchmarkLevelIterPrev(b *testing.B) { 689 const blockSize = 32 << 10 690 691 for _, restartInterval := range []int{16} { 692 b.Run(fmt.Sprintf("restart=%d", restartInterval), 693 func(b *testing.B) { 694 for _, count := range []int{5} { 695 b.Run(fmt.Sprintf("count=%d", count), 696 func(b *testing.B) { 697 readers, metas, _, cleanup := buildLevelIterTables(b, blockSize, restartInterval, count) 698 defer cleanup() 699 newIters := func( 700 _ context.Context, file *manifest.FileMetadata, _ *IterOptions, _ internalIterOpts, 701 ) (internalIterator, keyspan.FragmentIterator, error) { 702 iter, err := readers[file.FileNum].NewIter(nil /* lower */, nil /* upper */) 703 return iter, nil, err 704 } 705 l := newLevelIter(IterOptions{}, DefaultComparer, newIters, metas.Iter(), manifest.Level(level), internalIterOpts{}) 706 707 b.ResetTimer() 708 for i := 0; i < b.N; i++ { 709 key, _ := l.Prev() 710 if key == nil { 711 key, _ = l.Last() 712 } 713 _ = key 714 } 715 l.Close() 716 }) 717 } 718 }) 719 } 720 }