Skip to content

Commit 84f1c61

Browse files
authored
Merge pull request #1702 from github/move-tables/cli
Add CLI parameters for move-tables feature
2 parents 83aaa70 + 831de02 commit 84f1c61

3 files changed

Lines changed: 174 additions & 4 deletions

File tree

go/base/context.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,16 @@ type MigrationContext struct {
274274
SkipMetadataLockCheck bool
275275
IsOpenMetadataLockInstruments bool
276276

277+
// move tables:
278+
MoveTables struct {
279+
TableNames []string // List of table names to be moved.
280+
TargetHost string // Target hostname for the move. This must be a primary/writable host.
281+
TargetPort int // Target MySQL port for the move.
282+
TargetUser string // Target username for the move. If not specified, it will default to the source user.
283+
TargetPass string // Target password for the move. If not specified, it will default to the source password.
284+
TargetDatabase string // Target database name for the move. If not specified, it will default to the source database name.
285+
}
286+
277287
Log Logger
278288
}
279289

@@ -1032,6 +1042,11 @@ func (mctx *MigrationContext) CancelContext() {
10321042
}
10331043
}
10341044

1045+
// IsMoveTablesMode returns true if gh-ost should be used for moving tables instead of running a schema migration.
1046+
func (mctx *MigrationContext) IsMoveTablesMode() bool {
1047+
return len(mctx.MoveTables.TableNames) > 0
1048+
}
1049+
10351050
// SendWithContext attempts to send a value to a channel, but returns early
10361051
// if the context is cancelled. This prevents goroutine deadlocks when the
10371052
// channel receiver has exited due to an error.

go/cmd/gh-ost/main.go

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import (
1212
"os"
1313
"os/signal"
1414
"regexp"
15+
"slices"
16+
"strings"
1517
"syscall"
1618
"time"
1719

@@ -184,8 +186,16 @@ func main() {
184186
version := flag.Bool("version", false, "Print version & exit")
185187
checkFlag := flag.Bool("check-flag", false, "Check if another flag exists/supported. This allows for cross-version scripting. Exits with 0 when all additional provided flags exist, nonzero otherwise. You must provide (dummy) values for flags that require a value. Example: gh-ost --check-flag --cut-over-lock-timeout-seconds --nice-ratio 0")
186188
flag.StringVar(&migrationContext.ForceTmpTableName, "force-table-names", "", "table name prefix to be used on the temporary tables")
187-
flag.CommandLine.SetOutput(os.Stdout)
188189

190+
// move tables flags
191+
moveTables := flag.String("move-tables", "", "Comma delimited list of tables to move. e.g. 'table1,table2,table3'. This is a special mode that allows you to move tables between database clusters. This mode is mutually exclusive with --alter, --table, --test-on-replica, --migrate-on-replica and --revert.")
192+
flag.StringVar(&migrationContext.MoveTables.TargetHost, "target-host", "", "Target MySQL hostname for --move-tables mode. Must be specified if --move-tables is specified.")
193+
flag.IntVar(&migrationContext.MoveTables.TargetPort, "target-port", 3306, "Target MySQL port for --move-tables mode. Defaults to 3306.")
194+
flag.StringVar(&migrationContext.MoveTables.TargetUser, "target-user", "", "Target MySQL username for --move-tables mode. If not provided, uses the same user as the source connection")
195+
flag.StringVar(&migrationContext.MoveTables.TargetPass, "target-password", "", "Target MySQL password for --move-tables mode. If not provided, uses the same password as the source connection")
196+
flag.StringVar(&migrationContext.MoveTables.TargetDatabase, "target-database", "", "Target MySQL database name for --move-tables mode. If not provided, uses the same database name as the source connection")
197+
198+
flag.CommandLine.SetOutput(os.Stdout)
189199
flag.Parse()
190200

191201
if *checkFlag {
@@ -229,8 +239,8 @@ func main() {
229239

230240
migrationContext.SetConnectionCharset(*charset)
231241

232-
if migrationContext.AlterStatement == "" && !migrationContext.Revert {
233-
log.Fatal("--alter must be provided and statement must not be empty")
242+
if migrationContext.AlterStatement == "" && !migrationContext.Revert && *moveTables == "" {
243+
log.Fatal("--alter must be provided and statement must not be empty, or --revert must be used, or --move-tables must be used")
234244
}
235245
parser := sql.NewParserFromAlterStatement(migrationContext.AlterStatement)
236246
migrationContext.AlterStatementOptions = parser.GetAlterStatementOptions()
@@ -270,7 +280,7 @@ func main() {
270280
migrationContext.Log.Fatale(err)
271281
}
272282

273-
if migrationContext.OriginalTableName == "" {
283+
if migrationContext.OriginalTableName == "" && *moveTables == "" {
274284
if parser.HasExplicitTable() {
275285
migrationContext.OriginalTableName = parser.GetExplicitTable()
276286
} else {
@@ -340,6 +350,46 @@ func main() {
340350
migrationContext.Log.Warning("--exact-rowcount with --panic-on-warnings: row counts cannot be exact due to warning detection")
341351
}
342352

353+
if *moveTables != "" {
354+
if migrationContext.AlterStatement != "" {
355+
log.Fatal("--move-tables is mutually exclusive with --alter")
356+
}
357+
if migrationContext.OriginalTableName != "" {
358+
log.Fatal("--move-tables is mutually exclusive with --table")
359+
}
360+
if migrationContext.TestOnReplica {
361+
log.Fatal("--move-tables is mutually exclusive with --test-on-replica")
362+
}
363+
if migrationContext.MigrateOnReplica {
364+
log.Fatal("--move-tables is mutually exclusive with --migrate-on-replica")
365+
}
366+
if migrationContext.Revert {
367+
log.Fatal("--move-tables is mutually exclusive with --revert")
368+
}
369+
if migrationContext.MoveTables.TargetHost == "" {
370+
log.Fatal("--target-host must be specified when using --move-tables")
371+
}
372+
migrationContext.MoveTables.TableNames = strings.Split(*moveTables, ",")
373+
for i := range migrationContext.MoveTables.TableNames {
374+
migrationContext.MoveTables.TableNames[i] = strings.TrimSpace(migrationContext.MoveTables.TableNames[i])
375+
}
376+
migrationContext.MoveTables.TableNames = slices.DeleteFunc(migrationContext.MoveTables.TableNames, func(s string) bool { return s == "" })
377+
if len(migrationContext.MoveTables.TableNames) > 1 {
378+
// Future version will support moving multiple tables at the same time.
379+
// For now, we only support moving a single table at a time.
380+
log.Fatal("--move-tables currently supports only a single table")
381+
}
382+
if migrationContext.MoveTables.TargetUser == "" {
383+
migrationContext.MoveTables.TargetUser = migrationContext.CliUser
384+
}
385+
if migrationContext.MoveTables.TargetPass == "" {
386+
migrationContext.MoveTables.TargetPass = migrationContext.CliPassword
387+
}
388+
if migrationContext.MoveTables.TargetDatabase == "" {
389+
migrationContext.MoveTables.TargetDatabase = migrationContext.DatabaseName
390+
}
391+
}
392+
343393
switch *cutOver {
344394
case "atomic", "default", "":
345395
migrationContext.CutOverType = base.CutOverAtomic
@@ -410,6 +460,8 @@ func main() {
410460
var err error
411461
if migrationContext.Revert {
412462
err = migrator.Revert()
463+
} else if migrationContext.IsMoveTablesMode() {
464+
err = migrator.MoveTables()
413465
} else {
414466
err = migrator.Migrate()
415467
}

go/logic/migrator.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -790,6 +790,109 @@ func (mgtr *Migrator) Revert() error {
790790
return nil
791791
}
792792

793+
func (mgtr *Migrator) MoveTables() (err error) {
794+
mgtr.migrationContext.Log.Infof("Moving tables %v from %s to %s (%s)",
795+
mgtr.migrationContext.MoveTables.TableNames,
796+
sql.EscapeName(mgtr.migrationContext.DatabaseName),
797+
sql.EscapeName(mgtr.migrationContext.MoveTables.TargetDatabase), mgtr.migrationContext.MoveTables.TargetHost)
798+
mgtr.migrationContext.StartTime = time.Now()
799+
800+
// Ensure context is cancelled on exit (cleanup)
801+
defer mgtr.migrationContext.CancelContext()
802+
803+
if mgtr.migrationContext.Hostname, err = os.Hostname(); err != nil {
804+
return err
805+
}
806+
807+
go mgtr.listenOnPanicAbort()
808+
809+
// Run on-startup hook:
810+
if err := mgtr.hooksExecutor.OnStartup(); err != nil {
811+
return err
812+
}
813+
814+
// After this point, we'll need to teardown anything that's been started
815+
// so we don't leave things hanging around
816+
defer mgtr.teardown()
817+
818+
if err := mgtr.initiateInspector(); err != nil {
819+
return err
820+
}
821+
if err := mgtr.checkAbort(); err != nil {
822+
return err
823+
}
824+
if err := mgtr.initiateApplier(); err != nil {
825+
return err
826+
}
827+
if err := mgtr.checkAbort(); err != nil {
828+
return err
829+
}
830+
831+
// Validation complete! Run on-validated hook.
832+
if err := mgtr.hooksExecutor.OnValidated(); err != nil {
833+
return err
834+
}
835+
836+
if err := mgtr.initiateServer(); err != nil {
837+
return err
838+
}
839+
defer mgtr.server.RemoveSocketFile()
840+
841+
if err := mgtr.countTableRows(); err != nil {
842+
return err
843+
}
844+
if err := mgtr.addDMLEventsListener(); err != nil {
845+
return err
846+
}
847+
if err := mgtr.applier.ReadMigrationRangeValues(); err != nil {
848+
return err
849+
}
850+
851+
mgtr.initiateThrottler()
852+
853+
// Run on-before-row-copy hook
854+
if err := mgtr.hooksExecutor.OnBeforeRowCopy(); err != nil {
855+
return err
856+
}
857+
go func() {
858+
if err := mgtr.executeWriteFuncs(); err != nil {
859+
// Send error to PanicAbort to trigger abort
860+
_ = base.SendWithContext(mgtr.migrationContext.GetContext(), mgtr.migrationContext.PanicAbort, err)
861+
}
862+
}()
863+
go mgtr.iterateChunks()
864+
mgtr.migrationContext.MarkRowCopyStartTime()
865+
go mgtr.initiateStatus()
866+
867+
mgtr.migrationContext.Log.Debugf("Operating until row copy is complete")
868+
mgtr.consumeRowCopyComplete()
869+
mgtr.migrationContext.Log.Infof("Row copy complete")
870+
// Check if row copy was aborted due to error
871+
if err := mgtr.checkAbort(); err != nil {
872+
return err
873+
}
874+
if err := mgtr.hooksExecutor.OnRowCopyComplete(); err != nil {
875+
return err
876+
}
877+
878+
//TODO: cutover here
879+
880+
if err := mgtr.finalCleanup(); err != nil {
881+
return nil
882+
}
883+
if err := mgtr.hooksExecutor.OnSuccess(false); err != nil {
884+
return err
885+
}
886+
mgtr.migrationContext.Log.Infof("Done moving tables %v from %s to %s (%s)",
887+
mgtr.migrationContext.MoveTables.TableNames, sql.EscapeName(mgtr.migrationContext.DatabaseName),
888+
sql.EscapeName(mgtr.migrationContext.MoveTables.TargetDatabase), mgtr.migrationContext.MoveTables.TargetHost)
889+
// Final check for abort before declaring success
890+
if err := mgtr.checkAbort(); err != nil {
891+
return err
892+
}
893+
return nil
894+
}
895+
793896
// ExecOnFailureHook executes the onFailure hook, and this method is provided as the only external
794897
// hook access point
795898
func (mgtr *Migrator) ExecOnFailureHook() (err error) {

0 commit comments

Comments
 (0)