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: []