- working POC of row-copy iteration cycle

- initial work on table columns
- initial work on events streamer
This commit is contained in:
Shlomi Noach 2016-04-06 13:05:58 +02:00
parent f771016bd5
commit 5deff2adb6
6 changed files with 89 additions and 48 deletions

View File

@ -78,9 +78,9 @@ func (this *GoMySQLReader) ReadEntries(logFile string, startPos uint64, stopPos
for _, rows := range rowsEvent.Rows { for _, rows := range rowsEvent.Rows {
for j, d := range rows { for j, d := range rows {
if _, ok := d.([]byte); ok { if _, ok := d.([]byte); ok {
fmt.Print(fmt.Sprintf("yesbin %d:%q, %+v\n", j, d, reflect.TypeOf(d))) fmt.Print(fmt.Sprintf("%d:%q, %+v\n", j, d, reflect.TypeOf(d)))
} else { } else {
fmt.Print(fmt.Sprintf("notbin %d:%#v, %+v\n", j, d, reflect.TypeOf(d))) fmt.Print(fmt.Sprintf("%d:%#v, %+v\n", j, d, reflect.TypeOf(d)))
} }
} }
fmt.Println("---") fmt.Println("---")

View File

@ -190,16 +190,15 @@ func (this *Applier) IterationIsComplete() (bool, error) {
} }
func (this *Applier) CalculateNextIterationRangeEndValues() error { func (this *Applier) CalculateNextIterationRangeEndValues() error {
startingFromValues := this.migrationContext.MigrationRangeMinValues
this.migrationContext.MigrationIterationRangeMinValues = this.migrationContext.MigrationIterationRangeMaxValues this.migrationContext.MigrationIterationRangeMinValues = this.migrationContext.MigrationIterationRangeMaxValues
if this.migrationContext.MigrationIterationRangeMinValues != nil { if this.migrationContext.MigrationIterationRangeMinValues == nil {
startingFromValues = this.migrationContext.MigrationIterationRangeMinValues this.migrationContext.MigrationIterationRangeMinValues = this.migrationContext.MigrationRangeMinValues
} }
query, explodedArgs, err := sql.BuildUniqueKeyRangeEndPreparedQuery( query, explodedArgs, err := sql.BuildUniqueKeyRangeEndPreparedQuery(
this.migrationContext.DatabaseName, this.migrationContext.DatabaseName,
this.migrationContext.OriginalTableName, this.migrationContext.OriginalTableName,
this.migrationContext.UniqueKey.Columns, this.migrationContext.UniqueKey.Columns,
startingFromValues.AbstractValues(), this.migrationContext.MigrationIterationRangeMinValues.AbstractValues(),
this.migrationContext.MigrationRangeMaxValues.AbstractValues(), this.migrationContext.MigrationRangeMaxValues.AbstractValues(),
this.migrationContext.ChunkSize, this.migrationContext.ChunkSize,
fmt.Sprintf("iteration:%d", this.migrationContext.Iteration), fmt.Sprintf("iteration:%d", this.migrationContext.Iteration),
@ -224,7 +223,40 @@ func (this *Applier) CalculateNextIterationRangeEndValues() error {
return nil return nil
} }
this.migrationContext.MigrationIterationRangeMaxValues = iterationRangeMaxValues this.migrationContext.MigrationIterationRangeMaxValues = iterationRangeMaxValues
log.Debugf("column values: %s; iteration: %d; chunk-size: %d", this.migrationContext.MigrationIterationRangeMaxValues, this.migrationContext.Iteration, this.migrationContext.ChunkSize) log.Debugf(
"column values: [%s]..[%s]; iteration: %d; chunk-size: %d",
this.migrationContext.MigrationIterationRangeMinValues,
this.migrationContext.MigrationIterationRangeMaxValues,
this.migrationContext.Iteration,
this.migrationContext.ChunkSize,
)
return nil
}
func (this *Applier) ApplyIterationInsertQuery() error {
query, explodedArgs, err := sql.BuildRangeInsertPreparedQuery(
this.migrationContext.DatabaseName,
this.migrationContext.OriginalTableName,
this.migrationContext.GetGhostTableName(),
this.migrationContext.UniqueKey.Columns,
this.migrationContext.UniqueKey.Name,
this.migrationContext.UniqueKey.Columns,
this.migrationContext.MigrationIterationRangeMinValues.AbstractValues(),
this.migrationContext.MigrationIterationRangeMaxValues.AbstractValues(),
this.migrationContext.Iteration == 0,
)
if err != nil {
return err
}
if _, err := sqlutils.Exec(this.db, query, explodedArgs...); err != nil {
return err
}
log.Debugf(
"Issued INSERT on range: [%s]..[%s]; iteration: %d; chunk-size: %d",
this.migrationContext.MigrationIterationRangeMinValues,
this.migrationContext.MigrationIterationRangeMaxValues,
this.migrationContext.Iteration,
this.migrationContext.ChunkSize)
return nil return nil
} }

View File

@ -65,7 +65,7 @@ func (this *Inspector) InitDBConnections() (err error) {
return nil return nil
} }
func (this *Inspector) InspectTables() (uniqueKeys [](*sql.UniqueKey), err error) { func (this *Inspector) InspectOriginalTable() (uniqueKeys [](*sql.UniqueKey), err error) {
uniqueKeys, err = this.getCandidateUniqueKeys(this.migrationContext.OriginalTableName) uniqueKeys, err = this.getCandidateUniqueKeys(this.migrationContext.OriginalTableName)
if err != nil { if err != nil {
return uniqueKeys, err return uniqueKeys, err
@ -132,7 +132,7 @@ func (this *Inspector) validateGrants() error {
return log.Errorf("User has insufficient privileges for migration.") return log.Errorf("User has insufficient privileges for migration.")
} }
// validateConnection issues a simple can-connect to MySQL // validateBinlogs checks that binary log configuration is good to go
func (this *Inspector) validateBinlogs() error { func (this *Inspector) validateBinlogs() error {
query := `select @@global.log_bin, @@global.log_slave_updates, @@global.binlog_format` query := `select @@global.log_bin, @@global.log_slave_updates, @@global.binlog_format`
var hasBinaryLogs, logSlaveUpdates bool var hasBinaryLogs, logSlaveUpdates bool
@ -260,6 +260,29 @@ func (this *Inspector) countTableRows() error {
return nil return nil
} }
func (this *Inspector) getTableColumns(databaseName, tableName string) (columns sql.ColumnList, err error) {
query := fmt.Sprintf(`
show columns from %s.%s
`,
sql.EscapeName(databaseName),
sql.EscapeName(tableName),
)
err = sqlutils.QueryRowsMap(this.db, query, func(rowMap sqlutils.RowMap) error {
columns = append(columns, rowMap.GetString("Field"))
return nil
})
if err != nil {
return columns, err
}
if len(columns) == 0 {
return columns, log.Errorf("Found 0 columns on %s.%s. Bailing out",
sql.EscapeName(databaseName),
sql.EscapeName(tableName),
)
}
return columns, nil
}
// getCandidateUniqueKeys investigates a table and returns the list of unique keys // getCandidateUniqueKeys investigates a table and returns the list of unique keys
// candidate for chunking // candidate for chunking
func (this *Inspector) getCandidateUniqueKeys(tableName string) (uniqueKeys [](*sql.UniqueKey), err error) { func (this *Inspector) getCandidateUniqueKeys(tableName string) (uniqueKeys [](*sql.UniqueKey), err error) {

View File

@ -17,6 +17,7 @@ import (
type Migrator struct { type Migrator struct {
inspector *Inspector inspector *Inspector
applier *Applier applier *Applier
eventsStreamer *EventsStreamer
migrationContext *base.MigrationContext migrationContext *base.MigrationContext
} }
@ -38,24 +39,29 @@ func (this *Migrator) Migrate() (err error) {
return fmt.Errorf("It seems like this migration attempt to run directly on master. Preferably it would be executed on a replica (and this reduces load from the master). To proceed please provide --allow-on-master") return fmt.Errorf("It seems like this migration attempt to run directly on master. Preferably it would be executed on a replica (and this reduces load from the master). To proceed please provide --allow-on-master")
} }
log.Infof("Master found to be %+v", this.migrationContext.MasterConnectionConfig.Key) log.Infof("Master found to be %+v", this.migrationContext.MasterConnectionConfig.Key)
uniqueKeys, err := this.inspector.InspectTables() uniqueKeys, err := this.inspector.InspectOriginalTable()
if err != nil { if err != nil {
return err return err
} }
this.eventsStreamer = NewEventsStreamer()
if err := this.eventsStreamer.InitDBConnections(); err != nil {
return err
}
this.applier = NewApplier() this.applier = NewApplier()
if err := this.applier.InitDBConnections(); err != nil { if err := this.applier.InitDBConnections(); err != nil {
return err return err
} }
// if err := this.applier.CreateGhostTable(); err != nil { if err := this.applier.CreateGhostTable(); err != nil {
// log.Errorf("Unable to create ghost table, see further error details. Perhaps a previous migration failed without dropping the table? Bailing out") log.Errorf("Unable to create ghost table, see further error details. Perhaps a previous migration failed without dropping the table? Bailing out")
// return err return err
// } }
// if err := this.applier.AlterGhost(); err != nil { if err := this.applier.AlterGhost(); err != nil {
// log.Errorf("Unable to ALTER ghost table, see further error details. Bailing out") log.Errorf("Unable to ALTER ghost table, see further error details. Bailing out")
// return err return err
// } }
this.migrationContext.UniqueKey = uniqueKeys[0] this.migrationContext.UniqueKey = uniqueKeys[0] // TODO. Need to wait on replica till the ghost table exists and get shared keys
if err := this.applier.ReadMigrationRangeValues(); err != nil { if err := this.applier.ReadMigrationRangeValues(); err != nil {
return err return err
} }
@ -67,15 +73,17 @@ func (this *Migrator) Migrate() (err error) {
if isComplete { if isComplete {
break break
} }
err = this.applier.CalculateNextIterationRangeEndValues() if err = this.applier.CalculateNextIterationRangeEndValues(); err != nil {
if err != nil { return err
}
if err = this.applier.ApplyIterationInsertQuery(); err != nil {
return err return err
} }
this.migrationContext.Iteration++ this.migrationContext.Iteration++
} }
if err := this.applier.IterateTable(uniqueKeys[0]); err != nil { // if err := this.applier.IterateTable(uniqueKeys[0]); err != nil {
return err // return err
} // }
return nil return nil
} }

View File

@ -1,17 +1,6 @@
/* /*
Copyright 2015 Shlomi Noach, courtesy Booking.com Copyright 2015 Shlomi Noach, courtesy Booking.com
See https://github.com/github/gh-osc/blob/master/LICENSE
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/ */
package mysql package mysql

View File

@ -1,17 +1,6 @@
/* /*
Copyright 2015 Shlomi Noach, courtesy Booking.com Copyright 2015 Shlomi Noach, courtesy Booking.com
See https://github.com/github/gh-osc/blob/master/LICENSE
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/ */
package mysql package mysql