Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 21 additions & 15 deletions go/base/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,21 +80,22 @@ type MigrationContext struct {
OriginalTableName string
AlterStatement string

CountTableRows bool
ConcurrentCountTableRows bool
AllowedRunningOnMaster bool
AllowedMasterMaster bool
SwitchToRowBinlogFormat bool
AssumeRBR bool
SkipForeignKeyChecks bool
SkipStrictMode bool
NullableUniqueKeyAllowed bool
ApproveRenamedColumns bool
SkipRenamedColumns bool
IsTungsten bool
DiscardForeignKeys bool
AliyunRDS bool
GoogleCloudPlatform bool
CountTableRows bool
ConcurrentCountTableRows bool
AllowedRunningOnMaster bool
AllowedMasterMaster bool
SwitchToRowBinlogFormat bool
SwitchToFullBinlogRowImage bool
AssumeRBR bool
SkipForeignKeyChecks bool
SkipStrictMode bool
NullableUniqueKeyAllowed bool
ApproveRenamedColumns bool
SkipRenamedColumns bool
IsTungsten bool
DiscardForeignKeys bool
AliyunRDS bool
GoogleCloudPlatform bool

config ContextConfig
configMutex *sync.Mutex
Expand Down Expand Up @@ -313,6 +314,11 @@ func (this *MigrationContext) RequiresBinlogFormatChange() bool {
return this.OriginalBinlogFormat != "ROW"
}

// RequiresBinlogRowImageChange is `true` when the original binlog format isn't `FULL`
func (this *MigrationContext) RequiresBinlogRowImageChange() bool {
return this.OriginalBinlogRowImage != "FULL"
}

// GetApplierHostname is a safe access method to the applier hostname
func (this *MigrationContext) GetApplierHostname() string {
if this.ApplierConnectionConfig == nil {
Expand Down
1 change: 1 addition & 0 deletions go/cmd/gh-ost/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ func main() {
flag.BoolVar(&migrationContext.ForceNamedPanicCommand, "force-named-panic", false, "When true, the 'panic' interactive command must name the migrated table")

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.SwitchToFullBinlogRowImage, "switch-to-full-rbr", false, "let this tool automatically switch binary log row image to 'FULL' on the replica, if needed. The row image 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.AssumeRBR, "assume-rbr", false, "set to 'true' when you know for certain your server uses 'ROW' binlog_format. gh-ost is unable to tell, event after reading binlog_format, whether the replication process does indeed use 'ROW', and restarts replication to be certain RBR setting is applied. Such operation requires SUPER privileges which you might not have. Setting this flag avoids restarting replication and you can proceed to use gh-ost without SUPER privileges")
flag.BoolVar(&migrationContext.CutOverExponentialBackoff, "cut-over-exponential-backoff", false, "Wait exponentially longer intervals between failed cut-over attempts. Wait intervals obey a maximum configurable with 'exponential-backoff-max-interval').")
exponentialBackoffMaxInterval := flag.Int64("exponential-backoff-max-interval", 64, "Maximum number of seconds to wait between attempts when performing various operations with exponential backoff.")
Expand Down
64 changes: 54 additions & 10 deletions go/logic/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -293,9 +293,11 @@ func (this *Inspector) restartReplication() error {
return nil
}

// applyBinlogFormat sets ROW binlog format and restarts replication to make
// applyBinlogFormat sets ROW binlog format and FULL binlog row image and restarts replication to make
// the replication thread apply it.
func (this *Inspector) applyBinlogFormat() error {
changesApplied := false

if this.migrationContext.RequiresBinlogFormatChange() {
if !this.migrationContext.SwitchToRowBinlogFormat {
return fmt.Errorf("Existing binlog_format is %s. Am not switching it to ROW unless you specify --switch-to-rbr", this.migrationContext.OriginalBinlogFormat)
Expand All @@ -310,8 +312,28 @@ func (this *Inspector) applyBinlogFormat() error {
return err
}
log.Debugf("'ROW' binlog format applied")
changesApplied = true
}
if this.migrationContext.RequiresBinlogRowImageChange() {
if !this.migrationContext.SwitchToFullBinlogRowImage {
return fmt.Errorf("Existing binlog_row_image is %s. Am not switching it to FULL unless you specify --switch-to-rbr-full", this.migrationContext.OriginalBinlogRowImage)
}
if _, err := sqlutils.ExecNoPrepare(this.db, `set global binlog_row_image='FULL'`); err != nil {
return err
}
if _, err := sqlutils.ExecNoPrepare(this.db, `set session binlog_row_image='FULL'`); err != nil {
return err
}
if err := this.restartReplication(); err != nil {
return err
}
log.Debugf("'FULL' binlog row image applied")
changesApplied = true
}
if changesApplied {
return nil
}

// We already have RBR, no explicit switch
if !this.migrationContext.AssumeRBR {
if err := this.restartReplication(); err != nil {
Expand All @@ -321,6 +343,19 @@ func (this *Inspector) applyBinlogFormat() error {
return nil
}

// countReplicas returns the number of hosts replicating from `this.db`
func (this *Inspector) countReplicas() (int, error) {
countReplicas := 0

query := fmt.Sprintf(`show /* gh-ost */ slave hosts`)
err := sqlutils.QueryRowsMap(this.db, query, func(rowMap sqlutils.RowMap) error {
countReplicas++
return nil
})

return countReplicas, err
}

// validateBinlogs checks that binary log configuration is good to go
func (this *Inspector) validateBinlogs() error {
query := `select @@global.log_bin, @@global.binlog_format`
Expand All @@ -335,19 +370,17 @@ func (this *Inspector) validateBinlogs() error {
if !this.migrationContext.SwitchToRowBinlogFormat {
return fmt.Errorf("You must be using ROW binlog format. I can switch it for you, provided --switch-to-rbr and that %s:%d doesn't have replicas", this.connectionConfig.Key.Hostname, this.connectionConfig.Key.Port)
}
query := fmt.Sprintf(`show /* gh-ost */ slave hosts`)
countReplicas := 0
err := sqlutils.QueryRowsMap(this.db, query, func(rowMap sqlutils.RowMap) error {
countReplicas++
return nil
})

countReplicas, err := this.countReplicas()
if err != nil {
return err
}

if countReplicas > 0 {
return fmt.Errorf("%s:%d has %s binlog_format, but I'm too scared to change it to ROW because it has replicas. Bailing out", this.connectionConfig.Key.Hostname, this.connectionConfig.Key.Port, this.migrationContext.OriginalBinlogFormat)
return fmt.Errorf("%s:%d has '%s' binlog_format, but I'm too scared to change it to ROW because it has replicas. Bailing out", this.connectionConfig.Key.Hostname, this.connectionConfig.Key.Port, this.migrationContext.OriginalBinlogFormat)
}
log.Infof("%s:%d has %s binlog_format. I will change it to ROW, and will NOT change it back, even in the event of failure.", this.connectionConfig.Key.Hostname, this.connectionConfig.Key.Port, this.migrationContext.OriginalBinlogFormat)

log.Infof("%s:%d has '%s' binlog_format. I will change it to ROW, and will NOT change it back, even in the event of failure.", this.connectionConfig.Key.Hostname, this.connectionConfig.Key.Port, this.migrationContext.OriginalBinlogFormat)
}
query = `select @@global.binlog_row_image`
if err := this.db.QueryRow(query).Scan(&this.migrationContext.OriginalBinlogRowImage); err != nil {
Expand All @@ -356,7 +389,18 @@ func (this *Inspector) validateBinlogs() error {
}
this.migrationContext.OriginalBinlogRowImage = strings.ToUpper(this.migrationContext.OriginalBinlogRowImage)
if this.migrationContext.OriginalBinlogRowImage != "FULL" {
return fmt.Errorf("%s:%d has '%s' binlog_row_image, and only 'FULL' is supported. This operation cannot proceed. You may `set global binlog_row_image='full'` and try again", this.connectionConfig.Key.Hostname, this.connectionConfig.Key.Port, this.migrationContext.OriginalBinlogRowImage)
if !this.migrationContext.SwitchToFullBinlogRowImage {
return fmt.Errorf("%s:%d has '%s' binlog_row_image, and only 'FULL' is supported. I can switch it for you, provided --switch-to-full-rbr and that %s:%d doesn't have replicas. Alternatively, you may `set global binlog_row_image='full'` and try again", this.connectionConfig.Key.Hostname, this.connectionConfig.Key.Port, this.migrationContext.OriginalBinlogRowImage, this.connectionConfig.Key.Hostname, this.connectionConfig.Key.Port)
}

countReplicas, err := this.countReplicas()
if err != nil {
return err
}

if countReplicas > 0 {
return fmt.Errorf("%s:%d has '%s' binlog_row_image, but I'm too scared to change it to ROW because it has replicas. Bailing out", this.connectionConfig.Key.Hostname, this.connectionConfig.Key.Port, this.migrationContext.OriginalBinlogRowImage)
}
}

log.Infof("binary logs validated on %s:%d", this.connectionConfig.Key.Hostname, this.connectionConfig.Key.Port)
Expand Down