@@ -32,6 +32,10 @@ type twoLevelIterator[I any, PI indexBlockIterator[I], D any, PD dataBlockIterat
32
32
// false - any filtering happens at the top level.
33
33
useFilterBlock bool
34
34
lastBloomFilterMatched bool
35
+
36
+ // topLevelIndexLoaded is set to true if the top-level index block load
37
+ // operation completed successfully.
38
+ topLevelIndexLoaded bool
35
39
}
36
40
37
41
var _ Iterator = (* twoLevelIteratorRowBlocks )(nil )
@@ -45,6 +49,7 @@ func (i *twoLevelIterator[I, PI, D, PD]) loadSecondLevelIndexBlock(dir int8) loa
45
49
// the index fails.
46
50
PD (& i .secondLevel .data ).Invalidate ()
47
51
PI (& i .secondLevel .index ).Invalidate ()
52
+
48
53
if ! PI (& i .topLevelIndex ).Valid () {
49
54
return loadBlockFailed
50
55
}
@@ -87,6 +92,10 @@ func (i *twoLevelIterator[I, PI, D, PD]) loadSecondLevelIndexBlock(dir int8) loa
87
92
// appropriate bound, depending on the iteration direction, and returns either
88
93
// `blockIntersects` or `blockExcluded`.
89
94
func (i * twoLevelIterator [I , PI , D , PD ]) resolveMaybeExcluded (dir int8 ) intersectsResult {
95
+ if invariants .Enabled && ! i .topLevelIndexLoaded {
96
+ panic ("pebble: resolveMaybeExcluded called without loaded top-level index" )
97
+ }
98
+
90
99
// This iterator is configured with a bound-limited block property filter.
91
100
// The bpf determined this entire index block could be excluded from
92
101
// iteration based on the property encoded in the block handle. However, we
@@ -162,6 +171,7 @@ func newColumnBlockTwoLevelIterator(
162
171
}
163
172
i := twoLevelIterColumnBlockPool .Get ().(* twoLevelIteratorColumnBlocks )
164
173
i .secondLevel .init (ctx , r , opts )
174
+ i .secondLevel .indexLoaded = true
165
175
// Only check the bloom filter at the top level.
166
176
i .useFilterBlock = i .secondLevel .useFilterBlock
167
177
i .secondLevel .useFilterBlock = false
@@ -182,14 +192,7 @@ func newColumnBlockTwoLevelIterator(
182
192
objstorage .NoReadBefore , & i .secondLevel .vbRHPrealloc )
183
193
}
184
194
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
- }
195
+
193
196
return i , nil
194
197
}
195
198
@@ -211,6 +214,7 @@ func newRowBlockTwoLevelIterator(
211
214
}
212
215
i := twoLevelIterRowBlockPool .Get ().(* twoLevelIteratorRowBlocks )
213
216
i .secondLevel .init (ctx , r , opts )
217
+ i .secondLevel .indexLoaded = true
214
218
// Only check the bloom filter at the top level.
215
219
i .useFilterBlock = i .secondLevel .useFilterBlock
216
220
i .secondLevel .useFilterBlock = false
@@ -237,14 +241,6 @@ func newRowBlockTwoLevelIterator(
237
241
i .secondLevel .data .SetHasValuePrefix (true )
238
242
}
239
243
240
- topLevelIndexH , err := r .readTopLevelIndexBlock (ctx , i .secondLevel .readEnv .Block , i .secondLevel .indexFilterRH )
241
- if err == nil {
242
- err = i .topLevelIndex .InitHandle (r .Comparer , topLevelIndexH , opts .Transforms )
243
- }
244
- if err != nil {
245
- _ = i .Close ()
246
- return nil , err
247
- }
248
244
return i , nil
249
245
}
250
246
@@ -277,6 +273,10 @@ func (i *twoLevelIterator[I, PI, D, PD]) SeekGE(
277
273
err := i .secondLevel .err
278
274
i .secondLevel .err = nil // clear cached iteration error
279
275
276
+ if ! i .ensureTopLevelIndexLoaded () {
277
+ return nil
278
+ }
279
+
280
280
// The twoLevelIterator could be already exhausted. Utilize that when
281
281
// trySeekUsingNext is true. See the comment about data-exhausted, PGDE, and
282
282
// bounds-exhausted near the top of the file.
@@ -419,6 +419,10 @@ func (i *twoLevelIterator[I, PI, D, PD]) SeekPrefixGE(
419
419
err := i .secondLevel .err
420
420
i .secondLevel .err = nil // clear cached iteration error
421
421
422
+ if ! i .ensureTopLevelIndexLoaded () {
423
+ return nil
424
+ }
425
+
422
426
// The twoLevelIterator could be already exhausted. Utilize that when
423
427
// trySeekUsingNext is true. See the comment about data-exhausted, PGDE, and
424
428
// bounds-exhausted near the top of the file.
@@ -586,6 +590,11 @@ func (i *twoLevelIterator[I, PI, D, PD]) virtualLastSeekLE() *base.InternalKV {
586
590
panic ("unexpected virtualLastSeekLE with exclusive upper bounds" )
587
591
}
588
592
key := i .secondLevel .upper
593
+
594
+ if ! i .ensureTopLevelIndexLoaded () {
595
+ return nil
596
+ }
597
+
589
598
// Need to position the topLevelIndex.
590
599
//
591
600
// The previous exhausted state of singleLevelIterator is no longer
@@ -643,6 +652,10 @@ func (i *twoLevelIterator[I, PI, D, PD]) SeekLT(
643
652
// Seek optimization only applies until iterator is first positioned after SetBounds.
644
653
i .secondLevel .boundsCmp = 0
645
654
655
+ if ! i .ensureTopLevelIndexLoaded () {
656
+ return nil
657
+ }
658
+
646
659
var result loadBlockResult
647
660
// NB: Unlike SeekGE, we don't have a fast-path here since we don't know
648
661
// whether the topLevelIndex is positioned after the position that would
@@ -716,6 +729,10 @@ func (i *twoLevelIterator[I, PI, D, PD]) First() *base.InternalKV {
716
729
// Seek optimization only applies until iterator is first positioned after SetBounds.
717
730
i .secondLevel .boundsCmp = 0
718
731
732
+ if ! i .ensureTopLevelIndexLoaded () {
733
+ return nil
734
+ }
735
+
719
736
if ! PI (& i .topLevelIndex ).First () {
720
737
return nil
721
738
}
@@ -765,6 +782,10 @@ func (i *twoLevelIterator[I, PI, D, PD]) Last() *base.InternalKV {
765
782
// Seek optimization only applies until iterator is first positioned after SetBounds.
766
783
i .secondLevel .boundsCmp = 0
767
784
785
+ if ! i .ensureTopLevelIndexLoaded () {
786
+ return nil
787
+ }
788
+
768
789
if ! PI (& i .topLevelIndex ).Last () {
769
790
return nil
770
791
}
@@ -832,6 +853,11 @@ func (i *twoLevelIterator[I, PI, D, PD]) NextPrefix(succKey []byte) *base.Intern
832
853
833
854
// Did not find prefix in the existing second-level index block. This is the
834
855
// slow-path where we seek the iterator.
856
+
857
+ if ! i .ensureTopLevelIndexLoaded () {
858
+ return nil
859
+ }
860
+
835
861
if ! PI (& i .topLevelIndex ).SeekGE (succKey ) {
836
862
PD (& i .secondLevel .data ).Invalidate ()
837
863
PI (& i .secondLevel .index ).Invalidate ()
@@ -879,6 +905,10 @@ func (i *twoLevelIterator[I, PI, D, PD]) skipForward() *base.InternalKV {
879
905
return nil
880
906
}
881
907
908
+ if ! i .ensureTopLevelIndexLoaded () {
909
+ return nil
910
+ }
911
+
882
912
// It is possible that skipBackward went too far and the virtual table lower
883
913
// bound is after the first key in the block we are about to load, in which
884
914
// case we must use SeekGE below. The keys in the block we are about to load
@@ -956,6 +986,11 @@ func (i *twoLevelIterator[I, PI, D, PD]) skipBackward() *base.InternalKV {
956
986
if i .secondLevel .err != nil || i .secondLevel .exhaustedBounds < 0 {
957
987
return nil
958
988
}
989
+
990
+ if ! i .ensureTopLevelIndexLoaded () {
991
+ return nil
992
+ }
993
+
959
994
i .secondLevel .exhaustedBounds = 0
960
995
if ! PI (& i .topLevelIndex ).Prev () {
961
996
PD (& i .secondLevel .data ).Invalidate ()
@@ -1009,8 +1044,37 @@ func (i *twoLevelIterator[I, PI, D, PD]) SetupForCompaction() {
1009
1044
i .secondLevel .SetupForCompaction ()
1010
1045
}
1011
1046
1012
- // Close implements internalIterator.Close, as documented in the pebble
1013
- // package.
1047
+ func (i * twoLevelIterator [I , PI , D , PD ]) ensureTopLevelIndexLoaded () bool {
1048
+ if i .topLevelIndexLoaded {
1049
+ return true
1050
+ }
1051
+
1052
+ // Perform the deferred top-level index loading calls
1053
+ topLevelIndexH , err := i .secondLevel .reader .readTopLevelIndexBlock (
1054
+ i .secondLevel .ctx ,
1055
+ i .secondLevel .readEnv .Block ,
1056
+ i .secondLevel .indexFilterRH ,
1057
+ )
1058
+ if err != nil {
1059
+ i .secondLevel .err = err
1060
+ return false
1061
+ }
1062
+
1063
+ err = PI (& i .topLevelIndex ).InitHandle (
1064
+ i .secondLevel .reader .Comparer ,
1065
+ topLevelIndexH ,
1066
+ i .secondLevel .transforms ,
1067
+ )
1068
+ if err != nil {
1069
+ i .secondLevel .err = err
1070
+ return false
1071
+ }
1072
+
1073
+ i .topLevelIndexLoaded = true
1074
+ return true
1075
+ }
1076
+
1077
+ // Close implements internalIterator.Close, as documented in the pebble package.
1014
1078
func (i * twoLevelIterator [I , PI , D , PD ]) Close () error {
1015
1079
if invariants .Enabled && i .secondLevel .pool != nil {
1016
1080
panic ("twoLevelIterator's singleLevelIterator has its own non-nil pool" )
@@ -1021,6 +1085,7 @@ func (i *twoLevelIterator[I, PI, D, PD]) Close() error {
1021
1085
err = firstError (err , PI (& i .topLevelIndex ).Close ())
1022
1086
i .useFilterBlock = false
1023
1087
i .lastBloomFilterMatched = false
1088
+ i .topLevelIndexLoaded = false
1024
1089
if pool != nil {
1025
1090
pool .Put (i )
1026
1091
}
0 commit comments