From dd24283245e2f1fcbaac02cad634dab9b1119558 Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Wed, 14 Dec 2022 20:40:27 +0000 Subject: [PATCH 1/3] storage: add some `TestMVCCHistories` range key cases for `MVCCGet` Release note: None --- .../mvcc_histories/range_tombstone_gets | 69 +++++++++++++++++++ .../range_tombstone_gets_complex | 14 ++-- .../range_tombstone_scans_complex | 14 ++-- 3 files changed, 83 insertions(+), 14 deletions(-) diff --git a/pkg/storage/testdata/mvcc_histories/range_tombstone_gets b/pkg/storage/testdata/mvcc_histories/range_tombstone_gets index 367386d52afa..459adc81e1c1 100644 --- a/pkg/storage/testdata/mvcc_histories/range_tombstone_gets +++ b/pkg/storage/testdata/mvcc_histories/range_tombstone_gets @@ -146,6 +146,33 @@ get: "e" -> / @4.000000000,0 get: "e" -> intent {id=00000000 key="e" pri=0.00000000 epo=0 ts=6.000000000,0 min=0,0 seq=0} get: "e" -> / @4.000000000,0 +run ok +get k=f ts=1 +get k=f ts=2 +get k=f ts=3 +get k=f ts=4 +get k=f ts=5 +get k=f ts=6 +get k=f ts=1 tombstones +get k=f ts=2 tombstones +get k=f ts=3 tombstones +get k=f ts=4 tombstones +get k=f ts=5 tombstones +get k=f ts=6 tombstones +---- +get: "f" -> /BYTES/f1 @1.000000000,0 +get: "f" -> /BYTES/f1 @1.000000000,0 +get: "f" -> /BYTES/f3 @3.000000000,0 +get: "f" -> +get: "f" -> /BYTES/f5 @5.000000000,0 +get: "f" -> /BYTES/f5 @5.000000000,0 +get: "f" -> /BYTES/f1 @1.000000000,0 +get: "f" -> /BYTES/f1 @1.000000000,0 +get: "f" -> /BYTES/f3 @3.000000000,0 +get: "f" -> / @4.000000000,0 +get: "f" -> /BYTES/f5 @5.000000000,0 +get: "f" -> /BYTES/f5 @5.000000000,0 + run ok get k=g ts=3 get k=g ts=4 @@ -184,6 +211,36 @@ get: "h" -> /BYTES/h2 @2.000000000,0 get: "h" -> / @4.000000000,0 get: "h" -> / @4.000000000,0 +run ok +get k=j ts=3 +get k=j ts=4 +get k=j ts=5 +get k=j ts=3 tombstones +get k=j ts=4 tombstones +get k=j ts=5 tombstones +---- +get: "j" -> +get: "j" -> +get: "j" -> +get: "j" -> +get: "j" -> / @4.000000000,0 +get: "j" -> / @4.000000000,0 + +run ok +get k=k ts=3 +get k=k ts=4 +get k=k ts=5 +get k=k ts=3 tombstones +get k=k ts=4 tombstones +get k=k ts=5 tombstones +---- +get: "k" -> +get: "k" -> +get: "k" -> +get: "k" -> +get: "k" -> / @4.000000000,0 +get: "k" -> / @4.000000000,0 + # failOnMoreRecent: c run error get k=c ts=1 failOnMoreRecent @@ -264,6 +321,18 @@ get k=b ts=1 globalUncertaintyLimit=2 get: "b" -> error: (*roachpb.ReadWithinUncertaintyIntervalError:) ReadWithinUncertaintyIntervalError: read at time 1.000000000,0 encountered previous write with future timestamp 2.000000000,0 within uncertainty interval `t <= (local=0,0, global=0,0)`; observed timestamps: [] +# globalUncertaintyLimit: d +run ok +get k=d ts=1 globalUncertaintyLimit=1 +---- +get: "d" -> + +run error +get k=d ts=1 globalUncertaintyLimit=2 +---- +get: "d" -> +error: (*roachpb.ReadWithinUncertaintyIntervalError:) ReadWithinUncertaintyIntervalError: read at time 1.000000000,0 encountered previous write with future timestamp 2.000000000,0 within uncertainty interval `t <= (local=0,0, global=0,0)`; observed timestamps: [] + # globalUncertaintyLimit: g run ok get k=g ts=1 globalUncertaintyLimit=1 diff --git a/pkg/storage/testdata/mvcc_histories/range_tombstone_gets_complex b/pkg/storage/testdata/mvcc_histories/range_tombstone_gets_complex index 00b817931319..713638fa69b5 100644 --- a/pkg/storage/testdata/mvcc_histories/range_tombstone_gets_complex +++ b/pkg/storage/testdata/mvcc_histories/range_tombstone_gets_complex @@ -26,10 +26,10 @@ put k=d ts=4 v=d4 put k=e ts=3 v=e3 put k=f ts=2 v=f2 put k=g ts=2 v=g2 -del_range_ts k=f end=h ts=3 localTs=4 +del_range_ts k=f end=h ts=3 localTs=2 put k=f ts=4 v=f4 put k=g ts=4 v=g4 -del_range_ts k=c end=h ts=5 +del_range_ts k=c end=h ts=5 localTs=6 put k=f ts=6 v=f6 put k=h ts=3 v=h3 put k=k ts=5 v=k5 @@ -67,13 +67,13 @@ stats: key_count=+1 key_bytes=+14 val_count=+1 val_bytes=+7 live_count=+1 live_b stats: key_count=+1 key_bytes=+14 val_count=+1 val_bytes=+7 live_count=+1 live_bytes=+21 >> put k=g ts=2 v=g2 stats: key_count=+1 key_bytes=+14 val_count=+1 val_bytes=+7 live_count=+1 live_bytes=+21 ->> del_range_ts k=f end=h ts=3 localTs=4 -stats: range_key_count=+1 range_key_bytes=+13 range_val_count=+1 live_count=-2 live_bytes=-42 gc_bytes_age=+5335 +>> del_range_ts k=f end=h ts=3 localTs=2 +stats: range_key_count=+1 range_key_bytes=+13 range_val_count=+1 range_val_bytes=+13 live_count=-2 live_bytes=-42 gc_bytes_age=+6596 >> put k=f ts=4 v=f4 stats: key_bytes=+12 val_count=+1 val_bytes=+7 live_count=+1 live_bytes=+21 gc_bytes_age=-194 >> put k=g ts=4 v=g4 stats: key_bytes=+12 val_count=+1 val_bytes=+7 live_count=+1 live_bytes=+21 gc_bytes_age=-194 ->> del_range_ts k=c end=h ts=5 +>> del_range_ts k=c end=h ts=5 localTs=6 stats: range_key_count=+1 range_key_bytes=+49 range_val_count=+5 live_count=-4 live_bytes=-84 gc_bytes_age=+12665 >> put k=f ts=6 v=f6 stats: key_bytes=+12 val_count=+1 val_bytes=+7 live_count=+1 live_bytes=+21 gc_bytes_age=-190 @@ -93,7 +93,7 @@ rangekey: {a-b}/[1.000000000,0=/] rangekey: {b-c}/[3.000000000,0=/ 1.000000000,0=/] rangekey: {c-d}/[5.000000000,0=/ 3.000000000,0=/ 1.000000000,0=/] rangekey: {d-f}/[5.000000000,0=/ 1.000000000,0=/] -rangekey: {f-h}/[5.000000000,0=/ 3.000000000,0=/] +rangekey: {f-h}/[5.000000000,0=/ 3.000000000,0={localTs=2.000000000,0}/] rangekey: {h-k}/[1.000000000,0=/] rangekey: {l-n}/[5.000000000,0=/] rangekey: {n-o}/[5.000000000,0=/ 3.000000000,0=/] @@ -116,7 +116,7 @@ data: "h"/3.000000000,0 -> /BYTES/h3 meta: "j"/0,0 -> txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true data: "j"/7.000000000,0 -> /BYTES/j7 data: "k"/5.000000000,0 -> /BYTES/k5 -stats: key_count=9 key_bytes=210 val_count=16 val_bytes=242 range_key_count=8 range_key_bytes=158 range_val_count=14 live_count=5 live_bytes=249 gc_bytes_age=34691 intent_count=3 intent_bytes=57 separated_intent_count=3 intent_age=279 +stats: key_count=9 key_bytes=210 val_count=16 val_bytes=242 range_key_count=8 range_key_bytes=158 range_val_count=14 range_val_bytes=13 live_count=5 live_bytes=249 gc_bytes_age=35952 intent_count=3 intent_bytes=57 separated_intent_count=3 intent_age=279 # Run gets for all keys at all timestamps, with tombstones and intents. run ok diff --git a/pkg/storage/testdata/mvcc_histories/range_tombstone_scans_complex b/pkg/storage/testdata/mvcc_histories/range_tombstone_scans_complex index 9fceedd2aabf..f6d60bf3103e 100644 --- a/pkg/storage/testdata/mvcc_histories/range_tombstone_scans_complex +++ b/pkg/storage/testdata/mvcc_histories/range_tombstone_scans_complex @@ -26,10 +26,10 @@ put k=d ts=4 v=d4 put k=e ts=3 v=e3 put k=f ts=2 v=f2 put k=g ts=2 v=g2 -del_range_ts k=f end=h ts=3 localTs=4 +del_range_ts k=f end=h ts=3 localTs=2 put k=f ts=4 v=f4 put k=g ts=4 v=g4 -del_range_ts k=c end=h ts=5 +del_range_ts k=c end=h ts=5 localTs=6 put k=f ts=6 v=f6 put k=h ts=3 v=h3 put k=k ts=5 v=k5 @@ -67,13 +67,13 @@ stats: key_count=+1 key_bytes=+14 val_count=+1 val_bytes=+7 live_count=+1 live_b stats: key_count=+1 key_bytes=+14 val_count=+1 val_bytes=+7 live_count=+1 live_bytes=+21 >> put k=g ts=2 v=g2 stats: key_count=+1 key_bytes=+14 val_count=+1 val_bytes=+7 live_count=+1 live_bytes=+21 ->> del_range_ts k=f end=h ts=3 localTs=4 -stats: range_key_count=+1 range_key_bytes=+13 range_val_count=+1 live_count=-2 live_bytes=-42 gc_bytes_age=+5335 +>> del_range_ts k=f end=h ts=3 localTs=2 +stats: range_key_count=+1 range_key_bytes=+13 range_val_count=+1 range_val_bytes=+13 live_count=-2 live_bytes=-42 gc_bytes_age=+6596 >> put k=f ts=4 v=f4 stats: key_bytes=+12 val_count=+1 val_bytes=+7 live_count=+1 live_bytes=+21 gc_bytes_age=-194 >> put k=g ts=4 v=g4 stats: key_bytes=+12 val_count=+1 val_bytes=+7 live_count=+1 live_bytes=+21 gc_bytes_age=-194 ->> del_range_ts k=c end=h ts=5 +>> del_range_ts k=c end=h ts=5 localTs=6 stats: range_key_count=+1 range_key_bytes=+49 range_val_count=+5 live_count=-4 live_bytes=-84 gc_bytes_age=+12665 >> put k=f ts=6 v=f6 stats: key_bytes=+12 val_count=+1 val_bytes=+7 live_count=+1 live_bytes=+21 gc_bytes_age=-190 @@ -93,7 +93,7 @@ rangekey: {a-b}/[1.000000000,0=/] rangekey: {b-c}/[3.000000000,0=/ 1.000000000,0=/] rangekey: {c-d}/[5.000000000,0=/ 3.000000000,0=/ 1.000000000,0=/] rangekey: {d-f}/[5.000000000,0=/ 1.000000000,0=/] -rangekey: {f-h}/[5.000000000,0=/ 3.000000000,0=/] +rangekey: {f-h}/[5.000000000,0=/ 3.000000000,0={localTs=2.000000000,0}/] rangekey: {h-k}/[1.000000000,0=/] rangekey: {l-n}/[5.000000000,0=/] rangekey: {n-o}/[5.000000000,0=/ 3.000000000,0=/] @@ -116,7 +116,7 @@ data: "h"/3.000000000,0 -> /BYTES/h3 meta: "j"/0,0 -> txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true data: "j"/7.000000000,0 -> /BYTES/j7 data: "k"/5.000000000,0 -> /BYTES/k5 -stats: key_count=9 key_bytes=210 val_count=16 val_bytes=242 range_key_count=8 range_key_bytes=158 range_val_count=14 live_count=5 live_bytes=249 gc_bytes_age=34691 intent_count=3 intent_bytes=57 separated_intent_count=3 intent_age=279 +stats: key_count=9 key_bytes=210 val_count=16 val_bytes=242 range_key_count=8 range_key_bytes=158 range_val_count=14 range_val_bytes=13 live_count=5 live_bytes=249 gc_bytes_age=35952 intent_count=3 intent_bytes=57 separated_intent_count=3 intent_age=279 # Forward scans at all timestamps. run ok From f61a91c3b3ca2c7d68d93a4581211f62a1116bd5 Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Wed, 14 Dec 2022 20:56:19 +0000 Subject: [PATCH 2/3] storage: simplify some `pebbleMVCCScanner` iteration code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This also yields a nice performance boost for reverse scans, by removing unnecessary peeking which incurs key copies. ``` name old time/op new time/op delta MVCCReverseScan_Pebble/rows=1/versions=1/valueSize=8/numRangeKeys=0-24 5.65µs ± 2% 5.62µs ± 1% ~ (p=0.897 n=5+5) MVCCReverseScan_Pebble/rows=1/versions=10/valueSize=8/numRangeKeys=0-24 22.7µs ± 0% 22.9µs ± 1% +0.94% (p=0.032 n=5+5) MVCCReverseScan_Pebble/rows=100/versions=1/valueSize=8/numRangeKeys=0-24 43.0µs ± 1% 40.3µs ± 1% -6.18% (p=0.008 n=5+5) MVCCReverseScan_Pebble/rows=100/versions=10/valueSize=8/numRangeKeys=0-24 352µs ± 1% 340µs ± 2% -3.57% (p=0.008 n=5+5) MVCCReverseScan_Pebble/rows=10000/versions=1/valueSize=8/numRangeKeys=0-24 3.41ms ± 1% 3.21ms ± 1% -5.68% (p=0.008 n=5+5) MVCCReverseScan_Pebble/rows=10000/versions=10/valueSize=8/numRangeKeys=0-24 32.5ms ± 2% 31.1ms ± 6% ~ (p=0.095 n=5+5) ``` Release note: None --- pkg/storage/pebble_mvcc_scanner.go | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/pkg/storage/pebble_mvcc_scanner.go b/pkg/storage/pebble_mvcc_scanner.go index c2c1c958c25f..0b994b340634 100644 --- a/pkg/storage/pebble_mvcc_scanner.go +++ b/pkg/storage/pebble_mvcc_scanner.go @@ -1017,16 +1017,12 @@ func (p *pebbleMVCCScanner) backwardLatestVersion(key []byte, i int) bool { // "current" key directly). func (p *pebbleMVCCScanner) prevKey(key []byte) bool { for i := 0; i < p.itersBeforeSeek; i++ { - peekedKey, ok := p.iterPeekPrev() - if !ok { - return false - } - if !bytes.Equal(peekedKey, key) { - return p.backwardLatestVersion(peekedKey, i+1) - } if !p.iterPrev() { return false } + if !bytes.Equal(p.curUnsafeKey.Key, key) { + return p.backwardLatestVersion(p.curUnsafeKey.Key, i+1) + } } p.decrementItersBeforeSeek() @@ -1433,23 +1429,20 @@ func (p *pebbleMVCCScanner) iterNext() bool { p.parentReverse = false if p.reverse && p.peeked { // If we have peeked at the previous entry, we need to advance the iterator - // twice. + // to get back to the current entry. p.peeked = false if !p.iterValid() { // We were peeked off the beginning of iteration. Seek to the first - // entry, and then advance one step. + // entry, since that is the current entry. p.parent.SeekGE(MVCCKey{Key: p.start}) - if !p.iterValid() { - return false - } + } else { p.parent.Next() - return p.updateCurrent() } - p.parent.Next() if !p.iterValid() { return false } } + // Step forward from the current entry. p.parent.Next() return p.updateCurrent() } @@ -1459,9 +1452,9 @@ func (p *pebbleMVCCScanner) iterPrev() bool { p.parentReverse = true if p.peeked { p.peeked = false - return p.updateCurrent() + } else { + p.parent.Prev() } - p.parent.Prev() return p.updateCurrent() } From 2e7292da95031e199b231a0bc4d2285235abf25f Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Mon, 12 Dec 2022 11:25:29 +0000 Subject: [PATCH 3/3] storage: handle MVCC range keys in `pebbleMVCCScanner` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch removes `PointSynthesizingIter`, and instead implements MVCC range key handling in `pebbleMVCCScanner` directly. `PointSynthesizingIter` was a mistake. It was intended to reduce the complexity of integrating MVCC range key processing into `pebbleMVCCScanner`, but ended up introducing even more complexity at a significant performance cost. `MVCCScan` point synthesis has changed slightly: while point keys were previously synthesized at the start bound of an MVCC range key, they are now only synthesized above existing point keys. This was mostly an artifact of having to do conflict checks based on synthesized points (to ensure bare range keys were handled), but this is no longer necessary since `pebbleMVCCScanner` can inspect the range keys directly. `MVCCGet` point synthesis remains unchanged: we always synthesize point keys regardless of whether there is an existing point key below it. This is necessary for e.g. conflict checks on top of `MVCCGet`, such as transaction read refreshes. `MVCCScan` shows a significant performance gain with range keys, and negligible overhead without them. We still see a hefty performance penalty when range keys are present though, compared to the `numRangeKeys=0` case, which likely needs optimization in Pebble. ``` name old time/op new time/op delta MVCCScan_Pebble/rows=1/versions=1/valueSize=8/numRangeKeys=0-24 5.31µs ± 1% 5.39µs ± 2% ~ (p=0.095 n=5+5) MVCCScan_Pebble/rows=1/versions=1/valueSize=8/numRangeKeys=1-24 10.7µs ± 1% 10.2µs ± 3% -4.39% (p=0.008 n=5+5) MVCCScan_Pebble/rows=1/versions=1/valueSize=8/numRangeKeys=100-24 144µs ± 1% 94µs ± 2% -34.54% (p=0.008 n=5+5) MVCCScan_Pebble/rows=1/versions=10/valueSize=8/numRangeKeys=0-24 20.4µs ± 2% 20.6µs ± 3% ~ (p=0.421 n=5+5) MVCCScan_Pebble/rows=1/versions=10/valueSize=8/numRangeKeys=1-24 28.0µs ± 2% 26.7µs ± 2% -4.41% (p=0.008 n=5+5) MVCCScan_Pebble/rows=1/versions=10/valueSize=8/numRangeKeys=100-24 120µs ± 1% 82µs ± 1% -31.89% (p=0.008 n=5+5) MVCCScan_Pebble/rows=100/versions=1/valueSize=8/numRangeKeys=0-24 33.0µs ± 1% 33.3µs ± 1% ~ (p=0.056 n=5+5) MVCCScan_Pebble/rows=100/versions=1/valueSize=8/numRangeKeys=1-24 55.0µs ± 1% 46.1µs ± 1% -16.15% (p=0.008 n=5+5) MVCCScan_Pebble/rows=100/versions=1/valueSize=8/numRangeKeys=100-24 158µs ± 1% 142µs ± 1% -10.15% (p=0.008 n=5+5) MVCCScan_Pebble/rows=100/versions=10/valueSize=8/numRangeKeys=0-24 137µs ± 1% 139µs ± 3% +1.72% (p=0.032 n=5+5) MVCCScan_Pebble/rows=100/versions=10/valueSize=8/numRangeKeys=1-24 223µs ± 2% 187µs ± 0% -15.79% (p=0.008 n=5+5) MVCCScan_Pebble/rows=100/versions=10/valueSize=8/numRangeKeys=100-24 322µs ± 1% 264µs ± 1% -17.95% (p=0.008 n=5+5) MVCCScan_Pebble/rows=10000/versions=1/valueSize=8/numRangeKeys=0-24 2.41ms ± 1% 2.41ms ± 0% ~ (p=0.690 n=5+5) MVCCScan_Pebble/rows=10000/versions=1/valueSize=8/numRangeKeys=1-24 3.99ms ± 1% 3.19ms ± 0% -20.08% (p=0.016 n=5+4) MVCCScan_Pebble/rows=10000/versions=1/valueSize=8/numRangeKeys=100-24 4.74ms ± 1% 3.77ms ± 1% -20.53% (p=0.008 n=5+5) MVCCScan_Pebble/rows=10000/versions=10/valueSize=8/numRangeKeys=0-24 10.7ms ± 3% 10.7ms ± 3% ~ (p=0.421 n=5+5) MVCCScan_Pebble/rows=10000/versions=10/valueSize=8/numRangeKeys=1-24 18.1ms ± 1% 15.0ms ± 0% -17.33% (p=0.008 n=5+5) MVCCScan_Pebble/rows=10000/versions=10/valueSize=8/numRangeKeys=100-24 23.1ms ± 4% 16.4ms ± 6% -28.90% (p=0.008 n=5+5) ``` `MVCCScan` in reverse shows a moderate improvement, primarily due to the additional checks needed to detect bare range keys in reverse since it can land on them without `RangeKeyChanged` firing. ``` name old time/op new time/op delta MVCCReverseScan_Pebble/rows=1/versions=1/valueSize=8/numRangeKeys=0-24 5.69µs ± 2% 5.68µs ± 1% ~ (p=0.841 n=5+5) MVCCReverseScan_Pebble/rows=1/versions=1/valueSize=8/numRangeKeys=1-24 11.0µs ± 2% 10.8µs ± 1% -2.01% (p=0.016 n=5+5) MVCCReverseScan_Pebble/rows=1/versions=1/valueSize=8/numRangeKeys=100-24 100µs ± 1% 95µs ± 1% -5.67% (p=0.008 n=5+5) MVCCReverseScan_Pebble/rows=1/versions=10/valueSize=8/numRangeKeys=0-24 23.1µs ± 1% 23.4µs ± 1% ~ (p=0.056 n=5+5) MVCCReverseScan_Pebble/rows=1/versions=10/valueSize=8/numRangeKeys=1-24 31.5µs ± 1% 31.4µs ± 1% ~ (p=0.421 n=5+5) MVCCReverseScan_Pebble/rows=1/versions=10/valueSize=8/numRangeKeys=100-24 87.5µs ± 1% 91.7µs ± 1% +4.88% (p=0.008 n=5+5) MVCCReverseScan_Pebble/rows=100/versions=1/valueSize=8/numRangeKeys=0-24 40.6µs ± 1% 41.3µs ± 1% +1.60% (p=0.008 n=5+5) MVCCReverseScan_Pebble/rows=100/versions=1/valueSize=8/numRangeKeys=1-24 60.8µs ± 1% 55.8µs ± 1% -8.18% (p=0.008 n=5+5) MVCCReverseScan_Pebble/rows=100/versions=1/valueSize=8/numRangeKeys=100-24 159µs ± 1% 145µs ± 0% -8.49% (p=0.008 n=5+5) MVCCReverseScan_Pebble/rows=100/versions=10/valueSize=8/numRangeKeys=0-24 342µs ± 1% 344µs ± 3% ~ (p=1.000 n=5+5) MVCCReverseScan_Pebble/rows=100/versions=10/valueSize=8/numRangeKeys=1-24 486µs ± 1% 457µs ± 1% -6.05% (p=0.008 n=5+5) MVCCReverseScan_Pebble/rows=100/versions=10/valueSize=8/numRangeKeys=100-24 701µs ± 3% 663µs ± 2% -5.44% (p=0.008 n=5+5) MVCCReverseScan_Pebble/rows=10000/versions=1/valueSize=8/numRangeKeys=0-24 3.12ms ± 1% 3.17ms ± 0% +1.36% (p=0.008 n=5+5) MVCCReverseScan_Pebble/rows=10000/versions=1/valueSize=8/numRangeKeys=1-24 4.51ms ± 2% 4.09ms ± 1% -9.36% (p=0.008 n=5+5) MVCCReverseScan_Pebble/rows=10000/versions=1/valueSize=8/numRangeKeys=100-24 5.36ms ± 0% 4.66ms ± 1% -13.21% (p=0.008 n=5+5) MVCCReverseScan_Pebble/rows=10000/versions=10/valueSize=8/numRangeKeys=0-24 32.5ms ± 8% 31.6ms ± 6% ~ (p=0.310 n=5+5) MVCCReverseScan_Pebble/rows=10000/versions=10/valueSize=8/numRangeKeys=1-24 45.7ms ± 6% 40.2ms ±10% -12.14% (p=0.016 n=5+5) MVCCReverseScan_Pebble/rows=10000/versions=10/valueSize=8/numRangeKeys=100-24 64.5ms ± 6% 59.0ms ±11% -8.50% (p=0.032 n=5+5) ``` Scans across garbage show even better performance gains: ``` name old time/op new time/op delta MVCCScanGarbage_Pebble/rows=1/versions=1/numRangeKeys=0/tombstones=false-24 4.99µs ± 2% 4.81µs ± 1% -3.64% (p=0.008 n=5+5) MVCCScanGarbage_Pebble/rows=1/versions=1/numRangeKeys=0/tombstones=true-24 5.10µs ± 1% 5.03µs ± 1% -1.33% (p=0.032 n=5+5) MVCCScanGarbage_Pebble/rows=1/versions=1/numRangeKeys=1/tombstones=false-24 9.25µs ± 3% 8.47µs ± 2% -8.41% (p=0.008 n=5+5) MVCCScanGarbage_Pebble/rows=1/versions=1/numRangeKeys=1/tombstones=true-24 9.44µs ± 1% 8.94µs ± 3% -5.33% (p=0.008 n=5+5) MVCCScanGarbage_Pebble/rows=1/versions=1/numRangeKeys=100/tombstones=false-24 150µs ± 1% 96µs ± 1% -36.27% (p=0.008 n=5+5) MVCCScanGarbage_Pebble/rows=1/versions=1/numRangeKeys=100/tombstones=true-24 96.7µs ± 0% 95.4µs ± 1% -1.33% (p=0.008 n=5+5) MVCCScanGarbage_Pebble/rows=1/versions=10/numRangeKeys=0/tombstones=false-24 9.10µs ± 3% 9.24µs ± 2% ~ (p=0.151 n=5+5) MVCCScanGarbage_Pebble/rows=1/versions=10/numRangeKeys=0/tombstones=true-24 9.43µs ± 2% 9.41µs ± 2% ~ (p=0.841 n=5+5) MVCCScanGarbage_Pebble/rows=1/versions=10/numRangeKeys=1/tombstones=false-24 13.2µs ± 3% 12.6µs ± 2% -4.73% (p=0.008 n=5+5) MVCCScanGarbage_Pebble/rows=1/versions=10/numRangeKeys=1/tombstones=true-24 14.5µs ± 1% 13.8µs ± 2% -4.60% (p=0.008 n=5+5) MVCCScanGarbage_Pebble/rows=1/versions=10/numRangeKeys=100/tombstones=false-24 160µs ± 1% 105µs ± 1% -34.67% (p=0.008 n=5+5) MVCCScanGarbage_Pebble/rows=1/versions=10/numRangeKeys=100/tombstones=true-24 106µs ± 1% 105µs ± 1% ~ (p=0.548 n=5+5) MVCCScanGarbage_Pebble/rows=100/versions=1/numRangeKeys=0/tombstones=false-24 22.2µs ± 1% 22.6µs ± 1% +2.21% (p=0.008 n=5+5) MVCCScanGarbage_Pebble/rows=100/versions=1/numRangeKeys=0/tombstones=true-24 32.0µs ± 0% 32.2µs ± 1% +0.80% (p=0.016 n=5+5) MVCCScanGarbage_Pebble/rows=100/versions=1/numRangeKeys=1/tombstones=false-24 18.9µs ± 1% 18.2µs ± 1% -3.64% (p=0.008 n=5+5) MVCCScanGarbage_Pebble/rows=100/versions=1/numRangeKeys=1/tombstones=true-24 55.6µs ± 0% 45.4µs ± 1% -18.27% (p=0.008 n=5+5) MVCCScanGarbage_Pebble/rows=100/versions=1/numRangeKeys=100/tombstones=false-24 215µs ± 1% 120µs ± 2% -44.45% (p=0.008 n=5+5) MVCCScanGarbage_Pebble/rows=100/versions=1/numRangeKeys=100/tombstones=true-24 277µs ± 1% 148µs ± 1% -46.36% (p=0.008 n=5+5) MVCCScanGarbage_Pebble/rows=100/versions=10/numRangeKeys=0/tombstones=false-24 108µs ± 1% 109µs ± 3% ~ (p=1.000 n=5+5) MVCCScanGarbage_Pebble/rows=100/versions=10/numRangeKeys=0/tombstones=true-24 122µs ± 0% 122µs ± 2% ~ (p=0.421 n=5+5) MVCCScanGarbage_Pebble/rows=100/versions=10/numRangeKeys=1/tombstones=false-24 67.6µs ± 1% 67.0µs ± 1% ~ (p=0.095 n=5+5) MVCCScanGarbage_Pebble/rows=100/versions=10/numRangeKeys=1/tombstones=true-24 200µs ± 0% 168µs ± 1% -16.04% (p=0.008 n=5+5) MVCCScanGarbage_Pebble/rows=100/versions=10/numRangeKeys=100/tombstones=false-24 324µs ± 1% 178µs ± 1% -45.17% (p=0.008 n=5+5) MVCCScanGarbage_Pebble/rows=100/versions=10/numRangeKeys=100/tombstones=true-24 306µs ± 1% 274µs ± 1% -10.33% (p=0.008 n=5+5) MVCCScanGarbage_Pebble/rows=10000/versions=1/numRangeKeys=0/tombstones=false-24 1.61ms ± 0% 1.65ms ± 2% +2.27% (p=0.008 n=5+5) MVCCScanGarbage_Pebble/rows=10000/versions=1/numRangeKeys=0/tombstones=true-24 2.33ms ± 2% 2.35ms ± 1% ~ (p=0.151 n=5+5) MVCCScanGarbage_Pebble/rows=10000/versions=1/numRangeKeys=1/tombstones=false-24 141µs ± 0% 140µs ± 1% ~ (p=0.310 n=5+5) MVCCScanGarbage_Pebble/rows=10000/versions=1/numRangeKeys=1/tombstones=true-24 4.31ms ± 1% 3.25ms ± 0% -24.59% (p=0.008 n=5+5) MVCCScanGarbage_Pebble/rows=10000/versions=1/numRangeKeys=100/tombstones=false-24 6.64ms ± 8% 1.35ms ± 1% -79.67% (p=0.008 n=5+5) MVCCScanGarbage_Pebble/rows=10000/versions=1/numRangeKeys=100/tombstones=true-24 16.4ms ± 2% 4.0ms ± 1% -75.65% (p=0.008 n=5+5) MVCCScanGarbage_Pebble/rows=10000/versions=10/numRangeKeys=0/tombstones=false-24 9.33ms ± 1% 9.24ms ± 0% ~ (p=0.310 n=5+5) MVCCScanGarbage_Pebble/rows=10000/versions=10/numRangeKeys=0/tombstones=true-24 10.3ms ± 1% 10.1ms ± 1% -1.48% (p=0.008 n=5+5) MVCCScanGarbage_Pebble/rows=10000/versions=10/numRangeKeys=1/tombstones=false-24 213µs ± 2% 213µs ± 1% ~ (p=0.841 n=5+5) MVCCScanGarbage_Pebble/rows=10000/versions=10/numRangeKeys=1/tombstones=true-24 17.7ms ± 1% 14.4ms ± 3% -18.61% (p=0.008 n=5+5) MVCCScanGarbage_Pebble/rows=10000/versions=10/numRangeKeys=100/tombstones=false-24 11.1ms ± 4% 2.9ms ± 4% -73.89% (p=0.008 n=5+5) MVCCScanGarbage_Pebble/rows=10000/versions=10/numRangeKeys=100/tombstones=true-24 18.2ms ± 1% 15.2ms ± 4% -16.45% (p=0.008 n=5+5) ``` And `MVCCGet` performance remains largely unchanged, as expected given that the cost is dominated by the seek. ``` name old time/op new time/op delta MVCCGet_Pebble/batch=false/versions=1/valueSize=8/numRangeKeys=0-24 5.20µs ± 1% 5.23µs ± 2% ~ (p=0.841 n=5+5) MVCCGet_Pebble/batch=false/versions=1/valueSize=8/numRangeKeys=1-24 9.52µs ± 1% 9.23µs ± 1% -3.07% (p=0.008 n=5+5) MVCCGet_Pebble/batch=false/versions=1/valueSize=8/numRangeKeys=100-24 92.4µs ± 1% 91.6µs ± 1% -0.93% (p=0.032 n=5+5) MVCCGet_Pebble/batch=false/versions=10/valueSize=8/numRangeKeys=0-24 20.2µs ± 2% 20.2µs ± 3% ~ (p=0.651 n=5+5) MVCCGet_Pebble/batch=false/versions=10/valueSize=8/numRangeKeys=1-24 25.9µs ± 2% 25.5µs ± 1% ~ (p=0.056 n=5+5) MVCCGet_Pebble/batch=false/versions=10/valueSize=8/numRangeKeys=100-24 79.8µs ± 1% 80.1µs ± 2% ~ (p=1.000 n=5+5) MVCCGet_Pebble/batch=false/versions=100/valueSize=8/numRangeKeys=0-24 82.9µs ± 2% 83.3µs ± 1% ~ (p=0.310 n=5+5) MVCCGet_Pebble/batch=false/versions=100/valueSize=8/numRangeKeys=1-24 92.2µs ± 3% 90.2µs ± 1% ~ (p=0.095 n=5+5) MVCCGet_Pebble/batch=false/versions=100/valueSize=8/numRangeKeys=100-24 151µs ± 2% 149µs ± 3% ~ (p=0.690 n=5+5) MVCCGet_Pebble/batch=true/versions=1/valueSize=8/numRangeKeys=0-24 3.02µs ± 1% 3.04µs ± 1% ~ (p=0.310 n=5+5) MVCCGet_Pebble/batch=true/versions=1/valueSize=8/numRangeKeys=1-24 5.44µs ± 2% 5.29µs ± 3% ~ (p=0.056 n=5+5) MVCCGet_Pebble/batch=true/versions=1/valueSize=8/numRangeKeys=100-24 53.0µs ± 1% 52.7µs ± 1% ~ (p=0.095 n=5+5) MVCCGet_Pebble/batch=true/versions=10/valueSize=8/numRangeKeys=0-24 17.0µs ± 1% 17.1µs ± 2% ~ (p=1.000 n=5+5) MVCCGet_Pebble/batch=true/versions=10/valueSize=8/numRangeKeys=1-24 21.4µs ± 5% 20.9µs ± 3% ~ (p=0.151 n=5+5) MVCCGet_Pebble/batch=true/versions=10/valueSize=8/numRangeKeys=100-24 73.1µs ± 2% 72.7µs ± 1% ~ (p=0.841 n=5+5) MVCCGet_Pebble/batch=true/versions=100/valueSize=8/numRangeKeys=0-24 83.9µs ± 1% 84.3µs ± 1% ~ (p=0.310 n=5+5) MVCCGet_Pebble/batch=true/versions=100/valueSize=8/numRangeKeys=1-24 90.8µs ± 2% 90.5µs ± 1% ~ (p=1.000 n=5+5) MVCCGet_Pebble/batch=true/versions=100/valueSize=8/numRangeKeys=100-24 146µs ± 1% 146µs ± 0% ~ (p=0.556 n=5+4) ``` Release note: None --- docs/tech-notes/mvcc-range-tombstones.md | 43 +- pkg/storage/BUILD.bazel | 1 - pkg/storage/mvcc.go | 32 +- pkg/storage/mvcc_history_test.go | 5 +- pkg/storage/pebble_mvcc_scanner.go | 371 ++++-- pkg/storage/point_synthesizing_iter.go | 1065 ---------------- .../mvcc_histories/iter_prefix_next_key | 53 - .../range_tombstone_iter_point_synthesis | 1072 ----------------- ...tombstone_iter_point_synthesis_ts_conflict | 851 ------------- .../mvcc_histories/range_tombstone_scans | 34 +- .../range_tombstone_scans_complex | 49 +- ...ge_tombstone_scans_reverse_skip_regression | 11 +- .../range_tombstone_scans_ts_conflict | 8 +- 13 files changed, 309 insertions(+), 3286 deletions(-) delete mode 100644 pkg/storage/point_synthesizing_iter.go delete mode 100644 pkg/storage/testdata/mvcc_histories/range_tombstone_iter_point_synthesis delete mode 100644 pkg/storage/testdata/mvcc_histories/range_tombstone_iter_point_synthesis_ts_conflict diff --git a/docs/tech-notes/mvcc-range-tombstones.md b/docs/tech-notes/mvcc-range-tombstones.md index 9923de695f28..402778a5c358 100644 --- a/docs/tech-notes/mvcc-range-tombstones.md +++ b/docs/tech-notes/mvcc-range-tombstones.md @@ -402,39 +402,38 @@ However, callers may request tombstones to be emitted via the `Tombstones` option, e.g. for conflict checks and rangefeed value diffs. We do not want to burden the rest of the codebase with having to handle MVCC range tombstones explicitly, and therefore do not expose them directly. Instead, we synthesize -MVCC point tombstones at the start of MVCC range tombstones and wherever they -overlap MVCC point keys, via `pointSynthesizingIter`. +MVCC point tombstones. A scan emits synthetic point tombstones above existing +point keys, while a get emits synthetic point tombstones if a range tombstone +overlaps the key, regardless of whether there is an existing point key below it. Consider this example: ``` Time -4 [---|----------) -3 c3 +6 [---|----------) +5 c5 +4 [----------) +3 2 [----------) -1 c1 d1 +1 d1 a b c d e Key ``` -An `MVCCScan` with `Tombstones` across this span at timestamp >= 4 would emit -synthetic MVCC point tombstones at `a@4`, `b@4`, `c@4`, and `d@4`. - -Note, however, that these synthetic point tombstones are not always stable, -because the start key of MVCC range tombstones can change, e.g. due to iterator -truncation, fragmentation, and CRDB range splits or merges. For example, an -`MVCCScan` across `[bar-foo)` would emit a synthetic MVCC point tombstone at -`bar@4`, because this is the truncated start bound of the range tombstone, but -a scan across `[abc-def)` would not see `bar@4`, but instead see `abc@4`. The -same would happen if the CRDB range was split or merged at the key `bar`. +An `MVCCScan` with `Tombstones` across this span at timestamp >= 6 would emit +synthetic MVCC point tombstones at `c@6` and `d@6`. However, a scan at timestamp +3 would only emit a synthetic tombstone at `d@2`, because the range tombstone is +below the point `c@5` and point tombstones are not synthesized below point keys. +Similarly, a scan across `[a-b)` at any timestamp would not emit anything. Additionally, an `MVCCGet` will emit a synthetic point tombstone for the key -even if it has no existing point keys: `Get(bar)` at timestamp >= 4 would return -a tombstone `bar@4` even though there is no real point key at `bar`, as this -might be required e.g. for conflict checks. These synthetic tombstones would -similarly not be visible to an `MVCCScan`. - -Callers must take care not to rely on these point tombstones being stable, or -use an `MVCCIterator` that exposes MVCC range tombstones directly if needed. +even if it has no existing point key below it, as these might be required for +conflict checks. For example, `Get(bar)` at timestamp >= 6 would return a +tombstone `bar@6` even though there is no real point key at `bar`. Similarly, a +`Get(c)` at timestamp 3 would return `c@2` even though the point key `c@5` is +above it. These synthetic tombstones would not be visible to an `MVCCScan`. + +If callers need better visibility into range tombstones, they must use an +`MVCCIterator` that exposes them directly. ### KV APIs diff --git a/pkg/storage/BUILD.bazel b/pkg/storage/BUILD.bazel index 13ca83c693bc..ea52291b6638 100644 --- a/pkg/storage/BUILD.bazel +++ b/pkg/storage/BUILD.bazel @@ -30,7 +30,6 @@ go_library( "pebble_iterator.go", "pebble_merge.go", "pebble_mvcc_scanner.go", - "point_synthesizing_iter.go", "read_as_of_iterator.go", "replicas_storage.go", "row_counter.go", diff --git a/pkg/storage/mvcc.go b/pkg/storage/mvcc.go index 240bc30f645e..43e830b5ff65 100644 --- a/pkg/storage/mvcc.go +++ b/pkg/storage/mvcc.go @@ -959,13 +959,12 @@ func newMVCCIterator( // In tombstones mode, if the most recent value is a deletion tombstone, the // result will be a non-nil roachpb.Value whose RawBytes field is nil. // Otherwise, a deletion tombstone results in a nil roachpb.Value. MVCC range -// tombstones will be emitted as synthetic point tombstones, regardless of whether -// there is an existing point key. +// tombstones are emitted as synthetic point tombstones, even if there is no +// existing point key at the position. // // NB: Synthetic tombstones generated for MVCC range tombstones may not be -// visible to an MVCCScan if there is no existing point key at the key, since -// MVCCScan only synthesizes them at the MVCC range tombstone's start key and -// around existing keys. Callers must not rely on such semantics. +// visible to an MVCCScan, since MVCCScan will only synthesize point tombstones +// above existing point keys. // // In inconsistent mode, if an intent is encountered, it will be placed in the // dedicated return parameter. By contrast, in consistent mode, an intent will @@ -3817,21 +3816,11 @@ type MVCCScanResult struct { // In tombstones mode, if the most recent value for a key is a deletion // tombstone, the scan result will contain a roachpb.KeyValue for that key whose // RawBytes field is nil. Otherwise, the key-value pair will be omitted from the -// result entirely. For MVCC range tombstones, synthetic MVCC point tombstones -// will be emitted at the start of the range tombstone and where they overlap a -// point key. -// -// NB: Synthetic MVCC point tombstones emitted for MVCC range tombstones are -// not stable, nor are they fully deterministic. For example, the start key will -// be truncated by iterator bounds, so an MVCCScan over a given key span may see -// a synthetic point tombstone at its start (if it overlaps an MVCC range -// tombstone), but this will not be emitted if a broader span is used (a -// different point tombstone will be emitted instead). Similarly, a CRDB range -// split/merge will split/merge MVCC range tombstones, changing which point -// tombstones are emitted. Furthermore, MVCCGet will synthesize an MVCC point -// tombstone if it overlaps an MVCC range tombstone and there is no existing -// point key there, while an MVCCScan will not emit these. Callers must take -// care not to rely on such semantics for MVCC tombstones. +// result entirely. MVCC range tombstones will be emitted as synthetic point +// tombstones above existing point keys, but not below them and not if they +// don't overlap any point keys at all. This is unlike MVCCGet, which will +// always synthesize point tombstones if the key overlaps a range tombstone, +// regardless of whether a point key exists below it. // // When scanning inconsistently, any encountered intents will be placed in the // dedicated result parameter. By contrast, when scanning consistently, any @@ -3921,8 +3910,7 @@ func MVCCScanAsTxn( // scan options specify an inconsistent scan, all "ignored" intents will be // returned. In consistent mode, intents are only ever returned as part of a // WriteIntentError. In Tombstones mode, MVCC range tombstones are emitted as -// synthetic point tombstones at their start key and around overlapping point -// keys. +// synthetic point tombstones above existing point keys. func MVCCIterate( ctx context.Context, reader Reader, diff --git a/pkg/storage/mvcc_history_test.go b/pkg/storage/mvcc_history_test.go index ea9743f58749..88f4ac808ea9 100644 --- a/pkg/storage/mvcc_history_test.go +++ b/pkg/storage/mvcc_history_test.go @@ -99,7 +99,7 @@ var ( // scan [t=] [ts=[,]] [resolve [status=]] k= [end=] [inconsistent] [skipLocked] [tombstones] [reverse] [failOnMoreRecent] [localUncertaintyLimit=[,]] [globalUncertaintyLimit=[,]] [max=] [targetbytes=] [wholeRows[=]] [allowEmpty] // export [k=] [end=] [ts=[,]] [kTs=[,]] [startTs=[,]] [maxIntents=] [allRevisions] [targetSize=] [maxSize=] [stopMidKey] [fingerprint] // -// iter_new [k=] [end=] [prefix] [kind=key|keyAndIntents] [types=pointsOnly|pointsWithRanges|pointsAndRanges|rangesOnly] [pointSynthesis] [maskBelow=[,]] +// iter_new [k=] [end=] [prefix] [kind=key|keyAndIntents] [types=pointsOnly|pointsWithRanges|pointsAndRanges|rangesOnly] [maskBelow=[,]] // iter_new_incremental [k=] [end=] [startTs=[,]] [endTs=[,]] [types=pointsOnly|pointsWithRanges|pointsAndRanges|rangesOnly] [maskBelow=[,]] [intents=error|aggregate|emit] // iter_seek_ge k= [ts=[,]] // iter_seek_lt k= [ts=[,]] @@ -1599,9 +1599,6 @@ func cmdIterNew(e *evalCtx) error { r := e.newReader() iter := r.NewMVCCIterator(kind, opts) - if e.hasArg("pointSynthesis") { - iter = storage.NewPointSynthesizingIter(iter) - } iter = newMetamorphicIterator(e.t, e.metamorphicIterSeed(), iter).(storage.MVCCIterator) if opts.Prefix != iter.IsPrefix() { return errors.Errorf("prefix iterator returned IsPrefix=false") diff --git a/pkg/storage/pebble_mvcc_scanner.go b/pkg/storage/pebble_mvcc_scanner.go index 0b994b340634..a701476d5d23 100644 --- a/pkg/storage/pebble_mvcc_scanner.go +++ b/pkg/storage/pebble_mvcc_scanner.go @@ -307,20 +307,9 @@ func extractResultKey(repr []byte) roachpb.Key { return key.Key } -// pebbleMVCCScanner handles MVCCScan / MVCCGet using a Pebble iterator. If any -// MVCC range tombstones are encountered, it synthesizes MVCC point tombstones -// by switching to a pointSynthesizingIter. +// pebbleMVCCScanner handles MVCCScan / MVCCGet using a Pebble iterator. type pebbleMVCCScanner struct { parent MVCCIterator - // parentReverse is true if the previous parent positioning operation was a - // reverse operation (SeekLT or Prev). This is needed to correctly initialize - // pointIter when encountering an MVCC range tombstone in reverse. - // TODO(erikgrinaker): Consider adding MVCCIterator.IsReverse() instead. - parentReverse bool - // pointIter is a point synthesizing iterator that wraps and replaces parent - // when an MVCC range tombstone is encountered. A separate reference to it is - // kept in order to release it back to its pool when the scanner is done. - pointIter *PointSynthesizingIter // memAccount is used to account for the size of the scan results. memAccount *mon.BoundAccount // lockTable is used to determine whether keys are locked in the in-memory @@ -383,6 +372,8 @@ type pebbleMVCCScanner struct { curRawKey []byte curUnsafeValue MVCCValue curRawValue []byte + curRangeKeys MVCCRangeKeyStack + savedRangeKeys MVCCRangeKeyStack results pebbleResults intents pebble.Batch // mostRecentTS stores the largest timestamp observed that is equal to or @@ -446,9 +437,6 @@ var pebbleMVCCScannerPool = sync.Pool{ } func (p *pebbleMVCCScanner) release() { - if p.pointIter != nil { - p.pointIter.release() - } // Discard most memory references before placing in pool. *p = pebbleMVCCScanner{ keyBuf: p.keyBuf, @@ -484,26 +472,25 @@ func (p *pebbleMVCCScanner) init( // get seeks to the start key exactly once and adds one KV to the result set. func (p *pebbleMVCCScanner) get(ctx context.Context) { - // The iterator may already be positioned on a range key that SeekGE hits, in - // which case RangeKeyChanged() wouldn't fire, so we enable point synthesis - // here if needed. We check this before SeekGE, because in the typical case - // this will be a new, unpositioned iterator, which allows omitting the - // HasPointAndRange() call. - if ok, _ := p.parent.Valid(); ok { - if _, hasRange := p.parent.HasPointAndRange(); hasRange { - if !p.enablePointSynthesis() { - return - } - } - } - - p.parentReverse = false p.parent.SeekGE(MVCCKey{Key: p.start}) - if !p.updateCurrent() { + if !p.iterValid() { return } - p.getOne(ctx) + var added bool + if p.processRangeKeys(true /* seeked */, false /* reverse */) { + if p.updateCurrent() { + _, added = p.getOne(ctx) + } + } p.maybeFailOnMoreRecent() + // Unlike scans, if tombstones are enabled, we synthesize point tombstones for + // MVCC range tombstones even if there is no existing point key below it. + // These are often needed for e.g. conflict checks. + if p.tombstones && !added && p.err == nil { + if rkv, ok := p.coveredByRangeKey(hlc.MinTimestamp); ok { + p.addSynthetic(ctx, p.curRangeKeys.Bounds.Key, rkv) + } + } } // advance advances the iterator according to the current state of the state @@ -544,26 +531,15 @@ func (p *pebbleMVCCScanner) scan( return nil, 0, 0, errors.AssertionFailedf("cannot use wholeRows without trackLastOffsets") } - // The iterator may already be positioned on a range key that the seek hits, - // in which case RangeKeyChanged() wouldn't fire, so we enable point synthesis - // here if needed. We check this before seeking, because in the typical case - // this will be a new, unpositioned iterator, which allows omitting the - // HasPointAndRange() call. - if ok, _ := p.parent.Valid(); ok { - if _, hasRange := p.parent.HasPointAndRange(); hasRange { - if !p.enablePointSynthesis() { - return nil, 0, 0, p.err - } - } - } - if p.reverse { if !p.iterSeekReverse(MVCCKey{Key: p.end}) { + p.maybeFailOnMoreRecent() // may have seen a conflicting range key return nil, 0, 0, p.err } p.machine.fn = advanceKeyReverse } else { if !p.iterSeek(MVCCKey{Key: p.start}) { + p.maybeFailOnMoreRecent() // may have seen a conflicting range key return nil, 0, 0, p.err } p.machine.fn = advanceKeyForward @@ -693,8 +669,18 @@ func (p *pebbleMVCCScanner) uncertaintyError(ts hlc.Timestamp) (ok bool) { // reason, but the iteration should continue. // (ok=false, added=true) indicates that the KV was included into the result but // the iteration should stop. +// +// The scanner must be positioned on a point key, possibly with an overlapping +// range key. Range keys are processed separately in processRangeKeys(). func (p *pebbleMVCCScanner) getOne(ctx context.Context) (ok, added bool) { if !p.curUnsafeKey.Timestamp.IsEmpty() { + // Range key where read ts >= range key ts >= point key ts. Synthesize a + // point tombstone for it. Range key conflict checks are done in + // processRangeKeys(). + if rkv, ok := p.coveredByRangeKey(p.curUnsafeKey.Timestamp); ok { + return p.addSynthetic(ctx, p.curUnsafeKey.Key, rkv) + } + if extended, valid := p.tryDecodeCurrentValueSimple(); !valid { return false, false } else if extended { @@ -988,12 +974,14 @@ func (p *pebbleMVCCScanner) backwardLatestVersion(key []byte, i int) bool { p.keyBuf = append(p.keyBuf[:0], key...) for ; i < p.itersBeforeSeek; i++ { - peekedKey, ok := p.iterPeekPrev() + peekedKey, hasPoint, ok := p.iterPeekPrev() if !ok { // No previous entry exists, so we're at the latest version of key. return true } - if !bytes.Equal(peekedKey, p.keyBuf) { + // We may peek a bare range key with the same start bound as the point key, + // in which case we're also positioned on the latest point key version. + if !bytes.Equal(peekedKey, p.keyBuf) || !hasPoint { p.incrementItersBeforeSeek() return true } @@ -1057,8 +1045,13 @@ func (p *pebbleMVCCScanner) advanceKeyAtEndReverse() bool { // Iterating to the next key might have caused the iterator to reach the // end of the key space. If that happens, back up to the very last key. p.peeked = false - p.parentReverse = true p.parent.SeekLT(MVCCKey{Key: p.end}) + if !p.iterValid() { + return false + } + if !p.processRangeKeys(true /* seeked */, true /* reverse */) { + return false + } if !p.updateCurrent() { return false } @@ -1189,6 +1182,23 @@ func (p *pebbleMVCCScanner) add( return true /* ok */, true /* added */ } +// addSynthetic adds a synthetic point key for the given range key version. +func (p *pebbleMVCCScanner) addSynthetic( + ctx context.Context, key roachpb.Key, version MVCCRangeKeyVersion, +) (ok, added bool) { + p.keyBuf = EncodeMVCCKeyToBuf(p.keyBuf[:0], MVCCKey{Key: key, Timestamp: version.Timestamp}) + var value MVCCValue + var simple bool + value, simple, p.err = tryDecodeSimpleMVCCValue(version.Value) + if !simple && p.err == nil { + value, p.err = decodeExtendedMVCCValue(version.Value) + } + if p.err != nil { + return false, false + } + return p.add(ctx, key, p.keyBuf, value.Value.RawBytes) +} + // Seeks to the latest revision of the current key that's still less than or // equal to the specified timestamp and adds it to the result set. // - ok indicates whether the iteration should continue. @@ -1233,6 +1243,9 @@ func (p *pebbleMVCCScanner) seekVersion( } } if !uncertaintyCheck || p.curUnsafeKey.Timestamp.LessEq(p.ts) { + if rkv, ok := p.coveredByRangeKey(p.curUnsafeKey.Timestamp); ok { + return p.addSynthetic(ctx, p.curUnsafeKey.Key, rkv) + } return p.add(ctx, p.curUnsafeKey.Key, p.curRawKey, p.curUnsafeValue.Value.RawBytes) } // Iterate through uncertainty interval. Though we found a value in @@ -1268,6 +1281,9 @@ func (p *pebbleMVCCScanner) seekVersion( } } if !uncertaintyCheck || p.curUnsafeKey.Timestamp.LessEq(p.ts) { + if rkv, ok := p.coveredByRangeKey(p.curUnsafeKey.Timestamp); ok { + return p.addSynthetic(ctx, p.curUnsafeKey.Key, rkv) + } return p.add(ctx, p.curUnsafeKey.Key, p.curRawKey, p.curUnsafeValue.Value.RawBytes) } // Iterate through uncertainty interval. See the comment above about why @@ -1284,14 +1300,134 @@ func (p *pebbleMVCCScanner) seekVersion( } } +// coveredByRangeKey returns the topmost range key at the current position +// between the given timestamp and the read timestamp p.ts, if any. +// +// gcassert:inline +func (p *pebbleMVCCScanner) coveredByRangeKey(ts hlc.Timestamp) (rkv MVCCRangeKeyVersion, ok bool) { + // This code is a bit odd to fit it within the mid-stack inlining budget. We + // can't use p.curRangeKeys.IsEmpty(), nor early returns. + if len(p.curRangeKeys.Versions) > 0 { + rkv, ok = p.doCoveredByRangeKey(ts) + } + return rkv, ok +} + +// doCoveredByRangeKey is a helper for coveredByRangeKey to allow mid-stack +// inlining. It is only called when there are range keys present. +func (p *pebbleMVCCScanner) doCoveredByRangeKey(ts hlc.Timestamp) (MVCCRangeKeyVersion, bool) { + // In the common case when tombstones are disabled, range key masking will be + // enabled and so the point key will generally always be above the upper range + // key (unless we're reading in the past). We fast-path this here. + if p.tombstones || ts.LessEq(p.curRangeKeys.Newest()) { + if rkv, ok := p.curRangeKeys.FirstAtOrBelow(p.ts); ok && ts.LessEq(rkv.Timestamp) { + return rkv, true + } + } + return MVCCRangeKeyVersion{}, false +} + +// processRangeKeys will check for any newly encountered MVCC range keys (as +// determined by RangeKeyChanged), perform conflict checks for them, decode them +// into p.curRangeKeys, and skip across bare range keys until positioned on a +// point key or exhausted iterator. It must be called after every iterator +// positioning operation, to make sure it sees the RangeKeyChanged signal. +// Requires a valid iterator. Returns true if iteration can continue. +// +// seeked must be set to true following an iterator seek operation. In the +// forward direction, bare range keys are only possible with RangeKeyChanged or +// SeekGE, which allows omitting HasPointAndRange calls in the common Next case. +// It's also required to handle the case where the scanner is given a used +// iterator that may already be positioned on a range key such that the initial +// seek won't trigger RangeKeyChanged. +// +// reverse must be set to true if the previous iterator operation was a reverse +// operation (SeekLT or Prev). This determines the direction to skip in, and +// also requires checking for bare range keys after every step, since we'll +// land on them last. +func (p *pebbleMVCCScanner) processRangeKeys(seeked bool, reverse bool) bool { + + // Look for new range keys to process, and step across bare range keys until + // we land on a point key (or exhaust the iterator). + for { + // In the forward direction, we can only land on a bare range key when + // RangeKeyChanged fires (at its start bound) or when we SeekGE within it. + rangeKeyChanged := p.parent.RangeKeyChanged() + if !rangeKeyChanged && !reverse && !seeked { + return true + } + + // We fast-path the common no-range-key case. + hasPoint, hasRange := p.parent.HasPointAndRange() + if !hasRange { + p.curRangeKeys = MVCCRangeKeyStack{} + return true + } + + // Process new range keys. On the initial seek it's possible that we're + // given an iterator that's already positioned on a range key, so + // RangeKeyChanged won't fire -- we handle that case here as well. + if rangeKeyChanged || (seeked && p.curRangeKeys.IsEmpty()) { + p.curRangeKeys = p.parent.RangeKeys() + + // Check for conflicts with range keys at or above the read timestamp. + // We don't need to handle e.g. skipLocked, because range keys don't + // currently have intents. + if p.failOnMoreRecent { + if key := p.parent.UnsafeKey(); !hasPoint || !key.Timestamp.IsEmpty() { + if newest := p.curRangeKeys.Newest(); p.ts.LessEq(newest) { + p.mostRecentTS.Forward(newest) + if len(p.mostRecentKey) == 0 { + p.mostRecentKey = append(p.mostRecentKey[:0], key.Key...) + } + } + } + } + + // Check if any of the range keys are in the uncertainty interval. + if p.checkUncertainty { + for _, version := range p.curRangeKeys.Versions { + if version.Timestamp.LessEq(p.ts) { + break + } + var value MVCCValue + var simple bool + value, simple, p.err = tryDecodeSimpleMVCCValue(version.Value) + if !simple && p.err == nil { + value, p.err = decodeExtendedMVCCValue(version.Value) + } + if p.err != nil { + return false + } + localTS := value.GetLocalTimestamp(version.Timestamp) + if p.uncertainty.IsUncertain(version.Timestamp, localTS) { + return p.uncertaintyError(version.Timestamp) + } + } + } + } + + // If we're on a point key we're done, otherwise keep stepping. + if hasPoint { + return true + } + if !reverse { + p.parent.Next() + } else { + p.parent.Prev() + } + if !p.iterValid() { + return false + } + } +} + // Updates cur{RawKey, UnsafeKey, RawValue} to match record the iterator is // pointing to. Callers should call decodeCurrent{Metadata, Value} to decode // the raw value if they need it. +// +// Must only be called with a valid iterator. func (p *pebbleMVCCScanner) updateCurrent() bool { - if !p.iterValid() { - return false - } - p.curRawKey = p.parent.UnsafeRawMVCCKey() var err error @@ -1314,38 +1450,6 @@ func (p *pebbleMVCCScanner) updateCurrent() bool { return true } -// enablePointSynthesis wraps p.parent with a pointSynthesizingIter, which -// synthesizes MVCC point tombstones for MVCC range tombstones and never emits -// range keys itself. p.parent must be valid. -// -// Returns true if the iterator is valid. -func (p *pebbleMVCCScanner) enablePointSynthesis() bool { - if util.RaceEnabled { - if ok, _ := p.parent.Valid(); !ok { - panic(errors.AssertionFailedf("enablePointSynthesis called with invalid iter")) - } - if p.pointIter != nil { - panic(errors.AssertionFailedf("enablePointSynthesis called when already enabled")) - } - if _, hasRange := p.parent.HasPointAndRange(); !hasRange { - panic(errors.AssertionFailedf("enablePointSynthesis called on non-range-key position %s", - p.parent.UnsafeKey())) - } - } - pointIter, err := NewPointSynthesizingIterAtParent(p.parent, p.parentReverse) - if err != nil { - p.err = err - return false - } - if util.RaceEnabled { - if ok, _ := pointIter.Valid(); !ok { - panic(errors.AssertionFailedf("invalid pointSynthesizingIter with valid iter")) - } - } - p.pointIter, p.parent = pointIter, pointIter - return true -} - func (p *pebbleMVCCScanner) decodeCurrentMetadata() bool { if len(p.curRawValue) == 0 { p.err = errors.Errorf("zero-length mvcc metadata") @@ -1381,35 +1485,32 @@ func (p *pebbleMVCCScanner) iterValid() bool { } return false } - // Since iterValid() is called after every iterator positioning operation, it - // is convenient to check for any range keys and enable point synthesis here. - if p.parent.RangeKeyChanged() { - if !p.enablePointSynthesis() { - return false - } - } - if util.RaceEnabled && p.pointIter == nil { - if _, hasRange := p.parent.HasPointAndRange(); hasRange { - panic(errors.AssertionFailedf("range key did not trigger point synthesis at %s", - p.parent.UnsafeKey())) - } - } return true } // iterSeek seeks to the latest revision of the specified key (or a greater key). func (p *pebbleMVCCScanner) iterSeek(key MVCCKey) bool { - p.parentReverse = false p.clearPeeked() p.parent.SeekGE(key) + if !p.iterValid() { + return false + } + if !p.processRangeKeys(true /* seeked */, false /* reverse */) { + return false + } return p.updateCurrent() } // iterSeekReverse seeks to the latest revision of the key before the specified key. func (p *pebbleMVCCScanner) iterSeekReverse(key MVCCKey) bool { - p.parentReverse = true p.clearPeeked() p.parent.SeekLT(key) + if !p.iterValid() { + return false + } + if !p.processRangeKeys(true /* seeked */, true /* reverse */) { + return false + } if !p.updateCurrent() { // We have seeked to before the start key. Return. return false @@ -1426,45 +1527,71 @@ func (p *pebbleMVCCScanner) iterSeekReverse(key MVCCKey) bool { // iterNext advances to the next MVCC key. func (p *pebbleMVCCScanner) iterNext() bool { - p.parentReverse = false if p.reverse && p.peeked { // If we have peeked at the previous entry, we need to advance the iterator // to get back to the current entry. p.peeked = false if !p.iterValid() { - // We were peeked off the beginning of iteration. Seek to the first - // entry, since that is the current entry. + // We were peeked off the beginning of iteration. Seek to the first entry, + // since that is the current entry. We must process range keys because + // the seek can land on a bare range key that must be skipped. p.parent.SeekGE(MVCCKey{Key: p.start}) + if !p.iterValid() { + return false + } + if !p.processRangeKeys(true /* seeked */, false /* reverse */) { + return false + } } else { + // We don't need to process range key changes here, because curRangeKeys + // already contains the range keys at this position from before the peek. p.parent.Next() - } - if !p.iterValid() { - return false + if !p.iterValid() { + return false + } } } // Step forward from the current entry. p.parent.Next() + if !p.iterValid() { + return false + } + if !p.processRangeKeys(false /* seeked */, false /* reverse */) { + return false + } return p.updateCurrent() } // iterPrev advances to the previous MVCC Key. func (p *pebbleMVCCScanner) iterPrev() bool { - p.parentReverse = true if p.peeked { p.peeked = false } else { p.parent.Prev() } + if !p.iterValid() { + return false + } + if !p.processRangeKeys(false /* seeked */, true /* reverse */) { + return false + } return p.updateCurrent() } -// Peek the previous key and store the result in peekedKey. Note that this -// moves the iterator backward, while leaving p.cur{key,value,rawKey} untouched -// and therefore out of sync. iterPrev and iterNext take this into account. -func (p *pebbleMVCCScanner) iterPeekPrev() ([]byte, bool) { +// Peek the previous key and store the result in peekedKey, also returning +// whether the peeked key had a point key or only a bare range key. Note that +// this moves the iterator backward, while leaving p.cur{key,value,rawKey,RangeKeys} +// untouched and therefore out of sync. iterPrev and iterNext take this into +// account. +// +// NB: Unlike iterPrev() and iterNext(), iterPeekPrev() will not skip across +// bare range keys: we have to do conflict checks on any new range keys when we +// step onto them, which may happen on the next positioning operation. The +// returned hasPoint value will indicate whether the peeked position is a bare +// range key or not. +func (p *pebbleMVCCScanner) iterPeekPrev() ([]byte, bool, bool) { if !p.peeked { p.peeked = true - p.parentReverse = true // We need to save a copy of the current iterator key and value and adjust // curRawKey, curKey and curValue to point to this saved data. We use a // single buffer for this purpose: savedBuf. @@ -1475,6 +1602,15 @@ func (p *pebbleMVCCScanner) iterPeekPrev() ([]byte, bool) { // The raw key is always a prefix of the encoded MVCC key. Take advantage of this to // sub-slice the raw key directly, instead of calling SplitMVCCKey. p.curUnsafeKey.Key = p.curRawKey[:len(p.curUnsafeKey.Key)] + // We need to save copies of the current range keys too, but we can avoid + // this if we already saved them previously (if cur and saved share memory). + if curStart := p.curRangeKeys.Bounds.Key; len(curStart) > 0 { + savedStart := p.savedRangeKeys.Bounds.Key + if len(curStart) != len(savedStart) || &curStart[0] != &savedStart[0] { + p.curRangeKeys.CloneInto(&p.savedRangeKeys) + p.curRangeKeys = p.savedRangeKeys + } + } // With the current iterator state saved we can move the iterator to the // previous entry. @@ -1483,14 +1619,23 @@ func (p *pebbleMVCCScanner) iterPeekPrev() ([]byte, bool) { // The iterator is now invalid, but note that this case is handled in // both iterNext and iterPrev. In the former case, we'll position the // iterator at the first entry, and in the latter iteration will be done. - return nil, false + return nil, false, false } } else if !p.iterValid() { - return nil, false + return nil, false, false } peekedKey := p.parent.UnsafeKey() - return peekedKey.Key, true + + // We may land on a bare range key without RangeKeyChanged firing, but only at + // its start bound where the timestamp must be empty. HasPointAndRange() is + // not cheap, so we only check it when necessary. + hasPoint := true + if peekedKey.Timestamp.IsEmpty() { + hasPoint, _ = p.parent.HasPointAndRange() + } + + return peekedKey.Key, hasPoint, true } // Clear the peeked flag. Call this before any iterator operations. diff --git a/pkg/storage/point_synthesizing_iter.go b/pkg/storage/point_synthesizing_iter.go deleted file mode 100644 index 1746d00eba69..000000000000 --- a/pkg/storage/point_synthesizing_iter.go +++ /dev/null @@ -1,1065 +0,0 @@ -// Copyright 2022 The Cockroach Authors. -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package storage - -import ( - "bytes" - "sort" - "sync" - - "github.com/cockroachdb/cockroach/pkg/roachpb" - "github.com/cockroachdb/cockroach/pkg/util" - "github.com/cockroachdb/cockroach/pkg/util/hlc" - "github.com/cockroachdb/cockroach/pkg/util/protoutil" - "github.com/cockroachdb/cockroach/pkg/util/uuid" - "github.com/cockroachdb/errors" -) - -// pointSynthesizingIterPool reuses pointSynthesizingIters to avoid allocations. -var pointSynthesizingIterPool = sync.Pool{ - New: func() interface{} { - return &PointSynthesizingIter{} - }, -} - -// PointSynthesizingIter wraps an MVCCIterator, and synthesizes MVCC point keys -// for MVCC range keys above existing point keys (not below), and at the start -// of range keys (truncated to iterator bounds). It does not emit MVCC range -// keys at all, since these would appear to conflict with the synthesized point -// keys. -// -// During iteration, any range keys overlapping the current iterator position -// are kept in rangeKeys. When atPoint is true, the iterator is positioned on a -// real point key in the underlying iterator. Otherwise, it is positioned on a -// synthetic point key given by rangeKeysPos and rangeKeys[rangeKeysIdx]. -// rangeKeysEnd specifies where to end point synthesis at the current position, -// i.e. the first range key below an existing point key. -// -// The relative positioning of PointSynthesizingIter and the underlying iterator -// is as follows in the forward direction: -// -// - atPoint=true: rangeKeysIdx points to a range key following the point key, -// or beyond rangeKeysEnd when there are no further range keys at this -// key position. -// -// - atPoint=false: the underlying iterator is on a following key or exhausted. -// This can either be a different version of the current key or a different -// point/range key. -// -// This positioning is mirrored in the reverse direction. For example, when -// atPoint=true and rangeKeys are exhausted, rangeKeysIdx will be rangeKeysEnd -// in the forward direction and -1 in the reverse direction. Similarly, the -// underlying iterator is always >= rangeKeysPos in the forward direction and <= -// in reverse. -// -// See also assertInvariants() which asserts positioning invariants. -type PointSynthesizingIter struct { - // iter is the underlying MVCC iterator. - iter MVCCIterator - - // prefix indicates that the underlying iterator is a prefix iterator, which - // can only be on a single key. This allows omitting cloning and comparison. - prefix bool - - // reverse is true when the current iterator direction is in reverse, i.e. - // following a SeekLT or Prev call. - reverse bool - - // rangeKeys contains any range key versions that overlap the current key - // position, for which points will be synthesized. - rangeKeys MVCCRangeKeyVersions - - // rangeKeysBuf is a reusable buffer for rangeKeys. Non-prefix iterators use - // it as a CloneInto() target and place a reference in rangeKeys. - rangeKeysBuf MVCCRangeKeyVersions - - // rangeKeysPos is the current key position along the rangeKeys span, where - // points will be synthesized. It is only set if rangeKeys is non-empty, and - // may differ from the underlying iterator position. - rangeKeysPos roachpb.Key - - // rangeKeysIdx is the rangeKeys index of the current/pending range key - // to synthesize a point for. See struct comment for details. - rangeKeysIdx int - - // rangeKeysStart contains the start key of the current rangeKeys stack. It is - // stored separately, rather than holding the entire MVCCRangeKeyStack, to - // avoid cloning the EndKey. - rangeKeysStart roachpb.Key - - // rangeKeysStartPassed is true if the iterator has moved past the start bound - // of the current range key. This allows omitting a key comparison every time - // the iterator moves to a new key, but only in the forward direction. - rangeKeysStartPassed bool - - // rangeKeysEnd contains the exclusive index at which to stop synthesizing - // point keys, since points are not synthesized below existing point keys. - rangeKeysEnd int - - // atPoint is true if the synthesizing iterator is positioned on a real point - // key in the underlying iterator. See struct comment for details. - atPoint bool - - // pointConflict is true if the current iterator position is on a point key - // with the same timestamp as the range key, in which case the range key takes - // precedence and the real point key should be skipped. This shouldn't happen - // if MVCC conflict checks work correctly, but we'll be defensive as this has - // been seen to happen in randomized tests. atPoint is always false if this is - // true. - pointConflict bool - - // atRangeKeysPos is true if the underlying iterator is at rangeKeysPos. - atRangeKeysPos bool - - // The following fields memoize the state of the underlying iterator, and are - // updated every time its position changes. - iterValid bool - iterErr error - iterKey MVCCKey - iterHasPoint bool - iterHasRange bool - - // rangeKeyChanged keeps track of changes to the underlying iter's range keys. - // It is reset to false on every call to updateRangeKeys(), and accumulates - // changes during intermediate positioning operations. - rangeKeyChanged bool - - // seekKeyBuf is used to clone seek keys. - seekKeyBuf roachpb.Key - - // rawKeyBuf is used by UnsafeRaw(MVCC)Key. - rawKeyBuf []byte -} - -var _ MVCCIterator = (*PointSynthesizingIter)(nil) - -// NewPointSynthesizingIter creates a new pointSynthesizingIter, or gets one -// from the pool. -func NewPointSynthesizingIter(parent MVCCIterator) *PointSynthesizingIter { - iter := pointSynthesizingIterPool.Get().(*PointSynthesizingIter) - *iter = PointSynthesizingIter{ - iter: parent, - prefix: parent.IsPrefix(), - // Reuse pooled byte slices. - rangeKeysBuf: iter.rangeKeysBuf, - rangeKeysPos: iter.rangeKeysPos, - rangeKeysStart: iter.rangeKeysStart, - seekKeyBuf: iter.seekKeyBuf, - rawKeyBuf: iter.rawKeyBuf, - } - return iter -} - -// NewPointSynthesizingIterAtParent creates a new pointSynthesizingIter and -// loads the position from the parent iterator. -// -// If reverse is true, the point synthesizing iterator will reposition on -// a synthetic point tombstone after the current position if appropriate. -// Consider the following dataset: -// -// 2 a2 b2 -// 1 [---) -// a b -// -// If the caller was positioned on b@2, then called Prev() and landed on a@2, -// where it detected the [a-b)@2 range key and enabled point synthesis, it would -// appear to have skipped over the synthetic point tombstone at a@1. Setting -// reverse=true in this case will position the iterator on the synthetic point -// tombstone a@1. -// -// However, the above assumes that the previous positioning operation was from a -// different key -- if it was e.g. a SeekLT(a@1) then this repositioning will be -// incorrect. The caller must take care to use reverse only when appropriate. -func NewPointSynthesizingIterAtParent( - parent MVCCIterator, reverse bool, -) (*PointSynthesizingIter, error) { - iter := NewPointSynthesizingIter(parent) - iter.rangeKeyChanged = true // force range key detection - if ok, _ := iter.updateIter(); ok { - // updateSeekGEPosition may step parent and then compare against seekKey, so - // we need to clone it. - seekKey := parent.UnsafeKey() - iter.seekKeyBuf = append(iter.seekKeyBuf[:0], seekKey.Key...) - seekKey.Key = iter.seekKeyBuf - iter.updateSeekGEPosition(seekKey) - if err := iter.iterErr; err != nil { - iter.release() - return nil, err - } - - // If we're on the start key of a range key in the reverse direction, we - // must take care not to skip any synthetic point tombstones that the caller - // would have expected to be emitted. This essentially means switching the - // iterator to reverse iteration and repositioning on the oldest version - // (real or synthetic). - if reverse && len(iter.rangeKeys) > 0 && iter.rangeKeysPos.Equal(iter.rangeKeysStart) { - if !iter.atPoint { - if _, err := iter.iterPrev(); err != nil { - iter.release() - return nil, err - } - } - iter.reverse = true - iter.rangeKeysIdx = iter.rangeKeysEnd - 1 - iter.updateAtPoint() - } - } - if err := iter.iterErr; err != nil { - iter.release() - return nil, err - } - return iter, nil -} - -// Close implements MVCCIterator. -// -// Close will also close the underlying iterator. Use release() to release it -// back to the pool without closing the parent iterator. -func (i *PointSynthesizingIter) Close() { - i.iter.Close() - i.release() -} - -// release releases the iterator back into the pool. -func (i *PointSynthesizingIter) release() { - *i = PointSynthesizingIter{ - // Reuse slices. - rangeKeysBuf: i.rangeKeysBuf[:0], - rangeKeysPos: i.rangeKeysPos[:0], - rangeKeysStart: i.rangeKeysStart[:0], - seekKeyBuf: i.seekKeyBuf[:0], - rawKeyBuf: i.rawKeyBuf[:0], - } - pointSynthesizingIterPool.Put(i) -} - -// iterNext is a convenience function that calls iter.Next() -// and returns the value of updateIter(). -func (i *PointSynthesizingIter) iterNext() (bool, error) { - i.iter.Next() - return i.updateIter() -} - -// iterPrev is a convenience function that calls iter.Prev() -// and returns the value of updateIter(). -func (i *PointSynthesizingIter) iterPrev() (bool, error) { - i.iter.Prev() - return i.updateIter() -} - -// updateIter memoizes the iterator fields from the underlying iterator, and -// also keeps track of rangeKeyChanged. It must be called after every iterator -// positioning operation, and returns the iterator validity/error. -func (i *PointSynthesizingIter) updateIter() (bool, error) { - if i.iterValid, i.iterErr = i.iter.Valid(); i.iterValid { - i.iterHasPoint, i.iterHasRange = i.iter.HasPointAndRange() - i.iterKey = i.iter.UnsafeKey() - i.atRangeKeysPos = (i.prefix && i.iterHasRange) || - (!i.prefix && i.iterKey.Key.Equal(i.rangeKeysPos)) - i.rangeKeyChanged = i.rangeKeyChanged || i.iter.RangeKeyChanged() - } else { - i.iterHasPoint, i.iterHasRange = false, false - i.iterKey = MVCCKey{} - i.atRangeKeysPos = false - // recall previous i.rangeKeyChanged state - } - return i.iterValid, i.iterErr -} - -// updateRangeKeys updates i.rangeKeys and related fields with the underlying -// iterator state. It must be called very time the pointSynthesizingIter moves -// to a new key position, i.e. after exhausting all point/range keys at the -// current position. rangeKeysIdx and rangeKeysEnd are reset. -func (i *PointSynthesizingIter) updateRangeKeys() { - if !i.iterHasRange { - i.clearRangeKeys() - return - } - - // Detect and fetch new range keys in the underlying iterator since the - // last call to updateRangeKeys(). - if i.rangeKeyChanged { - i.rangeKeyChanged = false - if i.prefix { - // A prefix iterator will always be at the start bound of the range key, - // and never move onto a different range key, so we can omit the cloning. - i.rangeKeys = i.iter.RangeKeys().Versions - } else { - rangeKeys := i.iter.RangeKeys() - i.rangeKeysStart = append(i.rangeKeysStart[:0], rangeKeys.Bounds.Key...) - rangeKeys.Versions.CloneInto(&i.rangeKeysBuf) - i.rangeKeys = i.rangeKeysBuf - i.rangeKeysStartPassed = false // we'll compare below - } - } - - // Update the current iterator position. - i.rangeKeysPos = append(i.rangeKeysPos[:0], i.iterKey.Key...) - i.atRangeKeysPos = true // by definition - - // Update the rangeKeysEnd with the range keys to synthesize points for at - // this position. Notably, we synthesize for all range keys at their start - // bound, but otherwise only the ones above existing point keys. - // - // In the forward direction, once we step off rangeKeysStart we won't see a - // start bound again until we hit a new range key, so we can omit a key - // comparison for all point keys in between. This isn't true in reverse, - // unfortunately, where we only encounter the start key at the end. - if i.rangeKeysStartPassed && !i.reverse { - i.rangeKeysEnd = 0 - i.extendRangeKeysEnd() - } else if i.prefix || i.rangeKeysPos.Equal(i.rangeKeysStart) { - i.rangeKeysEnd = len(i.rangeKeys) - i.rangeKeysStartPassed = false - } else { - i.rangeKeysEnd = 0 - i.extendRangeKeysEnd() - i.rangeKeysStartPassed = !i.reverse - } - - // Reset rangeKeysIdx to the first range key. - if !i.reverse { - i.rangeKeysIdx = 0 - } else { - i.rangeKeysIdx = i.rangeKeysEnd - 1 // NB: -1 is correct with no range keys - } -} - -// extendRangeKeysEnd extends i.rangeKeysEnd below the current point key's -// timestamp in the underlying iterator. It never reduces i.rangeKeysEnd. -func (i *PointSynthesizingIter) extendRangeKeysEnd() { - if i.iterHasPoint && i.atRangeKeysPos && !i.iterKey.Timestamp.IsEmpty() { - if l := len(i.rangeKeys); i.rangeKeysEnd < l { - i.rangeKeysEnd = sort.Search(l-i.rangeKeysEnd, func(idx int) bool { - return i.rangeKeys[i.rangeKeysEnd+idx].Timestamp.Less(i.iterKey.Timestamp) - }) + i.rangeKeysEnd - } - } -} - -// updateAtPoint updates i.atPoint according to whether the synthesizing -// iterator is positioned on the real point key in the underlying iterator, as -// well as i.pointConflict. Requires i.rangeKeys to have been positioned first. -func (i *PointSynthesizingIter) updateAtPoint() { - if !i.iterHasPoint { - i.atPoint = false - } else if len(i.rangeKeys) == 0 { - i.atPoint = true - } else if !i.atRangeKeysPos { - i.atPoint = false - } else if !i.reverse { - i.atPoint = i.rangeKeysIdx >= i.rangeKeysEnd || !i.iterKey.Timestamp.IsSet() || - i.rangeKeys[i.rangeKeysIdx].Timestamp.Less(i.iterKey.Timestamp) - } else { - i.atPoint = i.rangeKeysIdx < 0 || (i.iterKey.Timestamp.IsSet() && - i.iterKey.Timestamp.Less(i.rangeKeys[i.rangeKeysIdx].Timestamp)) - } - i.pointConflict = !i.atPoint && i.iterHasPoint && i.atRangeKeysPos && - i.rangeKeys[i.rangeKeysIdx].Timestamp.Equal(i.iterKey.Timestamp) -} - -// updatePosition updates the synthesizing iterator for the position of the -// underlying iterator. This may step the underlying iterator to position it -// correctly relative to bare range keys. -func (i *PointSynthesizingIter) updatePosition() { - if !i.iterHasRange { - // Fast path: no range keys, so just clear range keys and bail out. - i.atPoint = i.iterHasPoint - i.pointConflict = false - i.clearRangeKeys() - - } else if !i.reverse { - // If we're on a bare range key in the forward direction, we populate the - // range keys but then step iter ahead before updating the point position. - // The next position may be a point key with the same key as the current - // range key, which must be interleaved with the synthetic points. - i.updateRangeKeys() - if i.iterHasRange && !i.iterHasPoint { - if _, err := i.iterNext(); err != nil { - return - } - i.extendRangeKeysEnd() - } - i.updateAtPoint() - - } else { - // If we're on a bare range key in the reverse direction, and we've already - // emitted synthetic points for this key (given by atRangeKeysPos), then we - // skip over the bare range key to avoid duplicates. - if i.iterHasRange && !i.iterHasPoint && i.atRangeKeysPos { - if _, err := i.iterPrev(); err != nil { - return - } - } - i.updateRangeKeys() - i.updateAtPoint() - } -} - -// clearRangeKeys resets the iterator by clearing out all range key state. -// gcassert:inline -func (i *PointSynthesizingIter) clearRangeKeys() { - if len(i.rangeKeys) != 0 { - i.rangeKeys = i.rangeKeys[:0] - i.rangeKeysPos = i.rangeKeysPos[:0] - i.rangeKeysStart = i.rangeKeysStart[:0] - i.rangeKeysEnd = 0 - i.rangeKeysStartPassed = false - } - if !i.reverse { - i.rangeKeysIdx = 0 - } else { - i.rangeKeysIdx = -1 - } -} - -// SeekGE implements MVCCIterator. -func (i *PointSynthesizingIter) SeekGE(seekKey MVCCKey) { - // The seek key may originate from UnsafeKey (see pebbleMVCCScanner), in which - // case it would be invalidated when we seek the parent iter below, so we have - // to clone it. We could be clever and try to detect if the backing array is - // the same as i.iterKey, but let's be obviously correct instead. - i.seekKeyBuf = append(i.seekKeyBuf[:0], seekKey.Key...) - seekKey.Key = i.seekKeyBuf - i.reverse = false - i.iter.SeekGE(seekKey) - if ok, _ := i.updateIter(); !ok { - i.updatePosition() - return - } - i.updateSeekGEPosition(seekKey) -} - -// SeekIntentGE implements MVCCIterator. -func (i *PointSynthesizingIter) SeekIntentGE(seekKey roachpb.Key, txnUUID uuid.UUID) { - i.seekKeyBuf = append(i.seekKeyBuf[:0], seekKey...) - seekKey = i.seekKeyBuf - i.reverse = false - i.iter.SeekIntentGE(seekKey, txnUUID) - if ok, _ := i.updateIter(); !ok { - i.updatePosition() - return - } - i.updateSeekGEPosition(MVCCKey{Key: seekKey}) -} - -// updateSeekGEPosition updates the iterator state following a SeekGE call, or -// to load the parent iterator's position in newPointSynthesizingIterAtParent. -func (i *PointSynthesizingIter) updateSeekGEPosition(seekKey MVCCKey) { - - // Fast path: no range key, so just reset the iterator and bail out. - if !i.iterHasRange { - i.atPoint = i.iterHasPoint - i.pointConflict = false - i.clearRangeKeys() - return - } - - // If we land in the middle of a bare range key then skip over it to the next - // point/range key. If prefix is enabled, we must be at its start key, so we - // can omit the comparison. - if i.iterHasRange && !i.iterHasPoint && !i.prefix && - !i.iter.RangeBounds().Key.Equal(i.iterKey.Key) { - if ok, _ := i.iterNext(); !ok { - i.updatePosition() - return - } - } - - i.updateRangeKeys() - - // If we're still at a bare range key, we must be at its start key. Move the - // iterator ahead to look for a point key at the same key. - if i.iterHasRange && !i.iterHasPoint { - if _, err := i.iterNext(); err != nil { - return - } - } - - // If we're seeking to a specific version, skip newer range keys. - if len(i.rangeKeys) > 0 && seekKey.Timestamp.IsSet() && - (i.prefix || seekKey.Key.Equal(i.rangeKeysPos)) { - i.rangeKeysIdx = sort.Search(i.rangeKeysEnd, func(idx int) bool { - return i.rangeKeys[idx].Timestamp.LessEq(seekKey.Timestamp) - }) - } - - i.updateAtPoint() - - // It's possible that we seeked past all of the range key versions. In this - // case, we have to reposition on the next key (current iter key). - if !i.atPoint && i.rangeKeysIdx >= i.rangeKeysEnd { - i.updatePosition() - } -} - -// Next implements MVCCIterator. -func (i *PointSynthesizingIter) Next() { - // When changing direction, flip the relative positioning with iter. - if i.reverse { - i.reverse = false - if !i.atPoint && len(i.rangeKeys) == 0 { // iterator was exhausted - if _, err := i.iterNext(); err != nil { - return - } - i.updatePosition() - return - } else if i.atPoint { - i.rangeKeysIdx++ - } else if i.pointConflict { - // point key and range key are at same position - } else if _, err := i.iterNext(); err != nil { - return - } - } - - // Step off the current point, either real or synthetic. - if i.atPoint { - if _, err := i.iterNext(); err != nil { - return - } - i.extendRangeKeysEnd() - } else if i.pointConflict { - if _, err := i.iterNext(); err != nil { - return - } - i.extendRangeKeysEnd() - i.rangeKeysIdx++ - } else { - i.rangeKeysIdx++ - } - i.updateAtPoint() - - // If we've exhausted the current range keys, update with the underlying - // iterator position (which must now be at a later key). - if !i.atPoint && i.rangeKeysIdx >= i.rangeKeysEnd { - i.updatePosition() - } -} - -// NextKey implements MVCCIterator. -func (i *PointSynthesizingIter) NextKey() { - // When changing direction, flip the relative positioning with iter. - // - // NB: This isn't really supported by the MVCCIterator interface, but we have - // best-effort handling in e.g. `pebbleIterator` and it's simple enough to - // implement, so we may as well. - if i.reverse { - i.reverse = false - if !i.atPoint && !i.pointConflict { - if _, err := i.iterNext(); err != nil { - return - } - } - } - // Don't call NextKey() if the underlying iterator is already on the next key. - if i.atPoint || i.atRangeKeysPos { - i.iter.NextKey() - if _, err := i.updateIter(); err != nil { - return - } - } - i.updatePosition() -} - -// SeekLT implements MVCCIterator. -func (i *PointSynthesizingIter) SeekLT(seekKey MVCCKey) { - i.seekKeyBuf = append(i.seekKeyBuf[:0], seekKey.Key...) - seekKey.Key = i.seekKeyBuf - i.reverse = true - i.iter.SeekLT(seekKey) - if ok, _ := i.updateIter(); !ok { - i.updatePosition() - return - } - - // Fast path: no range key, so just reset the iterator and bail out. - if !i.iterHasRange { - i.atPoint = i.iterHasPoint - i.pointConflict = false - i.clearRangeKeys() - return - } - - // If we did a versioned seek and find a range key that overlaps the seek key, - // we may have skipped over existing point key versions of the seek key. These - // would mandate that we synthesize point keys for the seek key after all, so - // we peek ahead to check for them. - // - // TODO(erikgrinaker): It might be faster to do an unversioned seek from the - // next key first to look for points. - var positioned bool - if seekKey.Timestamp.IsSet() && i.iterHasRange && - seekKey.Key.Compare(i.iter.RangeBounds().EndKey) < 0 { - if ok, err := i.iterNext(); err != nil { - return - } else if ok && i.iterHasPoint && i.iterKey.Key.Equal(seekKey.Key) { - i.updateRangeKeys() - positioned = true - } - if ok, _ := i.iterPrev(); !ok { - i.updatePosition() - return - } - } - - if !positioned { - i.updateRangeKeys() - } - - // If we're seeking to a specific version, skip over older range keys. - if seekKey.Timestamp.IsSet() && seekKey.Key.Equal(i.rangeKeysPos) { - i.rangeKeysIdx = sort.Search(i.rangeKeysEnd, func(idx int) bool { - return i.rangeKeys[idx].Timestamp.LessEq(seekKey.Timestamp) - }) - 1 - } - - i.updateAtPoint() - - // It's possible that we seeked past all of the range key versions. In this - // case, we have to reposition on the previous key (current iter key). - if !i.atPoint && i.rangeKeysIdx < 0 { - i.updatePosition() - } -} - -// Prev implements MVCCIterator. -func (i *PointSynthesizingIter) Prev() { - // When changing direction, flip the relative positioning with iter. - if !i.reverse { - i.reverse = true - if !i.atPoint && len(i.rangeKeys) == 0 { // iterator was exhausted - if _, err := i.iterPrev(); err != nil { - return - } - i.updatePosition() - return - } else if i.atPoint { - i.rangeKeysIdx-- - } else if i.pointConflict { - // point key and range key are at same position - } else if _, err := i.iterPrev(); err != nil { - return - } - } - - // Step off the current point key (real or synthetic). - if i.atPoint { - if _, err := i.iterPrev(); err != nil { - return - } - } else if i.pointConflict { - if _, err := i.iterPrev(); err != nil { - return - } - i.rangeKeysIdx-- - } else { - i.rangeKeysIdx-- - } - i.updateAtPoint() - - // If we've exhausted the current range keys, and we're not positioned on a - // point key at the current range key position, then update with the - // underlying iter position (which must be before the current key). - if i.rangeKeysIdx < 0 && (!i.atPoint || !i.atRangeKeysPos) { - i.updatePosition() - } -} - -// Valid implements MVCCIterator. -func (i *PointSynthesizingIter) Valid() (bool, error) { - valid := i.iterValid || - // On synthetic point key. - (i.iterErr == nil && !i.atPoint && i.rangeKeysIdx >= 0 && i.rangeKeysIdx < i.rangeKeysEnd) - - if util.RaceEnabled && valid { - if err := i.assertInvariants(); err != nil { - panic(err) - } - } - - return valid, i.iterErr -} - -// Key implements MVCCIterator. -func (i *PointSynthesizingIter) Key() MVCCKey { - return i.UnsafeKey().Clone() -} - -// UnsafeKey implements MVCCIterator. -func (i *PointSynthesizingIter) UnsafeKey() MVCCKey { - if i.atPoint { - return i.iterKey - } - if i.rangeKeysIdx >= i.rangeKeysEnd || i.rangeKeysIdx < 0 { - return MVCCKey{} - } - return MVCCKey{ - Key: i.rangeKeysPos, - Timestamp: i.rangeKeys[i.rangeKeysIdx].Timestamp, - } -} - -// UnsafeRawKey implements MVCCIterator. -func (i *PointSynthesizingIter) UnsafeRawKey() []byte { - if i.atPoint { - return i.iter.UnsafeRawKey() - } - i.rawKeyBuf = EncodeMVCCKeyToBuf(i.rawKeyBuf[:0], i.UnsafeKey()) - return i.rawKeyBuf -} - -// UnsafeRawMVCCKey implements MVCCIterator. -func (i *PointSynthesizingIter) UnsafeRawMVCCKey() []byte { - if i.atPoint { - return i.iter.UnsafeRawMVCCKey() - } - i.rawKeyBuf = EncodeMVCCKeyToBuf(i.rawKeyBuf[:0], i.UnsafeKey()) - return i.rawKeyBuf -} - -// Value implements MVCCIterator. -func (i *PointSynthesizingIter) Value() ([]byte, error) { - v, err := i.UnsafeValue() - if err != nil { - return nil, err - } - if v != nil { - return append([]byte{}, v...), nil - } - return nil, nil -} - -// UnsafeValue implements MVCCIterator. -func (i *PointSynthesizingIter) UnsafeValue() ([]byte, error) { - if i.atPoint { - return i.iter.UnsafeValue() - } - if i.rangeKeysIdx >= len(i.rangeKeys) || i.rangeKeysIdx < 0 { - return nil, nil - } - return i.rangeKeys[i.rangeKeysIdx].Value, nil -} - -// MVCCValueLenAndIsTombstone implements the MVCCIterator interface. -func (i *PointSynthesizingIter) MVCCValueLenAndIsTombstone() (int, bool, error) { - if i.atPoint { - return i.iter.MVCCValueLenAndIsTombstone() - } - if i.rangeKeysIdx >= len(i.rangeKeys) || i.rangeKeysIdx < 0 { - return 0, false, errors.Errorf("iter is not Valid") - } - val := i.rangeKeys[i.rangeKeysIdx].Value - // All range keys are tombstones - return len(val), true, nil -} - -// ValueLen implements the MVCCIterator interface. -func (i *PointSynthesizingIter) ValueLen() int { - if i.atPoint { - return i.iter.ValueLen() - } - if i.rangeKeysIdx >= len(i.rangeKeys) || i.rangeKeysIdx < 0 { - // Caller has violated invariant! - return 0 - } - return len(i.rangeKeys[i.rangeKeysIdx].Value) -} - -// ValueProto implements MVCCIterator. -func (i *PointSynthesizingIter) ValueProto(msg protoutil.Message) error { - v, err := i.UnsafeValue() - if err != nil { - return err - } - return protoutil.Unmarshal(v, msg) -} - -// HasPointAndRange implements MVCCIterator. -func (i *PointSynthesizingIter) HasPointAndRange() (bool, bool) { - return true, false -} - -// RangeBounds implements MVCCIterator. -func (i *PointSynthesizingIter) RangeBounds() roachpb.Span { - return roachpb.Span{} -} - -// RangeKeys implements MVCCIterator. -func (i *PointSynthesizingIter) RangeKeys() MVCCRangeKeyStack { - return MVCCRangeKeyStack{} -} - -// RangeKeyChanged implements MVCCIterator. -func (i *PointSynthesizingIter) RangeKeyChanged() bool { - return false -} - -// FindSplitKey implements MVCCIterator. -func (i *PointSynthesizingIter) FindSplitKey( - start, end, minSplitKey roachpb.Key, targetSize int64, -) (MVCCKey, error) { - return i.iter.FindSplitKey(start, end, minSplitKey, targetSize) -} - -// Stats implements MVCCIterator. -func (i *PointSynthesizingIter) Stats() IteratorStats { - return i.iter.Stats() -} - -// IsPrefix implements the MVCCIterator interface. -func (i *PointSynthesizingIter) IsPrefix() bool { - return i.prefix -} - -// assertInvariants asserts iterator invariants. The iterator must be valid. -func (i *PointSynthesizingIter) assertInvariants() error { - // Check general MVCCIterator API invariants. - if err := assertMVCCIteratorInvariants(i); err != nil { - return err - } - - // If the underlying iterator has errored, make sure we're not positioned on a - // synthetic point such that Valid() will surface the error. - if _, err := i.iter.Valid(); err != nil { - if !i.atPoint && i.rangeKeysIdx >= 0 && i.rangeKeysIdx < len(i.rangeKeys) { - return errors.NewAssertionErrorWithWrappedErrf(err, "iterator error with synthetic point %s", - i.rangeKeysPos) - } - return nil - } - - // Make sure the state of the underlying iterator matches the memoized state. - if ok, err := i.iter.Valid(); ok { - if !i.iterValid { - return errors.AssertionFailedf("iterValid %t != iter.Valid %t", i.iterValid, ok) - } - if i.iterErr != nil { - return errors.NewAssertionErrorWithWrappedErrf(i.iterErr, "valid iter with error") - } - if key := i.iter.UnsafeKey(); !i.iterKey.Equal(key) { - return errors.AssertionFailedf("iterKey %s != iter.UnsafeKey %s", i.iterKey, key) - } - if hasP, hasR := i.iter.HasPointAndRange(); hasP != i.iterHasPoint || hasR != i.iterHasRange { - return errors.AssertionFailedf( - "iterHasPoint %t, iterHasRange %t != iter.HasPointAndRange %t %t", - i.iterHasPoint, i.iterHasRange, hasP, hasR) - } - if i.atRangeKeysPos && !i.iterKey.Key.Equal(i.rangeKeysPos) { - return errors.AssertionFailedf("atRangeKeysPos true but iterKey %s != rangeKeysPos %s", - i.iterKey, i.rangeKeysPos) - } - } else { - if hasErr, iterHasErr := err != nil, i.iterErr != nil; hasErr != iterHasErr { - return errors.AssertionFailedf("i.iterErr %t != iter.Valid err %t", iterHasErr, hasErr) - } - if i.iterValid { - return errors.AssertionFailedf("invalid iterator with iterValid %t", i.iterValid) - } - if i.iterHasPoint || i.iterHasRange { - return errors.AssertionFailedf("invalid iterator with iterHasPoint %t, iterHasRange %t", - i.iterHasPoint, i.iterHasRange) - } - if i.atRangeKeysPos { - return errors.AssertionFailedf("invalid iterator with atRangeKeysPos %t", i.atRangeKeysPos) - } - } - - // In prefix mode, the iterator must never be used in reverse. - if i.prefix && i.reverse { - return errors.AssertionFailedf("prefix iterator used in reverse") - } - - // When atPoint is true, the underlying iterator must be valid and on a point. - // pointConflict must be false. - if i.atPoint { - if ok, _ := i.iter.Valid(); !ok { - return errors.AssertionFailedf("atPoint with invalid iter") - } - if hasPoint, _ := i.iter.HasPointAndRange(); !hasPoint { - return errors.AssertionFailedf("atPoint at non-point position %s", i.iter.UnsafeKey()) - } - if i.pointConflict { - return errors.AssertionFailedf("atPoint with pointConflict at %s", i.iter.UnsafeKey()) - } - } - - // rangeKeysEnd is never negative, and never greater than len(i.rangeKeys). - if i.rangeKeysEnd < 0 || i.rangeKeysEnd > len(i.rangeKeys) { - return errors.AssertionFailedf("invalid rangeKeysEnd %d with length %d", - i.rangeKeysEnd, len(i.rangeKeys)) - } - - // rangeKeysIdx is never more than 1 outside of the permitted slice interval - // (0 to rangeKeysEnd), and the excess depends on the direction: rangeKeysEnd - // in the forward direction, -1 in the reverse. - if i.rangeKeysIdx < 0 || i.rangeKeysIdx >= i.rangeKeysEnd { - if (!i.reverse && i.rangeKeysIdx != i.rangeKeysEnd) || (i.reverse && i.rangeKeysIdx != -1) { - return errors.AssertionFailedf("invalid rangeKeysIdx %d with rangeKeysEnd %d and reverse=%t", - i.rangeKeysIdx, i.rangeKeysEnd, i.reverse) - } - } - - // If rangeKeys is empty, atPoint is true unless exhausted and other state is - // cleared. In this case, there's nothing more to check. - if len(i.rangeKeys) == 0 { - if ok, _ := i.iter.Valid(); ok && !i.atPoint { - return errors.AssertionFailedf("no rangeKeys nor atPoint") - } - if len(i.rangeKeysPos) > 0 { - return errors.AssertionFailedf("no rangeKeys but rangeKeysPos %s", i.rangeKeysPos) - } - if len(i.rangeKeysStart) > 0 { - return errors.AssertionFailedf("no rangeKeys but rangeKeysStart %s", i.rangeKeysStart) - } - if i.rangeKeysStartPassed { - return errors.AssertionFailedf("no rangeKeys but rangeKeysStartPassed") - } - return nil - } - - // rangeKeysPos must be set when range keys are present. - if len(i.rangeKeysPos) == 0 { - return errors.AssertionFailedf("rangeKeysPos not set") - } - - // rangeKeysStart must be set, and rangeKeysPos must be at or after it. - // prefix iterators do not set rangeKeysStart. - if !i.prefix { - if len(i.rangeKeysStart) == 0 { - return errors.AssertionFailedf("no rangeKeysStart at %s", i.iter.UnsafeKey()) - } - if i.rangeKeysPos.Compare(i.rangeKeysStart) < 0 { - return errors.AssertionFailedf("rangeKeysPos %s not after rangeKeysStart %s", - i.rangeKeysPos, i.rangeKeysStart) - } - } else { - if len(i.rangeKeysStart) != 0 { - return errors.AssertionFailedf("rangeKeysStart set to %s for prefix iterator", i.rangeKeysStart) - } - } - - // rangeKeysStartPassed must match rangeKeysPos and rangeKeysStart. Note that - // it is not always correctly enabled when switching directions on a point key - // covered by a range key after rangeKeysStart, since we only update it when - // stepping onto a different key. - if i.rangeKeysStartPassed { - if i.prefix { - return errors.AssertionFailedf("rangeKeysStartPassed seen for prefix iterator") - } - if i.rangeKeysStartPassed && i.rangeKeysPos.Equal(i.rangeKeysStart) { - return errors.AssertionFailedf("rangeKeysStartPassed %t, but pos %s and start %s ", - i.rangeKeysStartPassed, i.rangeKeysPos, i.rangeKeysStart) - } - } - - // rangeKeysIdx must be valid if we're not on a point. - if !i.atPoint && (i.rangeKeysIdx < 0 || i.rangeKeysIdx >= i.rangeKeysEnd) { - return errors.AssertionFailedf("not atPoint with invalid rangeKeysIdx %d at %s", - i.rangeKeysIdx, i.rangeKeysPos) - } - - // If the underlying iterator is exhausted, then there's nothing more to - // check. We must either be on a synthetic point key or exhausted iterator. - if ok, _ := i.iter.Valid(); !ok { - return nil - } - - // We now have range keys and a non-exhausted iterator. - // - // prefix iterators must have range key bounds [key, key.Next), and be - // positioned on key. - if _, hasRange := i.iter.HasPointAndRange(); i.prefix && hasRange { - expect := roachpb.Span{Key: i.rangeKeysPos, EndKey: i.rangeKeysPos.Next()} - if bounds := i.iter.RangeBounds(); !bounds.Equal(expect) { - return errors.AssertionFailedf("unexpected range bounds %s with prefix, expected %s", - bounds, expect) - - } else if key := i.iter.UnsafeKey().Key; !key.Equal(bounds.Key) { - return errors.AssertionFailedf("iter not on prefix position %s, got %s", bounds, key) - } - } - - // Check for an overlapping point/range key timestamp. In this case, - // pointConflict must be true, atPoint must be false, and UnsafeValue() - // must return the range key's value. - var rangeKeyConflict MVCCRangeKeyVersion - if i.atRangeKeysPos && i.rangeKeysIdx >= 0 && i.rangeKeysIdx < i.rangeKeysEnd && - i.rangeKeys[i.rangeKeysIdx].Timestamp.Equal(i.iterKey.Timestamp) { - rangeKeyConflict = i.rangeKeys[i.rangeKeysIdx] - } - if rangeKeyConflict.Timestamp.IsSet() && !i.pointConflict { - return errors.AssertionFailedf( - "conflicting range key and point key without pointConflict at %s", i.iterKey) - } - if i.pointConflict { - if i.atPoint { - return errors.AssertionFailedf("pointConflict with atPoint at %s", i.iterKey) - } - if rangeKeyConflict.Timestamp.IsEmpty() { - return errors.AssertionFailedf("pointConflict with no matching range key at %s", i.iterKey) - } - if value, err := i.UnsafeValue(); err != nil { - return err - } else if !bytes.Equal(value, rangeKeyConflict.Value) { - return errors.AssertionFailedf("pointConflict not returning range key value at %s", i.iterKey) - } - // We don't need to check the relative positioning below, because - // we already checked it. - return nil - } - - // Check the relative positioning as minimum and maximum iter keys (in MVCC - // order). We can assume that overlapping range keys and point keys don't have - // the same timestamp, since this was checked above. - var minKey, maxKey MVCCKey - - // The iterator should never lag behind the range key position. - if !i.reverse { - minKey = MVCCKey{Key: i.rangeKeysPos} - } else { - maxKey = MVCCKey{Key: i.rangeKeysPos, Timestamp: hlc.MinTimestamp} - } - - // If we're not at a real point, then the iterator must be ahead of the - // current synthesized point. If we are on a point, then it must lie between - // the surrounding range keys (if they exist). - minIdx, maxIdx := -1, -1 - if !i.atPoint { - if !i.reverse { - minIdx = i.rangeKeysIdx - } else { - maxIdx = i.rangeKeysIdx - } - } else if !i.reverse { - minIdx = i.rangeKeysIdx - 1 - maxIdx = i.rangeKeysIdx - } else { - minIdx = i.rangeKeysIdx - maxIdx = i.rangeKeysIdx + 1 - } - if minIdx >= 0 && minIdx < i.rangeKeysEnd { - minKey = MVCCKey{Key: i.rangeKeysPos, Timestamp: i.rangeKeys[minIdx].Timestamp} - } - if maxIdx >= 0 && maxIdx < i.rangeKeysEnd { - maxKey = MVCCKey{Key: i.rangeKeysPos, Timestamp: i.rangeKeys[maxIdx].Timestamp} - } - - iterKey := i.iter.Key() - if minKey.Key != nil && iterKey.Compare(minKey) < 0 { - return errors.AssertionFailedf("iter %s below minimum key %s", iterKey, minKey) - } - if maxKey.Key != nil && iterKey.Compare(maxKey) > 0 { - return errors.AssertionFailedf("iter %s above maximum key %s", iterKey, maxKey) - } - - return nil -} diff --git a/pkg/storage/testdata/mvcc_histories/iter_prefix_next_key b/pkg/storage/testdata/mvcc_histories/iter_prefix_next_key index 601fcd2e9bb7..fa40248cc8de 100644 --- a/pkg/storage/testdata/mvcc_histories/iter_prefix_next_key +++ b/pkg/storage/testdata/mvcc_histories/iter_prefix_next_key @@ -224,56 +224,3 @@ iter_seek_ge: d{-\x00}/[6.000000000,0=/] ! iter_next_key: . iter_seek_ge: d{-\x00}/[6.000000000,0=/] ! iter_next_key: . - -# Point synthesis. -run ok -iter_new prefix types=pointsAndRanges pointSynthesis -iter_seek_ge k=a -iter_next_key -iter_seek_ge k=a ts=3 -iter_next_key -iter_seek_ge k=a ts=1 -iter_next_key -iter_seek_ge k=b -iter_next_key -iter_seek_ge k=b ts=3 -iter_next_key -iter_seek_ge k=b ts=1 -iter_next_key -iter_seek_ge k=c -iter_next_key -iter_seek_ge k=c ts=3 -iter_next_key -iter_seek_ge k=c ts=1 -iter_next_key -iter_seek_ge k=d -iter_next_key -iter_seek_ge k=d ts=3 -iter_next_key -iter_seek_ge k=d ts=1 -iter_next_key ----- -iter_seek_ge: "a"/5.000000000,0=/BYTES/a5 -iter_next_key: . -iter_seek_ge: "a"/3.000000000,0=/BYTES/a3 -iter_next_key: . -iter_seek_ge: "a"/1.000000000,0=/BYTES/a1 -iter_next_key: . -iter_seek_ge: "b"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true -iter_next_key: . -iter_seek_ge: "b"/3.000000000,0=/BYTES/b3 -iter_next_key: . -iter_seek_ge: "b"/1.000000000,0=/BYTES/b1 -iter_next_key: . -iter_seek_ge: "c"/6.000000000,0=/ -iter_next_key: . -iter_seek_ge: "c"/3.000000000,0=/BYTES/c3 -iter_next_key: . -iter_seek_ge: "c"/1.000000000,0=/BYTES/c1 -iter_next_key: . -iter_seek_ge: "d"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true -iter_next_key: . -iter_seek_ge: "d"/3.000000000,0=/BYTES/d3 -iter_next_key: . -iter_seek_ge: "d"/1.000000000,0=/BYTES/d1 -iter_next_key: . diff --git a/pkg/storage/testdata/mvcc_histories/range_tombstone_iter_point_synthesis b/pkg/storage/testdata/mvcc_histories/range_tombstone_iter_point_synthesis deleted file mode 100644 index e025578e4c80..000000000000 --- a/pkg/storage/testdata/mvcc_histories/range_tombstone_iter_point_synthesis +++ /dev/null @@ -1,1072 +0,0 @@ -# Tests pointSynthesizingIter. -# -# Sets up following dataset, where x is tombstone, o-o is range tombstone, [] is intent. -# -# T -# 7 [d7] [j7] -# 6 f6 -# 5 o-------------------o k5 o-----------o -# 4 x x d4 f4 g4 -# 3 o-------o e3 o-------oh3 o---o -# 2 a2 d2 f2 g2 -# 1 o-------------------o o-----------o -# a b c d e f g h i j k l m n o p -# -run ok -del_range_ts k=a end=f ts=1 -del_range_ts k=h end=k ts=1 -del_range_ts k=b end=d ts=3 -del_range_ts k=n end=o ts=3 -del_range_ts k=l end=o ts=5 -put k=a ts=2 v=a2 -del k=a ts=4 -del k=b ts=4 -put k=d ts=2 v=d2 -put k=d ts=4 v=d4 -put k=e ts=3 v=e3 -put k=f ts=2 v=f2 -put k=g ts=2 v=g2 -del_range_ts k=f end=h ts=3 localTs=4 -put k=f ts=4 v=f4 -put k=g ts=4 v=g4 -del_range_ts k=c end=h ts=5 -put k=f ts=6 v=f6 -put k=h ts=3 v=h3 -put k=k ts=5 v=k5 -with t=A - txn_begin ts=7 - put k=d v=d7 - put k=j v=j7 ----- -del: "a": found key true -del: "b": found key false ->> at end: -txn: "A" meta={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} lock=true stat=PENDING rts=7.000000000,0 wto=false gul=0,0 -rangekey: {a-b}/[1.000000000,0=/] -rangekey: {b-c}/[3.000000000,0=/ 1.000000000,0=/] -rangekey: {c-d}/[5.000000000,0=/ 3.000000000,0=/ 1.000000000,0=/] -rangekey: {d-f}/[5.000000000,0=/ 1.000000000,0=/] -rangekey: {f-h}/[5.000000000,0=/ 3.000000000,0=/] -rangekey: {h-k}/[1.000000000,0=/] -rangekey: {l-n}/[5.000000000,0=/] -rangekey: {n-o}/[5.000000000,0=/ 3.000000000,0=/] -data: "a"/4.000000000,0 -> / -data: "a"/2.000000000,0 -> /BYTES/a2 -data: "b"/4.000000000,0 -> / -meta: "d"/0,0 -> txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true -data: "d"/7.000000000,0 -> /BYTES/d7 -data: "d"/4.000000000,0 -> /BYTES/d4 -data: "d"/2.000000000,0 -> /BYTES/d2 -data: "e"/3.000000000,0 -> /BYTES/e3 -data: "f"/6.000000000,0 -> /BYTES/f6 -data: "f"/4.000000000,0 -> /BYTES/f4 -data: "f"/2.000000000,0 -> /BYTES/f2 -data: "g"/4.000000000,0 -> /BYTES/g4 -data: "g"/2.000000000,0 -> /BYTES/g2 -data: "h"/3.000000000,0 -> /BYTES/h3 -meta: "j"/0,0 -> txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true -data: "j"/7.000000000,0 -> /BYTES/j7 -data: "k"/5.000000000,0 -> /BYTES/k5 - -# Iterate across the entire span, forward and reverse. -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_ge k=a -iter_scan ----- -iter_seek_ge: "a"/4.000000000,0=/ -iter_scan: "a"/4.000000000,0=/ -iter_scan: "a"/2.000000000,0=/BYTES/a2 -iter_scan: "a"/1.000000000,0=/ -iter_scan: "b"/4.000000000,0=/ -iter_scan: "b"/3.000000000,0=/ -iter_scan: "b"/1.000000000,0=/ -iter_scan: "c"/5.000000000,0=/ -iter_scan: "c"/3.000000000,0=/ -iter_scan: "c"/1.000000000,0=/ -iter_scan: "d"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true -iter_scan: "d"/7.000000000,0=/BYTES/d7 -iter_scan: "d"/5.000000000,0=/ -iter_scan: "d"/4.000000000,0=/BYTES/d4 -iter_scan: "d"/2.000000000,0=/BYTES/d2 -iter_scan: "d"/1.000000000,0=/ -iter_scan: "e"/5.000000000,0=/ -iter_scan: "e"/3.000000000,0=/BYTES/e3 -iter_scan: "f"/6.000000000,0=/BYTES/f6 -iter_scan: "f"/5.000000000,0=/ -iter_scan: "f"/4.000000000,0=/BYTES/f4 -iter_scan: "f"/3.000000000,0=/ -iter_scan: "f"/2.000000000,0=/BYTES/f2 -iter_scan: "g"/5.000000000,0=/ -iter_scan: "g"/4.000000000,0=/BYTES/g4 -iter_scan: "g"/3.000000000,0=/ -iter_scan: "g"/2.000000000,0=/BYTES/g2 -iter_scan: "h"/3.000000000,0=/BYTES/h3 -iter_scan: "h"/1.000000000,0=/ -iter_scan: "j"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true -iter_scan: "j"/7.000000000,0=/BYTES/j7 -iter_scan: "k"/5.000000000,0=/BYTES/k5 -iter_scan: "l"/5.000000000,0=/ -iter_scan: "n"/5.000000000,0=/ -iter_scan: "n"/3.000000000,0=/ -iter_scan: . - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_lt k=z -iter_scan reverse ----- -iter_seek_lt: "n"/3.000000000,0=/ -iter_scan: "n"/3.000000000,0=/ -iter_scan: "n"/5.000000000,0=/ -iter_scan: "l"/5.000000000,0=/ -iter_scan: "k"/5.000000000,0=/BYTES/k5 -iter_scan: "j"/7.000000000,0=/BYTES/j7 -iter_scan: "j"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true -iter_scan: "h"/1.000000000,0=/ -iter_scan: "h"/3.000000000,0=/BYTES/h3 -iter_scan: "g"/2.000000000,0=/BYTES/g2 -iter_scan: "g"/3.000000000,0=/ -iter_scan: "g"/4.000000000,0=/BYTES/g4 -iter_scan: "g"/5.000000000,0=/ -iter_scan: "f"/2.000000000,0=/BYTES/f2 -iter_scan: "f"/3.000000000,0=/ -iter_scan: "f"/4.000000000,0=/BYTES/f4 -iter_scan: "f"/5.000000000,0=/ -iter_scan: "f"/6.000000000,0=/BYTES/f6 -iter_scan: "e"/3.000000000,0=/BYTES/e3 -iter_scan: "e"/5.000000000,0=/ -iter_scan: "d"/1.000000000,0=/ -iter_scan: "d"/2.000000000,0=/BYTES/d2 -iter_scan: "d"/4.000000000,0=/BYTES/d4 -iter_scan: "d"/5.000000000,0=/ -iter_scan: "d"/7.000000000,0=/BYTES/d7 -iter_scan: "d"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true -iter_scan: "c"/1.000000000,0=/ -iter_scan: "c"/3.000000000,0=/ -iter_scan: "c"/5.000000000,0=/ -iter_scan: "b"/1.000000000,0=/ -iter_scan: "b"/3.000000000,0=/ -iter_scan: "b"/4.000000000,0=/ -iter_scan: "a"/1.000000000,0=/ -iter_scan: "a"/2.000000000,0=/BYTES/a2 -iter_scan: "a"/4.000000000,0=/ -iter_scan: . - -# Iterate across the entire span using NextKey(). -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_ge k=a -iter_next_key -iter_next_key -iter_next_key -iter_next_key -iter_next_key -iter_next_key -iter_next_key -iter_next_key -iter_next_key -iter_next_key -iter_next_key -iter_next_key ----- -iter_seek_ge: "a"/4.000000000,0=/ -iter_next_key: "b"/4.000000000,0=/ -iter_next_key: "c"/5.000000000,0=/ -iter_next_key: "d"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true -iter_next_key: "e"/5.000000000,0=/ -iter_next_key: "f"/6.000000000,0=/BYTES/f6 -iter_next_key: "g"/5.000000000,0=/ -iter_next_key: "h"/3.000000000,0=/BYTES/h3 -iter_next_key: "j"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true -iter_next_key: "k"/5.000000000,0=/BYTES/k5 -iter_next_key: "l"/5.000000000,0=/ -iter_next_key: "n"/5.000000000,0=/ -iter_next_key: . - -# Unversioned seeks. -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_ge k=a -iter_seek_ge k=b -iter_seek_ge k=c -iter_seek_ge k=d -iter_seek_ge k=e -iter_seek_ge k=f -iter_seek_ge k=g -iter_seek_ge k=h -iter_seek_ge k=i -iter_seek_ge k=j -iter_seek_ge k=k -iter_seek_ge k=l -iter_seek_ge k=m -iter_seek_ge k=n -iter_seek_ge k=o ----- -iter_seek_ge: "a"/4.000000000,0=/ -iter_seek_ge: "b"/4.000000000,0=/ -iter_seek_ge: "c"/5.000000000,0=/ -iter_seek_ge: "d"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true -iter_seek_ge: "e"/5.000000000,0=/ -iter_seek_ge: "f"/6.000000000,0=/BYTES/f6 -iter_seek_ge: "g"/5.000000000,0=/ -iter_seek_ge: "h"/3.000000000,0=/BYTES/h3 -iter_seek_ge: "j"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true -iter_seek_ge: "j"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true -iter_seek_ge: "k"/5.000000000,0=/BYTES/k5 -iter_seek_ge: "l"/5.000000000,0=/ -iter_seek_ge: "n"/5.000000000,0=/ -iter_seek_ge: "n"/5.000000000,0=/ -iter_seek_ge: . - -run ok -iter_new types=pointsAndRanges pointSynthesis prefix -iter_seek_ge k=a -iter_seek_ge k=b -iter_seek_ge k=c -iter_seek_ge k=d -iter_seek_ge k=e -iter_seek_ge k=f -iter_seek_ge k=g -iter_seek_ge k=h -iter_seek_ge k=i -iter_seek_ge k=j -iter_seek_ge k=k -iter_seek_ge k=l -iter_seek_ge k=m -iter_seek_ge k=n -iter_seek_ge k=o ----- -iter_seek_ge: "a"/4.000000000,0=/ -iter_seek_ge: "b"/4.000000000,0=/ -iter_seek_ge: "c"/5.000000000,0=/ -iter_seek_ge: "d"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true -iter_seek_ge: "e"/5.000000000,0=/ -iter_seek_ge: "f"/6.000000000,0=/BYTES/f6 -iter_seek_ge: "g"/5.000000000,0=/ -iter_seek_ge: "h"/3.000000000,0=/BYTES/h3 -iter_seek_ge: "i"/1.000000000,0=/ -iter_seek_ge: "j"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true -iter_seek_ge: "k"/5.000000000,0=/BYTES/k5 -iter_seek_ge: "l"/5.000000000,0=/ -iter_seek_ge: "m"/5.000000000,0=/ -iter_seek_ge: "n"/5.000000000,0=/ -iter_seek_ge: . - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_lt k=o -iter_seek_lt k=n -iter_seek_lt k=m -iter_seek_lt k=l -iter_seek_lt k=k -iter_seek_lt k=j -iter_seek_lt k=i -iter_seek_lt k=h -iter_seek_lt k=g -iter_seek_lt k=f -iter_seek_lt k=e -iter_seek_lt k=d -iter_seek_lt k=c -iter_seek_lt k=b -iter_seek_lt k=a ----- -iter_seek_lt: "n"/3.000000000,0=/ -iter_seek_lt: "l"/5.000000000,0=/ -iter_seek_lt: "l"/5.000000000,0=/ -iter_seek_lt: "k"/5.000000000,0=/BYTES/k5 -iter_seek_lt: "j"/7.000000000,0=/BYTES/j7 -iter_seek_lt: "h"/1.000000000,0=/ -iter_seek_lt: "h"/1.000000000,0=/ -iter_seek_lt: "g"/2.000000000,0=/BYTES/g2 -iter_seek_lt: "f"/2.000000000,0=/BYTES/f2 -iter_seek_lt: "e"/3.000000000,0=/BYTES/e3 -iter_seek_lt: "d"/1.000000000,0=/ -iter_seek_lt: "c"/1.000000000,0=/ -iter_seek_lt: "b"/1.000000000,0=/ -iter_seek_lt: "a"/1.000000000,0=/ -iter_seek_lt: . - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_intent_ge k=a txn=A -iter_seek_intent_ge k=b txn=A -iter_seek_intent_ge k=c txn=A -iter_seek_intent_ge k=d txn=A -iter_seek_intent_ge k=e txn=A -iter_seek_intent_ge k=f txn=A -iter_seek_intent_ge k=g txn=A -iter_seek_intent_ge k=h txn=A -iter_seek_intent_ge k=i txn=A -iter_seek_intent_ge k=j txn=A -iter_seek_intent_ge k=k txn=A -iter_seek_intent_ge k=l txn=A -iter_seek_intent_ge k=m txn=A -iter_seek_intent_ge k=n txn=A -iter_seek_intent_ge k=o txn=A ----- -iter_seek_intent_ge: "a"/4.000000000,0=/ -iter_seek_intent_ge: "b"/4.000000000,0=/ -iter_seek_intent_ge: "c"/5.000000000,0=/ -iter_seek_intent_ge: "d"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true -iter_seek_intent_ge: "e"/5.000000000,0=/ -iter_seek_intent_ge: "f"/6.000000000,0=/BYTES/f6 -iter_seek_intent_ge: "g"/5.000000000,0=/ -iter_seek_intent_ge: "h"/3.000000000,0=/BYTES/h3 -iter_seek_intent_ge: "j"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true -iter_seek_intent_ge: "j"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true -iter_seek_intent_ge: "k"/5.000000000,0=/BYTES/k5 -iter_seek_intent_ge: "l"/5.000000000,0=/ -iter_seek_intent_ge: "n"/5.000000000,0=/ -iter_seek_intent_ge: "n"/5.000000000,0=/ -iter_seek_intent_ge: . - -run ok -iter_new types=pointsAndRanges pointSynthesis prefix -iter_seek_intent_ge k=a txn=A -iter_seek_intent_ge k=b txn=A -iter_seek_intent_ge k=c txn=A -iter_seek_intent_ge k=d txn=A -iter_seek_intent_ge k=e txn=A -iter_seek_intent_ge k=f txn=A -iter_seek_intent_ge k=g txn=A -iter_seek_intent_ge k=h txn=A -iter_seek_intent_ge k=i txn=A -iter_seek_intent_ge k=j txn=A -iter_seek_intent_ge k=k txn=A -iter_seek_intent_ge k=l txn=A -iter_seek_intent_ge k=m txn=A -iter_seek_intent_ge k=n txn=A -iter_seek_intent_ge k=o txn=A ----- -iter_seek_intent_ge: "a"/4.000000000,0=/ -iter_seek_intent_ge: "b"/4.000000000,0=/ -iter_seek_intent_ge: "c"/5.000000000,0=/ -iter_seek_intent_ge: "d"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true -iter_seek_intent_ge: "e"/5.000000000,0=/ -iter_seek_intent_ge: "f"/6.000000000,0=/BYTES/f6 -iter_seek_intent_ge: "g"/5.000000000,0=/ -iter_seek_intent_ge: "h"/3.000000000,0=/BYTES/h3 -iter_seek_intent_ge: "i"/1.000000000,0=/ -iter_seek_intent_ge: "j"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true -iter_seek_intent_ge: "k"/5.000000000,0=/BYTES/k5 -iter_seek_intent_ge: "l"/5.000000000,0=/ -iter_seek_intent_ge: "m"/5.000000000,0=/ -iter_seek_intent_ge: "n"/5.000000000,0=/ -iter_seek_intent_ge: . - -# Versioned seeks. -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_ge k=a ts=5 -iter_seek_ge k=a ts=4 -iter_seek_ge k=a ts=3 -iter_seek_ge k=a ts=2 -iter_seek_ge k=a ts=1 ----- -iter_seek_ge: "a"/4.000000000,0=/ -iter_seek_ge: "a"/4.000000000,0=/ -iter_seek_ge: "a"/2.000000000,0=/BYTES/a2 -iter_seek_ge: "a"/2.000000000,0=/BYTES/a2 -iter_seek_ge: "a"/1.000000000,0=/ - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_ge k=b ts=5 -iter_seek_ge k=b ts=4 -iter_seek_ge k=b ts=3 -iter_seek_ge k=b ts=2 -iter_seek_ge k=b ts=1 ----- -iter_seek_ge: "b"/4.000000000,0=/ -iter_seek_ge: "b"/4.000000000,0=/ -iter_seek_ge: "b"/3.000000000,0=/ -iter_seek_ge: "b"/1.000000000,0=/ -iter_seek_ge: "b"/1.000000000,0=/ - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_ge k=c ts=6 -iter_seek_ge k=c ts=5 -iter_seek_ge k=c ts=4 -iter_seek_ge k=c ts=3 -iter_seek_ge k=c ts=2 -iter_seek_ge k=c ts=1 ----- -iter_seek_ge: "c"/5.000000000,0=/ -iter_seek_ge: "c"/5.000000000,0=/ -iter_seek_ge: "c"/3.000000000,0=/ -iter_seek_ge: "c"/3.000000000,0=/ -iter_seek_ge: "c"/1.000000000,0=/ -iter_seek_ge: "c"/1.000000000,0=/ - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_ge k=d ts=0 -iter_seek_ge k=d ts=8 -iter_seek_ge k=d ts=7 -iter_seek_ge k=d ts=6 -iter_seek_ge k=d ts=5 -iter_seek_ge k=d ts=4 -iter_seek_ge k=d ts=3 -iter_seek_ge k=d ts=2 -iter_seek_ge k=d ts=1 ----- -iter_seek_ge: "d"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true -iter_seek_ge: "d"/7.000000000,0=/BYTES/d7 -iter_seek_ge: "d"/7.000000000,0=/BYTES/d7 -iter_seek_ge: "d"/5.000000000,0=/ -iter_seek_ge: "d"/5.000000000,0=/ -iter_seek_ge: "d"/4.000000000,0=/BYTES/d4 -iter_seek_ge: "d"/2.000000000,0=/BYTES/d2 -iter_seek_ge: "d"/2.000000000,0=/BYTES/d2 -iter_seek_ge: "d"/1.000000000,0=/ - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_ge k=e ts=6 -iter_seek_ge k=e ts=5 -iter_seek_ge k=e ts=4 -iter_seek_ge k=e ts=3 -iter_seek_ge k=e ts=2 -iter_seek_ge k=e ts=1 ----- -iter_seek_ge: "e"/5.000000000,0=/ -iter_seek_ge: "e"/5.000000000,0=/ -iter_seek_ge: "e"/3.000000000,0=/BYTES/e3 -iter_seek_ge: "e"/3.000000000,0=/BYTES/e3 -iter_seek_ge: "f"/6.000000000,0=/BYTES/f6 -iter_seek_ge: "f"/6.000000000,0=/BYTES/f6 - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_ge k=f ts=7 -iter_seek_ge k=f ts=6 -iter_seek_ge k=f ts=5 -iter_seek_ge k=f ts=4 -iter_seek_ge k=f ts=3 -iter_seek_ge k=f ts=2 -iter_seek_ge k=f ts=1 ----- -iter_seek_ge: "f"/6.000000000,0=/BYTES/f6 -iter_seek_ge: "f"/6.000000000,0=/BYTES/f6 -iter_seek_ge: "f"/5.000000000,0=/ -iter_seek_ge: "f"/4.000000000,0=/BYTES/f4 -iter_seek_ge: "f"/3.000000000,0=/ -iter_seek_ge: "f"/2.000000000,0=/BYTES/f2 -iter_seek_ge: "g"/5.000000000,0=/ - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_ge k=g ts=6 -iter_seek_ge k=g ts=5 -iter_seek_ge k=g ts=4 -iter_seek_ge k=g ts=3 -iter_seek_ge k=g ts=2 -iter_seek_ge k=g ts=1 ----- -iter_seek_ge: "g"/5.000000000,0=/ -iter_seek_ge: "g"/5.000000000,0=/ -iter_seek_ge: "g"/4.000000000,0=/BYTES/g4 -iter_seek_ge: "g"/3.000000000,0=/ -iter_seek_ge: "g"/2.000000000,0=/BYTES/g2 -iter_seek_ge: "h"/3.000000000,0=/BYTES/h3 - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_ge k=h ts=4 -iter_seek_ge k=h ts=3 -iter_seek_ge k=h ts=2 -iter_seek_ge k=h ts=1 ----- -iter_seek_ge: "h"/3.000000000,0=/BYTES/h3 -iter_seek_ge: "h"/3.000000000,0=/BYTES/h3 -iter_seek_ge: "h"/1.000000000,0=/ -iter_seek_ge: "h"/1.000000000,0=/ - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_ge k=i ts=2 -iter_seek_ge k=i ts=1 ----- -iter_seek_ge: "j"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true -iter_seek_ge: "j"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_ge k=j ts=8 -iter_seek_ge k=j ts=7 -iter_seek_ge k=j ts=6 -iter_seek_ge k=j ts=1 ----- -iter_seek_ge: "j"/7.000000000,0=/BYTES/j7 -iter_seek_ge: "j"/7.000000000,0=/BYTES/j7 -iter_seek_ge: "k"/5.000000000,0=/BYTES/k5 -iter_seek_ge: "k"/5.000000000,0=/BYTES/k5 - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_ge k=k ts=6 -iter_seek_ge k=k ts=5 -iter_seek_ge k=k ts=4 ----- -iter_seek_ge: "k"/5.000000000,0=/BYTES/k5 -iter_seek_ge: "k"/5.000000000,0=/BYTES/k5 -iter_seek_ge: "l"/5.000000000,0=/ - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_ge k=l ts=6 -iter_seek_ge k=l ts=5 -iter_seek_ge k=l ts=4 ----- -iter_seek_ge: "l"/5.000000000,0=/ -iter_seek_ge: "l"/5.000000000,0=/ -iter_seek_ge: "n"/5.000000000,0=/ - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_ge k=m ts=6 -iter_seek_ge k=m ts=5 -iter_seek_ge k=m ts=4 ----- -iter_seek_ge: "n"/5.000000000,0=/ -iter_seek_ge: "n"/5.000000000,0=/ -iter_seek_ge: "n"/5.000000000,0=/ - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_ge k=n ts=6 -iter_seek_ge k=n ts=5 -iter_seek_ge k=n ts=4 -iter_seek_ge k=n ts=3 -iter_seek_ge k=n ts=2 ----- -iter_seek_ge: "n"/5.000000000,0=/ -iter_seek_ge: "n"/5.000000000,0=/ -iter_seek_ge: "n"/3.000000000,0=/ -iter_seek_ge: "n"/3.000000000,0=/ -iter_seek_ge: . - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_ge k=o ts=6 -iter_seek_ge k=o ts=5 -iter_seek_ge k=o ts=4 -iter_seek_ge k=o ts=3 ----- -iter_seek_ge: . -iter_seek_ge: . -iter_seek_ge: . -iter_seek_ge: . - -# Versioned prefix seeks. -run ok -iter_new types=pointsAndRanges pointSynthesis prefix -iter_seek_ge k=e ts=6 -iter_seek_ge k=e ts=5 -iter_seek_ge k=e ts=4 -iter_seek_ge k=e ts=3 -iter_seek_ge k=e ts=2 -iter_seek_ge k=e ts=1 ----- -iter_seek_ge: "e"/5.000000000,0=/ -iter_seek_ge: "e"/5.000000000,0=/ -iter_seek_ge: "e"/3.000000000,0=/BYTES/e3 -iter_seek_ge: "e"/3.000000000,0=/BYTES/e3 -iter_seek_ge: "e"/1.000000000,0=/ -iter_seek_ge: "e"/1.000000000,0=/ - -run ok -iter_new types=pointsAndRanges pointSynthesis prefix -iter_seek_ge k=j ts=8 -iter_seek_ge k=j ts=7 -iter_seek_ge k=j ts=6 -iter_seek_ge k=j ts=1 ----- -iter_seek_ge: "j"/7.000000000,0=/BYTES/j7 -iter_seek_ge: "j"/7.000000000,0=/BYTES/j7 -iter_seek_ge: "j"/1.000000000,0=/ -iter_seek_ge: "j"/1.000000000,0=/ - -run ok -iter_new types=pointsAndRanges pointSynthesis prefix -iter_seek_ge k=l ts=6 -iter_seek_ge k=l ts=5 -iter_seek_ge k=l ts=4 ----- -iter_seek_ge: "l"/5.000000000,0=/ -iter_seek_ge: "l"/5.000000000,0=/ -iter_seek_ge: . - -run ok -iter_new types=pointsAndRanges pointSynthesis prefix -iter_seek_ge k=m ts=6 -iter_seek_ge k=m ts=5 -iter_seek_ge k=m ts=4 ----- -iter_seek_ge: "m"/5.000000000,0=/ -iter_seek_ge: "m"/5.000000000,0=/ -iter_seek_ge: . - -run ok -iter_new types=pointsAndRanges pointSynthesis prefix -iter_seek_ge k=n ts=6 -iter_seek_ge k=n ts=5 -iter_seek_ge k=n ts=4 -iter_seek_ge k=n ts=3 ----- -iter_seek_ge: "n"/5.000000000,0=/ -iter_seek_ge: "n"/5.000000000,0=/ -iter_seek_ge: "n"/3.000000000,0=/ -iter_seek_ge: "n"/3.000000000,0=/ - -run ok -iter_new types=pointsAndRanges pointSynthesis prefix -iter_seek_ge k=o ts=6 -iter_seek_ge k=o ts=5 -iter_seek_ge k=o ts=4 ----- -iter_seek_ge: . -iter_seek_ge: . -iter_seek_ge: . - -# Versioned reverse seeks. -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_lt k=a ts=1 -iter_seek_lt k=a ts=2 -iter_seek_lt k=a ts=3 -iter_seek_lt k=a ts=4 -iter_seek_lt k=a ts=5 ----- -iter_seek_lt: "a"/2.000000000,0=/BYTES/a2 -iter_seek_lt: "a"/4.000000000,0=/ -iter_seek_lt: "a"/4.000000000,0=/ -iter_seek_lt: . -iter_seek_lt: . - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_lt k=b ts=1 -iter_seek_lt k=b ts=2 -iter_seek_lt k=b ts=3 -iter_seek_lt k=b ts=4 -iter_seek_lt k=b ts=5 ----- -iter_seek_lt: "b"/3.000000000,0=/ -iter_seek_lt: "b"/3.000000000,0=/ -iter_seek_lt: "b"/4.000000000,0=/ -iter_seek_lt: "a"/1.000000000,0=/ -iter_seek_lt: "a"/1.000000000,0=/ - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_lt k=c ts=1 -iter_seek_lt k=c ts=2 -iter_seek_lt k=c ts=3 -iter_seek_lt k=c ts=4 -iter_seek_lt k=c ts=5 -iter_seek_lt k=c ts=6 ----- -iter_seek_lt: "c"/3.000000000,0=/ -iter_seek_lt: "c"/3.000000000,0=/ -iter_seek_lt: "c"/5.000000000,0=/ -iter_seek_lt: "c"/5.000000000,0=/ -iter_seek_lt: "b"/1.000000000,0=/ -iter_seek_lt: "b"/1.000000000,0=/ - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_lt k=d ts=1 -iter_seek_lt k=d ts=2 -iter_seek_lt k=d ts=3 -iter_seek_lt k=d ts=4 -iter_seek_lt k=d ts=5 -iter_seek_lt k=d ts=6 -iter_seek_lt k=d ts=7 -iter_seek_lt k=d ts=8 ----- -iter_seek_lt: "d"/2.000000000,0=/BYTES/d2 -iter_seek_lt: "d"/4.000000000,0=/BYTES/d4 -iter_seek_lt: "d"/4.000000000,0=/BYTES/d4 -iter_seek_lt: "d"/5.000000000,0=/ -iter_seek_lt: "d"/7.000000000,0=/BYTES/d7 -iter_seek_lt: "d"/7.000000000,0=/BYTES/d7 -iter_seek_lt: "d"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true -iter_seek_lt: "d"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_lt k=e ts=1 -iter_seek_lt k=e ts=2 -iter_seek_lt k=e ts=3 -iter_seek_lt k=e ts=4 -iter_seek_lt k=e ts=5 -iter_seek_lt k=e ts=6 ----- -iter_seek_lt: "e"/3.000000000,0=/BYTES/e3 -iter_seek_lt: "e"/3.000000000,0=/BYTES/e3 -iter_seek_lt: "e"/5.000000000,0=/ -iter_seek_lt: "e"/5.000000000,0=/ -iter_seek_lt: "d"/1.000000000,0=/ -iter_seek_lt: "d"/1.000000000,0=/ - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_lt k=f ts=1 -iter_seek_lt k=f ts=2 -iter_seek_lt k=f ts=3 -iter_seek_lt k=f ts=4 -iter_seek_lt k=f ts=5 -iter_seek_lt k=f ts=6 -iter_seek_lt k=f ts=7 ----- -iter_seek_lt: "f"/2.000000000,0=/BYTES/f2 -iter_seek_lt: "f"/3.000000000,0=/ -iter_seek_lt: "f"/4.000000000,0=/BYTES/f4 -iter_seek_lt: "f"/5.000000000,0=/ -iter_seek_lt: "f"/6.000000000,0=/BYTES/f6 -iter_seek_lt: "e"/3.000000000,0=/BYTES/e3 -iter_seek_lt: "e"/3.000000000,0=/BYTES/e3 - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_lt k=g ts=1 -iter_seek_lt k=g ts=2 -iter_seek_lt k=g ts=3 -iter_seek_lt k=g ts=4 -iter_seek_lt k=g ts=5 -iter_seek_lt k=g ts=6 ----- -iter_seek_lt: "g"/2.000000000,0=/BYTES/g2 -iter_seek_lt: "g"/3.000000000,0=/ -iter_seek_lt: "g"/4.000000000,0=/BYTES/g4 -iter_seek_lt: "g"/5.000000000,0=/ -iter_seek_lt: "f"/2.000000000,0=/BYTES/f2 -iter_seek_lt: "f"/2.000000000,0=/BYTES/f2 - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_lt k=h ts=1 -iter_seek_lt k=h ts=2 -iter_seek_lt k=h ts=3 -iter_seek_lt k=h ts=4 ----- -iter_seek_lt: "h"/3.000000000,0=/BYTES/h3 -iter_seek_lt: "h"/3.000000000,0=/BYTES/h3 -iter_seek_lt: "g"/2.000000000,0=/BYTES/g2 -iter_seek_lt: "g"/2.000000000,0=/BYTES/g2 - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_lt k=i ts=1 -iter_seek_lt k=i ts=2 ----- -iter_seek_lt: "h"/1.000000000,0=/ -iter_seek_lt: "h"/1.000000000,0=/ - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_lt k=j ts=1 -iter_seek_lt k=j ts=6 -iter_seek_lt k=j ts=7 -iter_seek_lt k=j ts=8 ----- -iter_seek_lt: "j"/7.000000000,0=/BYTES/j7 -iter_seek_lt: "j"/7.000000000,0=/BYTES/j7 -iter_seek_lt: "j"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true -iter_seek_lt: "j"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_lt k=k ts=1 -iter_seek_lt k=k ts=4 -iter_seek_lt k=k ts=5 -iter_seek_lt k=k ts=6 ----- -iter_seek_lt: "k"/5.000000000,0=/BYTES/k5 -iter_seek_lt: "k"/5.000000000,0=/BYTES/k5 -iter_seek_lt: "j"/7.000000000,0=/BYTES/j7 -iter_seek_lt: "j"/7.000000000,0=/BYTES/j7 - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_lt k=l ts=4 -iter_seek_lt k=l ts=5 -iter_seek_lt k=l ts=6 ----- -iter_seek_lt: "l"/5.000000000,0=/ -iter_seek_lt: "k"/5.000000000,0=/BYTES/k5 -iter_seek_lt: "k"/5.000000000,0=/BYTES/k5 - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_lt k=l ts=4 -iter_seek_lt k=l ts=5 -iter_seek_lt k=l ts=6 ----- -iter_seek_lt: "l"/5.000000000,0=/ -iter_seek_lt: "k"/5.000000000,0=/BYTES/k5 -iter_seek_lt: "k"/5.000000000,0=/BYTES/k5 - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_lt k=m ts=4 -iter_seek_lt k=m ts=5 -iter_seek_lt k=m ts=6 ----- -iter_seek_lt: "l"/5.000000000,0=/ -iter_seek_lt: "l"/5.000000000,0=/ -iter_seek_lt: "l"/5.000000000,0=/ - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_lt k=n ts=2 -iter_seek_lt k=n ts=3 -iter_seek_lt k=n ts=4 -iter_seek_lt k=n ts=5 -iter_seek_lt k=n ts=6 ----- -iter_seek_lt: "n"/3.000000000,0=/ -iter_seek_lt: "n"/5.000000000,0=/ -iter_seek_lt: "n"/5.000000000,0=/ -iter_seek_lt: "l"/5.000000000,0=/ -iter_seek_lt: "l"/5.000000000,0=/ - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_lt k=o ts=1 ----- -iter_seek_lt: "n"/3.000000000,0=/ - -# Seeks with opposite scans. -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_ge k=d -iter_scan reverse ----- -iter_seek_ge: "d"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true -iter_scan: "d"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true -iter_scan: "c"/1.000000000,0=/ -iter_scan: "c"/3.000000000,0=/ -iter_scan: "c"/5.000000000,0=/ -iter_scan: "b"/1.000000000,0=/ -iter_scan: "b"/3.000000000,0=/ -iter_scan: "b"/4.000000000,0=/ -iter_scan: "a"/1.000000000,0=/ -iter_scan: "a"/2.000000000,0=/BYTES/a2 -iter_scan: "a"/4.000000000,0=/ -iter_scan: . - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_ge k=d ts=5 -iter_scan reverse ----- -iter_seek_ge: "d"/5.000000000,0=/ -iter_scan: "d"/5.000000000,0=/ -iter_scan: "d"/7.000000000,0=/BYTES/d7 -iter_scan: "d"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true -iter_scan: "c"/1.000000000,0=/ -iter_scan: "c"/3.000000000,0=/ -iter_scan: "c"/5.000000000,0=/ -iter_scan: "b"/1.000000000,0=/ -iter_scan: "b"/3.000000000,0=/ -iter_scan: "b"/4.000000000,0=/ -iter_scan: "a"/1.000000000,0=/ -iter_scan: "a"/2.000000000,0=/BYTES/a2 -iter_scan: "a"/4.000000000,0=/ -iter_scan: . - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_lt k=g -iter_scan ----- -iter_seek_lt: "f"/2.000000000,0=/BYTES/f2 -iter_scan: "f"/2.000000000,0=/BYTES/f2 -iter_scan: "g"/5.000000000,0=/ -iter_scan: "g"/4.000000000,0=/BYTES/g4 -iter_scan: "g"/3.000000000,0=/ -iter_scan: "g"/2.000000000,0=/BYTES/g2 -iter_scan: "h"/3.000000000,0=/BYTES/h3 -iter_scan: "h"/1.000000000,0=/ -iter_scan: "j"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true -iter_scan: "j"/7.000000000,0=/BYTES/j7 -iter_scan: "k"/5.000000000,0=/BYTES/k5 -iter_scan: "l"/5.000000000,0=/ -iter_scan: "n"/5.000000000,0=/ -iter_scan: "n"/3.000000000,0=/ -iter_scan: . - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_lt k=g ts=2 -iter_scan ----- -iter_seek_lt: "g"/3.000000000,0=/ -iter_scan: "g"/3.000000000,0=/ -iter_scan: "g"/2.000000000,0=/BYTES/g2 -iter_scan: "h"/3.000000000,0=/BYTES/h3 -iter_scan: "h"/1.000000000,0=/ -iter_scan: "j"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true -iter_scan: "j"/7.000000000,0=/BYTES/j7 -iter_scan: "k"/5.000000000,0=/BYTES/k5 -iter_scan: "l"/5.000000000,0=/ -iter_scan: "n"/5.000000000,0=/ -iter_scan: "n"/3.000000000,0=/ -iter_scan: . - -# Try some direction changes. -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_ge k=e ts=4 -iter_prev -iter_next -iter_next -iter_prev ----- -iter_seek_ge: "e"/3.000000000,0=/BYTES/e3 -iter_prev: "e"/5.000000000,0=/ -iter_next: "e"/3.000000000,0=/BYTES/e3 -iter_next: "f"/6.000000000,0=/BYTES/f6 -iter_prev: "e"/3.000000000,0=/BYTES/e3 - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_lt k=e ts=4 -iter_next -iter_prev -iter_prev -iter_next ----- -iter_seek_lt: "e"/5.000000000,0=/ -iter_next: "e"/3.000000000,0=/BYTES/e3 -iter_prev: "e"/5.000000000,0=/ -iter_prev: "d"/1.000000000,0=/ -iter_next: "e"/5.000000000,0=/ - -run ok -iter_new kind=keys types=pointsAndRanges pointSynthesis -iter_seek_ge k=e ts=4 -iter_prev -iter_prev -iter_next_key -iter_next -iter_next_key -iter_prev -iter_prev -iter_next_key -iter_next ----- -iter_seek_ge: "e"/3.000000000,0=/BYTES/e3 -iter_prev: "e"/5.000000000,0=/ -iter_prev: "d"/1.000000000,0=/ -iter_next_key: "e"/5.000000000,0=/ -iter_next: "e"/3.000000000,0=/BYTES/e3 -iter_next_key: "f"/6.000000000,0=/BYTES/f6 -iter_prev: "e"/3.000000000,0=/BYTES/e3 -iter_prev: "e"/5.000000000,0=/ -iter_next_key: "f"/6.000000000,0=/BYTES/f6 -iter_next: "f"/5.000000000,0=/ - -run ok -iter_new kind=keys types=pointsAndRanges pointSynthesis -iter_seek_ge k=k ts=4 -iter_next_key -iter_prev -iter_next_key -iter_next -iter_prev ----- -iter_seek_ge: "l"/5.000000000,0=/ -iter_next_key: "n"/5.000000000,0=/ -iter_prev: "l"/5.000000000,0=/ -iter_next_key: "n"/5.000000000,0=/ -iter_next: "n"/3.000000000,0=/ -iter_prev: "n"/5.000000000,0=/ - -run ok -iter_new kind=keys types=pointsAndRanges pointSynthesis -iter_seek_ge k=e ts=3 -iter_prev -iter_next_key ----- -iter_seek_ge: "e"/3.000000000,0=/BYTES/e3 -iter_prev: "e"/5.000000000,0=/ -iter_next_key: "f"/6.000000000,0=/BYTES/f6 - -# Exhausting the iterator then reversing should work in both directions, -# both after a seek and after a step. -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_lt k=a -iter_next ----- -iter_seek_lt: . -iter_next: "a"/4.000000000,0=/ - -run ok -iter_new kind=keys types=pointsAndRanges pointSynthesis -iter_seek_lt k=a -iter_next_key ----- -iter_seek_lt: . -iter_next_key: "a"/4.000000000,0=/ - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_ge k=z -iter_prev ----- -iter_seek_ge: . -iter_prev: "n"/3.000000000,0=/ - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_lt k=z -iter_next -iter_next -iter_prev ----- -iter_seek_lt: "n"/3.000000000,0=/ -iter_next: . -iter_next: . -iter_prev: "n"/3.000000000,0=/ - -run ok -iter_new kind=keys types=pointsAndRanges pointSynthesis -iter_seek_lt k=z -iter_next_key -iter_next_key -iter_prev ----- -iter_seek_lt: "n"/3.000000000,0=/ -iter_next_key: . -iter_next_key: . -iter_prev: "n"/3.000000000,0=/ - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_ge k=a -iter_prev -iter_prev -iter_next ----- -iter_seek_ge: "a"/4.000000000,0=/ -iter_prev: . -iter_prev: . -iter_next: "a"/4.000000000,0=/ - -run ok -iter_new kind=keys types=pointsAndRanges pointSynthesis -iter_seek_ge k=a -iter_prev -iter_prev -iter_next_key ----- -iter_seek_ge: "a"/4.000000000,0=/ -iter_prev: . -iter_prev: . -iter_next_key: "a"/4.000000000,0=/ diff --git a/pkg/storage/testdata/mvcc_histories/range_tombstone_iter_point_synthesis_ts_conflict b/pkg/storage/testdata/mvcc_histories/range_tombstone_iter_point_synthesis_ts_conflict deleted file mode 100644 index 35b4680e9a2e..000000000000 --- a/pkg/storage/testdata/mvcc_histories/range_tombstone_iter_point_synthesis_ts_conflict +++ /dev/null @@ -1,851 +0,0 @@ -# Tests point synthesis where some point/range keys have the same timestamp. -# This shouldn't happen, but it's been seen to happen in randomized tests due to -# faulty conflict checks. In this case, the range key takes precedence. -# -# T -# 5 o-----------o -# 4 k4 l4 m4 n4 -# 3 a3 b3 oc3-d3--e3--f3--g3--h3--i3--j3--k3--l3--m3--n3--o3--op3 q3 r3 -# 2 e2 f2 g2 h2 -# 1 o---------------o -# a b c d e f g h i j k l m n o p q r -run ok -put k=e ts=2 v=e2 -put k=f ts=2 v=f2 -put k=g ts=2 v=g2 -put k=h ts=2 v=h2 -put k=a ts=3 v=a3 -put k=b ts=3 v=b3 -put k=c ts=3 v=c3 -put k=d ts=3 v=d3 -put k=e ts=3 v=e3 -put k=f ts=3 v=f3 -put k=g ts=3 v=g3 -put k=h ts=3 v=h3 -put k=i ts=3 v=i3 -put k=j ts=3 v=j3 -put k=k ts=3 v=k3 -put k=l ts=3 v=l3 -put k=m ts=3 v=m3 -put k=n ts=3 v=n3 -put k=o ts=3 v=o3 -put k=p ts=3 v=p3 -put k=q ts=3 v=q3 -put k=r ts=3 v=r3 -put k=k ts=4 v=k4 -put k=l ts=4 v=l4 -put k=m ts=4 v=m4 -put k=n ts=4 v=n4 -put_rangekey k=c end=g ts=1 -put_rangekey k=c end=p ts=3 -put_rangekey k=m end=p ts=5 ----- ->> at end: -rangekey: {c-g}/[3.000000000,0=/ 1.000000000,0=/] -rangekey: {g-m}/[3.000000000,0=/] -rangekey: {m-p}/[5.000000000,0=/ 3.000000000,0=/] -data: "a"/3.000000000,0 -> /BYTES/a3 -data: "b"/3.000000000,0 -> /BYTES/b3 -data: "c"/3.000000000,0 -> /BYTES/c3 -data: "d"/3.000000000,0 -> /BYTES/d3 -data: "e"/3.000000000,0 -> /BYTES/e3 -data: "e"/2.000000000,0 -> /BYTES/e2 -data: "f"/3.000000000,0 -> /BYTES/f3 -data: "f"/2.000000000,0 -> /BYTES/f2 -data: "g"/3.000000000,0 -> /BYTES/g3 -data: "g"/2.000000000,0 -> /BYTES/g2 -data: "h"/3.000000000,0 -> /BYTES/h3 -data: "h"/2.000000000,0 -> /BYTES/h2 -data: "i"/3.000000000,0 -> /BYTES/i3 -data: "j"/3.000000000,0 -> /BYTES/j3 -data: "k"/4.000000000,0 -> /BYTES/k4 -data: "k"/3.000000000,0 -> /BYTES/k3 -data: "l"/4.000000000,0 -> /BYTES/l4 -data: "l"/3.000000000,0 -> /BYTES/l3 -data: "m"/4.000000000,0 -> /BYTES/m4 -data: "m"/3.000000000,0 -> /BYTES/m3 -data: "n"/4.000000000,0 -> /BYTES/n4 -data: "n"/3.000000000,0 -> /BYTES/n3 -data: "o"/3.000000000,0 -> /BYTES/o3 -data: "p"/3.000000000,0 -> /BYTES/p3 -data: "q"/3.000000000,0 -> /BYTES/q3 -data: "r"/3.000000000,0 -> /BYTES/r3 - -# Iterate across the entire span, forward and reverse. -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_ge k=a -iter_scan ----- -iter_seek_ge: "a"/3.000000000,0=/BYTES/a3 -iter_scan: "a"/3.000000000,0=/BYTES/a3 -iter_scan: "b"/3.000000000,0=/BYTES/b3 -iter_scan: "c"/3.000000000,0=/ -iter_scan: "c"/1.000000000,0=/ -iter_scan: "d"/3.000000000,0=/ -iter_scan: "e"/3.000000000,0=/ -iter_scan: "e"/2.000000000,0=/BYTES/e2 -iter_scan: "f"/3.000000000,0=/ -iter_scan: "f"/2.000000000,0=/BYTES/f2 -iter_scan: "g"/3.000000000,0=/ -iter_scan: "g"/2.000000000,0=/BYTES/g2 -iter_scan: "h"/3.000000000,0=/ -iter_scan: "h"/2.000000000,0=/BYTES/h2 -iter_scan: "i"/3.000000000,0=/ -iter_scan: "j"/3.000000000,0=/ -iter_scan: "k"/4.000000000,0=/BYTES/k4 -iter_scan: "k"/3.000000000,0=/ -iter_scan: "l"/4.000000000,0=/BYTES/l4 -iter_scan: "l"/3.000000000,0=/ -iter_scan: "m"/5.000000000,0=/ -iter_scan: "m"/4.000000000,0=/BYTES/m4 -iter_scan: "m"/3.000000000,0=/ -iter_scan: "n"/5.000000000,0=/ -iter_scan: "n"/4.000000000,0=/BYTES/n4 -iter_scan: "n"/3.000000000,0=/ -iter_scan: "o"/5.000000000,0=/ -iter_scan: "o"/3.000000000,0=/ -iter_scan: "p"/3.000000000,0=/BYTES/p3 -iter_scan: "q"/3.000000000,0=/BYTES/q3 -iter_scan: "r"/3.000000000,0=/BYTES/r3 -iter_scan: . - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_lt k=z -iter_scan reverse ----- -iter_seek_lt: "r"/3.000000000,0=/BYTES/r3 -iter_scan: "r"/3.000000000,0=/BYTES/r3 -iter_scan: "q"/3.000000000,0=/BYTES/q3 -iter_scan: "p"/3.000000000,0=/BYTES/p3 -iter_scan: "o"/3.000000000,0=/ -iter_scan: "o"/5.000000000,0=/ -iter_scan: "n"/3.000000000,0=/ -iter_scan: "n"/4.000000000,0=/BYTES/n4 -iter_scan: "n"/5.000000000,0=/ -iter_scan: "m"/3.000000000,0=/ -iter_scan: "m"/4.000000000,0=/BYTES/m4 -iter_scan: "m"/5.000000000,0=/ -iter_scan: "l"/3.000000000,0=/ -iter_scan: "l"/4.000000000,0=/BYTES/l4 -iter_scan: "k"/3.000000000,0=/ -iter_scan: "k"/4.000000000,0=/BYTES/k4 -iter_scan: "j"/3.000000000,0=/ -iter_scan: "i"/3.000000000,0=/ -iter_scan: "h"/2.000000000,0=/BYTES/h2 -iter_scan: "h"/3.000000000,0=/ -iter_scan: "g"/2.000000000,0=/BYTES/g2 -iter_scan: "g"/3.000000000,0=/ -iter_scan: "f"/2.000000000,0=/BYTES/f2 -iter_scan: "f"/3.000000000,0=/ -iter_scan: "e"/2.000000000,0=/BYTES/e2 -iter_scan: "e"/3.000000000,0=/ -iter_scan: "d"/3.000000000,0=/ -iter_scan: "c"/1.000000000,0=/ -iter_scan: "c"/3.000000000,0=/ -iter_scan: "b"/3.000000000,0=/BYTES/b3 -iter_scan: "a"/3.000000000,0=/BYTES/a3 -iter_scan: . - -# Iterate across the entire span using NextKey(). -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_ge k=a -iter_next_key -iter_next_key -iter_next_key -iter_next_key -iter_next_key -iter_next_key -iter_next_key -iter_next_key -iter_next_key -iter_next_key -iter_next_key -iter_next_key -iter_next_key -iter_next_key -iter_next_key -iter_next_key -iter_next_key -iter_next_key ----- -iter_seek_ge: "a"/3.000000000,0=/BYTES/a3 -iter_next_key: "b"/3.000000000,0=/BYTES/b3 -iter_next_key: "c"/3.000000000,0=/ -iter_next_key: "d"/3.000000000,0=/ -iter_next_key: "e"/3.000000000,0=/ -iter_next_key: "f"/3.000000000,0=/ -iter_next_key: "g"/3.000000000,0=/ -iter_next_key: "h"/3.000000000,0=/ -iter_next_key: "i"/3.000000000,0=/ -iter_next_key: "j"/3.000000000,0=/ -iter_next_key: "k"/4.000000000,0=/BYTES/k4 -iter_next_key: "l"/4.000000000,0=/BYTES/l4 -iter_next_key: "m"/5.000000000,0=/ -iter_next_key: "n"/5.000000000,0=/ -iter_next_key: "o"/5.000000000,0=/ -iter_next_key: "p"/3.000000000,0=/BYTES/p3 -iter_next_key: "q"/3.000000000,0=/BYTES/q3 -iter_next_key: "r"/3.000000000,0=/BYTES/r3 -iter_next_key: . - -# Unversioned seeks. -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_ge k=a -iter_seek_ge k=b -iter_seek_ge k=c -iter_seek_ge k=d -iter_seek_ge k=e -iter_seek_ge k=f ----- -iter_seek_ge: "a"/3.000000000,0=/BYTES/a3 -iter_seek_ge: "b"/3.000000000,0=/BYTES/b3 -iter_seek_ge: "c"/3.000000000,0=/ -iter_seek_ge: "d"/3.000000000,0=/ -iter_seek_ge: "e"/3.000000000,0=/ -iter_seek_ge: "f"/3.000000000,0=/ - -run ok -iter_new types=pointsAndRanges pointSynthesis prefix -iter_seek_ge k=a -iter_seek_ge k=b -iter_seek_ge k=c -iter_seek_ge k=d -iter_seek_ge k=e -iter_seek_ge k=f -iter_seek_ge k=g -iter_seek_ge k=h -iter_seek_ge k=i -iter_seek_ge k=j -iter_seek_ge k=k -iter_seek_ge k=l -iter_seek_ge k=m -iter_seek_ge k=n -iter_seek_ge k=o -iter_seek_ge k=p -iter_seek_ge k=q -iter_seek_ge k=r -iter_seek_ge k=s ----- -iter_seek_ge: "a"/3.000000000,0=/BYTES/a3 -iter_seek_ge: "b"/3.000000000,0=/BYTES/b3 -iter_seek_ge: "c"/3.000000000,0=/ -iter_seek_ge: "d"/3.000000000,0=/ -iter_seek_ge: "e"/3.000000000,0=/ -iter_seek_ge: "f"/3.000000000,0=/ -iter_seek_ge: "g"/3.000000000,0=/ -iter_seek_ge: "h"/3.000000000,0=/ -iter_seek_ge: "i"/3.000000000,0=/ -iter_seek_ge: "j"/3.000000000,0=/ -iter_seek_ge: "k"/4.000000000,0=/BYTES/k4 -iter_seek_ge: "l"/4.000000000,0=/BYTES/l4 -iter_seek_ge: "m"/5.000000000,0=/ -iter_seek_ge: "n"/5.000000000,0=/ -iter_seek_ge: "o"/5.000000000,0=/ -iter_seek_ge: "p"/3.000000000,0=/BYTES/p3 -iter_seek_ge: "q"/3.000000000,0=/BYTES/q3 -iter_seek_ge: "r"/3.000000000,0=/BYTES/r3 -iter_seek_ge: . - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_lt k=s -iter_seek_lt k=r -iter_seek_lt k=q -iter_seek_lt k=p -iter_seek_lt k=o -iter_seek_lt k=n -iter_seek_lt k=m -iter_seek_lt k=l -iter_seek_lt k=k -iter_seek_lt k=j -iter_seek_lt k=i -iter_seek_lt k=h -iter_seek_lt k=g -iter_seek_lt k=f -iter_seek_lt k=e -iter_seek_lt k=d -iter_seek_lt k=c -iter_seek_lt k=b -iter_seek_lt k=a ----- -iter_seek_lt: "r"/3.000000000,0=/BYTES/r3 -iter_seek_lt: "q"/3.000000000,0=/BYTES/q3 -iter_seek_lt: "p"/3.000000000,0=/BYTES/p3 -iter_seek_lt: "o"/3.000000000,0=/ -iter_seek_lt: "n"/3.000000000,0=/ -iter_seek_lt: "m"/3.000000000,0=/ -iter_seek_lt: "l"/3.000000000,0=/ -iter_seek_lt: "k"/3.000000000,0=/ -iter_seek_lt: "j"/3.000000000,0=/ -iter_seek_lt: "i"/3.000000000,0=/ -iter_seek_lt: "h"/2.000000000,0=/BYTES/h2 -iter_seek_lt: "g"/2.000000000,0=/BYTES/g2 -iter_seek_lt: "f"/2.000000000,0=/BYTES/f2 -iter_seek_lt: "e"/2.000000000,0=/BYTES/e2 -iter_seek_lt: "d"/3.000000000,0=/ -iter_seek_lt: "c"/1.000000000,0=/ -iter_seek_lt: "b"/3.000000000,0=/BYTES/b3 -iter_seek_lt: "a"/3.000000000,0=/BYTES/a3 -iter_seek_lt: . - -# Versioned seeks. -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_ge k=c ts=4 -iter_seek_ge k=c ts=3 -iter_seek_ge k=c ts=2 ----- -iter_seek_ge: "c"/3.000000000,0=/ -iter_seek_ge: "c"/3.000000000,0=/ -iter_seek_ge: "c"/1.000000000,0=/ - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_ge k=d ts=4 -iter_seek_ge k=d ts=3 -iter_seek_ge k=d ts=2 ----- -iter_seek_ge: "d"/3.000000000,0=/ -iter_seek_ge: "d"/3.000000000,0=/ -iter_seek_ge: "e"/3.000000000,0=/ - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_ge k=e ts=4 -iter_seek_ge k=e ts=3 -iter_seek_ge k=e ts=2 ----- -iter_seek_ge: "e"/3.000000000,0=/ -iter_seek_ge: "e"/3.000000000,0=/ -iter_seek_ge: "e"/2.000000000,0=/BYTES/e2 - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_ge k=f ts=4 -iter_seek_ge k=f ts=3 -iter_seek_ge k=f ts=2 ----- -iter_seek_ge: "f"/3.000000000,0=/ -iter_seek_ge: "f"/3.000000000,0=/ -iter_seek_ge: "f"/2.000000000,0=/BYTES/f2 - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_ge k=g ts=4 -iter_seek_ge k=g ts=3 -iter_seek_ge k=g ts=2 ----- -iter_seek_ge: "g"/3.000000000,0=/ -iter_seek_ge: "g"/3.000000000,0=/ -iter_seek_ge: "g"/2.000000000,0=/BYTES/g2 - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_ge k=h ts=4 -iter_seek_ge k=h ts=3 -iter_seek_ge k=h ts=2 ----- -iter_seek_ge: "h"/3.000000000,0=/ -iter_seek_ge: "h"/3.000000000,0=/ -iter_seek_ge: "h"/2.000000000,0=/BYTES/h2 - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_ge k=i ts=4 -iter_seek_ge k=i ts=3 -iter_seek_ge k=i ts=2 ----- -iter_seek_ge: "i"/3.000000000,0=/ -iter_seek_ge: "i"/3.000000000,0=/ -iter_seek_ge: "j"/3.000000000,0=/ - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_ge k=j ts=4 -iter_seek_ge k=j ts=3 -iter_seek_ge k=j ts=2 ----- -iter_seek_ge: "j"/3.000000000,0=/ -iter_seek_ge: "j"/3.000000000,0=/ -iter_seek_ge: "k"/4.000000000,0=/BYTES/k4 - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_ge k=k ts=4 -iter_seek_ge k=k ts=3 -iter_seek_ge k=k ts=2 ----- -iter_seek_ge: "k"/4.000000000,0=/BYTES/k4 -iter_seek_ge: "k"/3.000000000,0=/ -iter_seek_ge: "l"/4.000000000,0=/BYTES/l4 - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_ge k=l ts=4 -iter_seek_ge k=l ts=3 -iter_seek_ge k=l ts=2 ----- -iter_seek_ge: "l"/4.000000000,0=/BYTES/l4 -iter_seek_ge: "l"/3.000000000,0=/ -iter_seek_ge: "m"/5.000000000,0=/ - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_ge k=m ts=5 -iter_seek_ge k=m ts=4 -iter_seek_ge k=m ts=3 -iter_seek_ge k=m ts=2 ----- -iter_seek_ge: "m"/5.000000000,0=/ -iter_seek_ge: "m"/4.000000000,0=/BYTES/m4 -iter_seek_ge: "m"/3.000000000,0=/ -iter_seek_ge: "n"/5.000000000,0=/ - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_ge k=n ts=5 -iter_seek_ge k=n ts=4 -iter_seek_ge k=n ts=3 -iter_seek_ge k=n ts=2 ----- -iter_seek_ge: "n"/5.000000000,0=/ -iter_seek_ge: "n"/4.000000000,0=/BYTES/n4 -iter_seek_ge: "n"/3.000000000,0=/ -iter_seek_ge: "o"/5.000000000,0=/ - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_ge k=o ts=5 -iter_seek_ge k=o ts=4 -iter_seek_ge k=o ts=3 -iter_seek_ge k=o ts=2 ----- -iter_seek_ge: "o"/5.000000000,0=/ -iter_seek_ge: "o"/3.000000000,0=/ -iter_seek_ge: "o"/3.000000000,0=/ -iter_seek_ge: "p"/3.000000000,0=/BYTES/p3 - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_ge k=p ts=4 -iter_seek_ge k=p ts=3 -iter_seek_ge k=p ts=2 ----- -iter_seek_ge: "p"/3.000000000,0=/BYTES/p3 -iter_seek_ge: "p"/3.000000000,0=/BYTES/p3 -iter_seek_ge: "q"/3.000000000,0=/BYTES/q3 - -# Versioned reverse seeks. -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_lt k=c ts=4 -iter_seek_lt k=c ts=3 -iter_seek_lt k=c ts=2 -iter_seek_lt k=c ts=1 ----- -iter_seek_lt: "b"/3.000000000,0=/BYTES/b3 -iter_seek_lt: "b"/3.000000000,0=/BYTES/b3 -iter_seek_lt: "c"/3.000000000,0=/ -iter_seek_lt: "c"/3.000000000,0=/ - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_lt k=d ts=4 -iter_seek_lt k=d ts=3 -iter_seek_lt k=d ts=2 -iter_seek_lt k=d ts=1 ----- -iter_seek_lt: "c"/1.000000000,0=/ -iter_seek_lt: "c"/1.000000000,0=/ -iter_seek_lt: "d"/3.000000000,0=/ -iter_seek_lt: "d"/3.000000000,0=/ - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_lt k=e ts=4 -iter_seek_lt k=e ts=3 -iter_seek_lt k=e ts=2 -iter_seek_lt k=e ts=1 ----- -iter_seek_lt: "d"/3.000000000,0=/ -iter_seek_lt: "d"/3.000000000,0=/ -iter_seek_lt: "e"/3.000000000,0=/ -iter_seek_lt: "e"/2.000000000,0=/BYTES/e2 - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_lt k=f ts=4 -iter_seek_lt k=f ts=3 -iter_seek_lt k=f ts=2 -iter_seek_lt k=f ts=1 ----- -iter_seek_lt: "e"/2.000000000,0=/BYTES/e2 -iter_seek_lt: "e"/2.000000000,0=/BYTES/e2 -iter_seek_lt: "f"/3.000000000,0=/ -iter_seek_lt: "f"/2.000000000,0=/BYTES/f2 - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_lt k=g ts=4 -iter_seek_lt k=g ts=3 -iter_seek_lt k=g ts=2 -iter_seek_lt k=g ts=1 ----- -iter_seek_lt: "f"/2.000000000,0=/BYTES/f2 -iter_seek_lt: "f"/2.000000000,0=/BYTES/f2 -iter_seek_lt: "g"/3.000000000,0=/ -iter_seek_lt: "g"/2.000000000,0=/BYTES/g2 - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_lt k=h ts=4 -iter_seek_lt k=h ts=3 -iter_seek_lt k=h ts=2 -iter_seek_lt k=h ts=1 ----- -iter_seek_lt: "g"/2.000000000,0=/BYTES/g2 -iter_seek_lt: "g"/2.000000000,0=/BYTES/g2 -iter_seek_lt: "h"/3.000000000,0=/ -iter_seek_lt: "h"/2.000000000,0=/BYTES/h2 - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_lt k=i ts=4 -iter_seek_lt k=i ts=3 -iter_seek_lt k=i ts=2 -iter_seek_lt k=i ts=1 ----- -iter_seek_lt: "h"/2.000000000,0=/BYTES/h2 -iter_seek_lt: "h"/2.000000000,0=/BYTES/h2 -iter_seek_lt: "i"/3.000000000,0=/ -iter_seek_lt: "i"/3.000000000,0=/ - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_lt k=j ts=4 -iter_seek_lt k=j ts=3 -iter_seek_lt k=j ts=2 -iter_seek_lt k=j ts=1 ----- -iter_seek_lt: "i"/3.000000000,0=/ -iter_seek_lt: "i"/3.000000000,0=/ -iter_seek_lt: "j"/3.000000000,0=/ -iter_seek_lt: "j"/3.000000000,0=/ - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_lt k=k ts=4 -iter_seek_lt k=k ts=3 -iter_seek_lt k=k ts=2 -iter_seek_lt k=k ts=1 ----- -iter_seek_lt: "j"/3.000000000,0=/ -iter_seek_lt: "k"/4.000000000,0=/BYTES/k4 -iter_seek_lt: "k"/3.000000000,0=/ -iter_seek_lt: "k"/3.000000000,0=/ - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_lt k=l ts=4 -iter_seek_lt k=l ts=3 -iter_seek_lt k=l ts=2 -iter_seek_lt k=l ts=1 ----- -iter_seek_lt: "k"/3.000000000,0=/ -iter_seek_lt: "l"/4.000000000,0=/BYTES/l4 -iter_seek_lt: "l"/3.000000000,0=/ -iter_seek_lt: "l"/3.000000000,0=/ - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_lt k=m ts=5 -iter_seek_lt k=m ts=4 -iter_seek_lt k=m ts=3 -iter_seek_lt k=m ts=2 -iter_seek_lt k=m ts=1 ----- -iter_seek_lt: "l"/3.000000000,0=/ -iter_seek_lt: "m"/5.000000000,0=/ -iter_seek_lt: "m"/4.000000000,0=/BYTES/m4 -iter_seek_lt: "m"/3.000000000,0=/ -iter_seek_lt: "m"/3.000000000,0=/ - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_lt k=n ts=5 -iter_seek_lt k=n ts=4 -iter_seek_lt k=n ts=3 -iter_seek_lt k=n ts=2 -iter_seek_lt k=n ts=1 ----- -iter_seek_lt: "m"/3.000000000,0=/ -iter_seek_lt: "n"/5.000000000,0=/ -iter_seek_lt: "n"/4.000000000,0=/BYTES/n4 -iter_seek_lt: "n"/3.000000000,0=/ -iter_seek_lt: "n"/3.000000000,0=/ - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_lt k=o ts=5 -iter_seek_lt k=o ts=4 -iter_seek_lt k=o ts=3 -iter_seek_lt k=o ts=2 -iter_seek_lt k=o ts=1 ----- -iter_seek_lt: "n"/3.000000000,0=/ -iter_seek_lt: "o"/5.000000000,0=/ -iter_seek_lt: "o"/5.000000000,0=/ -iter_seek_lt: "o"/3.000000000,0=/ -iter_seek_lt: "o"/3.000000000,0=/ - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_lt k=p ts=4 -iter_seek_lt k=p ts=3 -iter_seek_lt k=p ts=2 -iter_seek_lt k=p ts=1 ----- -iter_seek_lt: "o"/3.000000000,0=/ -iter_seek_lt: "o"/3.000000000,0=/ -iter_seek_lt: "p"/3.000000000,0=/BYTES/p3 -iter_seek_lt: "p"/3.000000000,0=/BYTES/p3 - -# Seeks with opposite scans. -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_ge k=e -iter_scan reverse ----- -iter_seek_ge: "e"/3.000000000,0=/ -iter_scan: "e"/3.000000000,0=/ -iter_scan: "d"/3.000000000,0=/ -iter_scan: "c"/1.000000000,0=/ -iter_scan: "c"/3.000000000,0=/ -iter_scan: "b"/3.000000000,0=/BYTES/b3 -iter_scan: "a"/3.000000000,0=/BYTES/a3 -iter_scan: . - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_ge k=i -iter_scan reverse ----- -iter_seek_ge: "i"/3.000000000,0=/ -iter_scan: "i"/3.000000000,0=/ -iter_scan: "h"/2.000000000,0=/BYTES/h2 -iter_scan: "h"/3.000000000,0=/ -iter_scan: "g"/2.000000000,0=/BYTES/g2 -iter_scan: "g"/3.000000000,0=/ -iter_scan: "f"/2.000000000,0=/BYTES/f2 -iter_scan: "f"/3.000000000,0=/ -iter_scan: "e"/2.000000000,0=/BYTES/e2 -iter_scan: "e"/3.000000000,0=/ -iter_scan: "d"/3.000000000,0=/ -iter_scan: "c"/1.000000000,0=/ -iter_scan: "c"/3.000000000,0=/ -iter_scan: "b"/3.000000000,0=/BYTES/b3 -iter_scan: "a"/3.000000000,0=/BYTES/a3 -iter_scan: . - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_ge k=n -iter_scan reverse ----- -iter_seek_ge: "n"/5.000000000,0=/ -iter_scan: "n"/5.000000000,0=/ -iter_scan: "m"/3.000000000,0=/ -iter_scan: "m"/4.000000000,0=/BYTES/m4 -iter_scan: "m"/5.000000000,0=/ -iter_scan: "l"/3.000000000,0=/ -iter_scan: "l"/4.000000000,0=/BYTES/l4 -iter_scan: "k"/3.000000000,0=/ -iter_scan: "k"/4.000000000,0=/BYTES/k4 -iter_scan: "j"/3.000000000,0=/ -iter_scan: "i"/3.000000000,0=/ -iter_scan: "h"/2.000000000,0=/BYTES/h2 -iter_scan: "h"/3.000000000,0=/ -iter_scan: "g"/2.000000000,0=/BYTES/g2 -iter_scan: "g"/3.000000000,0=/ -iter_scan: "f"/2.000000000,0=/BYTES/f2 -iter_scan: "f"/3.000000000,0=/ -iter_scan: "e"/2.000000000,0=/BYTES/e2 -iter_scan: "e"/3.000000000,0=/ -iter_scan: "d"/3.000000000,0=/ -iter_scan: "c"/1.000000000,0=/ -iter_scan: "c"/3.000000000,0=/ -iter_scan: "b"/3.000000000,0=/BYTES/b3 -iter_scan: "a"/3.000000000,0=/BYTES/a3 -iter_scan: . - - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_ge k=e ts=2 -iter_scan reverse ----- -iter_seek_ge: "e"/2.000000000,0=/BYTES/e2 -iter_scan: "e"/2.000000000,0=/BYTES/e2 -iter_scan: "e"/3.000000000,0=/ -iter_scan: "d"/3.000000000,0=/ -iter_scan: "c"/1.000000000,0=/ -iter_scan: "c"/3.000000000,0=/ -iter_scan: "b"/3.000000000,0=/BYTES/b3 -iter_scan: "a"/3.000000000,0=/BYTES/a3 -iter_scan: . - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_ge k=n ts=4 -iter_scan reverse ----- -iter_seek_ge: "n"/4.000000000,0=/BYTES/n4 -iter_scan: "n"/4.000000000,0=/BYTES/n4 -iter_scan: "n"/5.000000000,0=/ -iter_scan: "m"/3.000000000,0=/ -iter_scan: "m"/4.000000000,0=/BYTES/m4 -iter_scan: "m"/5.000000000,0=/ -iter_scan: "l"/3.000000000,0=/ -iter_scan: "l"/4.000000000,0=/BYTES/l4 -iter_scan: "k"/3.000000000,0=/ -iter_scan: "k"/4.000000000,0=/BYTES/k4 -iter_scan: "j"/3.000000000,0=/ -iter_scan: "i"/3.000000000,0=/ -iter_scan: "h"/2.000000000,0=/BYTES/h2 -iter_scan: "h"/3.000000000,0=/ -iter_scan: "g"/2.000000000,0=/BYTES/g2 -iter_scan: "g"/3.000000000,0=/ -iter_scan: "f"/2.000000000,0=/BYTES/f2 -iter_scan: "f"/3.000000000,0=/ -iter_scan: "e"/2.000000000,0=/BYTES/e2 -iter_scan: "e"/3.000000000,0=/ -iter_scan: "d"/3.000000000,0=/ -iter_scan: "c"/1.000000000,0=/ -iter_scan: "c"/3.000000000,0=/ -iter_scan: "b"/3.000000000,0=/BYTES/b3 -iter_scan: "a"/3.000000000,0=/BYTES/a3 -iter_scan: . - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_lt k=e -iter_scan ----- -iter_seek_lt: "d"/3.000000000,0=/ -iter_scan: "d"/3.000000000,0=/ -iter_scan: "e"/3.000000000,0=/ -iter_scan: "e"/2.000000000,0=/BYTES/e2 -iter_scan: "f"/3.000000000,0=/ -iter_scan: "f"/2.000000000,0=/BYTES/f2 -iter_scan: "g"/3.000000000,0=/ -iter_scan: "g"/2.000000000,0=/BYTES/g2 -iter_scan: "h"/3.000000000,0=/ -iter_scan: "h"/2.000000000,0=/BYTES/h2 -iter_scan: "i"/3.000000000,0=/ -iter_scan: "j"/3.000000000,0=/ -iter_scan: "k"/4.000000000,0=/BYTES/k4 -iter_scan: "k"/3.000000000,0=/ -iter_scan: "l"/4.000000000,0=/BYTES/l4 -iter_scan: "l"/3.000000000,0=/ -iter_scan: "m"/5.000000000,0=/ -iter_scan: "m"/4.000000000,0=/BYTES/m4 -iter_scan: "m"/3.000000000,0=/ -iter_scan: "n"/5.000000000,0=/ -iter_scan: "n"/4.000000000,0=/BYTES/n4 -iter_scan: "n"/3.000000000,0=/ -iter_scan: "o"/5.000000000,0=/ -iter_scan: "o"/3.000000000,0=/ -iter_scan: "p"/3.000000000,0=/BYTES/p3 -iter_scan: "q"/3.000000000,0=/BYTES/q3 -iter_scan: "r"/3.000000000,0=/BYTES/r3 -iter_scan: . - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_lt k=j -iter_scan ----- -iter_seek_lt: "i"/3.000000000,0=/ -iter_scan: "i"/3.000000000,0=/ -iter_scan: "j"/3.000000000,0=/ -iter_scan: "k"/4.000000000,0=/BYTES/k4 -iter_scan: "k"/3.000000000,0=/ -iter_scan: "l"/4.000000000,0=/BYTES/l4 -iter_scan: "l"/3.000000000,0=/ -iter_scan: "m"/5.000000000,0=/ -iter_scan: "m"/4.000000000,0=/BYTES/m4 -iter_scan: "m"/3.000000000,0=/ -iter_scan: "n"/5.000000000,0=/ -iter_scan: "n"/4.000000000,0=/BYTES/n4 -iter_scan: "n"/3.000000000,0=/ -iter_scan: "o"/5.000000000,0=/ -iter_scan: "o"/3.000000000,0=/ -iter_scan: "p"/3.000000000,0=/BYTES/p3 -iter_scan: "q"/3.000000000,0=/BYTES/q3 -iter_scan: "r"/3.000000000,0=/BYTES/r3 -iter_scan: . - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_lt k=o -iter_scan ----- -iter_seek_lt: "n"/3.000000000,0=/ -iter_scan: "n"/3.000000000,0=/ -iter_scan: "o"/5.000000000,0=/ -iter_scan: "o"/3.000000000,0=/ -iter_scan: "p"/3.000000000,0=/BYTES/p3 -iter_scan: "q"/3.000000000,0=/BYTES/q3 -iter_scan: "r"/3.000000000,0=/BYTES/r3 -iter_scan: . - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_lt k=e ts=2 -iter_scan ----- -iter_seek_lt: "e"/3.000000000,0=/ -iter_scan: "e"/3.000000000,0=/ -iter_scan: "e"/2.000000000,0=/BYTES/e2 -iter_scan: "f"/3.000000000,0=/ -iter_scan: "f"/2.000000000,0=/BYTES/f2 -iter_scan: "g"/3.000000000,0=/ -iter_scan: "g"/2.000000000,0=/BYTES/g2 -iter_scan: "h"/3.000000000,0=/ -iter_scan: "h"/2.000000000,0=/BYTES/h2 -iter_scan: "i"/3.000000000,0=/ -iter_scan: "j"/3.000000000,0=/ -iter_scan: "k"/4.000000000,0=/BYTES/k4 -iter_scan: "k"/3.000000000,0=/ -iter_scan: "l"/4.000000000,0=/BYTES/l4 -iter_scan: "l"/3.000000000,0=/ -iter_scan: "m"/5.000000000,0=/ -iter_scan: "m"/4.000000000,0=/BYTES/m4 -iter_scan: "m"/3.000000000,0=/ -iter_scan: "n"/5.000000000,0=/ -iter_scan: "n"/4.000000000,0=/BYTES/n4 -iter_scan: "n"/3.000000000,0=/ -iter_scan: "o"/5.000000000,0=/ -iter_scan: "o"/3.000000000,0=/ -iter_scan: "p"/3.000000000,0=/BYTES/p3 -iter_scan: "q"/3.000000000,0=/BYTES/q3 -iter_scan: "r"/3.000000000,0=/BYTES/r3 -iter_scan: . - -run ok -iter_new types=pointsAndRanges pointSynthesis -iter_seek_lt k=n ts=2 -iter_scan ----- -iter_seek_lt: "n"/3.000000000,0=/ -iter_scan: "n"/3.000000000,0=/ -iter_scan: "o"/5.000000000,0=/ -iter_scan: "o"/3.000000000,0=/ -iter_scan: "p"/3.000000000,0=/BYTES/p3 -iter_scan: "q"/3.000000000,0=/BYTES/q3 -iter_scan: "r"/3.000000000,0=/BYTES/r3 -iter_scan: . diff --git a/pkg/storage/testdata/mvcc_histories/range_tombstone_scans b/pkg/storage/testdata/mvcc_histories/range_tombstone_scans index 43bdf798c581..579d5016c952 100644 --- a/pkg/storage/testdata/mvcc_histories/range_tombstone_scans +++ b/pkg/storage/testdata/mvcc_histories/range_tombstone_scans @@ -120,10 +120,8 @@ scan: "a" -> / @3.000000000,0 scan: "b" -> / @2.000000000,0 scan: "c" -> / @4.000000000,0 scan: "d" -> / @4.000000000,0 -scan: "e" -> / @4.000000000,0 scan: "f" -> / @4.000000000,0 scan: "h" -> / @4.000000000,0 -scan: "j" -> / @4.000000000,0 run ok scan k=a end=z ts=5 tombstones @@ -132,10 +130,8 @@ scan: "a" -> / @3.000000000,0 scan: "b" -> / @2.000000000,0 scan: "c" -> / @4.000000000,0 scan: "d" -> / @4.000000000,0 -scan: "e" -> / @4.000000000,0 scan: "f" -> /BYTES/f5 @5.000000000,0 scan: "h" -> / @4.000000000,0 -scan: "j" -> / @4.000000000,0 run ok scan k=a end=z ts=6 tombstones inconsistent @@ -145,10 +141,8 @@ scan: "a" -> / @3.000000000,0 scan: "b" -> / @2.000000000,0 scan: "c" -> / @4.000000000,0 scan: "d" -> / @4.000000000,0 -scan: "e" -> / @4.000000000,0 scan: "f" -> /BYTES/f5 @5.000000000,0 scan: "h" -> / @4.000000000,0 -scan: "j" -> / @4.000000000,0 # Run bounded tombstone scans at 6 and 3. run ok @@ -170,13 +164,13 @@ scan: "b" -> / @2.000000000,0 scan: "c" -> / @4.000000000,0 scan: "d" -> / @4.000000000,0 scan: intent "e" {id=00000000 key="e" pri=0.00000000 epo=0 ts=6.000000000,0 min=0,0 seq=0} -scan: "e" -> / @4.000000000,0 +scan: "e"-"f" -> scan: "f" -> /BYTES/f5 @5.000000000,0 -scan: "g" -> / @4.000000000,0 +scan: "g"-"h" -> scan: "h" -> / @4.000000000,0 scan: "i"-"j" -> -scan: "j" -> / @4.000000000,0 -scan: "k" -> / @4.000000000,0 +scan: "j"-"k" -> +scan: "k"-"l" -> scan: "l"-"m" -> run ok @@ -274,10 +268,8 @@ scan: "a" -> / @3.000000000,0 run ok scan k=a end=z ts=4 tombstones reverse ---- -scan: "j" -> / @4.000000000,0 scan: "h" -> / @4.000000000,0 scan: "f" -> / @4.000000000,0 -scan: "e" -> / @4.000000000,0 scan: "d" -> / @4.000000000,0 scan: "c" -> / @4.000000000,0 scan: "b" -> / @2.000000000,0 @@ -286,10 +278,8 @@ scan: "a" -> / @3.000000000,0 run ok scan k=a end=z ts=5 tombstones reverse ---- -scan: "j" -> / @4.000000000,0 scan: "h" -> / @4.000000000,0 scan: "f" -> /BYTES/f5 @5.000000000,0 -scan: "e" -> / @4.000000000,0 scan: "d" -> / @4.000000000,0 scan: "c" -> / @4.000000000,0 scan: "b" -> / @2.000000000,0 @@ -299,10 +289,8 @@ run ok scan k=a end=z ts=6 tombstones inconsistent reverse ---- scan: intent "e" {id=00000000 key="e" pri=0.00000000 epo=0 ts=6.000000000,0 min=0,0 seq=0} -scan: "j" -> / @4.000000000,0 scan: "h" -> / @4.000000000,0 scan: "f" -> /BYTES/f5 @5.000000000,0 -scan: "e" -> / @4.000000000,0 scan: "d" -> / @4.000000000,0 scan: "c" -> / @4.000000000,0 scan: "b" -> / @2.000000000,0 @@ -328,13 +316,13 @@ scan: "b" -> / @2.000000000,0 scan: "c" -> / @4.000000000,0 scan: "d" -> / @4.000000000,0 scan: intent "e" {id=00000000 key="e" pri=0.00000000 epo=0 ts=6.000000000,0 min=0,0 seq=0} -scan: "e" -> / @4.000000000,0 +scan: "e"-"f" -> scan: "f" -> /BYTES/f5 @5.000000000,0 -scan: "g" -> / @4.000000000,0 +scan: "g"-"h" -> scan: "h" -> / @4.000000000,0 scan: "i"-"j" -> -scan: "j" -> / @4.000000000,0 -scan: "k" -> / @4.000000000,0 +scan: "j"-"k" -> +scan: "k"-"l" -> scan: "l"-"m" -> run ok @@ -369,14 +357,12 @@ run ok scan k=h end=z ts=4 tombstones ---- scan: "h" -> / @4.000000000,0 -scan: "j" -> / @4.000000000,0 run ok scan k=a end=h+ ts=4 tombstones reverse ---- scan: "h" -> / @4.000000000,0 scan: "f" -> / @4.000000000,0 -scan: "e" -> / @4.000000000,0 scan: "d" -> / @4.000000000,0 scan: "c" -> / @4.000000000,0 scan: "b" -> / @2.000000000,0 @@ -385,12 +371,12 @@ scan: "a" -> / @3.000000000,0 run ok scan k=k end=l ts=4 tombstones ---- -scan: "k" -> / @4.000000000,0 +scan: "k"-"l" -> run ok scan k=j end=k ts=4 tombstones ---- -scan: "j" -> / @4.000000000,0 +scan: "j"-"k" -> # failOnMoreRecent: a-d run error diff --git a/pkg/storage/testdata/mvcc_histories/range_tombstone_scans_complex b/pkg/storage/testdata/mvcc_histories/range_tombstone_scans_complex index f6d60bf3103e..e9c916d12015 100644 --- a/pkg/storage/testdata/mvcc_histories/range_tombstone_scans_complex +++ b/pkg/storage/testdata/mvcc_histories/range_tombstone_scans_complex @@ -166,78 +166,60 @@ scan: "k" -> /BYTES/k5 @5.000000000,0 run ok scan k=a end=z ts=1 tombstones inconsistent ---- -scan: "a" -> / @1.000000000,0 -scan: "b" -> / @1.000000000,0 -scan: "c" -> / @1.000000000,0 -scan: "d" -> / @1.000000000,0 -scan: "h" -> / @1.000000000,0 +scan: "a"-"z" -> run ok scan k=a end=z ts=2 tombstones inconsistent ---- scan: "a" -> /BYTES/a2 @2.000000000,0 -scan: "b" -> / @1.000000000,0 -scan: "c" -> / @1.000000000,0 scan: "d" -> /BYTES/d2 @2.000000000,0 scan: "f" -> /BYTES/f2 @2.000000000,0 scan: "g" -> /BYTES/g2 @2.000000000,0 -scan: "h" -> / @1.000000000,0 run ok scan k=a end=z ts=3 tombstones inconsistent ---- scan: "a" -> /BYTES/a2 @2.000000000,0 -scan: "b" -> / @3.000000000,0 -scan: "c" -> / @3.000000000,0 scan: "d" -> /BYTES/d2 @2.000000000,0 scan: "e" -> /BYTES/e3 @3.000000000,0 scan: "f" -> / @3.000000000,0 scan: "g" -> / @3.000000000,0 scan: "h" -> /BYTES/h3 @3.000000000,0 -scan: "n" -> / @3.000000000,0 run ok scan k=a end=z ts=4 tombstones inconsistent ---- scan: "a" -> / @4.000000000,0 scan: "b" -> / @4.000000000,0 -scan: "c" -> / @3.000000000,0 scan: "d" -> /BYTES/d4 @4.000000000,0 scan: "e" -> /BYTES/e3 @3.000000000,0 scan: "f" -> /BYTES/f4 @4.000000000,0 scan: "g" -> /BYTES/g4 @4.000000000,0 scan: "h" -> /BYTES/h3 @3.000000000,0 -scan: "n" -> / @3.000000000,0 run ok scan k=a end=z ts=5 tombstones inconsistent ---- scan: "a" -> / @4.000000000,0 scan: "b" -> / @4.000000000,0 -scan: "c" -> / @5.000000000,0 scan: "d" -> / @5.000000000,0 scan: "e" -> / @5.000000000,0 scan: "f" -> / @5.000000000,0 scan: "g" -> / @5.000000000,0 scan: "h" -> /BYTES/h3 @3.000000000,0 scan: "k" -> /BYTES/k5 @5.000000000,0 -scan: "l" -> / @5.000000000,0 -scan: "n" -> / @5.000000000,0 run ok scan k=a end=z ts=6 tombstones inconsistent ---- scan: "a" -> / @4.000000000,0 scan: "b" -> / @4.000000000,0 -scan: "c" -> / @5.000000000,0 scan: "d" -> / @5.000000000,0 scan: "e" -> / @5.000000000,0 scan: "f" -> /BYTES/f6 @6.000000000,0 scan: "g" -> / @5.000000000,0 scan: "h" -> /BYTES/h3 @3.000000000,0 scan: "k" -> /BYTES/k5 @5.000000000,0 -scan: "l" -> / @5.000000000,0 -scan: "n" -> / @5.000000000,0 run ok scan k=a end=z ts=7 tombstones inconsistent @@ -247,15 +229,12 @@ scan: intent "f" {id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min scan: intent "j" {id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} scan: "a" -> / @4.000000000,0 scan: "b" -> / @4.000000000,0 -scan: "c" -> / @5.000000000,0 scan: "d" -> / @5.000000000,0 scan: "e" -> / @5.000000000,0 scan: "f" -> /BYTES/f6 @6.000000000,0 scan: "g" -> / @5.000000000,0 scan: "h" -> /BYTES/h3 @3.000000000,0 scan: "k" -> /BYTES/k5 @5.000000000,0 -scan: "l" -> / @5.000000000,0 -scan: "n" -> / @5.000000000,0 # Reverse scans at all timestamps. run ok @@ -305,76 +284,58 @@ scan: "f" -> /BYTES/f6 @6.000000000,0 run ok scan k=a end=z ts=1 reverse tombstones inconsistent ---- -scan: "h" -> / @1.000000000,0 -scan: "d" -> / @1.000000000,0 -scan: "c" -> / @1.000000000,0 -scan: "b" -> / @1.000000000,0 -scan: "a" -> / @1.000000000,0 +scan: "a"-"z" -> run ok scan k=a end=z ts=2 reverse tombstones inconsistent ---- -scan: "h" -> / @1.000000000,0 scan: "g" -> /BYTES/g2 @2.000000000,0 scan: "f" -> /BYTES/f2 @2.000000000,0 scan: "d" -> /BYTES/d2 @2.000000000,0 -scan: "c" -> / @1.000000000,0 -scan: "b" -> / @1.000000000,0 scan: "a" -> /BYTES/a2 @2.000000000,0 run ok scan k=a end=z ts=3 reverse tombstones inconsistent ---- -scan: "n" -> / @3.000000000,0 scan: "h" -> /BYTES/h3 @3.000000000,0 scan: "g" -> / @3.000000000,0 scan: "f" -> / @3.000000000,0 scan: "e" -> /BYTES/e3 @3.000000000,0 scan: "d" -> /BYTES/d2 @2.000000000,0 -scan: "c" -> / @3.000000000,0 -scan: "b" -> / @3.000000000,0 scan: "a" -> /BYTES/a2 @2.000000000,0 run ok scan k=a end=z ts=4 reverse tombstones inconsistent ---- -scan: "n" -> / @3.000000000,0 scan: "h" -> /BYTES/h3 @3.000000000,0 scan: "g" -> /BYTES/g4 @4.000000000,0 scan: "f" -> /BYTES/f4 @4.000000000,0 scan: "e" -> /BYTES/e3 @3.000000000,0 scan: "d" -> /BYTES/d4 @4.000000000,0 -scan: "c" -> / @3.000000000,0 scan: "b" -> / @4.000000000,0 scan: "a" -> / @4.000000000,0 run ok scan k=a end=z ts=5 reverse tombstones inconsistent ---- -scan: "n" -> / @5.000000000,0 -scan: "l" -> / @5.000000000,0 scan: "k" -> /BYTES/k5 @5.000000000,0 scan: "h" -> /BYTES/h3 @3.000000000,0 scan: "g" -> / @5.000000000,0 scan: "f" -> / @5.000000000,0 scan: "e" -> / @5.000000000,0 scan: "d" -> / @5.000000000,0 -scan: "c" -> / @5.000000000,0 scan: "b" -> / @4.000000000,0 scan: "a" -> / @4.000000000,0 run ok scan k=a end=z ts=6 reverse tombstones inconsistent ---- -scan: "n" -> / @5.000000000,0 -scan: "l" -> / @5.000000000,0 scan: "k" -> /BYTES/k5 @5.000000000,0 scan: "h" -> /BYTES/h3 @3.000000000,0 scan: "g" -> / @5.000000000,0 scan: "f" -> /BYTES/f6 @6.000000000,0 scan: "e" -> / @5.000000000,0 scan: "d" -> / @5.000000000,0 -scan: "c" -> / @5.000000000,0 scan: "b" -> / @4.000000000,0 scan: "a" -> / @4.000000000,0 @@ -384,15 +345,12 @@ scan k=a end=z ts=7 reverse tombstones inconsistent scan: intent "j" {id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} scan: intent "f" {id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} scan: intent "d" {id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} -scan: "n" -> / @5.000000000,0 -scan: "l" -> / @5.000000000,0 scan: "k" -> /BYTES/k5 @5.000000000,0 scan: "h" -> /BYTES/h3 @3.000000000,0 scan: "g" -> / @5.000000000,0 scan: "f" -> /BYTES/f6 @6.000000000,0 scan: "e" -> / @5.000000000,0 scan: "d" -> / @5.000000000,0 -scan: "c" -> / @5.000000000,0 scan: "b" -> / @4.000000000,0 scan: "a" -> / @4.000000000,0 @@ -401,14 +359,11 @@ run ok scan k=k end=z ts=7 tombstones inconsistent ---- scan: "k" -> /BYTES/k5 @5.000000000,0 -scan: "l" -> / @5.000000000,0 -scan: "n" -> / @5.000000000,0 # Start and end scan in the middle of a bare range key. run ok scan k=ddd end=ggg ts=4 tombstones inconsistent ---- -scan: "ddd" -> / @1.000000000,0 scan: "e" -> /BYTES/e3 @3.000000000,0 scan: "f" -> /BYTES/f4 @4.000000000,0 scan: "g" -> /BYTES/g4 @4.000000000,0 diff --git a/pkg/storage/testdata/mvcc_histories/range_tombstone_scans_reverse_skip_regression b/pkg/storage/testdata/mvcc_histories/range_tombstone_scans_reverse_skip_regression index f2fcba081bd9..9b0676eb6919 100644 --- a/pkg/storage/testdata/mvcc_histories/range_tombstone_scans_reverse_skip_regression +++ b/pkg/storage/testdata/mvcc_histories/range_tombstone_scans_reverse_skip_regression @@ -6,10 +6,11 @@ # 1 [---) 1 x # a b a b # -# Recall that pebbleMVCCScanner only enables pointSynthesizingIter when it -# encounters a range key. In the case above, during a reverse scan, the [a-b)@1 -# range key will first become visible to pebbleMVCCScanner when it lands on a@2, -# so it enabled point synthesis positioned at the a@2 point key. Notice how the +# Previously, pebbleMVCCScanner used PointSynthesizingIter to synthesize point +# tombstones. PointSynthesizingIter was only enabled once a range key was +# encountered. In the case above, during a reverse scan, the [a-b)@1 range key +# will first become visible to pebbleMVCCScanner when it lands on a@2, so it +# enabled point synthesis positioned at the a@2 point key. Notice how the # iterator has now skipped over the synthetic point tombstone a@1. # # This is particularly problematic when combined with pebbleMVCCScanner peeking, @@ -71,7 +72,6 @@ run ok scan t=A k=a end=z reverse tombstones ---- scan: "b" -> /BYTES/b3 @3.000000000,0 -scan: "a" -> / @2.000000000,0 # And with a point between the range tombstone. We place the intent at the # lowest timestamp, which is a contrived/unrealistic scenario, and do tombstone @@ -120,7 +120,6 @@ run ok scan t=A k=a end=z reverse tombstones ts=2 ---- scan: "b" -> /BYTES/b1 @1.000000000,0 -scan: "a" -> / @2.000000000,0 run ok scan t=A k=a end=z reverse tombstones ts=1 diff --git a/pkg/storage/testdata/mvcc_histories/range_tombstone_scans_ts_conflict b/pkg/storage/testdata/mvcc_histories/range_tombstone_scans_ts_conflict index 147def984a08..529494e51bac 100644 --- a/pkg/storage/testdata/mvcc_histories/range_tombstone_scans_ts_conflict +++ b/pkg/storage/testdata/mvcc_histories/range_tombstone_scans_ts_conflict @@ -122,12 +122,11 @@ scan: "r" -> /BYTES/r3 @3.000000000,0 run ok scan k=a end=z ts=1 tombstones ---- -scan: "c" -> / @1.000000000,0 +scan: "a"-"z" -> run ok scan k=a end=z ts=2 tombstones ---- -scan: "c" -> / @1.000000000,0 scan: "e" -> /BYTES/e2 @2.000000000,0 scan: "f" -> /BYTES/f2 @2.000000000,0 scan: "g" -> /BYTES/g2 @2.000000000,0 @@ -212,7 +211,6 @@ scan: "h" -> / @3.000000000,0 run ok scan k=d end=i ts=2 tombstones ---- -scan: "d" -> / @1.000000000,0 scan: "e" -> /BYTES/e2 @2.000000000,0 scan: "f" -> /BYTES/f2 @2.000000000,0 scan: "g" -> /BYTES/g2 @2.000000000,0 @@ -289,7 +287,7 @@ scan: "a" -> /BYTES/a3 @3.000000000,0 run ok scan k=a end=z ts=1 reverse tombstones ---- -scan: "c" -> / @1.000000000,0 +scan: "a"-"z" -> run ok scan k=a end=z ts=2 reverse tombstones @@ -298,7 +296,6 @@ scan: "h" -> /BYTES/h2 @2.000000000,0 scan: "g" -> /BYTES/g2 @2.000000000,0 scan: "f" -> /BYTES/f2 @2.000000000,0 scan: "e" -> /BYTES/e2 @2.000000000,0 -scan: "c" -> / @1.000000000,0 run ok scan k=a end=z ts=3 reverse tombstones @@ -383,7 +380,6 @@ scan: "h" -> /BYTES/h2 @2.000000000,0 scan: "g" -> /BYTES/g2 @2.000000000,0 scan: "f" -> /BYTES/f2 @2.000000000,0 scan: "e" -> /BYTES/e2 @2.000000000,0 -scan: "d" -> / @1.000000000,0 run ok scan k=i end=o ts=3 reverse tombstones