-
Notifications
You must be signed in to change notification settings - Fork 2.3k
mysqlctl: add CloneFromDonor
#19064
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
mysqlctl: add CloneFromDonor
#19064
Changes from 25 commits
5458bcd
0fe8efd
8941459
94624dc
0342d74
95dccd1
ba66344
e66d1ee
0d57e26
06a370b
88340e5
01fabc0
ca83a7a
2cdb9c4
66e4998
8633eaf
13ace33
43a534f
0599231
dbe89f7
b44ad0f
d40490b
2be837f
1634726
6021d76
cbd004b
b8c0027
bf13376
c46e365
ceef59d
2089cf2
e63dc67
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 |
|---|---|---|
|
|
@@ -23,12 +23,21 @@ import ( | |
| "strings" | ||
| "time" | ||
|
|
||
| "github.com/spf13/pflag" | ||
|
|
||
| "vitess.io/vitess/go/mysql" | ||
| "vitess.io/vitess/go/mysql/capabilities" | ||
| "vitess.io/vitess/go/mysql/replication" | ||
| "vitess.io/vitess/go/mysql/sqlerror" | ||
| "vitess.io/vitess/go/sqltypes" | ||
| "vitess.io/vitess/go/vt/dbconfigs" | ||
| "vitess.io/vitess/go/vt/log" | ||
| topodatapb "vitess.io/vitess/go/vt/proto/topodata" | ||
| vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc" | ||
| "vitess.io/vitess/go/vt/servenv" | ||
| "vitess.io/vitess/go/vt/topo" | ||
| "vitess.io/vitess/go/vt/topo/topoproto" | ||
| "vitess.io/vitess/go/vt/utils" | ||
| "vitess.io/vitess/go/vt/vterrors" | ||
| "vitess.io/vitess/go/vt/vttls" | ||
| ) | ||
|
|
@@ -38,6 +47,98 @@ const ( | |
| cloneStatusQuery = "SELECT STATE, ERROR_NO, ERROR_MESSAGE FROM performance_schema.clone_status ORDER BY ID DESC LIMIT 1" | ||
| ) | ||
|
|
||
| var ( | ||
| cloneFromPrimary = false | ||
| cloneFromTablet = "" | ||
| ) | ||
|
|
||
| func init() { | ||
| // TODO: enable these flags for vttablet and vtbackup. | ||
| for _, cmd := range []string{ /*"vttablet", "vtbackup"*/ } { | ||
| servenv.OnParseFor(cmd, registerCloneFlags) | ||
| } | ||
| } | ||
|
|
||
| func registerCloneFlags(fs *pflag.FlagSet) { | ||
| utils.SetFlagBoolVar(fs, &cloneFromPrimary, "clone-from-primary", cloneFromPrimary, "Clone data from the primary tablet in the shard using MySQL CLONE REMOTE instead of restoring from backup. Requires MySQL 8.0.17+. Mutually exclusive with --clone-from-tablet.") | ||
| utils.SetFlagStringVar(fs, &cloneFromTablet, "clone-from-tablet", cloneFromTablet, "Clone data from this tablet using MySQL CLONE REMOTE instead of restoring from backup (tablet alias, e.g., zone1-123). Requires MySQL 8.0.17+. Mutually exclusive with --clone-from-primary.") | ||
| } | ||
|
|
||
| // CloneFromDonor clones data from the specified donor tablet using MySQL CLONE REMOTE. | ||
| // It returns the GTID position of the cloned data. | ||
| func CloneFromDonor(ctx context.Context, topoServer *topo.Server, mysqld MysqlDaemon, keyspace, shard string) (replication.Position, error) { | ||
| var donorAlias *topodatapb.TabletAlias | ||
| var err error | ||
|
|
||
| switch { | ||
| case cloneFromPrimary: | ||
| // Look up the primary tablet from topology. | ||
| log.Infof("Looking up primary tablet for shard %s/%s", keyspace, shard) | ||
|
||
| si, err := topoServer.GetShard(ctx, keyspace, shard) | ||
| if err != nil { | ||
| return replication.Position{}, fmt.Errorf("failed to get shard %s/%s: %v", keyspace, shard, err) | ||
| } | ||
| if topoproto.TabletAliasIsZero(si.PrimaryAlias) { | ||
| return replication.Position{}, fmt.Errorf("shard %s/%s has no primary", keyspace, shard) | ||
| } | ||
| donorAlias = si.PrimaryAlias | ||
| log.Infof("Found primary tablet: %s", topoproto.TabletAliasString(donorAlias)) | ||
| case cloneFromTablet != "": | ||
| // Parse the explicit donor tablet alias. | ||
| log.Infof("Starting clone-based backup from tablet %s", cloneFromTablet) | ||
maxenglander marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| donorAlias, err = topoproto.ParseTabletAlias(cloneFromTablet) | ||
| if err != nil { | ||
| return replication.Position{}, fmt.Errorf("invalid tablet alias %q: %v", cloneFromTablet, err) | ||
| } | ||
| default: | ||
| return replication.Position{}, errors.New("no donor specified") | ||
| } | ||
|
|
||
| // Get donor tablet info from topology. | ||
| donorTablet, err := topoServer.GetTablet(ctx, donorAlias) | ||
| if err != nil { | ||
| return replication.Position{}, fmt.Errorf("failed to get tablet %s from topology: %v", topoproto.TabletAliasString(donorAlias), err) | ||
| } | ||
|
|
||
| // Get clone credentials. | ||
| cloneConfig := dbconfigs.GlobalDBConfigs.CloneUser | ||
| if cloneConfig.User == "" { | ||
| return replication.Position{}, errors.New("clone user not configured; set --db-clone-user flag") | ||
| } | ||
|
|
||
| // Create the clone executor. | ||
| executor := &CloneExecutor{ | ||
| DonorHost: donorTablet.MysqlHostname, | ||
| DonorPort: int(donorTablet.MysqlPort), | ||
| DonorUser: cloneConfig.User, | ||
| DonorPassword: cloneConfig.Password, | ||
| UseSSL: cloneConfig.UseSSL, | ||
| } | ||
|
|
||
| log.Infof("Clone executor configured for donor %s:%d", executor.DonorHost, executor.DonorPort) | ||
|
|
||
| // Validate that the recipient (local) MySQL meets prerequisites. | ||
| if err := executor.validateRecipient(ctx, mysqld); err != nil { | ||
| return replication.Position{}, fmt.Errorf("recipient validation failed: %v", err) | ||
| } | ||
nickvanw marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| // Execute the clone operation. | ||
| // Note: ExecuteClone will wait for myqld to restart and for CLONE to report | ||
maxenglander marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| // success via performance_schema before returning. | ||
| if err := executor.ExecuteClone(ctx, mysqld, 5*time.Minute); err != nil { | ||
| return replication.Position{}, fmt.Errorf("clone execution failed: %v", err) | ||
| } | ||
nickvanw marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| // Get the GTID position from the cloned data. | ||
| pos, err := mysqld.PrimaryPosition(ctx) | ||
| if err != nil { | ||
| return replication.Position{}, fmt.Errorf("failed to get position after clone: %v", err) | ||
| } | ||
|
|
||
| log.Infof("Clone completed successfully at position %v", pos) | ||
| return pos, nil | ||
| } | ||
|
|
||
| // CloneExecutor handles MySQL CLONE REMOTE operations for backup and replica provisioning. | ||
| // It executes CLONE INSTANCE FROM on the recipient to clone data from a donor. | ||
| type CloneExecutor struct { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.