diff --git a/pkg/acceptance/adapter_test.go b/pkg/acceptance/adapter_test.go index de3722439b1e..32be85856f7d 100644 --- a/pkg/acceptance/adapter_test.go +++ b/pkg/acceptance/adapter_test.go @@ -39,7 +39,7 @@ func TestDockerC(t *testing.T) { } func TestDockerCSharp(t *testing.T) { - skip.WithIssue(t, 58218, "flaky test") + skip.WithIssue(t, 86852, "test not to up to date anymore") s := log.Scope(t) defer s.Close(t) diff --git a/pkg/acceptance/cluster/docker.go b/pkg/acceptance/cluster/docker.go index 33abb281c510..188f633a3e5f 100644 --- a/pkg/acceptance/cluster/docker.go +++ b/pkg/acceptance/cluster/docker.go @@ -287,9 +287,9 @@ func (c *Container) Restart(ctx context.Context, timeout *time.Duration) error { return nil } -// Wait waits for a running container to exit. -func (c *Container) Wait(ctx context.Context, condition container.WaitCondition) error { - waitOKBodyCh, errCh := c.cluster.client.ContainerWait(ctx, c.id, condition) +// WaitUntilNotRunning waits for a running container to exit. +func (c *Container) WaitUntilNotRunning(ctx context.Context) error { + waitOKBodyCh, errCh := c.cluster.client.ContainerWait(ctx, c.id, container.WaitConditionNotRunning) select { case err := <-errCh: return err diff --git a/pkg/acceptance/cluster/dockercluster.go b/pkg/acceptance/cluster/dockercluster.go index 9fb5bb22ac49..d88a3fa3757c 100644 --- a/pkg/acceptance/cluster/dockercluster.go +++ b/pkg/acceptance/cluster/dockercluster.go @@ -129,6 +129,7 @@ type DockerCluster struct { stopper *stop.Stopper monitorCtx context.Context monitorCtxCancelFunc func() + monitorDone chan struct{} clusterID string networkID string networkName string @@ -191,7 +192,7 @@ func CreateDocker( func (l *DockerCluster) expectEvent(c *Container, msgs ...string) { for index, ctr := range l.Nodes { - if c.id != ctr.id { + if ctr.Container == nil || c.id != ctr.id { continue } for _, status := range msgs { @@ -237,7 +238,7 @@ func (l *DockerCluster) OneShot( if err := l.oneshot.Start(ctx); err != nil { return err } - return l.oneshot.Wait(ctx, container.WaitConditionNotRunning) + return l.oneshot.WaitUntilNotRunning(ctx) } // stopOnPanic is invoked as a deferred function in Start in order to attempt @@ -374,7 +375,7 @@ func (l *DockerCluster) initCluster(ctx context.Context) { // and it'll get in the way of future runs. l.vols = c maybePanic(c.Start(ctx)) - maybePanic(c.Wait(ctx, container.WaitConditionNotRunning)) + maybePanic(c.WaitUntilNotRunning(ctx)) } // cockroachEntryPoint returns the value to be used as @@ -544,6 +545,7 @@ func (l *DockerCluster) processEvent(ctx context.Context, event events.Message) // If there's currently a oneshot container, ignore any die messages from // it because those are expected. if l.oneshot != nil && event.ID == l.oneshot.id && event.Status == eventDie { + log.Infof(ctx, "Docker event was: the oneshot container terminated") return true } @@ -585,7 +587,9 @@ func (l *DockerCluster) processEvent(ctx context.Context, event events.Message) return false } -func (l *DockerCluster) monitor(ctx context.Context) { +func (l *DockerCluster) monitor(ctx context.Context, monitorDone chan struct{}) { + defer close(monitorDone) + if log.V(1) { log.Infof(ctx, "events monitor starts") defer log.Infof(ctx, "events monitor exits") @@ -603,6 +607,9 @@ func (l *DockerCluster) monitor(ctx context.Context) { }) for { select { + case <-l.monitorCtx.Done(): + log.Infof(ctx, "monitor shutting down") + return false case err := <-errq: log.Infof(ctx, "event stream done, resetting...: %s", err) // Sometimes we get a random string-wrapped EOF error back. @@ -640,7 +647,8 @@ func (l *DockerCluster) Start(ctx context.Context) { log.Infof(ctx, "starting %d nodes", len(l.Nodes)) l.monitorCtx, l.monitorCtxCancelFunc = context.WithCancel(context.Background()) - go l.monitor(ctx) + l.monitorDone = make(chan struct{}) + go l.monitor(ctx, l.monitorDone) var wg sync.WaitGroup wg.Add(len(l.Nodes)) for _, node := range l.Nodes { @@ -661,7 +669,6 @@ func (l *DockerCluster) Start(ctx context.Context) { // the cluster (restart, kill, ...). In the event of a mismatch, the passed // Tester receives a fatal error. func (l *DockerCluster) Assert(ctx context.Context, t testing.TB) { - const almostZero = 50 * time.Millisecond filter := func(ch chan Event, wait time.Duration) *Event { select { case act := <-ch: @@ -673,17 +680,28 @@ func (l *DockerCluster) Assert(ctx context.Context, t testing.TB) { var events []Event for { + // The expected event channel is buffered and should contain + // all expected events already. + const almostZero = 15 * time.Millisecond exp := filter(l.expectedEvents, almostZero) if exp == nil { break } - act := filter(l.events, 15*time.Second) + t.Logf("expecting event: %v", exp) + // l.events is connected to the docker controller and may + // receive events more slowly. + const waitForDockerEvent = 15 * time.Second + act := filter(l.events, waitForDockerEvent) + t.Logf("got event: %v", act) if act == nil || *exp != *act { t.Fatalf("expected event %v, got %v (after %v)", exp, act, events) } events = append(events, *exp) } - if cur := filter(l.events, almostZero); cur != nil { + // At the end, we leave docker a bit more time to report a final event, + // if any. + const waitForLastDockerEvent = 1 * time.Second + if cur := filter(l.events, waitForLastDockerEvent); cur != nil { t.Fatalf("unexpected extra event %v (after %v)", cur, events) } if log.V(2) { @@ -713,13 +731,9 @@ func (l *DockerCluster) stop(ctx context.Context) { if l.monitorCtxCancelFunc != nil { l.monitorCtxCancelFunc() l.monitorCtxCancelFunc = nil + <-l.monitorDone } - if l.vols != nil { - maybePanic(l.vols.Kill(ctx)) - maybePanic(l.vols.Remove(ctx)) - l.vols = nil - } for i, n := range l.Nodes { if n.Container == nil { continue @@ -742,8 +756,14 @@ func (l *DockerCluster) stop(ctx context.Context) { log.Infof(ctx, "~~~ node %d CRASHED ~~~~", i) } maybePanic(n.Remove(ctx)) + n.Container = nil + } + + if l.vols != nil { + maybePanic(l.vols.Kill(ctx)) + maybePanic(l.vols.Remove(ctx)) + l.vols = nil } - l.Nodes = nil if l.networkID != "" { maybePanic( @@ -878,6 +898,7 @@ func (l *DockerCluster) Cleanup(ctx context.Context, preserveLogs bool) { } for _, v := range volumes { if preserveLogs && v.Name() == "logs" { + log.Infof(ctx, "preserving log directory: %s", l.volumesDir) continue } if err := os.RemoveAll(filepath.Join(l.volumesDir, v.Name())); err != nil { diff --git a/pkg/acceptance/util_docker.go b/pkg/acceptance/util_docker.go index 3bfac33a67c9..8104e2f59a14 100644 --- a/pkg/acceptance/util_docker.go +++ b/pkg/acceptance/util_docker.go @@ -22,6 +22,7 @@ import ( "github.com/cockroachdb/cockroach/pkg/base" "github.com/cockroachdb/cockroach/pkg/build/bazel" "github.com/cockroachdb/cockroach/pkg/security/username" + "github.com/cockroachdb/cockroach/pkg/util/log" "github.com/containerd/containerd/platforms" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" @@ -69,19 +70,6 @@ func testDocker( ) error { var err error RunDocker(t, func(t *testing.T) { - cfg := cluster.TestConfig{ - Name: name, - Duration: *flagDuration, - } - for i := 0; i < num; i++ { - cfg.Nodes = append(cfg.Nodes, cluster.NodeConfig{Stores: []cluster.StoreConfig{{}}}) - } - l := StartCluster(ctx, t, cfg).(*cluster.DockerCluster) - defer l.AssertAndStop(ctx, t) - - if len(l.Nodes) > 0 { - containerConfig.Env = append(containerConfig.Env, "PGHOST="+l.Hostname(0)) - } var pwd string pwd, err = os.Getwd() if err != nil { @@ -121,12 +109,46 @@ func testDocker( }() hostConfig.Binds = append(hostConfig.Binds, interactivetestsDir+":/mnt/interactive_tests") } + + // Prepare the docker cluster. + // We need to do this "under" the directory preparation above so as + // to prevent the test from crashing because the directory gets + // deleted before the container shutdown assertions get a chance to run. + cfg := cluster.TestConfig{ + Name: name, + Duration: *flagDuration, + } + for i := 0; i < num; i++ { + cfg.Nodes = append(cfg.Nodes, cluster.NodeConfig{Stores: []cluster.StoreConfig{{}}}) + } + l := StartCluster(ctx, t, cfg).(*cluster.DockerCluster) + + var preserveLogs bool + defer func() { + // Check the final health of the cluster nodes and + // stop the cluster after that. + l.AssertAndStop(ctx, t) + + // Note: we must be careful to clean up the volumes *after* + // the cluster has been shut down (in the `AssertAndStop` call). + // Otherwise, the directory removal will cause the cluster nodes + // to crash and report abnormal termination, even when the test + // succeeds otherwise. + log.Infof(ctx, "cleaning up docker volume") + l.Cleanup(ctx, preserveLogs) + }() + + if len(l.Nodes) > 0 { + containerConfig.Env = append(containerConfig.Env, "PGHOST="+l.Hostname(0)) + } + + log.Infof(ctx, "starting one-shot container") err = l.OneShot( ctx, acceptanceImage, types.ImagePullOptions{}, containerConfig, hostConfig, platforms.DefaultSpec(), "docker-"+name, ) - preserveLogs := err != nil - l.Cleanup(ctx, preserveLogs) + log.Infof(ctx, "one-shot container terminated: %v", err) + preserveLogs = err != nil }) return err } diff --git a/pkg/sql/sqlstats/insights/insights.proto b/pkg/sql/sqlstats/insights/insights.proto index 31c4a2fac5f5..4b053493b94a 100644 --- a/pkg/sql/sqlstats/insights/insights.proto +++ b/pkg/sql/sqlstats/insights/insights.proto @@ -30,7 +30,7 @@ enum Problem { SuboptimalPlan = 2; // This statement was slow because of contention. - HighWaitTime = 3; + HighContentionTime = 3; // This statement was slow because of being retried multiple times, again due // to contention. The "high" threshold may be configured by the diff --git a/pkg/sql/sqlstats/insights/problems.go b/pkg/sql/sqlstats/insights/problems.go index 5a03a641dbaf..6447697bd41e 100644 --- a/pkg/sql/sqlstats/insights/problems.go +++ b/pkg/sql/sqlstats/insights/problems.go @@ -21,6 +21,10 @@ func (p *problems) examine(stmt *Statement) (result []Problem) { result = append(result, Problem_SuboptimalPlan) } + if stmt.Contention != nil && *stmt.Contention >= LatencyThreshold.Get(&p.st.SV) { + result = append(result, Problem_HighContentionTime) + } + if stmt.Retries >= HighRetryCountThreshold.Get(&p.st.SV) { result = append(result, Problem_HighRetryCount) } diff --git a/pkg/sql/sqlstats/insights/problems_test.go b/pkg/sql/sqlstats/insights/problems_test.go index 7211493a17f4..47d1f77208f3 100644 --- a/pkg/sql/sqlstats/insights/problems_test.go +++ b/pkg/sql/sqlstats/insights/problems_test.go @@ -13,6 +13,7 @@ package insights import ( "context" "testing" + "time" "github.com/cockroachdb/cockroach/pkg/settings/cluster" "github.com/stretchr/testify/require" @@ -22,8 +23,11 @@ func TestProblems(t *testing.T) { ctx := context.Background() st := cluster.MakeTestingClusterSettings() p := &problems{st: st} + LatencyThreshold.Override(ctx, &st.SV, 100*time.Millisecond) HighRetryCountThreshold.Override(ctx, &st.SV, 10) + var latencyThreshold = LatencyThreshold.Get(&st.SV) + testCases := []struct { name string statement *Statement @@ -39,6 +43,11 @@ func TestProblems(t *testing.T) { statement: &Statement{IndexRecommendations: []string{"THIS IS AN INDEX RECOMMENDATION"}}, problems: []Problem{Problem_SuboptimalPlan}, }, + { + name: "high contention time", + statement: &Statement{Contention: &latencyThreshold}, + problems: []Problem{Problem_HighContentionTime}, + }, { name: "high retry count", statement: &Statement{Retries: 10}, diff --git a/pkg/sql/ttl/ttljob/ttljob_test.go b/pkg/sql/ttl/ttljob/ttljob_test.go index d4e1de778bff..cdc9fdd81878 100644 --- a/pkg/sql/ttl/ttljob/ttljob_test.go +++ b/pkg/sql/ttl/ttljob/ttljob_test.go @@ -420,7 +420,6 @@ func TestRowLevelTTLJobMultipleNodes(t *testing.T) { tableName, ) const rowsPerRange = 10 - const expiredRowsPerRange = rowsPerRange / 2 splitPoints := make([]serverutils.SplitPoint, len(splitAts)) for i, splitAt := range splitAts { newLeaseHolderIdx := (leaseHolderIdx + 1 + i) % numNodes diff --git a/pkg/storage/testdata/mvcc_histories/range_tombstone_gets_complex b/pkg/storage/testdata/mvcc_histories/range_tombstone_gets_complex new file mode 100644 index 000000000000..274f6e6e9608 --- /dev/null +++ b/pkg/storage/testdata/mvcc_histories/range_tombstone_gets_complex @@ -0,0 +1,276 @@ +# Tests MVCC gets across range tombstones, with a more complex dataset. +# +# Sets up the following dataset, where x is tombstone, o-o is range tombstone, [] is intent. +# +# T +# 7 [d7] [f7] [j7] +# 6 f6 +# 5 o-------------------o k5 o-----------o +# 4 x x d4 f4 g4 +# 3 o-------o e3 o-------oh3 o---o +# 2 a2 d2 f2 g2 +# 1 o-------------------o o-----------o +# a b c d e f g h i j k l m n o p +# +run ok +put_rangekey k=a end=f ts=1 +put_rangekey k=h end=k ts=1 +put_rangekey k=b end=d ts=3 +put_rangekey k=n end=o ts=3 +put_rangekey k=l end=o ts=5 +put k=a ts=2 v=a2 +del k=a ts=4 +del k=b ts=4 +put k=d ts=2 v=d2 +put k=d ts=4 v=d4 +put k=e ts=3 v=e3 +put k=f ts=2 v=f2 +put k=g ts=2 v=g2 +put_rangekey k=f end=h ts=3 localTs=4 +put k=f ts=4 v=f4 +put k=f ts=6 v=f6 +put k=g ts=4 v=g4 +put_rangekey k=c end=h ts=5 +put k=h ts=3 v=h3 +put k=k ts=5 v=k5 +with t=A + txn_begin ts=7 + put k=d v=d7 + put k=f v=f7 + put k=j v=j7 +---- +del: "a": found key true +del: "b": found key false +>> at end: +txn: "A" meta={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} lock=true stat=PENDING rts=7.000000000,0 wto=false gul=0,0 +rangekey: {a-b}/[1.000000000,0=/] +rangekey: {b-c}/[3.000000000,0=/ 1.000000000,0=/] +rangekey: {c-d}/[5.000000000,0=/ 3.000000000,0=/ 1.000000000,0=/] +rangekey: {d-f}/[5.000000000,0=/ 1.000000000,0=/] +rangekey: {f-h}/[5.000000000,0=/ 3.000000000,0={localTs=4.000000000,0}/] +rangekey: {h-k}/[1.000000000,0=/] +rangekey: {l-n}/[5.000000000,0=/] +rangekey: {n-o}/[5.000000000,0=/ 3.000000000,0=/] +data: "a"/4.000000000,0 -> / +data: "a"/2.000000000,0 -> /BYTES/a2 +data: "b"/4.000000000,0 -> / +meta: "d"/0,0 -> txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true +data: "d"/7.000000000,0 -> /BYTES/d7 +data: "d"/4.000000000,0 -> /BYTES/d4 +data: "d"/2.000000000,0 -> /BYTES/d2 +data: "e"/3.000000000,0 -> /BYTES/e3 +meta: "f"/0,0 -> txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true +data: "f"/7.000000000,0 -> /BYTES/f7 +data: "f"/6.000000000,0 -> /BYTES/f6 +data: "f"/4.000000000,0 -> /BYTES/f4 +data: "f"/2.000000000,0 -> /BYTES/f2 +data: "g"/4.000000000,0 -> /BYTES/g4 +data: "g"/2.000000000,0 -> /BYTES/g2 +data: "h"/3.000000000,0 -> /BYTES/h3 +meta: "j"/0,0 -> txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true +data: "j"/7.000000000,0 -> /BYTES/j7 +data: "k"/5.000000000,0 -> /BYTES/k5 + +# Run gets for all keys at all timestamps, with tombstones and intents. +run ok +get k=a ts=1 tombstones inconsistent +get k=a ts=2 tombstones inconsistent +get k=a ts=3 tombstones inconsistent +get k=a ts=4 tombstones inconsistent +get k=a ts=5 tombstones inconsistent +---- +get: "a" -> / @1.000000000,0 +get: "a" -> /BYTES/a2 @2.000000000,0 +get: "a" -> /BYTES/a2 @2.000000000,0 +get: "a" -> / @4.000000000,0 +get: "a" -> / @4.000000000,0 + +run ok +get k=b ts=1 tombstones inconsistent +get k=b ts=2 tombstones inconsistent +get k=b ts=3 tombstones inconsistent +get k=b ts=4 tombstones inconsistent +get k=b ts=5 tombstones inconsistent +---- +get: "b" -> / @1.000000000,0 +get: "b" -> / @1.000000000,0 +get: "b" -> / @3.000000000,0 +get: "b" -> / @4.000000000,0 +get: "b" -> / @4.000000000,0 + +run ok +get k=c ts=1 tombstones inconsistent +get k=c ts=2 tombstones inconsistent +get k=c ts=3 tombstones inconsistent +get k=c ts=4 tombstones inconsistent +get k=c ts=5 tombstones inconsistent +get k=c ts=6 tombstones inconsistent +---- +get: "c" -> / @1.000000000,0 +get: "c" -> / @1.000000000,0 +get: "c" -> / @3.000000000,0 +get: "c" -> / @3.000000000,0 +get: "c" -> / @5.000000000,0 +get: "c" -> / @5.000000000,0 + +run ok +get k=d ts=1 tombstones inconsistent +get k=d ts=2 tombstones inconsistent +get k=d ts=3 tombstones inconsistent +get k=d ts=4 tombstones inconsistent +get k=d ts=5 tombstones inconsistent +get k=d ts=6 tombstones inconsistent +get k=d ts=7 tombstones inconsistent +get k=d ts=8 tombstones inconsistent +---- +get: "d" -> / @1.000000000,0 +get: "d" -> /BYTES/d2 @2.000000000,0 +get: "d" -> /BYTES/d2 @2.000000000,0 +get: "d" -> /BYTES/d4 @4.000000000,0 +get: "d" -> / @5.000000000,0 +get: "d" -> / @5.000000000,0 +get: "d" -> intent {id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} +get: "d" -> / @5.000000000,0 +get: "d" -> intent {id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} +get: "d" -> / @5.000000000,0 + +run ok +get k=e ts=1 tombstones inconsistent +get k=e ts=2 tombstones 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" -> / @1.000000000,0 +get: "e" -> / @1.000000000,0 +get: "e" -> /BYTES/e3 @3.000000000,0 +get: "e" -> /BYTES/e3 @3.000000000,0 +get: "e" -> / @5.000000000,0 +get: "e" -> / @5.000000000,0 + +run ok +get k=f ts=1 tombstones inconsistent +get k=f ts=2 tombstones inconsistent +get k=f ts=3 tombstones inconsistent +get k=f ts=4 tombstones inconsistent +get k=f ts=5 tombstones inconsistent +get k=f ts=6 tombstones inconsistent +get k=f ts=7 tombstones inconsistent +get k=f ts=8 tombstones inconsistent +---- +get: "f" -> +get: "f" -> /BYTES/f2 @2.000000000,0 +get: "f" -> / @3.000000000,0 +get: "f" -> /BYTES/f4 @4.000000000,0 +get: "f" -> / @5.000000000,0 +get: "f" -> /BYTES/f6 @6.000000000,0 +get: "f" -> intent {id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} +get: "f" -> /BYTES/f6 @6.000000000,0 +get: "f" -> intent {id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} +get: "f" -> /BYTES/f6 @6.000000000,0 + +run ok +get k=g ts=1 tombstones inconsistent +get k=g ts=2 tombstones inconsistent +get k=g ts=3 tombstones inconsistent +get k=g ts=4 tombstones inconsistent +get k=g ts=5 tombstones inconsistent +get k=g ts=6 tombstones inconsistent +---- +get: "g" -> +get: "g" -> /BYTES/g2 @2.000000000,0 +get: "g" -> / @3.000000000,0 +get: "g" -> /BYTES/g4 @4.000000000,0 +get: "g" -> / @5.000000000,0 +get: "g" -> / @5.000000000,0 + +run ok +get k=h ts=1 tombstones inconsistent +get k=h ts=2 tombstones inconsistent +get k=h ts=3 tombstones inconsistent +get k=h ts=4 tombstones inconsistent +get k=h ts=5 tombstones inconsistent +get k=h ts=6 tombstones inconsistent +---- +get: "h" -> / @1.000000000,0 +get: "h" -> / @1.000000000,0 +get: "h" -> /BYTES/h3 @3.000000000,0 +get: "h" -> /BYTES/h3 @3.000000000,0 +get: "h" -> /BYTES/h3 @3.000000000,0 +get: "h" -> /BYTES/h3 @3.000000000,0 + +run ok +get k=i ts=1 tombstones inconsistent +get k=i ts=2 tombstones inconsistent +---- +get: "i" -> / @1.000000000,0 +get: "i" -> / @1.000000000,0 + +run ok +get k=j ts=1 tombstones inconsistent +get k=j ts=2 tombstones inconsistent +get k=j ts=6 tombstones inconsistent +get k=j ts=7 tombstones inconsistent +get k=j ts=8 tombstones inconsistent +---- +get: "j" -> / @1.000000000,0 +get: "j" -> / @1.000000000,0 +get: "j" -> / @1.000000000,0 +get: "j" -> intent {id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} +get: "j" -> / @1.000000000,0 +get: "j" -> intent {id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} +get: "j" -> / @1.000000000,0 + +run ok +get k=k ts=4 tombstones inconsistent +get k=k ts=5 tombstones inconsistent +get k=k ts=6 tombstones inconsistent +---- +get: "k" -> +get: "k" -> /BYTES/k5 @5.000000000,0 +get: "k" -> /BYTES/k5 @5.000000000,0 + +run ok +get k=l ts=4 tombstones inconsistent +get k=l ts=5 tombstones inconsistent +get k=l ts=6 tombstones inconsistent +---- +get: "l" -> +get: "l" -> / @5.000000000,0 +get: "l" -> / @5.000000000,0 + +run ok +get k=m ts=4 tombstones inconsistent +get k=m ts=5 tombstones inconsistent +get k=m ts=6 tombstones inconsistent +---- +get: "m" -> +get: "m" -> / @5.000000000,0 +get: "m" -> / @5.000000000,0 + +run ok +get k=n ts=2 tombstones inconsistent +get k=n ts=3 tombstones inconsistent +get k=n ts=4 tombstones inconsistent +get k=n ts=5 tombstones inconsistent +get k=n ts=6 tombstones inconsistent +---- +get: "n" -> +get: "n" -> / @3.000000000,0 +get: "n" -> / @3.000000000,0 +get: "n" -> / @5.000000000,0 +get: "n" -> / @5.000000000,0 + +run ok +get k=o ts=2 tombstones inconsistent +get k=o ts=3 tombstones inconsistent +get k=o ts=4 tombstones inconsistent +get k=o ts=5 tombstones inconsistent +get k=o ts=6 tombstones inconsistent +---- +get: "o" -> +get: "o" -> +get: "o" -> +get: "o" -> +get: "o" -> diff --git a/pkg/storage/testdata/mvcc_histories/range_tombstone_scans_complex b/pkg/storage/testdata/mvcc_histories/range_tombstone_scans_complex new file mode 100644 index 000000000000..125b7b029765 --- /dev/null +++ b/pkg/storage/testdata/mvcc_histories/range_tombstone_scans_complex @@ -0,0 +1,367 @@ +# Tests MVCC scans across range tombstones, with a more complex dataset. +# +# Sets up the following dataset, where x is tombstone, o-o is range tombstone, [] is intent. +# +# T +# 7 [d7] [f7] [j7] +# 6 f6 +# 5 o-------------------o k5 o-----------o +# 4 x x d4 f4 g4 +# 3 o-------o e3 o-------oh3 o---o +# 2 a2 d2 f2 g2 +# 1 o-------------------o o-----------o +# a b c d e f g h i j k l m n o p +# +run ok +put_rangekey k=a end=f ts=1 +put_rangekey k=h end=k ts=1 +put_rangekey k=b end=d ts=3 +put_rangekey k=n end=o ts=3 +put_rangekey k=l end=o ts=5 +put k=a ts=2 v=a2 +del k=a ts=4 +del k=b ts=4 +put k=d ts=2 v=d2 +put k=d ts=4 v=d4 +put k=e ts=3 v=e3 +put k=f ts=2 v=f2 +put k=g ts=2 v=g2 +put_rangekey k=f end=h ts=3 localTs=4 +put k=f ts=4 v=f4 +put k=f ts=6 v=f6 +put k=g ts=4 v=g4 +put_rangekey k=c end=h ts=5 +put k=h ts=3 v=h3 +put k=k ts=5 v=k5 +with t=A + txn_begin ts=7 + put k=d v=d7 + put k=f v=f7 + put k=j v=j7 +---- +del: "a": found key true +del: "b": found key false +>> at end: +txn: "A" meta={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} lock=true stat=PENDING rts=7.000000000,0 wto=false gul=0,0 +rangekey: {a-b}/[1.000000000,0=/] +rangekey: {b-c}/[3.000000000,0=/ 1.000000000,0=/] +rangekey: {c-d}/[5.000000000,0=/ 3.000000000,0=/ 1.000000000,0=/] +rangekey: {d-f}/[5.000000000,0=/ 1.000000000,0=/] +rangekey: {f-h}/[5.000000000,0=/ 3.000000000,0={localTs=4.000000000,0}/] +rangekey: {h-k}/[1.000000000,0=/] +rangekey: {l-n}/[5.000000000,0=/] +rangekey: {n-o}/[5.000000000,0=/ 3.000000000,0=/] +data: "a"/4.000000000,0 -> / +data: "a"/2.000000000,0 -> /BYTES/a2 +data: "b"/4.000000000,0 -> / +meta: "d"/0,0 -> txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true +data: "d"/7.000000000,0 -> /BYTES/d7 +data: "d"/4.000000000,0 -> /BYTES/d4 +data: "d"/2.000000000,0 -> /BYTES/d2 +data: "e"/3.000000000,0 -> /BYTES/e3 +meta: "f"/0,0 -> txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true +data: "f"/7.000000000,0 -> /BYTES/f7 +data: "f"/6.000000000,0 -> /BYTES/f6 +data: "f"/4.000000000,0 -> /BYTES/f4 +data: "f"/2.000000000,0 -> /BYTES/f2 +data: "g"/4.000000000,0 -> /BYTES/g4 +data: "g"/2.000000000,0 -> /BYTES/g2 +data: "h"/3.000000000,0 -> /BYTES/h3 +meta: "j"/0,0 -> txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} ts=7.000000000,0 del=false klen=12 vlen=7 mergeTs= txnDidNotUpdateMeta=true +data: "j"/7.000000000,0 -> /BYTES/j7 +data: "k"/5.000000000,0 -> /BYTES/k5 + +# Forward scans at all timestamps. +run ok +scan k=a end=z ts=1 +---- +scan: "a"-"z" -> + +run ok +scan k=a end=z ts=2 +---- +scan: "a" -> /BYTES/a2 @2.000000000,0 +scan: "d" -> /BYTES/d2 @2.000000000,0 +scan: "f" -> /BYTES/f2 @2.000000000,0 +scan: "g" -> /BYTES/g2 @2.000000000,0 + +run ok +scan k=a end=z ts=3 +---- +scan: "a" -> /BYTES/a2 @2.000000000,0 +scan: "d" -> /BYTES/d2 @2.000000000,0 +scan: "e" -> /BYTES/e3 @3.000000000,0 +scan: "h" -> /BYTES/h3 @3.000000000,0 + +run ok +scan k=a end=z ts=4 +---- +scan: "d" -> /BYTES/d4 @4.000000000,0 +scan: "e" -> /BYTES/e3 @3.000000000,0 +scan: "f" -> /BYTES/f4 @4.000000000,0 +scan: "g" -> /BYTES/g4 @4.000000000,0 +scan: "h" -> /BYTES/h3 @3.000000000,0 + +run ok +scan k=a end=z ts=5 +---- +scan: "h" -> /BYTES/h3 @3.000000000,0 +scan: "k" -> /BYTES/k5 @5.000000000,0 + +run ok +scan k=a end=z ts=6 +---- +scan: "f" -> /BYTES/f6 @6.000000000,0 +scan: "h" -> /BYTES/h3 @3.000000000,0 +scan: "k" -> /BYTES/k5 @5.000000000,0 + +# Forward scans at all timestamps with tombstones and intents. +run ok +scan k=a end=z ts=1 tombstones inconsistent +---- +scan: "a" -> / @1.000000000,0 +scan: "b" -> / @1.000000000,0 +scan: "c" -> / @1.000000000,0 +scan: "d" -> / @1.000000000,0 +scan: "h" -> / @1.000000000,0 + +run ok +scan k=a end=z ts=2 tombstones inconsistent +---- +scan: "a" -> /BYTES/a2 @2.000000000,0 +scan: "b" -> / @1.000000000,0 +scan: "c" -> / @1.000000000,0 +scan: "d" -> /BYTES/d2 @2.000000000,0 +scan: "f" -> /BYTES/f2 @2.000000000,0 +scan: "g" -> /BYTES/g2 @2.000000000,0 +scan: "h" -> / @1.000000000,0 + +run ok +scan k=a end=z ts=3 tombstones inconsistent +---- +scan: "a" -> /BYTES/a2 @2.000000000,0 +scan: "b" -> / @3.000000000,0 +scan: "c" -> / @3.000000000,0 +scan: "d" -> /BYTES/d2 @2.000000000,0 +scan: "e" -> /BYTES/e3 @3.000000000,0 +scan: "f" -> / @3.000000000,0 +scan: "g" -> / @3.000000000,0 +scan: "h" -> /BYTES/h3 @3.000000000,0 +scan: "n" -> / @3.000000000,0 + +run ok +scan k=a end=z ts=4 tombstones inconsistent +---- +scan: "a" -> / @4.000000000,0 +scan: "b" -> / @4.000000000,0 +scan: "c" -> / @3.000000000,0 +scan: "d" -> /BYTES/d4 @4.000000000,0 +scan: "e" -> /BYTES/e3 @3.000000000,0 +scan: "f" -> /BYTES/f4 @4.000000000,0 +scan: "g" -> /BYTES/g4 @4.000000000,0 +scan: "h" -> /BYTES/h3 @3.000000000,0 +scan: "n" -> / @3.000000000,0 + +run ok +scan k=a end=z ts=5 tombstones inconsistent +---- +scan: "a" -> / @4.000000000,0 +scan: "b" -> / @4.000000000,0 +scan: "c" -> / @5.000000000,0 +scan: "d" -> / @5.000000000,0 +scan: "e" -> / @5.000000000,0 +scan: "f" -> / @5.000000000,0 +scan: "g" -> / @5.000000000,0 +scan: "h" -> /BYTES/h3 @3.000000000,0 +scan: "k" -> /BYTES/k5 @5.000000000,0 +scan: "l" -> / @5.000000000,0 +scan: "n" -> / @5.000000000,0 + +run ok +scan k=a end=z ts=6 tombstones inconsistent +---- +scan: "a" -> / @4.000000000,0 +scan: "b" -> / @4.000000000,0 +scan: "c" -> / @5.000000000,0 +scan: "d" -> / @5.000000000,0 +scan: "e" -> / @5.000000000,0 +scan: "f" -> /BYTES/f6 @6.000000000,0 +scan: "g" -> / @5.000000000,0 +scan: "h" -> /BYTES/h3 @3.000000000,0 +scan: "k" -> /BYTES/k5 @5.000000000,0 +scan: "l" -> / @5.000000000,0 +scan: "n" -> / @5.000000000,0 + +run ok +scan k=a end=z ts=7 tombstones inconsistent +---- +scan: intent "d" {id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} +scan: intent "f" {id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} +scan: intent "j" {id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} +scan: "a" -> / @4.000000000,0 +scan: "b" -> / @4.000000000,0 +scan: "c" -> / @5.000000000,0 +scan: "d" -> / @5.000000000,0 +scan: "e" -> / @5.000000000,0 +scan: "f" -> /BYTES/f6 @6.000000000,0 +scan: "g" -> / @5.000000000,0 +scan: "h" -> /BYTES/h3 @3.000000000,0 +scan: "k" -> /BYTES/k5 @5.000000000,0 +scan: "l" -> / @5.000000000,0 +scan: "n" -> / @5.000000000,0 + +# Reverse scans at all timestamps. +run ok +scan k=a end=z ts=1 reverse +---- +scan: "a"-"z" -> + +run ok +scan k=a end=z ts=2 reverse +---- +scan: "g" -> /BYTES/g2 @2.000000000,0 +scan: "f" -> /BYTES/f2 @2.000000000,0 +scan: "d" -> /BYTES/d2 @2.000000000,0 +scan: "a" -> /BYTES/a2 @2.000000000,0 + +run ok +scan k=a end=z ts=3 reverse +---- +scan: "h" -> /BYTES/h3 @3.000000000,0 +scan: "e" -> /BYTES/e3 @3.000000000,0 +scan: "d" -> /BYTES/d2 @2.000000000,0 +scan: "a" -> /BYTES/a2 @2.000000000,0 + +run ok +scan k=a end=z ts=4 reverse +---- +scan: "h" -> /BYTES/h3 @3.000000000,0 +scan: "g" -> /BYTES/g4 @4.000000000,0 +scan: "f" -> /BYTES/f4 @4.000000000,0 +scan: "e" -> /BYTES/e3 @3.000000000,0 +scan: "d" -> /BYTES/d4 @4.000000000,0 + +run ok +scan k=a end=z ts=5 reverse +---- +scan: "k" -> /BYTES/k5 @5.000000000,0 +scan: "h" -> /BYTES/h3 @3.000000000,0 + +run ok +scan k=a end=z ts=6 reverse +---- +scan: "k" -> /BYTES/k5 @5.000000000,0 +scan: "h" -> /BYTES/h3 @3.000000000,0 +scan: "f" -> /BYTES/f6 @6.000000000,0 + +# Reverse scans at all timestamps with tombstones and intents. +run ok +scan k=a end=z ts=1 reverse tombstones inconsistent +---- +scan: "h" -> / @1.000000000,0 +scan: "d" -> / @1.000000000,0 +scan: "c" -> / @1.000000000,0 +scan: "b" -> / @1.000000000,0 +scan: "a" -> / @1.000000000,0 + +run ok +scan k=a end=z ts=2 reverse tombstones inconsistent +---- +scan: "h" -> / @1.000000000,0 +scan: "g" -> /BYTES/g2 @2.000000000,0 +scan: "f" -> /BYTES/f2 @2.000000000,0 +scan: "d" -> /BYTES/d2 @2.000000000,0 +scan: "c" -> / @1.000000000,0 +scan: "b" -> / @1.000000000,0 +scan: "a" -> /BYTES/a2 @2.000000000,0 + +run ok +scan k=a end=z ts=3 reverse tombstones inconsistent +---- +scan: "n" -> / @3.000000000,0 +scan: "h" -> /BYTES/h3 @3.000000000,0 +scan: "g" -> / @3.000000000,0 +scan: "f" -> / @3.000000000,0 +scan: "e" -> /BYTES/e3 @3.000000000,0 +scan: "d" -> /BYTES/d2 @2.000000000,0 +scan: "c" -> / @3.000000000,0 +scan: "b" -> / @3.000000000,0 +scan: "a" -> /BYTES/a2 @2.000000000,0 + +run ok +scan k=a end=z ts=4 reverse tombstones inconsistent +---- +scan: "n" -> / @3.000000000,0 +scan: "h" -> /BYTES/h3 @3.000000000,0 +scan: "g" -> /BYTES/g4 @4.000000000,0 +scan: "f" -> /BYTES/f4 @4.000000000,0 +scan: "e" -> /BYTES/e3 @3.000000000,0 +scan: "d" -> /BYTES/d4 @4.000000000,0 +scan: "c" -> / @3.000000000,0 +scan: "b" -> / @4.000000000,0 +scan: "a" -> / @4.000000000,0 + +run ok +scan k=a end=z ts=5 reverse tombstones inconsistent +---- +scan: "n" -> / @5.000000000,0 +scan: "l" -> / @5.000000000,0 +scan: "k" -> /BYTES/k5 @5.000000000,0 +scan: "h" -> /BYTES/h3 @3.000000000,0 +scan: "g" -> / @5.000000000,0 +scan: "f" -> / @5.000000000,0 +scan: "e" -> / @5.000000000,0 +scan: "d" -> / @5.000000000,0 +scan: "c" -> / @5.000000000,0 +scan: "b" -> / @4.000000000,0 +scan: "a" -> / @4.000000000,0 + +run ok +scan k=a end=z ts=6 reverse tombstones inconsistent +---- +scan: "n" -> / @5.000000000,0 +scan: "l" -> / @5.000000000,0 +scan: "k" -> /BYTES/k5 @5.000000000,0 +scan: "h" -> /BYTES/h3 @3.000000000,0 +scan: "g" -> / @5.000000000,0 +scan: "f" -> /BYTES/f6 @6.000000000,0 +scan: "e" -> / @5.000000000,0 +scan: "d" -> / @5.000000000,0 +scan: "c" -> / @5.000000000,0 +scan: "b" -> / @4.000000000,0 +scan: "a" -> / @4.000000000,0 + +run ok +scan k=a end=z ts=7 reverse tombstones inconsistent +---- +scan: intent "j" {id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} +scan: intent "f" {id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} +scan: intent "d" {id=00000000 key=/Min pri=0.00000000 epo=0 ts=7.000000000,0 min=0,0 seq=0} +scan: "n" -> / @5.000000000,0 +scan: "l" -> / @5.000000000,0 +scan: "k" -> /BYTES/k5 @5.000000000,0 +scan: "h" -> /BYTES/h3 @3.000000000,0 +scan: "g" -> / @5.000000000,0 +scan: "f" -> /BYTES/f6 @6.000000000,0 +scan: "e" -> / @5.000000000,0 +scan: "d" -> / @5.000000000,0 +scan: "c" -> / @5.000000000,0 +scan: "b" -> / @4.000000000,0 +scan: "a" -> / @4.000000000,0 + +# Start scan at bare point key. +run ok +scan k=k end=z ts=7 tombstones inconsistent +---- +scan: "k" -> /BYTES/k5 @5.000000000,0 +scan: "l" -> / @5.000000000,0 +scan: "n" -> / @5.000000000,0 + +# Start and end scan in the middle of a bare range key. +run ok +scan k=ddd end=ggg ts=4 tombstones inconsistent +---- +scan: "ddd" -> / @1.000000000,0 +scan: "e" -> /BYTES/e3 @3.000000000,0 +scan: "f" -> /BYTES/f4 @4.000000000,0 +scan: "g" -> /BYTES/g4 @4.000000000,0