-
Notifications
You must be signed in to change notification settings - Fork 2.3k
vtctl/workflow: stop reverse replication before Complete drops sources #20188
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
33916df
a5776df
091b5b8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -75,6 +75,12 @@ const ( | |
| // operation too. | ||
| shardTabletRefreshTimeout = time.Duration(30 * time.Second) | ||
|
|
||
| // reverseReplicationDrainTimeout bounds how long Complete waits for reverse | ||
| // streams to catch up to the target primary position before stopping them. | ||
| reverseReplicationDrainTimeout = 30 * time.Second | ||
|
Comment on lines
+78
to
+80
|
||
|
|
||
| stoppedForComplete = "stopped for complete" | ||
|
|
||
| // Use pt-osc's naming convention, this format also ensures vstreamer ignores such tables. | ||
| renameTableTemplate = "_%.59s_old" // limit table name to 64 characters | ||
|
|
||
|
|
@@ -1029,6 +1035,74 @@ func (ts *trafficSwitcher) buildTenantPredicate(ctx context.Context) (*sqlparser | |
| return tenantPredicate, nil | ||
| } | ||
|
|
||
| func (ts *trafficSwitcher) stopAndDrainReverseVReplication(ctx context.Context, waitTime time.Duration) error { | ||
| targetPositions := make(map[string]string) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think that we should do all of this work to "drain" the stream. The tables are normally deleted. In this case we are renaming them. But new data is ONLY in the keyspace where they were moved. We are holding onto locks and can potentially introduce a whole new set of edge cases and failure scenarios by doing this work and having it take a long time, time out, or error. I think that we should simply stop the workflow / delete the record for the reverse workflow. i.e. I think it's better to rename the function to just stop, and then only do that, stop it. |
||
| if err := ts.ForAllTargets(func(target *MigrationTarget) error { | ||
| pos, err := ts.TabletManagerClient().PrimaryPosition(ctx, target.GetPrimary().Tablet) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| targetPositions[target.GetShard().ShardName()] = pos | ||
| return nil | ||
| }); err != nil { | ||
| return err | ||
| } | ||
|
|
||
| ctx, cancel := context.WithTimeout(ctx, waitTime) | ||
| defer cancel() | ||
|
|
||
| return ts.ForAllSources(func(source *MigrationSource) error { | ||
| tabletAlias := topoproto.TabletAliasString(source.GetPrimary().GetAlias()) | ||
| res, err := ts.ws.tmc.ReadVReplicationWorkflow(ctx, source.GetPrimary().Tablet, &tabletmanagerdatapb.ReadVReplicationWorkflowRequest{ | ||
| Workflow: ts.ReverseWorkflowName(), | ||
| }) | ||
| if err != nil { | ||
| return vterrors.Wrapf(err, "reading reverse workflow %s on %s", ts.ReverseWorkflowName(), tabletAlias) | ||
| } | ||
| if res == nil || len(res.Streams) == 0 { | ||
| return nil | ||
| } | ||
| for _, stream := range res.Streams { | ||
| if stream.Bls == nil { | ||
| return vterrors.Errorf(vtrpcpb.Code_INTERNAL, "reverse vreplication stream %d on %s has no binlog source", | ||
| stream.Id, tabletAlias) | ||
| } | ||
| targetShard := stream.Bls.Shard | ||
| pos, ok := targetPositions[targetShard] | ||
| if !ok { | ||
| return vterrors.Errorf(vtrpcpb.Code_FAILED_PRECONDITION, | ||
| "reverse vreplication stream %d on %s reads from unknown target shard %s", | ||
| stream.Id, tabletAlias, targetShard) | ||
| } | ||
|
Comment on lines
+1066
to
+1076
|
||
| switch stream.State { | ||
| case binlogdatapb.VReplicationWorkflowState_Error: | ||
| return vterrors.Errorf(vtrpcpb.Code_FAILED_PRECONDITION, | ||
| "reverse vreplication stream %d on %s is in error state", stream.Id, tabletAlias) | ||
| case binlogdatapb.VReplicationWorkflowState_Copying: | ||
| return vterrors.Errorf(vtrpcpb.Code_FAILED_PRECONDITION, | ||
| "reverse vreplication stream %d on %s is still copying", stream.Id, tabletAlias) | ||
| case binlogdatapb.VReplicationWorkflowState_Stopped: | ||
| continue | ||
| case binlogdatapb.VReplicationWorkflowState_Running: | ||
| ts.Logger().Infof("Waiting for reverse stream %d on %s to catch up to target shard %s position %s", | ||
| stream.Id, tabletAlias, targetShard, pos) | ||
| if err := ts.TabletManagerClient().VReplicationWaitForPos(ctx, source.GetPrimary().Tablet, stream.Id, pos); err != nil { | ||
| return err | ||
| } | ||
|
Comment on lines
+1089
to
+1091
|
||
| default: | ||
| return vterrors.Errorf(vtrpcpb.Code_FAILED_PRECONDITION, | ||
| "reverse vreplication stream %d on %s is in state %s", stream.Id, tabletAlias, stream.State) | ||
| } | ||
| ts.Logger().Infof("Stopping reverse stream %d on %s for complete", stream.Id, tabletAlias) | ||
| if _, err := ts.TabletManagerClient().VReplicationExec(ctx, source.GetPrimary().Tablet, | ||
| binlogplayer.StopVReplication(stream.Id, stoppedForComplete)); err != nil { | ||
| return err | ||
| } | ||
| } | ||
|
Comment on lines
+1065
to
+1101
|
||
| return nil | ||
| }) | ||
| } | ||
|
|
||
| func (ts *trafficSwitcher) waitForCatchup(ctx context.Context, filteredReplicationWaitTime time.Duration) error { | ||
| ctx, cancel := context.WithTimeout(ctx, filteredReplicationWaitTime) | ||
| defer cancel() | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -548,21 +548,18 @@ func HashStreams(targetKeyspace string, targets map[string]*MigrationTarget) int | |
| } | ||
|
|
||
| func doValidateWorkflowHasCompleted(ctx context.Context, ts *trafficSwitcher) error { | ||
| wg := sync.WaitGroup{} | ||
| rec := concurrency.AllErrorRecorder{} | ||
| if ts.MigrationType() == binlogdatapb.MigrationType_SHARDS { | ||
| _ = ts.ForAllSources(func(source *MigrationSource) error { | ||
| wg.Add(1) | ||
| if err := ts.ForAllSources(func(source *MigrationSource) error { | ||
| if source.GetShard().IsPrimaryServing { | ||
| rec.RecordError(fmt.Errorf("shard %s is still serving", source.GetShard().ShardName())) | ||
| } | ||
| wg.Done() | ||
| return nil | ||
| }) | ||
| }); err != nil { | ||
| rec.RecordError(err) | ||
| } | ||
| } else { | ||
| _ = ts.ForAllTargets(func(target *MigrationTarget) error { | ||
| wg.Add(1) | ||
| defer wg.Done() | ||
| if err := ts.ForAllTargets(func(target *MigrationTarget) error { | ||
| res, err := ts.ws.tmc.ReadVReplicationWorkflow(ctx, target.GetPrimary().Tablet, &tabletmanagerdatapb.ReadVReplicationWorkflowRequest{ | ||
| Workflow: ts.WorkflowName(), | ||
| }) | ||
|
|
@@ -577,9 +574,13 @@ func doValidateWorkflowHasCompleted(ctx context.Context, ts *trafficSwitcher) er | |
| } | ||
| } | ||
| return nil | ||
| }) | ||
| }); err != nil { | ||
| rec.RecordError(err) | ||
| } | ||
| } | ||
| if err := validateReverseWorkflowForComplete(ctx, ts, &rec); err != nil { | ||
| rec.RecordError(err) | ||
| } | ||
| wg.Wait() | ||
|
|
||
| if !ts.keepRoutingRules { | ||
| // Check if table is routable. | ||
|
|
@@ -605,6 +606,38 @@ func doValidateWorkflowHasCompleted(ctx context.Context, ts *trafficSwitcher) er | |
| return nil | ||
| } | ||
|
|
||
| func validateReverseWorkflowForComplete(ctx context.Context, ts *trafficSwitcher, rec *concurrency.AllErrorRecorder) error { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need to validate it? Why can't we simply stop it / clean it up? |
||
| return ts.ForAllSources(func(source *MigrationSource) error { | ||
| tabletAlias := topoproto.TabletAliasString(source.GetPrimary().GetAlias()) | ||
| res, err := ts.ws.tmc.ReadVReplicationWorkflow(ctx, source.GetPrimary().Tablet, &tabletmanagerdatapb.ReadVReplicationWorkflowRequest{ | ||
| Workflow: ts.ReverseWorkflowName(), | ||
| }) | ||
| if err != nil { | ||
| rec.RecordError(vterrors.Wrapf(err, "reading reverse workflow %s on %s", ts.ReverseWorkflowName(), tabletAlias)) | ||
| return nil | ||
| } | ||
|
Comment on lines
+612
to
+618
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
| if res == nil || len(res.Streams) == 0 { | ||
| return nil | ||
| } | ||
| for _, stream := range res.Streams { | ||
| switch stream.State { | ||
| case binlogdatapb.VReplicationWorkflowState_Running, | ||
| binlogdatapb.VReplicationWorkflowState_Stopped: | ||
| case binlogdatapb.VReplicationWorkflowState_Error: | ||
| rec.RecordError(fmt.Errorf("reverse vreplication stream %d is in error state on %s", | ||
| stream.Id, tabletAlias)) | ||
| case binlogdatapb.VReplicationWorkflowState_Copying: | ||
| rec.RecordError(fmt.Errorf("reverse vreplication stream %d is still copying on %s", | ||
| stream.Id, tabletAlias)) | ||
| default: | ||
| rec.RecordError(fmt.Errorf("reverse vreplication stream %d is in state %s on %s", | ||
| stream.Id, stream.State, tabletAlias)) | ||
| } | ||
| } | ||
| return nil | ||
| }) | ||
| } | ||
|
|
||
| // ReverseWorkflowName returns the "reversed" name of a workflow. For a | ||
| // "forward" workflow, this is the workflow name with "_reverse" appended, and | ||
| // for a "reversed" workflow, this is the workflow name with the "_reverse" | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are these changes necessary? It's best to eliminate unnecessary changes if we can.