@@ -164,6 +164,10 @@ type singleLevelIterator[I any, PI indexBlockIterator[I], D any, PD dataBlockIte
164
164
// a match is high).
165
165
useFilterBlock bool
166
166
lastBloomFilterMatched bool
167
+ // lastOpWasSeekPrefixGE tracks if the previous operation was SeekPrefixGE
168
+ // that returned nil due to bloom filter miss. Used for invariant checking
169
+ // in Next() to ensure the block is not invalidated when it doesn't have to be.
170
+ lastOpWasSeekPrefixGE invariants.Value [bool ]
167
171
168
172
// indexLoaded is set to true if the index block load operation completed
169
173
// successfully.
@@ -646,6 +650,8 @@ func (i *singleLevelIterator[I, PI, D, PD]) trySeekLTUsingPrevWithinBlock(
646
650
func (i * singleLevelIterator [I , PI , D , PD ]) SeekGE (
647
651
key []byte , flags base.SeekGEFlags ,
648
652
) * base.InternalKV {
653
+ // Clear the tracking flag since this is a new absolute positioning operation
654
+ i .lastOpWasSeekPrefixGE .Set (false )
649
655
// The synthetic key is no longer relevant and must be cleared.
650
656
i .synthetic .atSyntheticKey = false
651
657
@@ -890,6 +896,8 @@ func (i *singleLevelIterator[I, PI, D, PD]) seekPrefixGE(
890
896
// this method. Hence, we can use the existing iterator position if the last
891
897
// SeekPrefixGE did not fail bloom filter matching.
892
898
899
+ // Clear the tracking flag initially; will be set later if bloom filter returns false
900
+ i .lastOpWasSeekPrefixGE .Set (false )
893
901
err := i .err
894
902
i .err = nil // clear cached iteration error
895
903
if i .useFilterBlock {
@@ -901,15 +909,17 @@ func (i *singleLevelIterator[I, PI, D, PD]) seekPrefixGE(
901
909
// Check prefix bloom filter.
902
910
var mayContain bool
903
911
mayContain , i .err = i .bloomFilterMayContain (prefix )
904
- if i .err != nil || ! mayContain {
905
- // In the i.err == nil case, this invalidation may not be necessary for
906
- // correctness, and may be a place to optimize later by reusing the
907
- // already loaded block. It was necessary in earlier versions of the code
908
- // since the caller was allowed to call Next when SeekPrefixGE returned
909
- // nil. This is no longer allowed.
912
+ if i .err != nil {
910
913
PD (& i .data ).Invalidate ()
911
914
return nil
912
915
}
916
+ if ! mayContain {
917
+ // In the no-error bloom filter miss case, the key is definitely not in table.
918
+ // We can avoid invalidating the already loaded block since the caller is
919
+ // not allowed to call Next when SeekPrefixGE returns nil.
920
+ i .lastOpWasSeekPrefixGE .Set (true )
921
+ return nil
922
+ }
913
923
i .lastBloomFilterMatched = true
914
924
}
915
925
if flags .TrySeekUsingNext () {
@@ -1077,8 +1087,11 @@ func (i *singleLevelIterator[I, PI, D, PD]) virtualLastSeekLE() *base.InternalKV
1077
1087
func (i * singleLevelIterator [I , PI , D , PD ]) SeekLT (
1078
1088
key []byte , flags base.SeekLTFlags ,
1079
1089
) * base.InternalKV {
1090
+ // Clear the tracking flag since this is a new absolute positioning operation
1091
+ i .lastOpWasSeekPrefixGE .Set (false )
1080
1092
// The synthetic key is no longer relevant and must be cleared.
1081
1093
i .synthetic .atSyntheticKey = false
1094
+
1082
1095
if i .readEnv .Virtual != nil {
1083
1096
// Might have to fix upper bound since virtual sstable bounds are not
1084
1097
// known to callers of SeekLT.
@@ -1189,8 +1202,11 @@ func (i *singleLevelIterator[I, PI, D, PD]) SeekLT(
1189
1202
// to ensure that key is greater than or equal to the lower bound (e.g. via a
1190
1203
// call to SeekGE(lower)).
1191
1204
func (i * singleLevelIterator [I , PI , D , PD ]) First () * base.InternalKV {
1205
+ // Clear the tracking flag since this is a new absolute positioning operation
1206
+ i .lastOpWasSeekPrefixGE .Set (false )
1192
1207
// The synthetic key is no longer relevant and must be cleared.
1193
1208
i .synthetic .atSyntheticKey = false
1209
+
1194
1210
// If we have a lower bound, use SeekGE. Note that in general this is not
1195
1211
// supported usage, except when the lower bound is there because the table is
1196
1212
// virtual.
@@ -1260,8 +1276,11 @@ func (i *singleLevelIterator[I, PI, D, PD]) firstInternal() *base.InternalKV {
1260
1276
// to ensure that key is less than the upper bound (e.g. via a call to
1261
1277
// SeekLT(upper))
1262
1278
func (i * singleLevelIterator [I , PI , D , PD ]) Last () * base.InternalKV {
1279
+ // Clear the tracking flag since this is a new absolute positioning operation
1280
+ i .lastOpWasSeekPrefixGE .Set (false )
1263
1281
// The synthetic key is no longer relevant and must be cleared.
1264
1282
i .synthetic .atSyntheticKey = false
1283
+
1265
1284
if i .readEnv .Virtual != nil {
1266
1285
return i .maybeVerifyKey (i .virtualLast ())
1267
1286
}
@@ -1324,6 +1343,16 @@ func (i *singleLevelIterator[I, PI, D, PD]) lastInternal() *base.InternalKV {
1324
1343
// Note: compactionIterator.Next mirrors the implementation of Iterator.Next
1325
1344
// due to performance. Keep the two in sync.
1326
1345
func (i * singleLevelIterator [I , PI , D , PD ]) Next () * base.InternalKV {
1346
+ if invariants .Enabled && i .lastOpWasSeekPrefixGE .Get () {
1347
+ // If the previous operation was SeekPrefixGE that returned nil due to bloom
1348
+ // filter miss, the data block should not have been invalidated. This assertion
1349
+ // ensures the optimization to preserve loaded blocks is working correctly.
1350
+ if PD (& i .data ).IsDataInvalidated () {
1351
+ panic ("pebble: data block was invalidated after SeekPrefixGE returned nil due to bloom filter miss" )
1352
+ }
1353
+ }
1354
+ // Clear the tracking flag since this is no longer the next operation after SeekPrefixGE
1355
+ i .lastOpWasSeekPrefixGE .Set (false )
1327
1356
1328
1357
// The SeekPrefixGE might have returned a synthetic key with latest suffix
1329
1358
// contained in the sstable. If the caller is calling Next(), that means
@@ -1335,6 +1364,7 @@ func (i *singleLevelIterator[I, PI, D, PD]) Next() *base.InternalKV {
1335
1364
i .synthetic .atSyntheticKey = false
1336
1365
return i .seekPrefixGE (i .reader .Comparer .Split .Prefix (i .synthetic .seekKey ), i .synthetic .seekKey , base .SeekGEFlagsNone )
1337
1366
}
1367
+
1338
1368
if i .exhaustedBounds == + 1 {
1339
1369
panic ("Next called even though exhausted upper bound" )
1340
1370
}
@@ -1362,6 +1392,8 @@ func (i *singleLevelIterator[I, PI, D, PD]) Next() *base.InternalKV {
1362
1392
1363
1393
// NextPrefix implements (base.InternalIterator).NextPrefix.
1364
1394
func (i * singleLevelIterator [I , PI , D , PD ]) NextPrefix (succKey []byte ) * base.InternalKV {
1395
+ // Clear the tracking flag since this is a relative positioning operation
1396
+ i .lastOpWasSeekPrefixGE .Set (false )
1365
1397
if i .exhaustedBounds == + 1 {
1366
1398
panic ("NextPrefix called even though exhausted upper bound" )
1367
1399
}
@@ -1439,6 +1471,8 @@ func (i *singleLevelIterator[I, PI, D, PD]) NextPrefix(succKey []byte) *base.Int
1439
1471
// Prev implements internalIterator.Prev, as documented in the pebble
1440
1472
// package.
1441
1473
func (i * singleLevelIterator [I , PI , D , PD ]) Prev () * base.InternalKV {
1474
+ // Clear the tracking flag since this is a relative positioning operation
1475
+ i .lastOpWasSeekPrefixGE .Set (false )
1442
1476
if i .exhaustedBounds == - 1 {
1443
1477
panic ("Prev called even though exhausted lower bound" )
1444
1478
}
0 commit comments