Skip to content

Commit fe43e7b

Browse files
committed
sstable: lazy load the index block in two level iterator
This commit implements the index block lazy loading in two-level iterator as default behavior. The index block loading is now deferred until first access. Implements #3248
1 parent f4af03a commit fe43e7b

File tree

2 files changed

+95
-18
lines changed

2 files changed

+95
-18
lines changed

sstable/reader_iter_test.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,18 @@ func TestIteratorErrorOnInit(t *testing.T) {
6868
require.Error(t, iter.Error())
6969
iter.Close()
7070
} else {
71-
_, err := newRowBlockTwoLevelIterator(context.Background(), r, IterOptions{
71+
iter, err := newRowBlockTwoLevelIterator(context.Background(), r, IterOptions{
7272
Transforms: NoTransforms,
7373
FilterBlockSizeLimit: NeverUseFilterBlock,
7474
Env: ReadEnv{Block: block.ReadEnv{Stats: &stats, BufferPool: &pool}},
7575
ReaderProvider: MakeTrivialReaderProvider(r),
7676
})
77-
require.Error(t, err)
77+
// Two-level iterators use lazy loading - creation succeeds but first access fails
78+
require.NoError(t, err)
79+
// Error should surface when trying to use the iterator
80+
_ = iter.First()
81+
require.Error(t, iter.Error())
82+
iter.Close()
7883
}
7984
}
8085
}

sstable/reader_iter_two_lvl.go

Lines changed: 88 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ type twoLevelIterator[I any, PI indexBlockIterator[I], D any, PD dataBlockIterat
3232
// false - any filtering happens at the top level.
3333
useFilterBlock bool
3434
lastBloomFilterMatched bool
35+
36+
// Lazy loading flag for top-level index block
37+
topLevelIndexLoaded bool
3538
}
3639

3740
var _ Iterator = (*twoLevelIteratorRowBlocks)(nil)
@@ -45,6 +48,14 @@ func (i *twoLevelIterator[I, PI, D, PD]) loadSecondLevelIndexBlock(dir int8) loa
4548
// the index fails.
4649
PD(&i.secondLevel.data).Invalidate()
4750
PI(&i.secondLevel.index).Invalidate()
51+
52+
if !i.topLevelIndexLoaded {
53+
if err := i.ensureTopLevelIndexLoaded(); err != nil {
54+
i.secondLevel.err = err
55+
return loadBlockFailed
56+
}
57+
}
58+
4859
if !PI(&i.topLevelIndex).Valid() {
4960
return loadBlockFailed
5061
}
@@ -182,14 +193,9 @@ func newColumnBlockTwoLevelIterator(
182193
objstorage.NoReadBefore, &i.secondLevel.vbRHPrealloc)
183194
}
184195
i.secondLevel.data.InitOnce(r.keySchema, r.Comparer, &i.secondLevel.internalValueConstructor)
185-
topLevelIndexH, err := r.readTopLevelIndexBlock(ctx, i.secondLevel.readEnv.Block, i.secondLevel.indexFilterRH)
186-
if err == nil {
187-
err = i.topLevelIndex.InitHandle(r.Comparer, topLevelIndexH, opts.Transforms)
188-
}
189-
if err != nil {
190-
_ = i.Close()
191-
return nil, err
192-
}
196+
197+
// Use lazy loading by default - top-level index will be loaded on first access
198+
i.topLevelIndexLoaded = false
193199
return i, nil
194200
}
195201

@@ -236,14 +242,8 @@ func newRowBlockTwoLevelIterator(
236242
i.secondLevel.data.SetHasValuePrefix(true)
237243
}
238244

239-
topLevelIndexH, err := r.readTopLevelIndexBlock(ctx, i.secondLevel.readEnv.Block, i.secondLevel.indexFilterRH)
240-
if err == nil {
241-
err = i.topLevelIndex.InitHandle(r.Comparer, topLevelIndexH, opts.Transforms)
242-
}
243-
if err != nil {
244-
_ = i.Close()
245-
return nil, err
246-
}
245+
// Use lazy loading by default - top-level index will be loaded on first access
246+
i.topLevelIndexLoaded = false
247247
return i, nil
248248
}
249249

@@ -286,6 +286,13 @@ func (i *twoLevelIterator[I, PI, D, PD]) SeekGE(
286286
return nil
287287
}
288288

289+
if !i.topLevelIndexLoaded {
290+
if err := i.ensureTopLevelIndexLoaded(); err != nil {
291+
i.secondLevel.err = err
292+
return nil
293+
}
294+
}
295+
289296
// SeekGE performs various step-instead-of-seeking optimizations: eg enabled
290297
// by trySeekUsingNext, or by monotonically increasing bounds (i.boundsCmp).
291298

@@ -464,6 +471,13 @@ func (i *twoLevelIterator[I, PI, D, PD]) SeekPrefixGE(
464471
// than the index block containing the sought key, resulting in a wasteful
465472
// block load.
466473

474+
if !i.topLevelIndexLoaded {
475+
if err := i.ensureTopLevelIndexLoaded(); err != nil {
476+
i.secondLevel.err = err
477+
return nil
478+
}
479+
}
480+
467481
var dontSeekWithinSingleLevelIter bool
468482
if PI(&i.topLevelIndex).IsDataInvalidated() || !PI(&i.topLevelIndex).Valid() || PI(&i.secondLevel.index).IsDataInvalidated() || err != nil ||
469483
(i.secondLevel.boundsCmp <= 0 && !flags.TrySeekUsingNext()) || PI(&i.topLevelIndex).SeparatorLT(key) {
@@ -623,6 +637,13 @@ func (i *twoLevelIterator[I, PI, D, PD]) virtualLastSeekLE() *base.InternalKV {
623637
func (i *twoLevelIterator[I, PI, D, PD]) SeekLT(
624638
key []byte, flags base.SeekLTFlags,
625639
) *base.InternalKV {
640+
if !i.topLevelIndexLoaded {
641+
if err := i.ensureTopLevelIndexLoaded(); err != nil {
642+
i.secondLevel.err = err
643+
return nil
644+
}
645+
}
646+
626647
if i.secondLevel.readEnv.Virtual != nil {
627648
// Might have to fix upper bound since virtual sstable bounds are not
628649
// known to callers of SeekLT.
@@ -704,6 +725,12 @@ func (i *twoLevelIterator[I, PI, D, PD]) SeekLT(
704725
// to ensure that key is greater than or equal to the lower bound (e.g. via a
705726
// call to SeekGE(lower)).
706727
func (i *twoLevelIterator[I, PI, D, PD]) First() *base.InternalKV {
728+
if !i.topLevelIndexLoaded {
729+
if err := i.ensureTopLevelIndexLoaded(); err != nil {
730+
i.secondLevel.err = err
731+
return nil
732+
}
733+
}
707734
// If we have a lower bound, use SeekGE. Note that in general this is not
708735
// supported usage, except when the lower bound is there because the table is
709736
// virtual.
@@ -749,6 +776,13 @@ func (i *twoLevelIterator[I, PI, D, PD]) First() *base.InternalKV {
749776
// to ensure that key is less than the upper bound (e.g. via a call to
750777
// SeekLT(upper))
751778
func (i *twoLevelIterator[I, PI, D, PD]) Last() *base.InternalKV {
779+
if !i.topLevelIndexLoaded {
780+
if err := i.ensureTopLevelIndexLoaded(); err != nil {
781+
i.secondLevel.err = err
782+
return nil
783+
}
784+
}
785+
752786
if i.secondLevel.readEnv.Virtual != nil {
753787
if i.secondLevel.endKeyInclusive {
754788
return i.virtualLast()
@@ -796,6 +830,12 @@ func (i *twoLevelIterator[I, PI, D, PD]) Last() *base.InternalKV {
796830
// Note: twoLevelCompactionIterator.Next mirrors the implementation of
797831
// twoLevelIterator.Next due to performance. Keep the two in sync.
798832
func (i *twoLevelIterator[I, PI, D, PD]) Next() *base.InternalKV {
833+
if !i.topLevelIndexLoaded {
834+
if err := i.ensureTopLevelIndexLoaded(); err != nil {
835+
i.secondLevel.err = err
836+
return nil
837+
}
838+
}
799839
// Seek optimization only applies until iterator is first positioned after SetBounds.
800840
i.secondLevel.boundsCmp = 0
801841
if i.secondLevel.err != nil {
@@ -814,6 +854,13 @@ func (i *twoLevelIterator[I, PI, D, PD]) NextPrefix(succKey []byte) *base.Intern
814854
if i.secondLevel.exhaustedBounds == +1 {
815855
panic("Next called even though exhausted upper bound")
816856
}
857+
858+
if !i.topLevelIndexLoaded {
859+
if err := i.ensureTopLevelIndexLoaded(); err != nil {
860+
i.secondLevel.err = err
861+
return nil
862+
}
863+
}
817864
// Seek optimization only applies until iterator is first positioned after SetBounds.
818865
i.secondLevel.boundsCmp = 0
819866
if i.secondLevel.err != nil {
@@ -861,6 +908,12 @@ func (i *twoLevelIterator[I, PI, D, PD]) NextPrefix(succKey []byte) *base.Intern
861908
// Prev implements internalIterator.Prev, as documented in the pebble
862909
// package.
863910
func (i *twoLevelIterator[I, PI, D, PD]) Prev() *base.InternalKV {
911+
if !i.topLevelIndexLoaded {
912+
if err := i.ensureTopLevelIndexLoaded(); err != nil {
913+
i.secondLevel.err = err
914+
return nil
915+
}
916+
}
864917
// Seek optimization only applies until iterator is first positioned after SetBounds.
865918
i.secondLevel.boundsCmp = 0
866919
if i.secondLevel.err != nil {
@@ -1020,8 +1073,27 @@ func (i *twoLevelIterator[I, PI, D, PD]) Close() error {
10201073
err = firstError(err, PI(&i.topLevelIndex).Close())
10211074
i.useFilterBlock = false
10221075
i.lastBloomFilterMatched = false
1076+
i.topLevelIndexLoaded = false
10231077
if pool != nil {
10241078
pool.Put(i)
10251079
}
10261080
return err
10271081
}
1082+
1083+
func (i *twoLevelIterator[I, PI, D, PD]) ensureTopLevelIndexLoaded() error {
1084+
if i.topLevelIndexLoaded {
1085+
return nil
1086+
}
1087+
1088+
topLevelIndexH, err := i.secondLevel.reader.readTopLevelIndexBlock(i.secondLevel.ctx,
1089+
i.secondLevel.readEnv.Block, i.secondLevel.indexFilterRH)
1090+
if err == nil {
1091+
err = PI(&i.topLevelIndex).InitHandle(i.secondLevel.reader.Comparer,
1092+
topLevelIndexH, i.secondLevel.transforms)
1093+
}
1094+
if err != nil {
1095+
return err
1096+
}
1097+
i.topLevelIndexLoaded = true
1098+
return nil
1099+
}

0 commit comments

Comments
 (0)