diff --git a/pkg/kv/kvserver/rangefeed/catchup_scan.go b/pkg/kv/kvserver/rangefeed/catchup_scan.go index 51003b88dd34..aa6b73041586 100644 --- a/pkg/kv/kvserver/rangefeed/catchup_scan.go +++ b/pkg/kv/kvserver/rangefeed/catchup_scan.go @@ -34,6 +34,7 @@ import ( type simpleCatchupIter interface { storage.SimpleMVCCIterator NextIgnoringTime() + RangeKeysIgnoringTime() storage.MVCCRangeKeyStack } type simpleCatchupIterAdapter struct { @@ -44,6 +45,10 @@ func (i simpleCatchupIterAdapter) NextIgnoringTime() { i.SimpleMVCCIterator.Next() } +func (i simpleCatchupIterAdapter) RangeKeysIgnoringTime() storage.MVCCRangeKeyStack { + return i.SimpleMVCCIterator.RangeKeys() +} + var _ simpleCatchupIter = simpleCatchupIterAdapter{} // CatchUpIterator is an iterator for catchup-scans. @@ -145,8 +150,6 @@ func (i *CatchUpIterator) CatchUpScan(outputFn outputEventFn, withDiff bool) err // Emit any new MVCC range tombstones when their start key is encountered. // Range keys can currently only be MVCC range tombstones. - // We need to verify that the range tombstone is visible at the catch-up - // timestamp, since we might have come here after a call to NextIgnoringTime. // // TODO(erikgrinaker): Find a faster/better way to detect range key changes // that doesn't involve constant comparisons. Pebble probably already knows, @@ -161,11 +164,6 @@ func (i *CatchUpIterator) CatchUpScan(outputFn outputEventFn, withDiff bool) err // Emit events for these MVCC range tombstones, in chronological order. versions := i.RangeKeys().Versions for j := len(versions) - 1; j >= 0; j-- { - if !i.startTime.LessEq(versions[j].Timestamp) { - // This range tombstone isn't visible by this catch-up scan. - continue - } - var span roachpb.Span a, span.Key = a.Copy(rangeBounds.Key, 0) a, span.EndKey = a.Copy(rangeBounds.EndKey, 0) @@ -285,12 +283,8 @@ func (i *CatchUpIterator) CatchUpScan(outputFn outputEventFn, withDiff bool) err } // If an MVCC range tombstone exists between this value and the next // one, we don't emit the value after all -- it should be a tombstone. - // - // TODO(erikgrinaker): We can't save range keys when we detect changes - // to rangeKeysStart above, because NextIgnoringTime() could reveal - // additional MVCC range tombstones below StartTime that cover this - // point. We need to find a more performant way to handle this. - if !hasRange || !i.RangeKeys().HasBetween(ts, reorderBuf[l].Val.Value.Timestamp) { + // The RangeKeysIgnoringTime() call is cheap, no need for caching. + if !i.RangeKeysIgnoringTime().HasBetween(ts, reorderBuf[l].Val.Value.Timestamp) { // TODO(sumeer): find out if it is deliberate that we are not populating // PrevValue.Timestamp. reorderBuf[l].Val.PrevValue.RawBytes = val diff --git a/pkg/storage/mvcc.go b/pkg/storage/mvcc.go index 66fc8c4e1108..9f1031f1d724 100644 --- a/pkg/storage/mvcc.go +++ b/pkg/storage/mvcc.go @@ -2622,16 +2622,8 @@ func MVCCClearTimeRange( break } - // Because we're using NextIgnoringTime() to look for older keys, it's - // possible that the iterator will surface range keys outside of the time - // bounds, so we need to do additional filtering here. - // // TODO(erikgrinaker): Consider a Clone() variant that can reuse a buffer. - // See also TODO in Clone() to use a single allocation for all byte - // slices. - rangeKeys := iter.RangeKeys() - rangeKeys.Trim(startTime.Next(), endTime) - clearRangeKeys = rangeKeys.Clone() + clearRangeKeys = iter.RangeKeys().Clone() } if hasPoint, _ := iter.HasPointAndRange(); !hasPoint { @@ -2662,7 +2654,7 @@ func MVCCClearTimeRange( // cleared key, then we didn't restore it after all, but we must still // adjust the stats for the range tombstone. if !restoredMeta.Deleted { - if v, ok := iter.RangeKeys().FirstAbove(k.Timestamp); ok { + if v, ok := iter.RangeKeysIgnoringTime().FirstAbove(k.Timestamp); ok { if v.Timestamp.LessEq(clearedMeta.Timestamp.ToTimestamp()) { restoredMeta.Deleted = true restoredMeta.KeyBytes = 0 @@ -2700,14 +2692,9 @@ func MVCCClearTimeRange( // we cleared or a different range tombstone below the one we cleared. if !v.IsTombstone() { if v, ok := clearRangeKeys.FirstAbove(k.Timestamp); ok { - // TODO(erikgrinaker): We have to fetch the complete set of range keys - // as seen by this key -- these may or may not be filtered by timestamp - // depending on whether we did a NextIgnoringTime(), so we have to fetch - // the entire set rather than using clearedRangeKeys. We should optimize - // this somehow. if !clearedMetaKey.Key.Equal(k.Key) || !clearedMeta.Timestamp.ToTimestamp().LessEq(v.Timestamp) { - if !iter.RangeKeys().HasBetween(v.Timestamp.Prev(), k.Timestamp) { + if !iter.RangeKeysIgnoringTime().HasBetween(k.Timestamp, v.Timestamp.Prev()) { ms.Add(enginepb.MVCCStats{ LastUpdateNanos: v.Timestamp.WallTime, LiveCount: 1, @@ -2921,15 +2908,17 @@ func MVCCPredicateDeleteRange( // // 2) The latest key is live, matches the predicates, and has a // timestamp below EndTime. - continueRun := func(k MVCCKey, iter SimpleMVCCIterator, + continueRun := func(k MVCCKey, iter *MVCCIncrementalIterator, ) (toContinue bool, isPointTombstone bool, isRangeTombstone bool, err error) { - hasPointKey, hasRangeKey := iter.HasPointAndRange() + // We need to see the full, unfiltered set of range keys, ignoring time + // bounds. The RangeKeysIgnoringTime() call is cheap. + hasPointKey, _ := iter.HasPointAndRange() + rangeKeys := iter.RangeKeysIgnoringTime() + hasRangeKey := !rangeKeys.IsEmpty() + if hasRangeKey { - // TODO (msbutler): cache the range keys while the range bounds remain - // constant, since iter.RangeKeys() is expensive. Manual caching may not be necessary if - // https://github.com/cockroachdb/cockroach/issues/84379 lands. - newestRangeKey := iter.RangeKeys().Versions[0].Timestamp - if endTime.LessEq(newestRangeKey) { + newestRangeKey := rangeKeys.Newest() + if endTime.LessEq(rangeKeys.Newest()) { return false, false, false, roachpb.NewWriteTooOldError( endTime, newestRangeKey.Next(), k.Key.Clone()) } diff --git a/pkg/storage/mvcc_history_test.go b/pkg/storage/mvcc_history_test.go index e37376013e22..791c64069bc2 100644 --- a/pkg/storage/mvcc_history_test.go +++ b/pkg/storage/mvcc_history_test.go @@ -1703,6 +1703,13 @@ func printIter(e *evalCtx) { e.results.buf.Printf("%s:", e.td.Cmd) defer e.results.buf.Printf("\n") + // MVCCIncrementalIterator has odd behavior following a NextIgnoringTime() + // call, so we detect this and adjust expectations. + var incrIterIgnoringTime bool + if incrIter, ok := e.bareIter().(*MVCCIncrementalIterator); ok { + incrIterIgnoringTime = incrIter.ignoringTime + } + ok, err := e.iter.Valid() if err != nil { e.results.buf.Printf(" err=%v", err) @@ -1715,7 +1722,9 @@ func printIter(e *evalCtx) { } hasPoint, hasRange := e.iter.HasPointAndRange() if !hasPoint && !hasRange { - e.t.Fatalf("valid iterator at %s without point nor range keys", e.iter.UnsafeKey()) + if !incrIterIgnoringTime || e.mvccIncrementalIter().RangeKeysIgnoringTime().IsEmpty() { + e.t.Fatalf("valid iterator at %s without point nor range keys", e.iter.UnsafeKey()) + } } if hasPoint { @@ -1750,6 +1759,29 @@ func printIter(e *evalCtx) { e.results.buf.Printf("]") } + if incrIterIgnoringTime { + rangeKeys := e.mvccIncrementalIter().RangeKeysIgnoringTime() + if !rangeKeys.Equal(e.iter.RangeKeys()) { + e.results.buf.Printf(" (+%s/[", rangeKeys.Bounds) + for i, version := range rangeKeys.Versions { + value, err := DecodeMVCCValue(version.Value) + if err != nil { + e.Fatalf("%v", err) + } + if i > 0 { + e.results.buf.Printf(" ") + } + e.results.buf.Printf("%s=%s", version.Timestamp, value) + } + e.results.buf.Printf("]") + if e.mvccIncrementalIter().RangeKeyChangedIgnoringTime() { + e.results.buf.Printf(" !") + } + e.results.buf.Printf(")") + + } + } + if checkAndUpdateRangeKeyChanged(e) { e.results.buf.Printf(" !") } @@ -1759,10 +1791,19 @@ func checkAndUpdateRangeKeyChanged(e *evalCtx) bool { rangeKeyChanged := e.iter.RangeKeyChanged() rangeKeys := e.iter.RangeKeys() - // For MVCCIncrementalIterator, Next(Key)IgnoringTime() may reveal additional - // range key versions, but RangeKeyChanged only applies to the filtered set. - if incrIter, ok := e.bareIter().(*MVCCIncrementalIterator); ok && incrIter.ignoringTime { - rangeKeys = incrIter.rangeKeys + if incrIter, ok := e.bareIter().(*MVCCIncrementalIterator); ok { + // For MVCCIncrementalIterator, make sure RangeKeyChangedIgnoringTime() fires + // whenever RangeKeyChanged() does. The inverse is not true. + rangeKeyChangedIgnoringTime := incrIter.RangeKeyChangedIgnoringTime() + if rangeKeyChanged && !rangeKeyChangedIgnoringTime { + e.t.Fatalf("RangeKeyChanged=%t but RangeKeyChangedIgnoringTime=%t", + rangeKeyChanged, incrIter.RangeKeyChangedIgnoringTime()) + } + // If RangeKeyChangedIgnoringTime() fires, and RangeKeyChanged() doesn't, + // then RangeKeys() must be empty. + if rangeKeyChangedIgnoringTime && !rangeKeyChanged && !rangeKeys.IsEmpty() { + e.t.Fatalf("RangeKeyChangedIgnoringTime without RangeKeyChanged, but RangeKeys not empty") + } } if rangeKeyChanged != !rangeKeys.Equal(e.iterRangeKeys) { diff --git a/pkg/storage/mvcc_incremental_iterator.go b/pkg/storage/mvcc_incremental_iterator.go index 19011c07c288..0976261edbfc 100644 --- a/pkg/storage/mvcc_incremental_iterator.go +++ b/pkg/storage/mvcc_incremental_iterator.go @@ -106,6 +106,10 @@ type MVCCIncrementalIterator struct { // positioning operation. rangeKeyChanged bool + // rangeKeyChangedIgnoringTime is true if i.rangeKeysIgnoringTime changed + // during the previous positioning operation. + rangeKeyChangedIgnoringTime bool + // ignoringTime is true if the iterator is currently ignoring time bounds, // i.e. following a call to NextIgnoringTime(). ignoringTime bool @@ -251,6 +255,7 @@ func (i *MVCCIncrementalIterator) SeekGE(startKey MVCCKey) { i.iter.SeekGE(startKey) i.advance(true /* seeked */) i.rangeKeyChanged = !prevRangeKey.Equal(i.rangeKeys.Bounds.Key) // Is there a better way? + i.rangeKeyChangedIgnoringTime = i.rangeKeyChanged } // Close implements SimpleMVCCIterator. @@ -476,8 +481,8 @@ func (i *MVCCIncrementalIterator) updateRangeKeys() (bool, bool) { // intent policy is MVCCIncrementalIterIntentPolicyError. func (i *MVCCIncrementalIterator) advance(seeked bool) { i.ignoringTime = false - i.rangeKeyChanged = false - hadRange := !i.rangeKeys.IsEmpty() + i.rangeKeyChanged, i.rangeKeyChangedIgnoringTime = false, false + hadRange, hadRangeIgnoringTime := !i.rangeKeys.IsEmpty(), !i.rangeKeysIgnoringTime.IsEmpty() for { if !i.updateValid() { return @@ -499,9 +504,12 @@ func (i *MVCCIncrementalIterator) advance(seeked bool) { var newRangeKey bool if rangeKeyChanged { i.hasPoint, i.hasRange = i.updateRangeKeys() - i.rangeKeyChanged = hadRange || i.hasRange // !hasRange → !hasRange is no change newRangeKey = i.hasRange + // NB: !hasRange → !hasRange is not a change. + i.rangeKeyChanged = hadRange || i.hasRange + i.rangeKeyChangedIgnoringTime = hadRangeIgnoringTime || !i.rangeKeysIgnoringTime.IsEmpty() + // If we're on a visible, bare range key then we're done. If the range key // was filtered out by the time bounds (the !hasPoint && !hasRange case), // then we move on to the next key. @@ -581,38 +589,54 @@ func (i *MVCCIncrementalIterator) UnsafeKey() MVCCKey { } // HasPointAndRange implements SimpleMVCCIterator. +// +// This only returns hasRange=true if there are filtered range keys present. +// Thus, it is possible for this to return hasPoint=false,hasRange=false +// following a NextIgnoringTime() call if positioned on a bare, filtered +// range key. In this case, the range keys are available via +// RangeKeysIgnoringTime(). func (i *MVCCIncrementalIterator) HasPointAndRange() (bool, bool) { - if i.ignoringTime { - return i.iter.HasPointAndRange() - } return i.hasPoint, i.hasRange } // RangeBounds implements SimpleMVCCIterator. +// +// This only returns the filtered range key bounds. Thus, if a +// NextIgnoringTime() call moves onto an otherwise hidden range key, this will +// still return an empty span. These hidden range keys are available via +// RangeKeysIgnoringTime(). func (i *MVCCIncrementalIterator) RangeBounds() roachpb.Span { - if i.ignoringTime { - return i.rangeKeysIgnoringTime.Bounds - } return i.rangeKeys.Bounds } // RangeKeys implements SimpleMVCCIterator. func (i *MVCCIncrementalIterator) RangeKeys() MVCCRangeKeyStack { - if i.ignoringTime { - return i.rangeKeysIgnoringTime - } return i.rangeKeys } +// RangeKeysIgnoringTime returns the range keys at the current position, +// ignoring time bounds. This call is cheap, so callers do not need to perform +// their own caching. +func (i *MVCCIncrementalIterator) RangeKeysIgnoringTime() MVCCRangeKeyStack { + return i.rangeKeysIgnoringTime +} + // RangeKeyChanged implements SimpleMVCCIterator. // // RangeKeyChanged only applies to the filtered set of range keys. If an -// IgnoringTime() operation reveals addition range keys or versions, these do -// not trigger RangeKeyChanged(). +// IgnoringTime() operation reveals additional range keys or versions, these do +// not trigger RangeKeyChanged(). See also RangeKeyChangedIgnoringTime(). func (i *MVCCIncrementalIterator) RangeKeyChanged() bool { return i.rangeKeyChanged } +// RangeKeyChangedIgnoringTime is like RangeKeyChanged, but returns true if the +// range keys returned by RangeKeysIgnoringTime() changed since the previous +// positioning operation -- in particular, after a Next(Key)IgnoringTime() call. +func (i *MVCCIncrementalIterator) RangeKeyChangedIgnoringTime() bool { + return i.rangeKeyChangedIgnoringTime +} + // UnsafeValue implements SimpleMVCCIterator. func (i *MVCCIncrementalIterator) UnsafeValue() []byte { if !i.hasPoint { @@ -625,7 +649,7 @@ func (i *MVCCIncrementalIterator) UnsafeValue() []byte { // intent policy. func (i *MVCCIncrementalIterator) updateIgnoreTime() { i.ignoringTime = true - i.rangeKeyChanged = false + i.rangeKeyChanged, i.rangeKeyChangedIgnoringTime = false, false hadRange := !i.rangeKeys.IsEmpty() for { if !i.updateValid() { @@ -635,6 +659,7 @@ func (i *MVCCIncrementalIterator) updateIgnoreTime() { if i.iter.RangeKeyChanged() { i.hasPoint, i.hasRange = i.updateRangeKeys() i.rangeKeyChanged = hadRange || i.hasRange // !hasRange → !hasRange is no change + i.rangeKeyChangedIgnoringTime = true if !i.hasPoint { i.meta.Reset() return @@ -672,7 +697,16 @@ func (i *MVCCIncrementalIterator) updateIgnoreTime() { // Intents within and outside the (StartTime, EndTime] time range are handled // according to the iterator policy. // -// RangeKeyChanged() will only fire if the time bound range keys change. +// NB: Range key methods only respect the filtered set of range keys. To access +// unfiltered range keys, use RangeKeysIgnoringTime(). This implies that if this +// call steps onto a range key that's entirely outside of the time bounds: +// +// * HasPointAndRange() will return false,false if on a bare range key. +// +// * RangeKeyChanged() will not fire, unless stepping off of a range key +// within the time bounds. +// +// * RangeBounds() and RangeKeys() will return empty results. func (i *MVCCIncrementalIterator) NextIgnoringTime() { i.iter.Next() i.updateIgnoreTime() @@ -683,7 +717,7 @@ func (i *MVCCIncrementalIterator) NextIgnoringTime() { // forward. Intents within and outside the (StartTime, EndTime] time range are // handled according to the iterator policy. // -// RangeKeyChanged() will only fire if the time bound range keys change. +// NB: See NextIgnoringTime comment for important details about range keys. func (i *MVCCIncrementalIterator) NextKeyIgnoringTime() { i.iter.NextKey() i.updateIgnoreTime() diff --git a/pkg/storage/testdata/mvcc_histories/range_key_iter_incremental b/pkg/storage/testdata/mvcc_histories/range_key_iter_incremental index 80ba9bf39d86..94be0cf80003 100644 --- a/pkg/storage/testdata/mvcc_histories/range_key_iter_incremental +++ b/pkg/storage/testdata/mvcc_histories/range_key_iter_incremental @@ -1003,7 +1003,7 @@ iter_seek_ge k=c iter_next_ignoring_time ---- iter_seek_ge: {c-d}/[3.000000000,0=/] ! -iter_next_ignoring_time: "d"/8.000000000,0=/BYTES/d8 {d-f}/[5.000000000,0=/ 1.000000000,0=/] ! +iter_next_ignoring_time: "d"/8.000000000,0=/BYTES/d8 (+{d-f}/[5.000000000,0=/ 1.000000000,0=/] !) ! run error iter_new_incremental types=pointsAndRanges k=a end=z startTs=2 endTs=10 intents=error @@ -1020,7 +1020,7 @@ iter_seek_ge k=c iter_next_key_ignoring_time ---- iter_seek_ge: {c-d}/[3.000000000,0=/] ! -iter_next_key_ignoring_time: "d"/8.000000000,0=/BYTES/d8 {d-f}/[5.000000000,0=/ 1.000000000,0=/] ! +iter_next_key_ignoring_time: "d"/8.000000000,0=/BYTES/d8 (+{d-f}/[5.000000000,0=/ 1.000000000,0=/] !) ! run error iter_new_incremental types=pointsAndRanges k=a end=z startTs=2 endTs=10 intents=error @@ -1081,26 +1081,26 @@ iter_next_ignoring_time iter_next_ignoring_time ---- iter_seek_ge: "a"/4.000000000,0=/ -iter_next_ignoring_time: "a"/2.000000000,0=/BYTES/a2 {a-b}/[1.000000000,0=/] -iter_next_ignoring_time: {b-c}/[3.000000000,0=/ 1.000000000,0=/] ! -iter_next_ignoring_time: "b"/4.000000000,0=/ {b-c}/[3.000000000,0=/ 1.000000000,0=/] -iter_next_ignoring_time: {c-d}/[5.000000000,0=/ 3.000000000,0=/ 1.000000000,0=/] ! -iter_next_ignoring_time: "d"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=8.000000000,0 min=0,0 seq=0} ts=8.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true {d-f}/[5.000000000,0=/ 1.000000000,0=/] ! -iter_next_ignoring_time: "d"/8.000000000,0=/BYTES/d8 {d-f}/[5.000000000,0=/ 1.000000000,0=/] -iter_next_ignoring_time: "d"/4.000000000,0=/BYTES/d4 {d-f}/[5.000000000,0=/ 1.000000000,0=/] -iter_next_ignoring_time: "e"/3.000000000,0=/BYTES/e3 {d-f}/[5.000000000,0=/ 1.000000000,0=/] -iter_next_ignoring_time: {f-g}/[5.000000000,0=/ 3.000000000,0=/ 1.000000000,0=/] ! -iter_next_ignoring_time: "f"/6.000000000,0=/BYTES/f6 {f-g}/[5.000000000,0=/ 3.000000000,0=/ 1.000000000,0=/] -iter_next_ignoring_time: "f"/4.000000000,0=/BYTES/f4 {f-g}/[5.000000000,0=/ 3.000000000,0=/ 1.000000000,0=/] -iter_next_ignoring_time: "f"/2.000000000,0=/BYTES/f2 {f-g}/[5.000000000,0=/ 3.000000000,0=/ 1.000000000,0=/] -iter_next_ignoring_time: {g-h}/[3.000000000,0=/ 1.000000000,0=/] ! -iter_next_ignoring_time: "g"/4.000000000,0=/BYTES/g4 {g-h}/[3.000000000,0=/ 1.000000000,0=/] -iter_next_ignoring_time: "g"/2.000000000,0=/BYTES/g2 {g-h}/[3.000000000,0=/ 1.000000000,0=/] -iter_next_ignoring_time: {h-k}/[1.000000000,0=/] ! -iter_next_ignoring_time: "h"/4.000000000,0=/ {h-k}/[1.000000000,0=/] -iter_next_ignoring_time: "h"/3.000000000,0=/BYTES/h3 {h-k}/[1.000000000,0=/] -iter_next_ignoring_time: "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 {h-k}/[1.000000000,0=/] -iter_next_ignoring_time: "j"/7.000000000,0=/BYTES/j7 {h-k}/[1.000000000,0=/] +iter_next_ignoring_time: "a"/2.000000000,0=/BYTES/a2 (+{a-b}/[1.000000000,0=/]) +iter_next_ignoring_time: {b-c}/[3.000000000,0=/] (+{b-c}/[3.000000000,0=/ 1.000000000,0=/] !) ! +iter_next_ignoring_time: "b"/4.000000000,0=/ {b-c}/[3.000000000,0=/] (+{b-c}/[3.000000000,0=/ 1.000000000,0=/]) +iter_next_ignoring_time: {c-d}/[3.000000000,0=/] (+{c-d}/[5.000000000,0=/ 3.000000000,0=/ 1.000000000,0=/] !) ! +iter_next_ignoring_time: "d"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=8.000000000,0 min=0,0 seq=0} ts=8.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true (+{d-f}/[5.000000000,0=/ 1.000000000,0=/] !) ! +iter_next_ignoring_time: "d"/8.000000000,0=/BYTES/d8 (+{d-f}/[5.000000000,0=/ 1.000000000,0=/]) +iter_next_ignoring_time: "d"/4.000000000,0=/BYTES/d4 (+{d-f}/[5.000000000,0=/ 1.000000000,0=/]) +iter_next_ignoring_time: "e"/3.000000000,0=/BYTES/e3 (+{d-f}/[5.000000000,0=/ 1.000000000,0=/]) +iter_next_ignoring_time: {f-g}/[3.000000000,0=/] (+{f-g}/[5.000000000,0=/ 3.000000000,0=/ 1.000000000,0=/] !) ! +iter_next_ignoring_time: "f"/6.000000000,0=/BYTES/f6 {f-g}/[3.000000000,0=/] (+{f-g}/[5.000000000,0=/ 3.000000000,0=/ 1.000000000,0=/]) +iter_next_ignoring_time: "f"/4.000000000,0=/BYTES/f4 {f-g}/[3.000000000,0=/] (+{f-g}/[5.000000000,0=/ 3.000000000,0=/ 1.000000000,0=/]) +iter_next_ignoring_time: "f"/2.000000000,0=/BYTES/f2 {f-g}/[3.000000000,0=/] (+{f-g}/[5.000000000,0=/ 3.000000000,0=/ 1.000000000,0=/]) +iter_next_ignoring_time: {g-h}/[3.000000000,0=/] (+{g-h}/[3.000000000,0=/ 1.000000000,0=/] !) ! +iter_next_ignoring_time: "g"/4.000000000,0=/BYTES/g4 {g-h}/[3.000000000,0=/] (+{g-h}/[3.000000000,0=/ 1.000000000,0=/]) +iter_next_ignoring_time: "g"/2.000000000,0=/BYTES/g2 {g-h}/[3.000000000,0=/] (+{g-h}/[3.000000000,0=/ 1.000000000,0=/]) +iter_next_ignoring_time: (+{h-k}/[1.000000000,0=/] !) ! +iter_next_ignoring_time: "h"/4.000000000,0=/ (+{h-k}/[1.000000000,0=/]) +iter_next_ignoring_time: "h"/3.000000000,0=/BYTES/h3 (+{h-k}/[1.000000000,0=/]) +iter_next_ignoring_time: "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 (+{h-k}/[1.000000000,0=/]) +iter_next_ignoring_time: "j"/7.000000000,0=/BYTES/j7 (+{h-k}/[1.000000000,0=/]) iter_next_ignoring_time: "k"/5.000000000,0=/BYTES/k5 iter_next_ignoring_time: "l"/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_ignoring_time: "l"/7.000000000,0=/BYTES/l7 @@ -1117,7 +1117,7 @@ iter_next_ignoring_time iter_next ---- iter_seek_ge: "a"/4.000000000,0=/ -iter_next_ignoring_time: "a"/2.000000000,0=/BYTES/a2 {a-b}/[1.000000000,0=/] +iter_next_ignoring_time: "a"/2.000000000,0=/BYTES/a2 (+{a-b}/[1.000000000,0=/]) iter_next: {b-c}/[3.000000000,0=/] ! # Switch from ignoring to respecting time at range key. @@ -1132,7 +1132,7 @@ iter_next iter_seek_ge: "a"/4.000000000,0=/ iter_next: {b-c}/[3.000000000,0=/] ! iter_next: "b"/4.000000000,0=/ {b-c}/[3.000000000,0=/] -iter_next_ignoring_time: {c-d}/[5.000000000,0=/ 3.000000000,0=/ 1.000000000,0=/] ! +iter_next_ignoring_time: {c-d}/[3.000000000,0=/] (+{c-d}/[5.000000000,0=/ 3.000000000,0=/ 1.000000000,0=/] !) ! iter_next: "d"/4.000000000,0=/BYTES/d4 ! # Switch between ignoring and respecting time with NextIgnoringTime across span. @@ -1159,19 +1159,19 @@ iter_next ---- iter_seek_ge: "a"/4.000000000,0=/ iter_next: {b-c}/[3.000000000,0=/] ! -iter_next_ignoring_time: "b"/4.000000000,0=/ {b-c}/[3.000000000,0=/ 1.000000000,0=/] +iter_next_ignoring_time: "b"/4.000000000,0=/ {b-c}/[3.000000000,0=/] (+{b-c}/[3.000000000,0=/ 1.000000000,0=/]) iter_next: {c-d}/[3.000000000,0=/] ! -iter_next_ignoring_time: "d"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=8.000000000,0 min=0,0 seq=0} ts=8.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true {d-f}/[5.000000000,0=/ 1.000000000,0=/] ! +iter_next_ignoring_time: "d"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=8.000000000,0 min=0,0 seq=0} ts=8.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true (+{d-f}/[5.000000000,0=/ 1.000000000,0=/] !) ! iter_next: "d"/4.000000000,0=/BYTES/d4 -iter_next_ignoring_time: "e"/3.000000000,0=/BYTES/e3 {d-f}/[5.000000000,0=/ 1.000000000,0=/] +iter_next_ignoring_time: "e"/3.000000000,0=/BYTES/e3 (+{d-f}/[5.000000000,0=/ 1.000000000,0=/]) iter_next: {f-g}/[3.000000000,0=/] ! -iter_next_ignoring_time: "f"/6.000000000,0=/BYTES/f6 {f-g}/[5.000000000,0=/ 3.000000000,0=/ 1.000000000,0=/] +iter_next_ignoring_time: "f"/6.000000000,0=/BYTES/f6 {f-g}/[3.000000000,0=/] (+{f-g}/[5.000000000,0=/ 3.000000000,0=/ 1.000000000,0=/]) iter_next: "f"/4.000000000,0=/BYTES/f4 {f-g}/[3.000000000,0=/] -iter_next_ignoring_time: "f"/2.000000000,0=/BYTES/f2 {f-g}/[5.000000000,0=/ 3.000000000,0=/ 1.000000000,0=/] +iter_next_ignoring_time: "f"/2.000000000,0=/BYTES/f2 {f-g}/[3.000000000,0=/] (+{f-g}/[5.000000000,0=/ 3.000000000,0=/ 1.000000000,0=/]) iter_next: {g-h}/[3.000000000,0=/] ! -iter_next_ignoring_time: "g"/4.000000000,0=/BYTES/g4 {g-h}/[3.000000000,0=/ 1.000000000,0=/] +iter_next_ignoring_time: "g"/4.000000000,0=/BYTES/g4 {g-h}/[3.000000000,0=/] (+{g-h}/[3.000000000,0=/ 1.000000000,0=/]) iter_next: "h"/4.000000000,0=/ ! -iter_next_ignoring_time: "h"/3.000000000,0=/BYTES/h3 {h-k}/[1.000000000,0=/] +iter_next_ignoring_time: "h"/3.000000000,0=/BYTES/h3 (+{h-k}/[1.000000000,0=/]) iter_next: {m-n}/[3.000000000,0={localTs=2.000000000,0}/] ! iter_next_ignoring_time: "m"/8.000000000,0=/BYTES/m8 {m-n}/[3.000000000,0={localTs=2.000000000,0}/] iter_next: . @@ -1183,7 +1183,7 @@ iter_seek_ge k=f iter_next_ignoring_time ---- iter_seek_ge: {f-g}/[5.000000000,0=/] ! -iter_next_ignoring_time: {g-h}/[3.000000000,0=/ 1.000000000,0=/] ! +iter_next_ignoring_time: (+{g-h}/[3.000000000,0=/ 1.000000000,0=/] !) ! # Test NextKeyIgnoringTime(). run ok @@ -1204,14 +1204,14 @@ iter_next_key_ignoring_time iter_next_key_ignoring_time ---- iter_seek_ge: "a"/4.000000000,0=/ -iter_next_key_ignoring_time: {b-c}/[3.000000000,0=/ 1.000000000,0=/] ! -iter_next_key_ignoring_time: {c-d}/[5.000000000,0=/ 3.000000000,0=/ 1.000000000,0=/] ! -iter_next_key_ignoring_time: "d"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=8.000000000,0 min=0,0 seq=0} ts=8.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true {d-f}/[5.000000000,0=/ 1.000000000,0=/] ! -iter_next_key_ignoring_time: "e"/3.000000000,0=/BYTES/e3 {d-f}/[5.000000000,0=/ 1.000000000,0=/] -iter_next_key_ignoring_time: {f-g}/[5.000000000,0=/ 3.000000000,0=/ 1.000000000,0=/] ! -iter_next_key_ignoring_time: {g-h}/[3.000000000,0=/ 1.000000000,0=/] ! -iter_next_key_ignoring_time: {h-k}/[1.000000000,0=/] ! -iter_next_key_ignoring_time: "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 {h-k}/[1.000000000,0=/] +iter_next_key_ignoring_time: {b-c}/[3.000000000,0=/] (+{b-c}/[3.000000000,0=/ 1.000000000,0=/] !) ! +iter_next_key_ignoring_time: {c-d}/[3.000000000,0=/] (+{c-d}/[5.000000000,0=/ 3.000000000,0=/ 1.000000000,0=/] !) ! +iter_next_key_ignoring_time: "d"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=8.000000000,0 min=0,0 seq=0} ts=8.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true (+{d-f}/[5.000000000,0=/ 1.000000000,0=/] !) ! +iter_next_key_ignoring_time: "e"/3.000000000,0=/BYTES/e3 (+{d-f}/[5.000000000,0=/ 1.000000000,0=/]) +iter_next_key_ignoring_time: {f-g}/[3.000000000,0=/] (+{f-g}/[5.000000000,0=/ 3.000000000,0=/ 1.000000000,0=/] !) ! +iter_next_key_ignoring_time: {g-h}/[3.000000000,0=/] (+{g-h}/[3.000000000,0=/ 1.000000000,0=/] !) ! +iter_next_key_ignoring_time: (+{h-k}/[1.000000000,0=/] !) ! +iter_next_key_ignoring_time: "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 (+{h-k}/[1.000000000,0=/]) iter_next_key_ignoring_time: "k"/5.000000000,0=/BYTES/k5 iter_next_key_ignoring_time: "l"/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_ignoring_time: "m"/0,0=txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=8.000000000,0 min=0,0 seq=0} ts=8.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true {m-n}/[3.000000000,0={localTs=2.000000000,0}/] ! @@ -1238,15 +1238,15 @@ iter_next ---- iter_seek_ge: "a"/4.000000000,0=/ iter_next: {b-c}/[3.000000000,0=/] ! -iter_next_key_ignoring_time: {c-d}/[5.000000000,0=/ 3.000000000,0=/ 1.000000000,0=/] ! +iter_next_key_ignoring_time: {c-d}/[3.000000000,0=/] (+{c-d}/[5.000000000,0=/ 3.000000000,0=/ 1.000000000,0=/] !) ! iter_next: "d"/4.000000000,0=/BYTES/d4 ! -iter_next_key_ignoring_time: "e"/3.000000000,0=/BYTES/e3 {d-f}/[5.000000000,0=/ 1.000000000,0=/] +iter_next_key_ignoring_time: "e"/3.000000000,0=/BYTES/e3 (+{d-f}/[5.000000000,0=/ 1.000000000,0=/]) iter_next: {f-g}/[3.000000000,0=/] ! -iter_next_key_ignoring_time: {g-h}/[3.000000000,0=/ 1.000000000,0=/] ! +iter_next_key_ignoring_time: {g-h}/[3.000000000,0=/] (+{g-h}/[3.000000000,0=/ 1.000000000,0=/] !) ! iter_next: "g"/4.000000000,0=/BYTES/g4 {g-h}/[3.000000000,0=/] -iter_next_key_ignoring_time: {h-k}/[1.000000000,0=/] ! +iter_next_key_ignoring_time: (+{h-k}/[1.000000000,0=/] !) ! iter_next: "h"/4.000000000,0=/ -iter_next_key_ignoring_time: "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 {h-k}/[1.000000000,0=/] +iter_next_key_ignoring_time: "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 (+{h-k}/[1.000000000,0=/]) iter_next: {m-n}/[3.000000000,0={localTs=2.000000000,0}/] ! iter_next_key_ignoring_time: "o"/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: . @@ -1258,7 +1258,7 @@ iter_seek_ge k=f iter_next_key_ignoring_time ---- iter_seek_ge: {f-g}/[5.000000000,0=/] ! -iter_next_key_ignoring_time: {g-h}/[3.000000000,0=/ 1.000000000,0=/] ! +iter_next_key_ignoring_time: (+{g-h}/[3.000000000,0=/ 1.000000000,0=/] !) ! # Try some scans, bounded and unbounded, with only point keys. run ok @@ -1647,7 +1647,7 @@ iter_next_ignoring_time iter_next_ignoring_time ---- iter_seek_ge: {f-g}/[5.000000000,0=/] ! -iter_next_ignoring_time: "f"/6.000000000,0=/BYTES/f6 {f-g}/[5.000000000,0=/ 3.000000000,0=/ 1.000000000,0=/] -iter_next_ignoring_time: {g-h}/[3.000000000,0=/ 1.000000000,0=/] ! -iter_next_ignoring_time: "g"/4.000000000,0=/BYTES/g4 {g-h}/[3.000000000,0=/ 1.000000000,0=/] -iter_next_ignoring_time: {h-k}/[1.000000000,0=/] +iter_next_ignoring_time: "f"/6.000000000,0=/BYTES/f6 {f-g}/[5.000000000,0=/] (+{f-g}/[5.000000000,0=/ 3.000000000,0=/ 1.000000000,0=/]) +iter_next_ignoring_time: (+{g-h}/[3.000000000,0=/ 1.000000000,0=/] !) ! +iter_next_ignoring_time: "g"/4.000000000,0=/BYTES/g4 (+{g-h}/[3.000000000,0=/ 1.000000000,0=/]) +iter_next_ignoring_time: (+{h-k}/[1.000000000,0=/] !)