Skip to content

Commit d925b88

Browse files
committed
db: add SkipPoint iterator option
Add a new IterOption that allows a user to configure an arbitrary predicate of point keys that should be skipped in the form of a function. The Iterator consults this predicate for every internal key. Use this new SkipPoint function to remove the complicated logic within the metamorphic test for ensuring determinisim of RangeKeyChanged() in the presence of time-bound block-property filters. A SkipPoint predicate may be combined with a block-property filter to achieve deterministic iteration.
1 parent b5677d8 commit d925b88

File tree

8 files changed

+247
-239
lines changed

8 files changed

+247
-239
lines changed

iterator.go

+49-6
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,21 @@ func (i *Iterator) findNextEntry(limit []byte) {
550550
return
551551
}
552552

553+
// If the user has configured a SkipPoint function, invoke it to see
554+
// whether we should skip over the current user key.
555+
if i.opts.SkipPoint != nil && key.Kind() != InternalKeyKindRangeKeySet && i.opts.SkipPoint(i.iterKey.UserKey) {
556+
// NB: We could call nextUserKey, but in some cases the SkipPoint
557+
// predicate function might be cheaper than nextUserKey's key copy
558+
// and key comparison. This should be the case for MVCC suffix
559+
// comparisons, for example. In the future, we could expand the
560+
// SkipPoint interface to give the implementor more control over
561+
// whether we skip over just the internal key, the user key, or even
562+
// the key prefix.
563+
i.stats.ForwardStepCount[InternalIterCall]++
564+
i.iterKey, i.iterValue = i.iter.Next()
565+
continue
566+
}
567+
553568
switch key.Kind() {
554569
case InternalKeyKindRangeKeySet:
555570
// Save the current key.
@@ -619,6 +634,13 @@ func (i *Iterator) findNextEntry(limit []byte) {
619634
}
620635

621636
func (i *Iterator) nextPointCurrentUserKey() bool {
637+
// If the user has configured a SkipPoint function and the current user key
638+
// would be skipped by it, there's no need to step forward looking for a
639+
// point key. If we were to find one, it should be skipped anyways.
640+
if i.opts.SkipPoint != nil && i.opts.SkipPoint(i.key) {
641+
return false
642+
}
643+
622644
i.pos = iterPosCurForward
623645

624646
i.iterKey, i.iterValue = i.iter.Next()
@@ -911,6 +933,26 @@ func (i *Iterator) findPrevEntry(limit []byte) {
911933
}
912934
}
913935

936+
// If the user has configured a SkipPoint function, invoke it to see
937+
// whether we should skip over the current user key.
938+
if i.opts.SkipPoint != nil && key.Kind() != InternalKeyKindRangeKeySet && i.opts.SkipPoint(key.UserKey) {
939+
// NB: We could call prevUserKey, but in some cases the SkipPoint
940+
// predicate function might be cheaper than prevUserKey's key copy
941+
// and key comparison. This should be the case for MVCC suffix
942+
// comparisons, for example. In the future, we could expand the
943+
// SkipPoint interface to give the implementor more control over
944+
// whether we skip over just the internal key, the user key, or even
945+
// the key prefix.
946+
i.stats.ReverseStepCount[InternalIterCall]++
947+
i.iterKey, i.iterValue = i.iter.Prev()
948+
if limit != nil && i.iterKey != nil && i.cmp(limit, i.iterKey.UserKey) > 0 && !i.rangeKeyWithinLimit(limit) {
949+
i.iterValidityState = IterAtLimit
950+
i.pos = iterPosCurReversePaused
951+
return
952+
}
953+
continue
954+
}
955+
914956
switch key.Kind() {
915957
case InternalKeyKindRangeKeySet:
916958
// Range key start boundary markers are interleaved with the maximum
@@ -948,12 +990,12 @@ func (i *Iterator) findPrevEntry(limit []byte) {
948990
// Compare with the limit. We could optimize by only checking when
949991
// we step to the previous user key, but detecting that requires a
950992
// comparison too. Note that this position may already passed a
951-
// number of versions of this user key, but they are all deleted,
952-
// so the fact that a subsequent Prev*() call will not see them is
993+
// number of versions of this user key, but they are all deleted, so
994+
// the fact that a subsequent Prev*() call will not see them is
953995
// harmless. Also note that this is the only place in the loop,
954-
// other than the firstLoopIter case above, where we could step
955-
// to a different user key and start processing it for returning
956-
// to the caller.
996+
// other than the firstLoopIter and SkipPoint cases above, where we
997+
// could step to a different user key and start processing it for
998+
// returning to the caller.
957999
if limit != nil && i.iterKey != nil && i.cmp(limit, i.iterKey.UserKey) > 0 && !i.rangeKeyWithinLimit(limit) {
9581000
i.iterValidityState = IterAtLimit
9591001
i.pos = iterPosCurReversePaused
@@ -2428,7 +2470,8 @@ func (i *Iterator) SetOptions(o *IterOptions) {
24282470
// If either options specify block property filters for an iterator stack,
24292471
// reconstruct it.
24302472
if i.pointIter != nil && (closeBoth || len(o.PointKeyFilters) > 0 || len(i.opts.PointKeyFilters) > 0 ||
2431-
o.RangeKeyMasking.Filter != nil || i.opts.RangeKeyMasking.Filter != nil) {
2473+
o.RangeKeyMasking.Filter != nil || i.opts.RangeKeyMasking.Filter != nil || o.SkipPoint != nil ||
2474+
i.opts.SkipPoint != nil) {
24322475
i.err = firstError(i.err, i.pointIter.Close())
24332476
i.pointIter = nil
24342477
}

iterator_histories_test.go

+11
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,17 @@ func TestIterHistories(t *testing.T) {
309309
o.PointKeyFilters = []sstable.BlockPropertyFilter{
310310
sstable.NewTestKeysBlockPropertyFilter(min, max),
311311
}
312+
o.SkipPoint = func(k []byte) bool {
313+
i := testkeys.Comparer.Split(k)
314+
if i == len(k) {
315+
return false
316+
}
317+
v, err := testkeys.ParseSuffix(k[i:])
318+
if err != nil {
319+
return false
320+
}
321+
return uint64(v) < min || uint64(v) >= max
322+
}
312323
case "snapshot":
313324
s, err := strconv.ParseUint(arg.Vals[0], 10, 64)
314325
if err != nil {

metamorphic/ops.go

+19-11
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/cockroachdb/pebble/internal/base"
1919
"github.com/cockroachdb/pebble/internal/keyspan"
2020
"github.com/cockroachdb/pebble/internal/private"
21+
"github.com/cockroachdb/pebble/internal/testkeys"
2122
"github.com/cockroachdb/pebble/objstorage/objstorageprovider"
2223
"github.com/cockroachdb/pebble/sstable"
2324
"github.com/cockroachdb/pebble/vfs"
@@ -711,7 +712,7 @@ func (o *newIterOp) run(t *test, h historyRecorder) {
711712
// close this iter and retry NewIter
712713
_ = i.Close()
713714
}
714-
t.setIter(o.iterID, i, o.filterMin, o.filterMax)
715+
t.setIter(o.iterID, i)
715716

716717
// Trash the bounds to ensure that Pebble doesn't rely on the stability of
717718
// the user-provided bounds.
@@ -763,13 +764,7 @@ func (o *newIterUsingCloneOp) run(t *test, h historyRecorder) {
763764
if err != nil {
764765
panic(err)
765766
}
766-
filterMin, filterMax := o.filterMin, o.filterMax
767-
if cloneOpts.IterOptions == nil {
768-
// We're adopting the same block property filters as iter, so we need to
769-
// adopt the same run-time filters to ensure determinism.
770-
filterMin, filterMax = iter.filterMin, iter.filterMax
771-
}
772-
t.setIter(o.iterID, i, filterMin, filterMax)
767+
t.setIter(o.iterID, i)
773768
h.Recordf("%s // %v", o, i.Error())
774769
}
775770

@@ -853,9 +848,6 @@ func (o *iterSetOptionsOp) run(t *test, h historyRecorder) {
853848
rand.Read(opts.LowerBound[:])
854849
rand.Read(opts.UpperBound[:])
855850

856-
// Adjust the iterator's filters.
857-
i.filterMin, i.filterMax = o.filterMin, o.filterMax
858-
859851
h.Recordf("%s // %v", o, i.Error())
860852
}
861853

@@ -893,6 +885,22 @@ func iterOptions(o iterOpts) *pebble.IterOptions {
893885
opts.PointKeyFilters = []pebble.BlockPropertyFilter{
894886
sstable.NewTestKeysBlockPropertyFilter(o.filterMin, o.filterMax),
895887
}
888+
// Enforce the timestamp bounds in SkipPoint, so that the iterator never
889+
// returns a key outside the filterMin, filterMax bounds. This provides
890+
// deterministic iteration.
891+
opts.SkipPoint = func(k []byte) (skip bool) {
892+
n := testkeys.Comparer.Split(k)
893+
if n == len(k) {
894+
// No suffix, don't skip it.
895+
return false
896+
}
897+
v, err := testkeys.ParseSuffix(k[n:])
898+
if err != nil {
899+
panic(err)
900+
}
901+
ts := uint64(v)
902+
return ts < o.filterMin || ts >= o.filterMax
903+
}
896904
}
897905
return opts
898906
}

0 commit comments

Comments
 (0)