From 6733f95313753c3b74ce4be62a7dd288cdb53986 Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Sat, 25 Jun 2022 16:17:22 +0000 Subject: [PATCH] storage: add MVCC range tombstone handling in scans and gets This patch adds MVCC range tombstone handling for scans and gets. In the basic case, this simply means that point keys below an MVCC range tombstone are not visible. When tombstones are requested by the caller, the MVCC range tombstones themselves are never exposed, to avoid having to explicitly handle these throughout the codebase. Instead, synthetic MVCC point tombstones are emitted at the start of MVCC range tombstones and wherever they overlap a point key (above and below). Additionally, point gets return synthetic point tombstones if they overlap an MVCC range tombstone even if no existing point key exists. This is based on `pointSynthesizingIter`, which avoids additional logic in `pebbleMVCCScanner`. 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. Existing callers have been audited to ensure they are not affected. Point tombstone synthesis must be enabled even when the caller has not requested tombstones, because they must always be taken into account for conflict/uncertainty checks. However, in these cases we enable range key masking below the read timestamp, omitting any covered points since these are no longer needed. Release note: None --- pkg/storage/mvcc.go | 61 +- pkg/storage/pebble_mvcc_scanner.go | 46 +- .../mvcc_histories/range_tombstone_conflicts | 37 +- .../mvcc_histories/range_tombstone_gets | 328 +++++++++++ .../mvcc_histories/range_tombstone_scans | 533 ++++++++++++++++++ 5 files changed, 953 insertions(+), 52 deletions(-) create mode 100644 pkg/storage/testdata/mvcc_histories/range_tombstone_gets create mode 100644 pkg/storage/testdata/mvcc_histories/range_tombstone_scans diff --git a/pkg/storage/mvcc.go b/pkg/storage/mvcc.go index 5998bdcf9146..ffa61a1b362a 100644 --- a/pkg/storage/mvcc.go +++ b/pkg/storage/mvcc.go @@ -738,7 +738,14 @@ 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. +// 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. +// +// 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. // // 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 @@ -759,7 +766,10 @@ func newMVCCIterator( func MVCCGet( ctx context.Context, reader Reader, key roachpb.Key, timestamp hlc.Timestamp, opts MVCCGetOptions, ) (*roachpb.Value, *roachpb.Intent, error) { - iter := newMVCCIterator(reader, timestamp, !opts.Tombstones, IterOptions{Prefix: true}) + iter := newMVCCIterator(reader, timestamp, !opts.Tombstones, IterOptions{ + KeyTypes: IterKeyTypePointsAndRanges, + Prefix: true, + }) defer iter.Close() value, intent, err := mvccGet(ctx, iter, key, timestamp, opts) return value.ToPointer(), intent, err @@ -840,20 +850,6 @@ func mvccGet( return value, intent, nil } -// TODO(erikgrinaker): This is temporary until mvccGet always uses point -// synthesis (which requires read-path optimizations). -func mvccGetWithPointSynthesis( - ctx context.Context, - iter MVCCIterator, - key roachpb.Key, - timestamp hlc.Timestamp, - opts MVCCGetOptions, -) (value optionalValue, intent *roachpb.Intent, err error) { - pointIter := newPointSynthesizingIter(iter, true /* emitOnSeekGE */) - defer pointIter.release() // NB: not Close(), since we don't own iter - return mvccGet(ctx, pointIter, key, timestamp, opts) -} - // MVCCGetAsTxn constructs a temporary transaction from the given transaction // metadata and calls MVCCGet as that transaction. This method is required // only for reading intents of a transaction when only its metadata is known @@ -1194,9 +1190,7 @@ func maybeGetValue( var exVal optionalValue if exists { var err error - exVal, _, err = mvccGetWithPointSynthesis(ctx, iter, key, readTimestamp, MVCCGetOptions{ - Tombstones: true, - }) + exVal, _, err = mvccGet(ctx, iter, key, readTimestamp, MVCCGetOptions{Tombstones: true}) if err != nil { return roachpb.Value{}, err } @@ -1264,7 +1258,7 @@ func replayTransactionalWrite( // This is a special case. This is when the intent hasn't made it // to the intent history yet. We must now assert the value written // in the intent to the value we're trying to write. - writtenValue, _, err = mvccGetWithPointSynthesis(ctx, iter, key, timestamp, MVCCGetOptions{ + writtenValue, _, err = mvccGet(ctx, iter, key, timestamp, MVCCGetOptions{ Txn: txn, Tombstones: true, }) @@ -1319,7 +1313,7 @@ func replayTransactionalWrite( // last committed value on the key. Since we want the last committed // value on the key, we must make an inconsistent read so we ignore // our previous intents here. - exVal, _, err = mvccGetWithPointSynthesis(ctx, iter, key, timestamp, MVCCGetOptions{ + exVal, _, err = mvccGet(ctx, iter, key, timestamp, MVCCGetOptions{ Inconsistent: true, Tombstones: true, }) @@ -1560,7 +1554,7 @@ func mvccPutInternal( // // Since we want the last committed value on the key, we must make // an inconsistent read so we ignore our previous intents here. - exVal, _, err = mvccGetWithPointSynthesis(ctx, iter, key, readTimestamp, MVCCGetOptions{ + exVal, _, err = mvccGet(ctx, iter, key, readTimestamp, MVCCGetOptions{ Inconsistent: true, Tombstones: true, }) @@ -2738,7 +2732,21 @@ 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. +// 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. // // When scanning inconsistently, any encountered intents will be placed in the // dedicated result parameter. By contrast, when scanning consistently, any @@ -2762,6 +2770,7 @@ func MVCCScan( opts MVCCScanOptions, ) (MVCCScanResult, error) { iter := newMVCCIterator(reader, timestamp, !opts.Tombstones, IterOptions{ + KeyTypes: IterKeyTypePointsAndRanges, LowerBound: key, UpperBound: endKey, }) @@ -2778,6 +2787,7 @@ func MVCCScanToBytes( opts MVCCScanOptions, ) (MVCCScanResult, error) { iter := newMVCCIterator(reader, timestamp, !opts.Tombstones, IterOptions{ + KeyTypes: IterKeyTypePointsAndRanges, LowerBound: key, UpperBound: endKey, }) @@ -2814,7 +2824,9 @@ func MVCCScanAsTxn( // the reverse flag is set, the iterator will be moved in reverse order. If the // 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. +// WriteIntentError. In Tombstones mode, MVCC range tombstones are emitted as +// synthetic point tombstones at their start key and around overlapping point +// keys. func MVCCIterate( ctx context.Context, reader Reader, @@ -2824,6 +2836,7 @@ func MVCCIterate( f func(roachpb.KeyValue) error, ) ([]roachpb.Intent, error) { iter := newMVCCIterator(reader, timestamp, !opts.Tombstones, IterOptions{ + KeyTypes: IterKeyTypePointsAndRanges, LowerBound: key, UpperBound: endKey, }) diff --git a/pkg/storage/pebble_mvcc_scanner.go b/pkg/storage/pebble_mvcc_scanner.go index 1f7362e27314..9241ecb393d7 100644 --- a/pkg/storage/pebble_mvcc_scanner.go +++ b/pkg/storage/pebble_mvcc_scanner.go @@ -281,10 +281,19 @@ func extractResultKey(repr []byte) roachpb.Key { return key.Key } -// Go port of mvccScanner in libroach/mvcc.h. Stores all variables relating to -// one MVCCGet / MVCCScan call. +// 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. type pebbleMVCCScanner struct { parent MVCCIterator + // 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. + // + // TODO(erikgrinaker): pooling and reusing this together with the + // pebbleMVCCScanner would be more efficient, but only if MVCC range + // tombstones are frequent. We expect them to be rare, so we don't for now. + pointIter *pointSynthesizingIter // memAccount is used to account for the size of the scan results. memAccount *mon.BoundAccount reverse bool @@ -373,6 +382,9 @@ 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, @@ -1070,6 +1082,9 @@ func (p *pebbleMVCCScanner) updateCurrent() bool { if !p.iterValid() { return false } + if !p.maybeEnablePointSynthesis() { + return false + } p.curRawKey = p.parent.UnsafeRawMVCCKey() @@ -1089,6 +1104,30 @@ func (p *pebbleMVCCScanner) updateCurrent() bool { return true } +// maybeEnablePointSynthesis checks if p.parent is on an MVCC range tombstone. +// If it is, it wraps and replaces p.parent with a pointSynthesizingIter, which +// synthesizes MVCC point tombstones for MVCC range tombstones and never emits +// range keys itself. +// +// Returns the iterator validity after a switch, and otherwise assumes the +// iterator was valid when called and returns true if there is no change. +func (p *pebbleMVCCScanner) maybeEnablePointSynthesis() bool { + if _, hasRange := p.parent.HasPointAndRange(); hasRange { + // TODO(erikgrinaker): We have to seek to the current iterator position to + // correctly initialize pointSynthesizingIter, but we could just load the + // underlying iterator state instead. We'll optimize this later. + // + // NB: The seek must use a cloned key, because it repositions the underlying + // iterator and then uses the given seek key for comparisons, which is not + // safe with UnsafeKey(). + p.pointIter = newPointSynthesizingIter(p.parent, p.isGet) + p.pointIter.SeekGE(p.parent.Key()) + p.parent = p.pointIter + return p.iterValid() + } + return true +} + func (p *pebbleMVCCScanner) decodeCurrentMetadata() bool { if len(p.curRawValue) == 0 { p.err = errors.Errorf("zero-length mvcc metadata") @@ -1213,6 +1252,9 @@ func (p *pebbleMVCCScanner) iterPeekPrev() ([]byte, bool) { // iterator at the first entry, and in the latter iteration will be done. return nil, false } + if !p.maybeEnablePointSynthesis() { + return nil, false + } } else if !p.iterValid() { return nil, false } diff --git a/pkg/storage/testdata/mvcc_histories/range_tombstone_conflicts b/pkg/storage/testdata/mvcc_histories/range_tombstone_conflicts index 99c342af8aed..2c8a397dcfd3 100644 --- a/pkg/storage/testdata/mvcc_histories/range_tombstone_conflicts +++ b/pkg/storage/testdata/mvcc_histories/range_tombstone_conflicts @@ -75,17 +75,12 @@ data: "j"/1.000000000,0 -> /INT/1 error: (*withstack.withStack:) "b"/0,0: put is inline=true, but existing value is inline=false # DeleteRange at ts=5 should error with WriteTooOldError. -# -# TODO(erikgrinaker): This should error on c rather than d, but won't do so -# until MVCCScan respects MVCC range tombstones. Until it does, the -# put will actually do a write at the new timestamp. run error del_range k=a end=f ts=5 ---- >> at end: rangekey: {a-c}/[3.000000000,0=/] rangekey: {c-k}/[5.000000000,0=/ 3.000000000,0=/] -data: "d"/5.000000000,1 -> / data: "d"/1.000000000,0 -> /BYTES/d1 data: "e"/2.000000000,0 -> / data: "e"/1.000000000,0 -> /BYTES/e1 @@ -95,7 +90,7 @@ data: "g"/7.000000000,0 -> /BYTES/7 data: "g"/1.000000000,0 -> /BYTES/g1 data: "i"/1.000000000,0 -> /INT/1 data: "j"/1.000000000,0 -> /INT/1 -error: (*roachpb.WriteTooOldError:) WriteTooOldError: write for key "d" at timestamp 5.000000000,0 too old; wrote at 5.000000000,1 +error: (*roachpb.WriteTooOldError:) WriteTooOldError: write for key "c" at timestamp 5.000000000,0 too old; wrote at 5.000000000,1 # Point key below range tombstones should error, but is written anyway at a # higher timestamp. @@ -110,7 +105,6 @@ put k=c ts=3 v=c3 rangekey: {a-c}/[3.000000000,0=/] rangekey: {c-k}/[5.000000000,0=/ 3.000000000,0=/] data: "c"/5.000000000,1 -> /BYTES/c3 -data: "d"/5.000000000,1 -> / data: "d"/1.000000000,0 -> /BYTES/d1 data: "e"/2.000000000,0 -> / data: "e"/1.000000000,0 -> /BYTES/e1 @@ -129,8 +123,7 @@ put k=d ts=3 v=d3 rangekey: {a-c}/[3.000000000,0=/] rangekey: {c-k}/[5.000000000,0=/ 3.000000000,0=/] data: "c"/5.000000000,1 -> /BYTES/c3 -data: "d"/5.000000000,2 -> /BYTES/d3 -data: "d"/5.000000000,1 -> / +data: "d"/5.000000000,1 -> /BYTES/d3 data: "d"/1.000000000,0 -> /BYTES/d1 data: "e"/2.000000000,0 -> / data: "e"/1.000000000,0 -> /BYTES/e1 @@ -140,7 +133,7 @@ data: "g"/7.000000000,0 -> /BYTES/7 data: "g"/1.000000000,0 -> /BYTES/g1 data: "i"/1.000000000,0 -> /INT/1 data: "j"/1.000000000,0 -> /INT/1 -error: (*roachpb.WriteTooOldError:) WriteTooOldError: write for key "d" at timestamp 3.000000000,0 too old; wrote at 5.000000000,2 +error: (*roachpb.WriteTooOldError:) WriteTooOldError: write for key "d" at timestamp 3.000000000,0 too old; wrote at 5.000000000,1 run error put k=e ts=3 v=e3 @@ -149,8 +142,7 @@ put k=e ts=3 v=e3 rangekey: {a-c}/[3.000000000,0=/] rangekey: {c-k}/[5.000000000,0=/ 3.000000000,0=/] data: "c"/5.000000000,1 -> /BYTES/c3 -data: "d"/5.000000000,2 -> /BYTES/d3 -data: "d"/5.000000000,1 -> / +data: "d"/5.000000000,1 -> /BYTES/d3 data: "d"/1.000000000,0 -> /BYTES/d1 data: "e"/5.000000000,1 -> /BYTES/e3 data: "e"/2.000000000,0 -> / @@ -171,8 +163,7 @@ cput k=f ts=7 v=f7 cond=f1 rangekey: {a-c}/[3.000000000,0=/] rangekey: {c-k}/[5.000000000,0=/ 3.000000000,0=/] data: "c"/5.000000000,1 -> /BYTES/c3 -data: "d"/5.000000000,2 -> /BYTES/d3 -data: "d"/5.000000000,1 -> / +data: "d"/5.000000000,1 -> /BYTES/d3 data: "d"/1.000000000,0 -> /BYTES/d1 data: "e"/5.000000000,1 -> /BYTES/e3 data: "e"/2.000000000,0 -> / @@ -195,8 +186,7 @@ with t=A ts=7 rangekey: {a-c}/[3.000000000,0=/] rangekey: {c-k}/[5.000000000,0=/ 3.000000000,0=/] data: "c"/5.000000000,1 -> /BYTES/c3 -data: "d"/5.000000000,2 -> /BYTES/d3 -data: "d"/5.000000000,1 -> / +data: "d"/5.000000000,1 -> /BYTES/d3 data: "d"/1.000000000,0 -> /BYTES/d1 data: "e"/5.000000000,1 -> /BYTES/e3 data: "e"/2.000000000,0 -> / @@ -222,8 +212,7 @@ txn: "A" meta={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0, rangekey: {a-c}/[3.000000000,0=/] rangekey: {c-k}/[5.000000000,0=/ 3.000000000,0=/] data: "c"/5.000000000,1 -> /BYTES/c3 -data: "d"/5.000000000,2 -> /BYTES/d3 -data: "d"/5.000000000,1 -> / +data: "d"/5.000000000,1 -> /BYTES/d3 data: "d"/1.000000000,0 -> /BYTES/d1 data: "e"/5.000000000,1 -> /BYTES/e3 data: "e"/2.000000000,0 -> / @@ -244,8 +233,7 @@ initput k=f ts=7 v=f7 failOnTombstones rangekey: {a-c}/[3.000000000,0=/] rangekey: {c-k}/[5.000000000,0=/ 3.000000000,0=/] data: "c"/5.000000000,1 -> /BYTES/c3 -data: "d"/5.000000000,2 -> /BYTES/d3 -data: "d"/5.000000000,1 -> / +data: "d"/5.000000000,1 -> /BYTES/d3 data: "d"/1.000000000,0 -> /BYTES/d1 data: "e"/5.000000000,1 -> /BYTES/e3 data: "e"/2.000000000,0 -> / @@ -269,8 +257,7 @@ initput k=f ts=7 v=f7 rangekey: {a-c}/[3.000000000,0=/] rangekey: {c-k}/[5.000000000,0=/ 3.000000000,0=/] data: "c"/5.000000000,1 -> /BYTES/c3 -data: "d"/5.000000000,2 -> /BYTES/d3 -data: "d"/5.000000000,1 -> / +data: "d"/5.000000000,1 -> /BYTES/d3 data: "d"/1.000000000,0 -> /BYTES/d1 data: "e"/5.000000000,1 -> /BYTES/e3 data: "e"/2.000000000,0 -> / @@ -292,8 +279,7 @@ increment k=i ts=2 rangekey: {a-c}/[3.000000000,0=/] rangekey: {c-k}/[5.000000000,0=/ 3.000000000,0=/] data: "c"/5.000000000,1 -> /BYTES/c3 -data: "d"/5.000000000,2 -> /BYTES/d3 -data: "d"/5.000000000,1 -> / +data: "d"/5.000000000,1 -> /BYTES/d3 data: "d"/1.000000000,0 -> /BYTES/d1 data: "e"/5.000000000,1 -> /BYTES/e3 data: "e"/2.000000000,0 -> / @@ -317,8 +303,7 @@ inc: current value = 1 rangekey: {a-c}/[3.000000000,0=/] rangekey: {c-k}/[5.000000000,0=/ 3.000000000,0=/] data: "c"/5.000000000,1 -> /BYTES/c3 -data: "d"/5.000000000,2 -> /BYTES/d3 -data: "d"/5.000000000,1 -> / +data: "d"/5.000000000,1 -> /BYTES/d3 data: "d"/1.000000000,0 -> /BYTES/d1 data: "e"/5.000000000,1 -> /BYTES/e3 data: "e"/2.000000000,0 -> / diff --git a/pkg/storage/testdata/mvcc_histories/range_tombstone_gets b/pkg/storage/testdata/mvcc_histories/range_tombstone_gets new file mode 100644 index 000000000000..d68445ee75f6 --- /dev/null +++ b/pkg/storage/testdata/mvcc_histories/range_tombstone_gets @@ -0,0 +1,328 @@ +# Tests MVCC gets across range tombstones. +# +# Sets up the following dataset, where x is tombstone, o-o is range tombstone, [] is intent. +# +# T +# 6 [e6] +# 5 f5 +# 4 o-----------------------o o-------o [j-l)@4 has localTs=3 +# 3 x d3 f3 +# 2 o---------------o h2 +# 1 a1 x c1 f1 +# a b c d e f g h i j k l +# +run ok +put k=a ts=1 v=a1 +del k=b ts=1 +put k=c ts=1 v=c1 +put k=f ts=1 v=f1 +del_range_ts k=a end=e ts=2 +del k=a ts=3 +put k=d ts=3 v=d3 +put k=f ts=3 v=f3 +put k=h ts=2 v=h2 +del_range_ts k=c end=i ts=4 +put k=f ts=5 v=f5 +del_range_ts k=j end=l ts=4 localTs=3 +with t=A + txn_begin k=e ts=6 + put k=e v=e6 +---- +>> at end: +txn: "A" meta={id=00000000 key="e" pri=0.00000000 epo=0 ts=6.000000000,0 min=0,0 seq=0} lock=true stat=PENDING rts=6.000000000,0 wto=false gul=0,0 +rangekey: {a-c}/[2.000000000,0=/] +rangekey: {c-e}/[4.000000000,0=/ 2.000000000,0=/] +rangekey: {e-i}/[4.000000000,0=/] +rangekey: {j-l}/[4.000000000,0={localTs=3.000000000,0}/] +data: "a"/3.000000000,0 -> / +data: "a"/1.000000000,0 -> /BYTES/a1 +data: "b"/1.000000000,0 -> / +data: "c"/1.000000000,0 -> /BYTES/c1 +data: "d"/3.000000000,0 -> /BYTES/d3 +meta: "e"/0,0 -> txn={id=00000000 key="e" pri=0.00000000 epo=0 ts=6.000000000,0 min=0,0 seq=0} ts=6.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true +data: "e"/6.000000000,0 -> /BYTES/e6 +data: "f"/5.000000000,0 -> /BYTES/f5 +data: "f"/3.000000000,0 -> /BYTES/f3 +data: "f"/1.000000000,0 -> /BYTES/f1 +data: "h"/2.000000000,0 -> /BYTES/h2 + +# Run gets for all keys and all timestamps. +run ok +get k=a ts=1 +get k=a ts=2 +get k=a ts=3 +get k=a ts=1 tombstones +get k=a ts=2 tombstones +get k=a ts=3 tombstones +---- +get: "a" -> /BYTES/a1 @1.000000000,0 +get: "a" -> +get: "a" -> +get: "a" -> /BYTES/a1 @1.000000000,0 +get: "a" -> / @2.000000000,0 +get: "a" -> / @3.000000000,0 + +run ok +get k=b ts=1 +get k=b ts=2 +get k=b ts=3 +get k=b ts=1 tombstones +get k=b ts=2 tombstones +get k=b ts=3 tombstones +---- +get: "b" -> +get: "b" -> +get: "b" -> +get: "b" -> / @1.000000000,0 +get: "b" -> / @2.000000000,0 +get: "b" -> / @2.000000000,0 + +run ok +get k=c ts=1 +get k=c ts=2 +get k=c ts=3 +get k=c ts=4 +get k=c ts=5 +get k=c ts=1 tombstones +get k=c ts=2 tombstones +get k=c ts=3 tombstones +get k=c ts=4 tombstones +get k=c ts=5 tombstones +---- +get: "c" -> /BYTES/c1 @1.000000000,0 +get: "c" -> +get: "c" -> +get: "c" -> +get: "c" -> +get: "c" -> /BYTES/c1 @1.000000000,0 +get: "c" -> / @2.000000000,0 +get: "c" -> / @2.000000000,0 +get: "c" -> / @4.000000000,0 +get: "c" -> / @4.000000000,0 + +run ok +get k=d ts=1 +get k=d ts=2 +get k=d ts=3 +get k=d ts=4 +get k=d ts=5 +get k=d ts=1 tombstones +get k=d ts=2 tombstones +get k=d ts=3 tombstones +get k=d ts=4 tombstones +get k=d ts=5 tombstones +---- +get: "d" -> +get: "d" -> +get: "d" -> /BYTES/d3 @3.000000000,0 +get: "d" -> +get: "d" -> +get: "d" -> +get: "d" -> / @2.000000000,0 +get: "d" -> /BYTES/d3 @3.000000000,0 +get: "d" -> / @4.000000000,0 +get: "d" -> / @4.000000000,0 + +run ok +get k=e ts=3 inconsistent +get k=e ts=4 inconsistent +get k=e ts=5 inconsistent +get k=e ts=6 inconsistent +get k=e ts=3 tombstones inconsistent +get k=e ts=4 tombstones inconsistent +get k=e ts=5 tombstones inconsistent +get k=e ts=6 tombstones inconsistent +---- +get: "e" -> +get: "e" -> +get: "e" -> +get: "e" -> intent {id=00000000 key="e" pri=0.00000000 epo=0 ts=6.000000000,0 min=0,0 seq=0} +get: "e" -> +get: "e" -> +get: "e" -> / @4.000000000,0 +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=g ts=3 +get k=g ts=4 +get k=g ts=5 +get k=g ts=3 tombstones +get k=g ts=4 tombstones +get k=g ts=5 tombstones +---- +get: "g" -> +get: "g" -> +get: "g" -> +get: "g" -> +get: "g" -> / @4.000000000,0 +get: "g" -> / @4.000000000,0 + +run ok +get k=h ts=1 +get k=h ts=2 +get k=h ts=3 +get k=h ts=4 +get k=h ts=5 +get k=h ts=1 tombstones +get k=h ts=2 tombstones +get k=h ts=3 tombstones +get k=h ts=4 tombstones +get k=h ts=5 tombstones +---- +get: "h" -> +get: "h" -> /BYTES/h2 @2.000000000,0 +get: "h" -> /BYTES/h2 @2.000000000,0 +get: "h" -> +get: "h" -> +get: "h" -> +get: "h" -> /BYTES/h2 @2.000000000,0 +get: "h" -> /BYTES/h2 @2.000000000,0 +get: "h" -> / @4.000000000,0 +get: "h" -> / @4.000000000,0 + +# failOnMoreRecent: c +run error +get k=c ts=1 failOnMoreRecent +---- +get: "c" -> +error: (*roachpb.WriteTooOldError:) WriteTooOldError: write for key "c" at timestamp 1.000000000,0 too old; wrote at 4.000000000,1 + +run error +get k=c ts=2 failOnMoreRecent +---- +get: "c" -> +error: (*roachpb.WriteTooOldError:) WriteTooOldError: write for key "c" at timestamp 2.000000000,0 too old; wrote at 4.000000000,1 + +run error +get k=c ts=3 failOnMoreRecent +---- +get: "c" -> +error: (*roachpb.WriteTooOldError:) WriteTooOldError: write for key "c" at timestamp 3.000000000,0 too old; wrote at 4.000000000,1 + +run error +get k=c ts=4 failOnMoreRecent +---- +get: "c" -> +error: (*roachpb.WriteTooOldError:) WriteTooOldError: write for key "c" at timestamp 4.000000000,0 too old; wrote at 4.000000000,1 + +run ok +get k=c ts=5 failOnMoreRecent +---- +get: "c" -> + +# failOnMoreRecent: e +run error +get k=e ts=3 failOnMoreRecent +---- +get: "e" -> +error: (*roachpb.WriteIntentError:) conflicting intents on "e" + +run error +get k=e ts=4 failOnMoreRecent +---- +get: "e" -> +error: (*roachpb.WriteIntentError:) conflicting intents on "e" + +run error +get k=e ts=5 failOnMoreRecent +---- +get: "e" -> +error: (*roachpb.WriteIntentError:) conflicting intents on "e" + +# failOnMoreRecent: g +run error +get k=g ts=3 failOnMoreRecent +---- +get: "g" -> +error: (*roachpb.WriteTooOldError:) WriteTooOldError: write for key "g" at timestamp 3.000000000,0 too old; wrote at 4.000000000,1 + +run error +get k=g ts=4 failOnMoreRecent +---- +get: "g" -> +error: (*roachpb.WriteTooOldError:) WriteTooOldError: write for key "g" at timestamp 4.000000000,0 too old; wrote at 4.000000000,1 + +run ok +get k=g ts=5 failOnMoreRecent +---- +get: "g" -> + + +# globalUncertaintyLimit: b +run ok +get k=b ts=1 globalUncertaintyLimit=1 +---- +get: "b" -> + +run error +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: g +run ok +get k=g ts=1 globalUncertaintyLimit=1 +---- +get: "g" -> + +run ok +get k=g ts=1 globalUncertaintyLimit=3 +---- +get: "g" -> + +run error +get k=g ts=1 globalUncertaintyLimit=4 +---- +get: "g" -> +error: (*roachpb.ReadWithinUncertaintyIntervalError:) ReadWithinUncertaintyIntervalError: read at time 1.000000000,0 encountered previous write with future timestamp 4.000000000,0 within uncertainty interval `t <= (local=0,0, global=0,0)`; observed timestamps: [] + +run ok +get k=g ts=4 globalUncertaintyLimit=5 +---- +get: "g" -> + +# globalUncertaintyLimit: h +run ok +get k=h ts=2 globalUncertaintyLimit=2 +---- +get: "h" -> /BYTES/h2 @2.000000000,0 + +run ok +get k=h ts=2 globalUncertaintyLimit=3 +---- +get: "h" -> /BYTES/h2 @2.000000000,0 + +run error +get k=h ts=2 globalUncertaintyLimit=4 +---- +get: "h" -> +error: (*roachpb.ReadWithinUncertaintyIntervalError:) ReadWithinUncertaintyIntervalError: read at time 2.000000000,0 encountered previous write with future timestamp 4.000000000,0 within uncertainty interval `t <= (local=0,0, global=0,0)`; observed timestamps: [] + +# Test local timestamp uncertainty for [j-l)@4 with localTs=3. Normally, +# globalUncertaintyLimit=4 would error. However, localUncertaintyLimit<4 +# disables this via observed timestamps, but not if +# localTs<=localUncertaintyLimit. +run error +get k=k ts=1 globalUncertaintyLimit=4 +---- +get: "k" -> +error: (*roachpb.ReadWithinUncertaintyIntervalError:) ReadWithinUncertaintyIntervalError: read at time 1.000000000,0 encountered previous write with future timestamp 4.000000000,0 within uncertainty interval `t <= (local=0,0, global=0,0)`; observed timestamps: [] + +run ok +get k=k ts=1 globalUncertaintyLimit=4 localUncertaintyLimit=1 +---- +get: "k" -> + +run ok +get k=k ts=1 globalUncertaintyLimit=4 localUncertaintyLimit=2 +---- +get: "k" -> + +run error +get k=k ts=1 globalUncertaintyLimit=4 localUncertaintyLimit=3 +---- +get: "k" -> +error: (*roachpb.ReadWithinUncertaintyIntervalError:) ReadWithinUncertaintyIntervalError: read at time 1.000000000,0 encountered previous write with future timestamp 4.000000000,0 within uncertainty interval `t <= (local=3.000000000,0, global=0,0)`; observed timestamps: [] diff --git a/pkg/storage/testdata/mvcc_histories/range_tombstone_scans b/pkg/storage/testdata/mvcc_histories/range_tombstone_scans new file mode 100644 index 000000000000..d9b95a621909 --- /dev/null +++ b/pkg/storage/testdata/mvcc_histories/range_tombstone_scans @@ -0,0 +1,533 @@ +# Tests MVCC scans across range tombstones. +# +# Sets up the following dataset, where x is tombstone, o-o is range tombstone, [] is intent. +# +# T +# 6 [e6] +# 5 f5 +# 4 o-----------------------o o-------o [j-l)@4 has localTs=3 +# 3 x d3 f3 +# 2 o---------------o h2 +# 1 a1 x c1 f1 +# a b c d e f g h i j k l +# +run ok +put k=a ts=1 v=a1 +del k=b ts=1 +put k=c ts=1 v=c1 +put k=f ts=1 v=f1 +del_range_ts k=a end=e ts=2 +del k=a ts=3 +put k=d ts=3 v=d3 +put k=f ts=3 v=f3 +put k=h ts=2 v=h2 +del_range_ts k=c end=i ts=4 +put k=f ts=5 v=f5 +del_range_ts k=j end=l ts=4 localTs=3 +with t=A + txn_begin k=e ts=6 + put k=e v=e6 +---- +>> at end: +txn: "A" meta={id=00000000 key="e" pri=0.00000000 epo=0 ts=6.000000000,0 min=0,0 seq=0} lock=true stat=PENDING rts=6.000000000,0 wto=false gul=0,0 +rangekey: {a-c}/[2.000000000,0=/] +rangekey: {c-e}/[4.000000000,0=/ 2.000000000,0=/] +rangekey: {e-i}/[4.000000000,0=/] +rangekey: {j-l}/[4.000000000,0={localTs=3.000000000,0}/] +data: "a"/3.000000000,0 -> / +data: "a"/1.000000000,0 -> /BYTES/a1 +data: "b"/1.000000000,0 -> / +data: "c"/1.000000000,0 -> /BYTES/c1 +data: "d"/3.000000000,0 -> /BYTES/d3 +meta: "e"/0,0 -> txn={id=00000000 key="e" pri=0.00000000 epo=0 ts=6.000000000,0 min=0,0 seq=0} ts=6.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true +data: "e"/6.000000000,0 -> /BYTES/e6 +data: "f"/5.000000000,0 -> /BYTES/f5 +data: "f"/3.000000000,0 -> /BYTES/f3 +data: "f"/1.000000000,0 -> /BYTES/f1 +data: "h"/2.000000000,0 -> /BYTES/h2 + +# Run non-tombstone scans at all timestamps. +run ok +scan k=a end=z ts=1 +---- +scan: "a" -> /BYTES/a1 @1.000000000,0 +scan: "c" -> /BYTES/c1 @1.000000000,0 +scan: "f" -> /BYTES/f1 @1.000000000,0 + +run ok +scan k=a end=z ts=2 +---- +scan: "f" -> /BYTES/f1 @1.000000000,0 +scan: "h" -> /BYTES/h2 @2.000000000,0 + +run ok +scan k=a end=z ts=3 +---- +scan: "d" -> /BYTES/d3 @3.000000000,0 +scan: "f" -> /BYTES/f3 @3.000000000,0 +scan: "h" -> /BYTES/h2 @2.000000000,0 + +run ok +scan k=a end=z ts=4 +---- +scan: "a"-"z" -> + +run ok +scan k=a end=z ts=5 +---- +scan: "f" -> /BYTES/f5 @5.000000000,0 + +run ok +scan k=a end=z ts=6 inconsistent +---- +scan: intent "e" {id=00000000 key="e" pri=0.00000000 epo=0 ts=6.000000000,0 min=0,0 seq=0} +scan: "f" -> /BYTES/f5 @5.000000000,0 + +# Run tombstone scans at all timestamps. +run ok +scan k=a end=z ts=1 tombstones +---- +scan: "a" -> /BYTES/a1 @1.000000000,0 +scan: "b" -> / @1.000000000,0 +scan: "c" -> /BYTES/c1 @1.000000000,0 +scan: "f" -> /BYTES/f1 @1.000000000,0 + +run ok +scan k=a end=z ts=2 tombstones +---- +scan: "a" -> / @2.000000000,0 +scan: "b" -> / @2.000000000,0 +scan: "c" -> / @2.000000000,0 +scan: "d" -> / @2.000000000,0 +scan: "f" -> /BYTES/f1 @1.000000000,0 +scan: "h" -> /BYTES/h2 @2.000000000,0 + +run ok +scan k=a end=z ts=3 tombstones +---- +scan: "a" -> / @3.000000000,0 +scan: "b" -> / @2.000000000,0 +scan: "c" -> / @2.000000000,0 +scan: "d" -> /BYTES/d3 @3.000000000,0 +scan: "f" -> /BYTES/f3 @3.000000000,0 +scan: "h" -> /BYTES/h2 @2.000000000,0 + +run ok +scan k=a end=z ts=4 tombstones +---- +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 +---- +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 +---- +scan: intent "e" {id=00000000 key="e" pri=0.00000000 epo=0 ts=6.000000000,0 min=0,0 seq=0} +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 +scan k=a end=b ts=6 tombstones inconsistent +scan k=b end=c ts=6 tombstones inconsistent +scan k=c end=d ts=6 tombstones inconsistent +scan k=d end=e ts=6 tombstones inconsistent +scan k=e end=f ts=6 tombstones inconsistent +scan k=f end=g ts=6 tombstones inconsistent +scan k=g end=h ts=6 tombstones inconsistent +scan k=h end=i ts=6 tombstones inconsistent +scan k=i end=j ts=6 tombstones inconsistent +scan k=j end=k ts=6 tombstones inconsistent +scan k=k end=l ts=6 tombstones inconsistent +scan k=l end=m ts=6 tombstones inconsistent +---- +scan: "a" -> / @3.000000000,0 +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: "f" -> /BYTES/f5 @5.000000000,0 +scan: "g" -> / @4.000000000,0 +scan: "h" -> / @4.000000000,0 +scan: "i"-"j" -> +scan: "j" -> / @4.000000000,0 +scan: "k" -> / @4.000000000,0 +scan: "l"-"m" -> + +run ok +scan k=a end=b ts=3 tombstones inconsistent +scan k=b end=c ts=3 tombstones inconsistent +scan k=c end=d ts=3 tombstones inconsistent +scan k=d end=e ts=3 tombstones inconsistent +scan k=e end=f ts=3 tombstones inconsistent +scan k=f end=g ts=3 tombstones inconsistent +scan k=g end=h ts=3 tombstones inconsistent +scan k=h end=i ts=3 tombstones inconsistent +scan k=i end=j ts=3 tombstones inconsistent +scan k=j end=k ts=3 tombstones inconsistent +scan k=k end=l ts=3 tombstones inconsistent +scan k=l end=m ts=3 tombstones inconsistent +---- +scan: "a" -> / @3.000000000,0 +scan: "b" -> / @2.000000000,0 +scan: "c" -> / @2.000000000,0 +scan: "d" -> /BYTES/d3 @3.000000000,0 +scan: "e"-"f" -> +scan: "f" -> /BYTES/f3 @3.000000000,0 +scan: "g"-"h" -> +scan: "h" -> /BYTES/h2 @2.000000000,0 +scan: "i"-"j" -> +scan: "j"-"k" -> +scan: "k"-"l" -> +scan: "l"-"m" -> + +# Run non-tombstone scans at all timestamps in reverse. +run ok +scan k=a end=z ts=1 reverse +---- +scan: "f" -> /BYTES/f1 @1.000000000,0 +scan: "c" -> /BYTES/c1 @1.000000000,0 +scan: "a" -> /BYTES/a1 @1.000000000,0 + +run ok +scan k=a end=z ts=2 reverse +---- +scan: "h" -> /BYTES/h2 @2.000000000,0 +scan: "f" -> /BYTES/f1 @1.000000000,0 + +run ok +scan k=a end=z ts=3 reverse +---- +scan: "h" -> /BYTES/h2 @2.000000000,0 +scan: "f" -> /BYTES/f3 @3.000000000,0 +scan: "d" -> /BYTES/d3 @3.000000000,0 + +run ok +scan k=a end=z ts=4 reverse +---- +scan: "a"-"z" -> + +run ok +scan k=a end=z ts=5 reverse +---- +scan: "f" -> /BYTES/f5 @5.000000000,0 + +run ok +scan k=a end=z ts=6 inconsistent reverse +---- +scan: intent "e" {id=00000000 key="e" pri=0.00000000 epo=0 ts=6.000000000,0 min=0,0 seq=0} +scan: "f" -> /BYTES/f5 @5.000000000,0 + +# Run tombstone scans at all timestamps in reverse. +run ok +scan k=a end=z ts=1 tombstones reverse +---- +scan: "f" -> /BYTES/f1 @1.000000000,0 +scan: "c" -> /BYTES/c1 @1.000000000,0 +scan: "b" -> / @1.000000000,0 +scan: "a" -> /BYTES/a1 @1.000000000,0 + +run ok +scan k=a end=z ts=2 tombstones reverse +---- +scan: "h" -> /BYTES/h2 @2.000000000,0 +scan: "f" -> /BYTES/f1 @1.000000000,0 +scan: "d" -> / @2.000000000,0 +scan: "c" -> / @2.000000000,0 +scan: "b" -> / @2.000000000,0 +scan: "a" -> / @2.000000000,0 + +run ok +scan k=a end=z ts=3 tombstones reverse +---- +scan: "h" -> /BYTES/h2 @2.000000000,0 +scan: "f" -> /BYTES/f3 @3.000000000,0 +scan: "d" -> /BYTES/d3 @3.000000000,0 +scan: "c" -> / @2.000000000,0 +scan: "b" -> / @2.000000000,0 +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 +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 +scan: "a" -> / @3.000000000,0 + +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 +scan: "a" -> / @3.000000000,0 + +# Run bounded tombstone scans in reverse, at 6 and 3. +run ok +scan k=a end=b ts=6 tombstones inconsistent reverse +scan k=b end=c ts=6 tombstones inconsistent reverse +scan k=c end=d ts=6 tombstones inconsistent reverse +scan k=d end=e ts=6 tombstones inconsistent reverse +scan k=e end=f ts=6 tombstones inconsistent reverse +scan k=f end=g ts=6 tombstones inconsistent reverse +scan k=g end=h ts=6 tombstones inconsistent reverse +scan k=h end=i ts=6 tombstones inconsistent reverse +scan k=i end=j ts=6 tombstones inconsistent reverse +scan k=j end=k ts=6 tombstones inconsistent reverse +scan k=k end=l ts=6 tombstones inconsistent reverse +scan k=l end=m ts=6 tombstones inconsistent reverse +---- +scan: "a" -> / @3.000000000,0 +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: "f" -> /BYTES/f5 @5.000000000,0 +scan: "g" -> / @4.000000000,0 +scan: "h" -> / @4.000000000,0 +scan: "i"-"j" -> +scan: "j" -> / @4.000000000,0 +scan: "k" -> / @4.000000000,0 +scan: "l"-"m" -> + +run ok +scan k=a end=b ts=3 tombstones inconsistent reverse +scan k=b end=c ts=3 tombstones inconsistent reverse +scan k=c end=d ts=3 tombstones inconsistent reverse +scan k=d end=e ts=3 tombstones inconsistent reverse +scan k=e end=f ts=3 tombstones inconsistent reverse +scan k=f end=g ts=3 tombstones inconsistent reverse +scan k=g end=h ts=3 tombstones inconsistent reverse +scan k=h end=i ts=3 tombstones inconsistent reverse +scan k=i end=j ts=3 tombstones inconsistent reverse +scan k=j end=k ts=3 tombstones inconsistent reverse +scan k=k end=l ts=3 tombstones inconsistent reverse +scan k=l end=m ts=3 tombstones inconsistent reverse +---- +scan: "a" -> / @3.000000000,0 +scan: "b" -> / @2.000000000,0 +scan: "c" -> / @2.000000000,0 +scan: "d" -> /BYTES/d3 @3.000000000,0 +scan: "e"-"f" -> +scan: "f" -> /BYTES/f3 @3.000000000,0 +scan: "g"-"h" -> +scan: "h" -> /BYTES/h2 @2.000000000,0 +scan: "i"-"j" -> +scan: "j"-"k" -> +scan: "k"-"l" -> +scan: "l"-"m" -> + +# failOnMoreRecent: a-d +run error +scan k=a end=d ts=3 failOnMoreRecent +---- +scan: "a"-"d" -> +error: (*roachpb.WriteTooOldError:) WriteTooOldError: write for key "a" at timestamp 3.000000000,0 too old; wrote at 4.000000000,1 + +run error +scan k=a end=d ts=4 failOnMoreRecent +---- +scan: "a"-"d" -> +error: (*roachpb.WriteTooOldError:) WriteTooOldError: write for key "c" at timestamp 4.000000000,0 too old; wrote at 4.000000000,1 + +run ok +scan k=a end=d ts=5 failOnMoreRecent +---- +scan: "a"-"d" -> + +# failOnMoreRecent: c-d +run error +scan k=c end=d ts=1 failOnMoreRecent +---- +scan: "c"-"d" -> +error: (*roachpb.WriteTooOldError:) WriteTooOldError: write for key "c" at timestamp 1.000000000,0 too old; wrote at 4.000000000,1 + +run error +scan k=c end=d ts=2 failOnMoreRecent +---- +scan: "c"-"d" -> +error: (*roachpb.WriteTooOldError:) WriteTooOldError: write for key "c" at timestamp 2.000000000,0 too old; wrote at 4.000000000,1 + +run error +scan k=c end=d ts=3 failOnMoreRecent +---- +scan: "c"-"d" -> +error: (*roachpb.WriteTooOldError:) WriteTooOldError: write for key "c" at timestamp 3.000000000,0 too old; wrote at 4.000000000,1 + +run error +scan k=c end=d ts=4 failOnMoreRecent +---- +scan: "c"-"d" -> +error: (*roachpb.WriteTooOldError:) WriteTooOldError: write for key "c" at timestamp 4.000000000,0 too old; wrote at 4.000000000,1 + +run ok +scan k=c end=d ts=5 failOnMoreRecent +---- +scan: "c"-"d" -> + +# failOnMoreRecent: e-f +run error +scan k=e end=f ts=3 failOnMoreRecent +---- +scan: "e"-"f" -> +error: (*roachpb.WriteIntentError:) conflicting intents on "e" + +run error +scan k=e end=f ts=4 failOnMoreRecent +---- +scan: "e"-"f" -> +error: (*roachpb.WriteIntentError:) conflicting intents on "e" + +run error +scan k=e end=f ts=5 failOnMoreRecent +---- +scan: "e"-"f" -> +error: (*roachpb.WriteIntentError:) conflicting intents on "e" + +# failOnMoreRecent: g-h +run error +scan k=g end=h ts=3 failOnMoreRecent +---- +scan: "g"-"h" -> +error: (*roachpb.WriteTooOldError:) WriteTooOldError: write for key "g" at timestamp 3.000000000,0 too old; wrote at 4.000000000,1 + +run error +scan k=g end=h ts=4 failOnMoreRecent +---- +scan: "g"-"h" -> +error: (*roachpb.WriteTooOldError:) WriteTooOldError: write for key "g" at timestamp 4.000000000,0 too old; wrote at 4.000000000,1 + +run ok +scan k=g end=h ts=5 failOnMoreRecent +---- +scan: "g"-"h" -> + + +# globalUncertaintyLimit: b-d +run ok +scan k=b end=d ts=3 globalUncertaintyLimit=3 +---- +scan: "b"-"d" -> + +run error +scan k=b end=d ts=3 globalUncertaintyLimit=4 +---- +scan: "b"-"d" -> +error: (*roachpb.ReadWithinUncertaintyIntervalError:) ReadWithinUncertaintyIntervalError: read at time 3.000000000,0 encountered previous write with future timestamp 4.000000000,0 within uncertainty interval `t <= (local=0,0, global=0,0)`; observed timestamps: [] + +run ok +scan k=b end=d ts=4 globalUncertaintyLimit=5 +---- +scan: "b"-"d" -> + +# globalUncertaintyLimit: g-h +run ok +scan k=g end=h ts=1 globalUncertaintyLimit=1 +---- +scan: "g"-"h" -> + +run ok +scan k=g end=h ts=1 globalUncertaintyLimit=3 +---- +scan: "g"-"h" -> + +run error +scan k=g end=h ts=1 globalUncertaintyLimit=4 +---- +scan: "g"-"h" -> +error: (*roachpb.ReadWithinUncertaintyIntervalError:) ReadWithinUncertaintyIntervalError: read at time 1.000000000,0 encountered previous write with future timestamp 4.000000000,0 within uncertainty interval `t <= (local=0,0, global=0,0)`; observed timestamps: [] + +run ok +scan k=g end=h ts=4 globalUncertaintyLimit=5 +---- +scan: "g"-"h" -> + +# globalUncertaintyLimit: h-i +run ok +scan k=h end=i ts=2 globalUncertaintyLimit=2 +---- +scan: "h" -> /BYTES/h2 @2.000000000,0 + +run ok +scan k=h end=i ts=2 globalUncertaintyLimit=3 +---- +scan: "h" -> /BYTES/h2 @2.000000000,0 + +run error +scan k=h end=i ts=2 globalUncertaintyLimit=4 +---- +scan: "h"-"i" -> +error: (*roachpb.ReadWithinUncertaintyIntervalError:) ReadWithinUncertaintyIntervalError: read at time 2.000000000,0 encountered previous write with future timestamp 4.000000000,0 within uncertainty interval `t <= (local=0,0, global=0,0)`; observed timestamps: [] + +# Test local timestamp uncertainty for [j-l)@4 with localTs=3. Normally, +# globalUncertaintyLimit=4 would error. However, localUncertaintyLimit<4 +# disables this via observed timestamps, but not if +# localTs<=localUncertaintyLimit. +run error +scan k=j end=l ts=1 globalUncertaintyLimit=4 +---- +scan: "j"-"l" -> +error: (*roachpb.ReadWithinUncertaintyIntervalError:) ReadWithinUncertaintyIntervalError: read at time 1.000000000,0 encountered previous write with future timestamp 4.000000000,0 within uncertainty interval `t <= (local=0,0, global=0,0)`; observed timestamps: [] + +run ok +scan k=j end=l ts=1 globalUncertaintyLimit=4 localUncertaintyLimit=1 +---- +scan: "j"-"l" -> + +run ok +scan k=j end=l ts=1 globalUncertaintyLimit=4 localUncertaintyLimit=2 +---- +scan: "j"-"l" -> + +run error +scan k=j end=l ts=1 globalUncertaintyLimit=4 localUncertaintyLimit=3 +---- +scan: "j"-"l" -> +error: (*roachpb.ReadWithinUncertaintyIntervalError:) ReadWithinUncertaintyIntervalError: read at time 1.000000000,0 encountered previous write with future timestamp 4.000000000,0 within uncertainty interval `t <= (local=3.000000000,0, global=0,0)`; observed timestamps: []