- quick-and-bumpy-swap-tables
uses quicker swap tables, at the expense of a period where the table does not exist (non atomic renames)
- refactored lock-and-swap code, in preparation for atomic swap
This commit is contained in:
parent
fbe226ebb2
commit
54c6d059b5
@ -47,9 +47,10 @@ type MigrationContext struct {
|
|||||||
ThrottleAdditionalFlagFile string
|
ThrottleAdditionalFlagFile string
|
||||||
MaxLoad map[string]int64
|
MaxLoad map[string]int64
|
||||||
|
|
||||||
Noop bool
|
Noop bool
|
||||||
TestOnReplica bool
|
TestOnReplica bool
|
||||||
OkToDropTable bool
|
OkToDropTable bool
|
||||||
|
QuickAndBumpySwapTables bool
|
||||||
|
|
||||||
TableEngine string
|
TableEngine string
|
||||||
RowsEstimate int64
|
RowsEstimate int64
|
||||||
|
@ -33,6 +33,7 @@ func main() {
|
|||||||
executeFlag := flag.Bool("execute", false, "actually execute the alter & migrate the table. Default is noop: do some tests and exit")
|
executeFlag := flag.Bool("execute", false, "actually execute the alter & migrate the table. Default is noop: do some tests and exit")
|
||||||
flag.BoolVar(&migrationContext.TestOnReplica, "test-on-replica", false, "Have the migration run on a replica, not on the master. At the end of migration tables are not swapped; gh-osc issues `STOP SLAVE` and you can compare the two tables for building trust")
|
flag.BoolVar(&migrationContext.TestOnReplica, "test-on-replica", false, "Have the migration run on a replica, not on the master. At the end of migration tables are not swapped; gh-osc issues `STOP SLAVE` and you can compare the two tables for building trust")
|
||||||
flag.BoolVar(&migrationContext.OkToDropTable, "ok-to-drop-table", false, "Shall the tool drop the old table at end of operation. DROPping tables can be a long locking operation, which is why I'm not doing it by default. I'm an online tool, yes?")
|
flag.BoolVar(&migrationContext.OkToDropTable, "ok-to-drop-table", false, "Shall the tool drop the old table at end of operation. DROPping tables can be a long locking operation, which is why I'm not doing it by default. I'm an online tool, yes?")
|
||||||
|
flag.BoolVar(&migrationContext.QuickAndBumpySwapTables, "quick-and-bumpy-swap-tables", false, "Shall the tool issue a faster swapping of tables at end of operation, at the cost of causing a brief period of time when the table does not exist? This will cause queries on table to fail with error (as opposed to being locked for a longer duration of a swap)")
|
||||||
|
|
||||||
flag.BoolVar(&migrationContext.SwitchToRowBinlogFormat, "switch-to-rbr", false, "let this tool automatically switch binary log format to 'ROW' on the replica, if needed. The format will NOT be switched back. I'm too scared to do that, and wish to protect you if you happen to execute another migration while this one is running")
|
flag.BoolVar(&migrationContext.SwitchToRowBinlogFormat, "switch-to-rbr", false, "let this tool automatically switch binary log format to 'ROW' on the replica, if needed. The format will NOT be switched back. I'm too scared to do that, and wish to protect you if you happen to execute another migration while this one is running")
|
||||||
flag.Int64Var(&migrationContext.ChunkSize, "chunk-size", 1000, "amount of rows to handle in each iteration (allowed range: 100-100,000)")
|
flag.Int64Var(&migrationContext.ChunkSize, "chunk-size", 1000, "amount of rows to handle in each iteration (allowed range: 100-100,000)")
|
||||||
@ -87,6 +88,9 @@ func main() {
|
|||||||
if migrationContext.AllowedRunningOnMaster && migrationContext.TestOnReplica {
|
if migrationContext.AllowedRunningOnMaster && migrationContext.TestOnReplica {
|
||||||
log.Fatalf("--allow-on-master and --test-on-replica are mutually exclusive")
|
log.Fatalf("--allow-on-master and --test-on-replica are mutually exclusive")
|
||||||
}
|
}
|
||||||
|
if migrationContext.QuickAndBumpySwapTables && migrationContext.TestOnReplica {
|
||||||
|
log.Fatalf("--quick-and-bumpy-swap-tables and --test-on-replica are mutually exclusive (the former implies migrating on master)")
|
||||||
|
}
|
||||||
if err := migrationContext.ReadMaxLoad(*maxLoad); err != nil {
|
if err := migrationContext.ReadMaxLoad(*maxLoad); err != nil {
|
||||||
log.Fatale(err)
|
log.Fatale(err)
|
||||||
}
|
}
|
||||||
|
@ -299,48 +299,96 @@ func (this *Migrator) stopWritesAndCompleteMigration() (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
this.throttle(func() {
|
this.throttle(func() {
|
||||||
log.Debugf("throttling before LOCK TABLES")
|
log.Debugf("throttling before swapping tables")
|
||||||
})
|
})
|
||||||
|
|
||||||
if this.migrationContext.TestOnReplica {
|
if this.migrationContext.TestOnReplica {
|
||||||
log.Debugf("testing on replica. Instead of LOCK tables I will STOP SLAVE")
|
return this.stopWritesAndCompleteMigrationOnReplica()
|
||||||
if err := this.retryOperation(this.applier.StopSlaveIOThread); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err := this.retryOperation(this.applier.LockTables); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// Running on master
|
||||||
|
if this.migrationContext.QuickAndBumpySwapTables {
|
||||||
|
return this.stopWritesAndCompleteMigrationOnMasterQuickAndBumpy()
|
||||||
|
}
|
||||||
|
return this.stopWritesAndCompleteMigrationOnMasterViaLock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Migrator) stopWritesAndCompleteMigrationOnMasterQuickAndBumpy() (err error) {
|
||||||
|
if err := this.retryOperation(this.applier.LockTables); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
this.applier.WriteChangelogState(string(AllEventsUpToLockProcessed))
|
this.applier.WriteChangelogState(string(AllEventsUpToLockProcessed))
|
||||||
log.Debugf("Waiting for events up to lock")
|
log.Debugf("Waiting for events up to lock")
|
||||||
<-this.allEventsUpToLockProcessed
|
<-this.allEventsUpToLockProcessed
|
||||||
log.Debugf("Done waiting for events up to lock")
|
log.Debugf("Done waiting for events up to lock")
|
||||||
|
|
||||||
if this.migrationContext.TestOnReplica {
|
if err := this.retryOperation(this.applier.SwapTables); err != nil {
|
||||||
log.Info("Table duplicated with new schema. Am not touching the original table. You may now compare the two tables to gain trust into this tool's operation")
|
return err
|
||||||
} else {
|
}
|
||||||
if err := this.retryOperation(this.applier.SwapTables); err != nil {
|
if err := this.retryOperation(this.applier.UnlockTables); err != nil {
|
||||||
return err
|
return err
|
||||||
|
}
|
||||||
|
if this.migrationContext.OkToDropTable {
|
||||||
|
dropTableFunc := func() error {
|
||||||
|
return this.applier.dropTable(this.migrationContext.GetOldTableName())
|
||||||
}
|
}
|
||||||
if err := this.retryOperation(this.applier.UnlockTables); err != nil {
|
if err := this.retryOperation(dropTableFunc); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if this.migrationContext.OkToDropTable {
|
|
||||||
dropTableFunc := func() error {
|
|
||||||
return this.applier.dropTable(this.migrationContext.GetOldTableName())
|
|
||||||
}
|
|
||||||
if err := this.retryOperation(dropTableFunc); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lockAndRenameDuration := this.migrationContext.RenameTablesEndTime.Sub(this.migrationContext.LockTablesStartTime)
|
lockAndRenameDuration := this.migrationContext.RenameTablesEndTime.Sub(this.migrationContext.LockTablesStartTime)
|
||||||
renameDuration := this.migrationContext.RenameTablesEndTime.Sub(this.migrationContext.RenameTablesStartTime)
|
renameDuration := this.migrationContext.RenameTablesEndTime.Sub(this.migrationContext.RenameTablesStartTime)
|
||||||
log.Debugf("Lock & rename duration: %s (rename only: %s). During this time, queries on %s were locked or failing", lockAndRenameDuration, renameDuration, sql.EscapeName(this.migrationContext.OriginalTableName))
|
log.Debugf("Lock & rename duration: %s (rename only: %s). During this time, queries on %s were locked or failing", lockAndRenameDuration, renameDuration, sql.EscapeName(this.migrationContext.OriginalTableName))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *Migrator) stopWritesAndCompleteMigrationOnMasterViaLock() (err error) {
|
||||||
|
if err := this.retryOperation(this.applier.LockTables); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
this.applier.WriteChangelogState(string(AllEventsUpToLockProcessed))
|
||||||
|
log.Debugf("Waiting for events up to lock")
|
||||||
|
<-this.allEventsUpToLockProcessed
|
||||||
|
log.Debugf("Done waiting for events up to lock")
|
||||||
|
|
||||||
|
if err := this.retryOperation(this.applier.SwapTables); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := this.retryOperation(this.applier.UnlockTables); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if this.migrationContext.OkToDropTable {
|
||||||
|
dropTableFunc := func() error {
|
||||||
|
return this.applier.dropTable(this.migrationContext.GetOldTableName())
|
||||||
|
}
|
||||||
|
if err := this.retryOperation(dropTableFunc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lockAndRenameDuration := this.migrationContext.RenameTablesEndTime.Sub(this.migrationContext.LockTablesStartTime)
|
||||||
|
renameDuration := this.migrationContext.RenameTablesEndTime.Sub(this.migrationContext.RenameTablesStartTime)
|
||||||
|
log.Debugf("Lock & rename duration: %s (rename only: %s). During this time, queries on %s were locked or failing", lockAndRenameDuration, renameDuration, sql.EscapeName(this.migrationContext.OriginalTableName))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Migrator) stopWritesAndCompleteMigrationOnReplica() (err error) {
|
||||||
|
log.Debugf("testing on replica. Instead of LOCK tables I will STOP SLAVE")
|
||||||
|
if err := this.retryOperation(this.applier.StopSlaveIOThread); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
this.applier.WriteChangelogState(string(AllEventsUpToLockProcessed))
|
||||||
|
log.Debugf("Waiting for events up to lock")
|
||||||
|
<-this.allEventsUpToLockProcessed
|
||||||
|
log.Debugf("Done waiting for events up to lock")
|
||||||
|
|
||||||
|
log.Info("Table duplicated with new schema. Am not touching the original table. Replication is stopped. You may now compare the two tables to gain trust into this tool's operation")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (this *Migrator) initiateInspector() (err error) {
|
func (this *Migrator) initiateInspector() (err error) {
|
||||||
this.inspector = NewInspector()
|
this.inspector = NewInspector()
|
||||||
if err := this.inspector.InitDBConnections(); err != nil {
|
if err := this.inspector.InitDBConnections(); err != nil {
|
||||||
|
Loading…
Reference in New Issue
Block a user