Skip to content

Commit b290e7b

Browse files
authored
Merge pull request #1709 from github/danieljoos/move-tables-throttler
Move Tables: throttle based on target credentials
2 parents bb029da + 985d4de commit b290e7b

7 files changed

Lines changed: 150 additions & 11 deletions

File tree

go/cmd/gh-ost/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ func main() {
134134

135135
maxLagMillis := flag.Int64("max-lag-millis", 1500, "replication lag at which to throttle operation")
136136
replicationLagQuery := flag.String("replication-lag-query", "", "Deprecated. gh-ost uses an internal, subsecond resolution query")
137-
throttleControlReplicas := flag.String("throttle-control-replicas", "", "List of replicas on which to check for lag; comma delimited. Example: myhost1.com:3306,myhost2.com,myhost3.com:3307")
137+
throttleControlReplicas := flag.String("throttle-control-replicas", "", "List of replicas on which to check for lag; comma delimited. Example: myhost1.com:3306,myhost2.com,myhost3.com:3307. In move-tables mode, these replicas are expected to be in the target cluster. Specified target credentials will be used for the connection.")
138138
throttleQuery := flag.String("throttle-query", "", "when given, issued (every second) to check if operation should throttle. Expecting to return zero for no-throttle, >0 for throttle. Query is issued on the migrated server. Make sure this query is lightweight")
139139
throttleHTTP := flag.String("throttle-http", "", "when given, gh-ost checks given URL via HEAD request; any response code other than 200 (OK) causes throttling; make sure it has low latency response")
140140
flag.Int64Var(&migrationContext.ThrottleHTTPIntervalMillis, "throttle-http-interval-millis", 100, "Number of milliseconds to wait before triggering another HTTP throttle check")

go/logic/applier.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1929,8 +1929,12 @@ func (apl *Applier) AtomicCutoverRename(sessionIdChan chan int64, tablesRenamed
19291929
}
19301930

19311931
func (apl *Applier) ShowStatusVariable(variableName string) (result int64, err error) {
1932+
targetDB := apl.db
1933+
if apl.migrationContext.IsMoveTablesMode() {
1934+
targetDB = apl.moveTablesTargetDB
1935+
}
19321936
query := fmt.Sprintf(`show /* gh-ost */ global status like '%s'`, variableName)
1933-
if err := apl.db.QueryRow(query).Scan(&variableName, &result); err != nil {
1937+
if err := targetDB.QueryRow(query).Scan(&variableName, &result); err != nil {
19341938
return 0, err
19351939
}
19361940
return result, nil

go/logic/applier_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2072,6 +2072,62 @@ func (suite *ApplierTestSuite) TestApplyIterationMoveTableCopyQueriesNoRows() {
20722072
suite.Require().Equal(0, count)
20732073
}
20742074

2075+
func (suite *ApplierTestSuite) TestShowStatusVariable() {
2076+
ctx := context.Background()
2077+
2078+
_, err := suite.db.ExecContext(ctx, fmt.Sprintf("CREATE TABLE %s (id INT, item_id INT);", getTestTableName()))
2079+
suite.Require().NoError(err)
2080+
2081+
connectionConfig, err := getTestConnectionConfig(ctx, suite.mysqlContainer)
2082+
suite.Require().NoError(err)
2083+
2084+
migrationContext := newTestMigrationContext()
2085+
migrationContext.ApplierConnectionConfig = connectionConfig
2086+
migrationContext.SetConnectionConfig("innodb")
2087+
2088+
applier := NewApplier(migrationContext)
2089+
defer applier.Teardown()
2090+
2091+
suite.Require().NoError(applier.InitDBConnections())
2092+
2093+
// Uptime is always present in `SHOW GLOBAL STATUS` and is non-negative.
2094+
result, err := applier.ShowStatusVariable("Uptime")
2095+
suite.Require().NoError(err)
2096+
suite.Require().GreaterOrEqual(result, int64(0))
2097+
}
2098+
2099+
func (suite *ApplierTestSuite) TestShowStatusVariableMoveTablesMode() {
2100+
ctx := context.Background()
2101+
2102+
_, err := suite.db.ExecContext(ctx, fmt.Sprintf("CREATE TABLE %s (id INT, item_id INT);", getTestTableName()))
2103+
suite.Require().NoError(err)
2104+
2105+
connectionConfig, err := getTestConnectionConfig(ctx, suite.mysqlContainer)
2106+
suite.Require().NoError(err)
2107+
2108+
migrationContext := newTestMigrationContext()
2109+
migrationContext.ApplierConnectionConfig = connectionConfig
2110+
migrationContext.MoveTables.ConnectionConfig = connectionConfig
2111+
migrationContext.SetConnectionConfig("innodb")
2112+
migrationContext.OriginalTableColumns = sql.NewColumnList([]string{"id", "item_id"})
2113+
migrationContext.MoveTables.TableNames = []string{testMysqlTableName}
2114+
migrationContext.MoveTables.TargetDatabase = testMysqlDatabaseOther
2115+
2116+
applier := NewApplier(migrationContext)
2117+
defer applier.Teardown()
2118+
2119+
suite.Require().NoError(applier.InitDBConnections())
2120+
2121+
// In move-tables mode the status variable must be read from the
2122+
// move-tables target DB connection rather than the applier DB.
2123+
suite.Require().True(migrationContext.IsMoveTablesMode())
2124+
suite.Require().NotNil(applier.moveTablesTargetDB)
2125+
2126+
result, err := applier.ShowStatusVariable("Uptime")
2127+
suite.Require().NoError(err)
2128+
suite.Require().GreaterOrEqual(result, int64(0))
2129+
}
2130+
20752131
func TestApplier(t *testing.T) {
20762132
if testing.Short() {
20772133
t.Skip("skipping applier test suite in short mode")

go/logic/migrator.go

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1809,17 +1809,13 @@ func (mgtr *Migrator) addDMLEventsListener() error {
18091809

18101810
// initiateThrottler kicks in the throttling collection and the throttling checks.
18111811
func (mgtr *Migrator) initiateThrottler() {
1812-
if mgtr.migrationContext.IsMoveTablesMode() {
1813-
// TODO(chriskirkland): throttle against the target cluster
1814-
mgtr.migrationContext.Log.Info("Skipping throttling in move tables mode")
1815-
return
1816-
}
1817-
18181812
mgtr.throttler = NewThrottler(mgtr.migrationContext, mgtr.applier, mgtr.inspector, mgtr.appVersion)
18191813

18201814
go mgtr.throttler.initiateThrottlerCollection(mgtr.firstThrottlingCollected)
18211815
mgtr.migrationContext.Log.Infof("Waiting for first throttle metrics to be collected")
1822-
<-mgtr.firstThrottlingCollected // replication lag
1816+
if !mgtr.migrationContext.IsMoveTablesMode() {
1817+
<-mgtr.firstThrottlingCollected // replication lag
1818+
}
18231819
<-mgtr.firstThrottlingCollected // HTTP status
18241820
<-mgtr.firstThrottlingCollected // other, general metrics
18251821
mgtr.migrationContext.Log.Infof("First throttle metrics collected")

go/logic/throttler.go

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,17 @@ func (thlr *Throttler) collectReplicationLag(firstThrottlingCollected chan<- boo
176176
}
177177
}
178178

179+
// controlReplicaConnectionConfig returns the connection config used to read
180+
// replication lag from a control replica. In move-tables mode the lag is read
181+
// from the target cluster's replicas, otherwise from the source (inspector)
182+
// cluster's replicas.
183+
func (thlr *Throttler) controlReplicaConnectionConfig(replicaKey mysql.InstanceKey) *mysql.ConnectionConfig {
184+
if thlr.migrationContext.IsMoveTablesMode() {
185+
return thlr.migrationContext.MoveTables.ConnectionConfig.DuplicateCredentials(replicaKey)
186+
}
187+
return thlr.migrationContext.InspectorConnectionConfig.DuplicateCredentials(replicaKey)
188+
}
189+
179190
// collectControlReplicasLag polls all the control replicas to get maximum lag value
180191
func (thlr *Throttler) collectControlReplicasLag() {
181192
if atomic.LoadInt64(&thlr.migrationContext.HibernateUntil) > 0 {
@@ -198,6 +209,14 @@ func (thlr *Throttler) collectControlReplicasLag() {
198209
return lag, err
199210
}
200211

212+
if thlr.migrationContext.IsMoveTablesMode() {
213+
dbVersion, err := mysql.GetDBVersion(thlr.migrationContext.Uuid, dbUri)
214+
if err != nil {
215+
return lag, err
216+
}
217+
return mysql.GetReplicationLagFromSlaveStatus(dbVersion, db)
218+
}
219+
201220
if err := db.QueryRow(replicationLagQuery).Scan(&heartbeatValue); err != nil {
202221
return lag, err
203222
}
@@ -213,7 +232,8 @@ func (thlr *Throttler) collectControlReplicasLag() {
213232
}
214233
lagResults := make(chan *mysql.ReplicationLagResult, instanceKeyMap.Len())
215234
for replicaKey := range *instanceKeyMap {
216-
connectionConfig := thlr.migrationContext.InspectorConnectionConfig.DuplicateCredentials(replicaKey)
235+
connectionConfig := thlr.controlReplicaConnectionConfig(replicaKey)
236+
217237
if err := connectionConfig.RegisterTLSConfig(); err != nil {
218238
return &mysql.ReplicationLagResult{Err: err}
219239
}
@@ -443,7 +463,9 @@ func (thlr *Throttler) collectGeneralThrottleMetrics() error {
443463
// that may affect throttling. There are several components, all running independently,
444464
// that collect such metrics.
445465
func (thlr *Throttler) initiateThrottlerCollection(firstThrottlingCollected chan<- bool) {
446-
go thlr.collectReplicationLag(firstThrottlingCollected)
466+
if !thlr.migrationContext.IsMoveTablesMode() {
467+
go thlr.collectReplicationLag(firstThrottlingCollected)
468+
}
447469
go thlr.collectControlReplicasLag()
448470
go thlr.collectThrottleHTTPStatus(firstThrottlingCollected)
449471

go/logic/throttler_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/stretchr/testify/assert"
1414

1515
"github.com/github/gh-ost/go/base"
16+
"github.com/github/gh-ost/go/mysql"
1617
)
1718

1819
func newTestThrottler() *Throttler {
@@ -86,6 +87,40 @@ func TestThrottleReturnsOnContextCancellation(t *testing.T) {
8687
}
8788
}
8889

90+
func TestControlReplicaConnectionConfig(t *testing.T) {
91+
replicaKey := mysql.InstanceKey{Hostname: "replica-host", Port: 3307}
92+
93+
t.Run("uses inspector connection config when not in move-tables mode", func(t *testing.T) {
94+
thlr := newTestThrottler()
95+
thlr.migrationContext.InspectorConnectionConfig.Key = mysql.InstanceKey{Hostname: "source-host", Port: 3306}
96+
thlr.migrationContext.InspectorConnectionConfig.User = "source-user"
97+
thlr.migrationContext.InspectorConnectionConfig.Password = "source-pass"
98+
99+
connectionConfig := thlr.controlReplicaConnectionConfig(replicaKey)
100+
101+
assert.False(t, thlr.migrationContext.IsMoveTablesMode())
102+
assert.Equal(t, replicaKey, connectionConfig.Key)
103+
assert.Equal(t, "source-user", connectionConfig.User)
104+
assert.Equal(t, "source-pass", connectionConfig.Password)
105+
})
106+
107+
t.Run("uses move-tables connection config when in move-tables mode", func(t *testing.T) {
108+
thlr := newTestThrottler()
109+
thlr.migrationContext.MoveTables.TableNames = []string{"my_table"}
110+
thlr.migrationContext.MoveTables.ConnectionConfig = mysql.NewConnectionConfig()
111+
thlr.migrationContext.MoveTables.ConnectionConfig.Key = mysql.InstanceKey{Hostname: "target-host", Port: 3306}
112+
thlr.migrationContext.MoveTables.ConnectionConfig.User = "target-user"
113+
thlr.migrationContext.MoveTables.ConnectionConfig.Password = "target-pass"
114+
115+
connectionConfig := thlr.controlReplicaConnectionConfig(replicaKey)
116+
117+
assert.True(t, thlr.migrationContext.IsMoveTablesMode())
118+
assert.Equal(t, replicaKey, connectionConfig.Key)
119+
assert.Equal(t, "target-user", connectionConfig.User)
120+
assert.Equal(t, "target-pass", connectionConfig.Password)
121+
})
122+
}
123+
89124
func TestThrottleCallsOnThrottledCallback(t *testing.T) {
90125
thlr := newTestThrottler()
91126
thlr.migrationContext.SetThrottled(true, "test", base.NoThrottleReasonHint)

go/mysql/utils.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ func (rlg *ReplicationLagResult) HasLag() bool {
4747
// knownDBs is a DB cache by uri
4848
var knownDBs map[string]*gosql.DB = make(map[string]*gosql.DB)
4949
var knownDBsMutex = &sync.Mutex{}
50+
var knownDBsVersions map[string]string = make(map[string]string)
51+
var knownDBsVersionsMutex = &sync.Mutex{}
5052

5153
func GetDB(migrationUuid string, mysql_uri string) (db *gosql.DB, exists bool, err error) {
5254
cacheKey := migrationUuid + ":" + mysql_uri
@@ -66,6 +68,30 @@ func GetDB(migrationUuid string, mysql_uri string) (db *gosql.DB, exists bool, e
6668
return db, exists, nil
6769
}
6870

71+
// GetDBVersion returns the MySQL version for a given mysql_uri, and caches it for future calls.
72+
// Uses GetDB to get a connection to the database.
73+
func GetDBVersion(migrationUuid string, mysql_uri string) (dbVersion string, err error) {
74+
cacheKey := migrationUuid + ":" + mysql_uri
75+
76+
knownDBsVersionsMutex.Lock()
77+
defer knownDBsVersionsMutex.Unlock()
78+
79+
if dbVersion, exists := knownDBsVersions[cacheKey]; exists {
80+
return dbVersion, nil
81+
}
82+
83+
db, _, err := GetDB(migrationUuid, mysql_uri)
84+
if err != nil {
85+
return "", err
86+
}
87+
var version string
88+
if err := db.QueryRow(`select @@global.version`).Scan(&version); err != nil {
89+
return "", err
90+
}
91+
knownDBsVersions[cacheKey] = version
92+
return version, nil
93+
}
94+
6995
// GetReplicationLagFromSlaveStatus returns replication lag for a given db; via SHOW SLAVE STATUS
7096
func GetReplicationLagFromSlaveStatus(dbVersion string, informationSchemaDb *gosql.DB) (replicationLag time.Duration, err error) {
7197
showReplicaStatusQuery := fmt.Sprintf("show %s", ReplicaTermFor(dbVersion, `slave status`))

0 commit comments

Comments
 (0)