@@ -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,12 @@ 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
+
53
+ // Ensure top-level index is loaded before accessing it
54
+ if ! i .ensureTopLevelIndexLoaded () {
55
+ return loadBlockFailed
56
+ }
57
+
48
58
if ! PI (& i .topLevelIndex ).Valid () {
49
59
return loadBlockFailed
50
60
}
@@ -87,6 +97,10 @@ func (i *twoLevelIterator[I, PI, D, PD]) loadSecondLevelIndexBlock(dir int8) loa
87
97
// appropriate bound, depending on the iteration direction, and returns either
88
98
// `blockIntersects` or `blockExcluded`.
89
99
func (i * twoLevelIterator [I , PI , D , PD ]) resolveMaybeExcluded (dir int8 ) intersectsResult {
100
+ if invariants .Enabled && ! i .topLevelIndexLoaded {
101
+ panic ("pebble: resolveMaybeExcluded called without loaded top-level index" )
102
+ }
103
+
90
104
// This iterator is configured with a bound-limited block property filter.
91
105
// The bpf determined this entire index block could be excluded from
92
106
// iteration based on the property encoded in the block handle. However, we
@@ -162,6 +176,7 @@ func newColumnBlockTwoLevelIterator(
162
176
}
163
177
i := twoLevelIterColumnBlockPool .Get ().(* twoLevelIteratorColumnBlocks )
164
178
i .secondLevel .init (ctx , r , opts )
179
+ i .secondLevel .indexLoaded = true
165
180
// Only check the bloom filter at the top level.
166
181
i .useFilterBlock = i .secondLevel .useFilterBlock
167
182
i .secondLevel .useFilterBlock = false
@@ -181,14 +196,7 @@ func newColumnBlockTwoLevelIterator(
181
196
objstorage .NoReadBefore , & i .secondLevel .vbRHPrealloc )
182
197
}
183
198
i .secondLevel .data .InitOnce (r .keySchema , r .Comparer , & i .secondLevel .internalValueConstructor )
184
- topLevelIndexH , err := r .readTopLevelIndexBlock (ctx , i .secondLevel .readEnv .Block , i .secondLevel .indexFilterRH )
185
- if err == nil {
186
- err = i .topLevelIndex .InitHandle (r .Comparer , topLevelIndexH , opts .Transforms )
187
- }
188
- if err != nil {
189
- _ = i .Close ()
190
- return nil , err
191
- }
199
+
192
200
return i , nil
193
201
}
194
202
@@ -210,6 +218,7 @@ func newRowBlockTwoLevelIterator(
210
218
}
211
219
i := twoLevelIterRowBlockPool .Get ().(* twoLevelIteratorRowBlocks )
212
220
i .secondLevel .init (ctx , r , opts )
221
+ i .secondLevel .indexLoaded = true
213
222
// Only check the bloom filter at the top level.
214
223
i .useFilterBlock = i .secondLevel .useFilterBlock
215
224
i .secondLevel .useFilterBlock = false
@@ -235,14 +244,6 @@ func newRowBlockTwoLevelIterator(
235
244
i .secondLevel .data .SetHasValuePrefix (true )
236
245
}
237
246
238
- topLevelIndexH , err := r .readTopLevelIndexBlock (ctx , i .secondLevel .readEnv .Block , i .secondLevel .indexFilterRH )
239
- if err == nil {
240
- err = i .topLevelIndex .InitHandle (r .Comparer , topLevelIndexH , opts .Transforms )
241
- }
242
- if err != nil {
243
- _ = i .Close ()
244
- return nil , err
245
- }
246
247
return i , nil
247
248
}
248
249
@@ -275,6 +276,10 @@ func (i *twoLevelIterator[I, PI, D, PD]) SeekGE(
275
276
err := i .secondLevel .err
276
277
i .secondLevel .err = nil // clear cached iteration error
277
278
279
+ if ! i .ensureTopLevelIndexLoaded () {
280
+ return nil
281
+ }
282
+
278
283
// The twoLevelIterator could be already exhausted. Utilize that when
279
284
// trySeekUsingNext is true. See the comment about data-exhausted, PGDE, and
280
285
// bounds-exhausted near the top of the file.
@@ -417,6 +422,10 @@ func (i *twoLevelIterator[I, PI, D, PD]) SeekPrefixGE(
417
422
err := i .secondLevel .err
418
423
i .secondLevel .err = nil // clear cached iteration error
419
424
425
+ if ! i .ensureTopLevelIndexLoaded () {
426
+ return nil
427
+ }
428
+
420
429
// The twoLevelIterator could be already exhausted. Utilize that when
421
430
// trySeekUsingNext is true. See the comment about data-exhausted, PGDE, and
422
431
// bounds-exhausted near the top of the file.
@@ -584,6 +593,11 @@ func (i *twoLevelIterator[I, PI, D, PD]) virtualLastSeekLE() *base.InternalKV {
584
593
panic ("unexpected virtualLastSeekLE with exclusive upper bounds" )
585
594
}
586
595
key := i .secondLevel .upper
596
+
597
+ if ! i .ensureTopLevelIndexLoaded () {
598
+ return nil
599
+ }
600
+
587
601
// Need to position the topLevelIndex.
588
602
//
589
603
// The previous exhausted state of singleLevelIterator is no longer
@@ -641,6 +655,10 @@ func (i *twoLevelIterator[I, PI, D, PD]) SeekLT(
641
655
// Seek optimization only applies until iterator is first positioned after SetBounds.
642
656
i .secondLevel .boundsCmp = 0
643
657
658
+ if ! i .ensureTopLevelIndexLoaded () {
659
+ return nil
660
+ }
661
+
644
662
var result loadBlockResult
645
663
// NB: Unlike SeekGE, we don't have a fast-path here since we don't know
646
664
// whether the topLevelIndex is positioned after the position that would
@@ -714,6 +732,10 @@ func (i *twoLevelIterator[I, PI, D, PD]) First() *base.InternalKV {
714
732
// Seek optimization only applies until iterator is first positioned after SetBounds.
715
733
i .secondLevel .boundsCmp = 0
716
734
735
+ if ! i .ensureTopLevelIndexLoaded () {
736
+ return nil
737
+ }
738
+
717
739
if ! PI (& i .topLevelIndex ).First () {
718
740
return nil
719
741
}
@@ -763,6 +785,10 @@ func (i *twoLevelIterator[I, PI, D, PD]) Last() *base.InternalKV {
763
785
// Seek optimization only applies until iterator is first positioned after SetBounds.
764
786
i .secondLevel .boundsCmp = 0
765
787
788
+ if ! i .ensureTopLevelIndexLoaded () {
789
+ return nil
790
+ }
791
+
766
792
if ! PI (& i .topLevelIndex ).Last () {
767
793
return nil
768
794
}
@@ -830,6 +856,11 @@ func (i *twoLevelIterator[I, PI, D, PD]) NextPrefix(succKey []byte) *base.Intern
830
856
831
857
// Did not find prefix in the existing second-level index block. This is the
832
858
// slow-path where we seek the iterator.
859
+
860
+ if ! i .ensureTopLevelIndexLoaded () {
861
+ return nil
862
+ }
863
+
833
864
if ! PI (& i .topLevelIndex ).SeekGE (succKey ) {
834
865
PD (& i .secondLevel .data ).Invalidate ()
835
866
PI (& i .secondLevel .index ).Invalidate ()
@@ -877,6 +908,10 @@ func (i *twoLevelIterator[I, PI, D, PD]) skipForward() *base.InternalKV {
877
908
return nil
878
909
}
879
910
911
+ if ! i .ensureTopLevelIndexLoaded () {
912
+ return nil
913
+ }
914
+
880
915
// It is possible that skipBackward went too far and the virtual table lower
881
916
// bound is after the first key in the block we are about to load, in which
882
917
// case we must use SeekGE below. The keys in the block we are about to load
@@ -954,6 +989,11 @@ func (i *twoLevelIterator[I, PI, D, PD]) skipBackward() *base.InternalKV {
954
989
if i .secondLevel .err != nil || i .secondLevel .exhaustedBounds < 0 {
955
990
return nil
956
991
}
992
+
993
+ if ! i .ensureTopLevelIndexLoaded () {
994
+ return nil
995
+ }
996
+
957
997
i .secondLevel .exhaustedBounds = 0
958
998
if ! PI (& i .topLevelIndex ).Prev () {
959
999
PD (& i .secondLevel .data ).Invalidate ()
@@ -1007,8 +1047,39 @@ func (i *twoLevelIterator[I, PI, D, PD]) SetupForCompaction() {
1007
1047
i .secondLevel .SetupForCompaction ()
1008
1048
}
1009
1049
1010
- // Close implements internalIterator.Close, as documented in the pebble
1011
- // package.
1050
+ func (i * twoLevelIterator [I , PI , D , PD ]) ensureTopLevelIndexLoaded () bool {
1051
+ if i .topLevelIndexLoaded && i .secondLevel .err == nil {
1052
+ return true
1053
+ }
1054
+
1055
+ // Perform the deferred top-level index loading calls
1056
+ topLevelIndexH , err := i .secondLevel .reader .readTopLevelIndexBlock (
1057
+ i .secondLevel .ctx ,
1058
+ i .secondLevel .readEnv .Block ,
1059
+ i .secondLevel .indexFilterRH ,
1060
+ )
1061
+ if err != nil {
1062
+ i .secondLevel .err = err
1063
+ i .topLevelIndexLoaded = false
1064
+ return false
1065
+ }
1066
+
1067
+ err = PI (& i .topLevelIndex ).InitHandle (
1068
+ i .secondLevel .reader .Comparer ,
1069
+ topLevelIndexH ,
1070
+ i .secondLevel .transforms ,
1071
+ )
1072
+ if err != nil {
1073
+ i .secondLevel .err = err
1074
+ i .topLevelIndexLoaded = false
1075
+ return false
1076
+ }
1077
+
1078
+ i .topLevelIndexLoaded = true
1079
+ return true
1080
+ }
1081
+
1082
+ // Close implements internalIterator.Close, as documented in the pebble package.
1012
1083
func (i * twoLevelIterator [I , PI , D , PD ]) Close () error {
1013
1084
if invariants .Enabled && i .secondLevel .pool != nil {
1014
1085
panic ("twoLevelIterator's singleLevelIterator has its own non-nil pool" )
@@ -1019,6 +1090,7 @@ func (i *twoLevelIterator[I, PI, D, PD]) Close() error {
1019
1090
err = firstError (err , PI (& i .topLevelIndex ).Close ())
1020
1091
i .useFilterBlock = false
1021
1092
i .lastBloomFilterMatched = false
1093
+ i .topLevelIndexLoaded = false
1022
1094
if pool != nil {
1023
1095
pool .Put (i )
1024
1096
}
0 commit comments