vitess.io/vitess@v0.16.2/go/vt/vttablet/tabletmanager/vreplication/vreplicator.go (about) 1 /* 2 Copyright 2019 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package vreplication 18 19 import ( 20 "encoding/json" 21 "fmt" 22 "math" 23 "sort" 24 "strconv" 25 "strings" 26 "time" 27 28 "vitess.io/vitess/go/timer" 29 "vitess.io/vitess/go/vt/schema" 30 "vitess.io/vitess/go/vt/sqlparser" 31 "vitess.io/vitess/go/vt/vterrors" 32 33 querypb "vitess.io/vitess/go/vt/proto/query" 34 vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc" 35 36 "vitess.io/vitess/go/vt/vtgate/evalengine" 37 38 "context" 39 40 "vitess.io/vitess/go/mysql" 41 "vitess.io/vitess/go/sqltypes" 42 "vitess.io/vitess/go/vt/binlog/binlogplayer" 43 "vitess.io/vitess/go/vt/log" 44 "vitess.io/vitess/go/vt/mysqlctl" 45 46 binlogdatapb "vitess.io/vitess/go/vt/proto/binlogdata" 47 tabletmanagerdatapb "vitess.io/vitess/go/vt/proto/tabletmanagerdata" 48 ) 49 50 var ( 51 // idleTimeout is set to slightly above 1s, compared to heartbeatTime 52 // set by VStreamer at slightly below 1s. This minimizes conflicts 53 // between the two timeouts. 54 idleTimeout = 1100 * time.Millisecond 55 56 dbLockRetryDelay = 1 * time.Second 57 58 // vreplicationMinimumHeartbeatUpdateInterval overrides vreplicationHeartbeatUpdateInterval if the latter is higher than this 59 // to ensure that it satisfies liveness criteria implicitly expected by internal processes like Online DDL 60 vreplicationMinimumHeartbeatUpdateInterval = 60 61 62 vreplicationExperimentalFlagOptimizeInserts int64 = 1 63 ) 64 65 const ( 66 getSQLModeQuery = `SELECT @@session.sql_mode AS sql_mode` 67 // SQLMode should be used whenever performing a schema change as part of a vreplication 68 // workflow to ensure that you set a permissive SQL mode as defined by 69 // VReplication. We follow MySQL's model for recreating database objects 70 // on a target -- using SQL statements generated from a source -- which 71 // ensures that we can recreate them regardless of the sql_mode that was 72 // in effect on the source when it was created: 73 // https://github.com/mysql/mysql-server/blob/3290a66c89eb1625a7058e0ef732432b6952b435/client/mysqldump.cc#L795-L818 74 SQLMode = "NO_AUTO_VALUE_ON_ZERO" 75 StrictSQLMode = "STRICT_ALL_TABLES,NO_AUTO_VALUE_ON_ZERO" 76 setSQLModeQueryf = `SET @@session.sql_mode='%s'` 77 78 sqlCreatePostCopyAction = `insert into _vt.post_copy_action(vrepl_id, table_name, action) 79 values(%a, %a, convert(%a using utf8mb4))` 80 sqlGetPostCopyActions = `select id, action from _vt.post_copy_action where vrepl_id=%a and 81 table_name=%a` 82 // sqlGetPostCopyActionsForTable gets a write lock on all post_copy_action 83 // rows for the table and should only be called from within an explicit 84 // multi-statement transaction in order to hold those locks until the 85 // related work is finished as this is a concurrency control mechanism. 86 sqlGetAndLockPostCopyActionsForTable = `select id, vrepl_id, action from _vt.post_copy_action where id in 87 ( 88 select pca.id from _vt.post_copy_action as pca inner join _vt.vreplication as vr on (pca.vrepl_id = vr.id) 89 where pca.table_name=%a 90 ) for update` 91 sqlGetPostCopyActionTaskByType = `select json_unquote(json_extract(action, '$.task')) as task from _vt.post_copy_action where 92 json_unquote(json_extract(action, '$.type'))=%a and vrepl_id=%a and table_name=%a` 93 sqlDeletePostCopyAction = `delete from _vt.post_copy_action where vrepl_id=%a and 94 table_name=%a and id=%a` 95 ) 96 97 type ComponentName string 98 99 const ( 100 VPlayerComponentName ComponentName = "vplayer" 101 VCopierComponentName ComponentName = "vcopier" 102 VStreamerComponentName ComponentName = "vstreamer" 103 RowStreamerComponentName ComponentName = "rowstreamer" 104 ) 105 106 // vreplicator provides the core logic to start vreplication streams 107 type vreplicator struct { 108 vre *Engine 109 id uint32 110 dbClient *vdbClient 111 // source 112 source *binlogdatapb.BinlogSource 113 sourceVStreamer VStreamerClient 114 state string 115 stats *binlogplayer.Stats 116 // mysqld is used to fetch the local schema. 117 mysqld mysqlctl.MysqlDaemon 118 colInfoMap map[string][]*ColumnInfo 119 120 originalFKCheckSetting int64 121 originalSQLMode string 122 123 WorkflowType int32 124 WorkflowName string 125 126 throttleUpdatesRateLimiter *timer.RateLimiter 127 } 128 129 // newVReplicator creates a new vreplicator. The valid fields from the source are: 130 // Keyspace, Shard, Filter, OnDdl, ExternalMySql and StopAfterCopy. 131 // The Filter consists of Rules. Each Rule has a Match and an (inner) Filter field. 132 // The Match can be a table name or, if it begins with a "/", a wildcard. 133 // The Filter can be empty: get all rows and columns. 134 // The Filter can be a keyrange, like "-80": get all rows that are within the keyrange. 135 // The Filter can be a select expression. Examples. 136 // 137 // "select * from t", same as an empty Filter, 138 // "select * from t where in_keyrange('-80')", same as "-80", 139 // "select * from t where in_keyrange(col1, 'hash', '-80')", 140 // "select col1, col2 from t where...", 141 // "select col1, keyspace_id() as ksid from t where...", 142 // "select id, count(*), sum(price) from t group by id", 143 // "select * from t where customer_id=1 and val = 'newton'". 144 // Only "in_keyrange" expressions, integer and string comparisons are supported in the where clause. 145 // The select expressions can be any valid non-aggregate expressions, 146 // or count(*), or sum(col). 147 // If the target column name does not match the source expression, an 148 // alias like "a+b as targetcol" must be used. 149 // More advanced constructs can be used. Please see the table plan builder 150 // documentation for more info. 151 func newVReplicator(id uint32, source *binlogdatapb.BinlogSource, sourceVStreamer VStreamerClient, stats *binlogplayer.Stats, dbClient binlogplayer.DBClient, mysqld mysqlctl.MysqlDaemon, vre *Engine) *vreplicator { 152 if vreplicationHeartbeatUpdateInterval > vreplicationMinimumHeartbeatUpdateInterval { 153 log.Warningf("The supplied value for vreplication_heartbeat_update_interval:%d seconds is larger than the maximum allowed:%d seconds, vreplication will fallback to %d", 154 vreplicationHeartbeatUpdateInterval, vreplicationMinimumHeartbeatUpdateInterval, vreplicationMinimumHeartbeatUpdateInterval) 155 } 156 return &vreplicator{ 157 vre: vre, 158 id: id, 159 source: source, 160 sourceVStreamer: sourceVStreamer, 161 stats: stats, 162 dbClient: newVDBClient(dbClient, stats), 163 mysqld: mysqld, 164 165 throttleUpdatesRateLimiter: timer.NewRateLimiter(time.Second), 166 } 167 } 168 169 // Replicate starts a vreplication stream. It can be in one of three phases: 170 // 1. Init: If a request is issued with no starting position, we assume that the 171 // contents of the tables must be copied first. During this phase, the list of 172 // tables to be copied is inserted into the copy_state table. A successful insert 173 // gets us out of this phase. 174 // 2. Copy: If the copy_state table has rows, then we are in this phase. During this 175 // phase, we repeatedly invoke copyNext until all the tables are copied. After each 176 // table is successfully copied, it's removed from the copy_state table. We exit this 177 // phase when there are no rows left in copy_state. 178 // 3. Replicate: In this phase, we replicate binlog events indefinitely, unless 179 // a stop position was requested. This phase differs from the Init phase because 180 // there is a replication position. 181 // If a request had a starting position, then we go directly into phase 3. 182 // During these phases, the state of vreplication is reported as 'Init', 'Copying', 183 // or 'Running'. They all mean the same thing. The difference in the phases depends 184 // on the criteria defined above. The different states reported are mainly 185 // informational. The 'Stopped' state is, however, honored. 186 // All phases share the same plan building framework. We leverage the fact the 187 // row representation of a read (during copy) and a binlog event are identical. 188 // However, there are some subtle differences, explained in the plan builder 189 // code. 190 func (vr *vreplicator) Replicate(ctx context.Context) error { 191 err := vr.replicate(ctx) 192 if err != nil { 193 if err := vr.setMessage(err.Error()); err != nil { 194 binlogplayer.LogError("Failed to set error state", err) 195 } 196 } 197 return err 198 } 199 200 func (vr *vreplicator) replicate(ctx context.Context) error { 201 // Manage SQL_MODE in the same way that mysqldump does. 202 // Save the original sql_mode, set it to a permissive mode, 203 // and then reset it back to the original value at the end. 204 resetFunc, err := vr.setSQLMode(ctx, vr.dbClient) 205 defer resetFunc() 206 if err != nil { 207 return err 208 } 209 210 colInfo, err := vr.buildColInfoMap(ctx) 211 if err != nil { 212 return err 213 } 214 vr.colInfoMap = colInfo 215 if err := vr.getSettingFKCheck(); err != nil { 216 return err 217 } 218 //defensive guard, should be a no-op since it should happen after copy is done 219 defer vr.resetFKCheckAfterCopy(vr.dbClient) 220 221 for { 222 select { 223 case <-ctx.Done(): 224 return nil 225 default: 226 } 227 // This rollback is a no-op. It's here for safety 228 // in case the functions below leave transactions open. 229 vr.dbClient.Rollback() 230 231 settings, numTablesToCopy, err := vr.loadSettings(ctx, vr.dbClient) 232 if err != nil { 233 return err 234 } 235 // If any of the operations below changed state to Stopped or Error, we should return. 236 if settings.State == binlogplayer.BlpStopped || settings.State == binlogplayer.BlpError { 237 return nil 238 } 239 switch { 240 case numTablesToCopy != 0: 241 if err := vr.clearFKCheck(vr.dbClient); err != nil { 242 log.Warningf("Unable to clear FK check %v", err) 243 return err 244 } 245 if err := newVCopier(vr).copyNext(ctx, settings); err != nil { 246 vr.stats.ErrorCounts.Add([]string{"Copy"}, 1) 247 return err 248 } 249 settings, numTablesToCopy, err = vr.loadSettings(ctx, vr.dbClient) 250 if err != nil { 251 return err 252 } 253 if numTablesToCopy == 0 { 254 if err := vr.insertLog(LogCopyEnd, fmt.Sprintf("Copy phase completed at gtid %s", settings.StartPos)); err != nil { 255 return err 256 } 257 } 258 case settings.StartPos.IsZero(): 259 if err := newVCopier(vr).initTablesForCopy(ctx); err != nil { 260 vr.stats.ErrorCounts.Add([]string{"Copy"}, 1) 261 return err 262 } 263 default: 264 if err := vr.resetFKCheckAfterCopy(vr.dbClient); err != nil { 265 log.Warningf("Unable to reset FK check %v", err) 266 return err 267 } 268 if vr.source.StopAfterCopy { 269 return vr.setState(binlogplayer.BlpStopped, "Stopped after copy.") 270 } 271 if err := vr.setState(binlogplayer.BlpRunning, ""); err != nil { 272 vr.stats.ErrorCounts.Add([]string{"Replicate"}, 1) 273 return err 274 } 275 return newVPlayer(vr, settings, nil, mysql.Position{}, "replicate").play(ctx) 276 } 277 } 278 } 279 280 // ColumnInfo is used to store charset and collation 281 type ColumnInfo struct { 282 Name string 283 CharSet string 284 Collation string 285 DataType string 286 ColumnType string 287 IsPK bool 288 IsGenerated bool 289 } 290 291 func (vr *vreplicator) buildColInfoMap(ctx context.Context) (map[string][]*ColumnInfo, error) { 292 req := &tabletmanagerdatapb.GetSchemaRequest{Tables: []string{"/.*/"}, ExcludeTables: []string{"/" + schema.GCTableNameExpression + "/"}} 293 schema, err := vr.mysqld.GetSchema(ctx, vr.dbClient.DBName(), req) 294 if err != nil { 295 return nil, err 296 } 297 queryTemplate := "select character_set_name, collation_name, column_name, data_type, column_type, extra from information_schema.columns where table_schema=%s and table_name=%s;" 298 colInfoMap := make(map[string][]*ColumnInfo) 299 for _, td := range schema.TableDefinitions { 300 query := fmt.Sprintf(queryTemplate, encodeString(vr.dbClient.DBName()), encodeString(td.Name)) 301 qr, err := vr.mysqld.FetchSuperQuery(ctx, query) 302 if err != nil { 303 return nil, err 304 } 305 if len(qr.Rows) == 0 { 306 return nil, fmt.Errorf("no data returned from information_schema.columns") 307 } 308 309 var pks []string 310 if len(td.PrimaryKeyColumns) != 0 { 311 // Use the PK 312 pks = td.PrimaryKeyColumns 313 } else { 314 // Use a PK equivalent if one exists 315 if pks, err = vr.mysqld.GetPrimaryKeyEquivalentColumns(ctx, vr.dbClient.DBName(), td.Name); err != nil { 316 return nil, err 317 } 318 // Fall back to using every column in the table if there's no PK or PKE 319 if len(pks) == 0 { 320 pks = td.Columns 321 } 322 } 323 var colInfo []*ColumnInfo 324 for _, row := range qr.Rows { 325 charSet := "" 326 collation := "" 327 columnName := "" 328 isPK := false 329 isGenerated := false 330 var dataType, columnType string 331 columnName = row[2].ToString() 332 var currentField *querypb.Field 333 for _, field := range td.Fields { 334 if field.Name == columnName { 335 currentField = field 336 break 337 } 338 } 339 if currentField == nil { 340 continue 341 } 342 dataType = row[3].ToString() 343 columnType = row[4].ToString() 344 if sqltypes.IsText(currentField.Type) { 345 charSet = row[0].ToString() 346 collation = row[1].ToString() 347 } 348 if dataType == "" || columnType == "" { 349 return nil, fmt.Errorf("no dataType/columnType found in information_schema.columns for table %s, column %s", td.Name, columnName) 350 } 351 for _, pk := range pks { 352 if columnName == pk { 353 isPK = true 354 } 355 } 356 extra := strings.ToLower(row[5].ToString()) 357 if strings.Contains(extra, "stored generated") || strings.Contains(extra, "virtual generated") { 358 isGenerated = true 359 } 360 colInfo = append(colInfo, &ColumnInfo{ 361 Name: columnName, 362 CharSet: charSet, 363 Collation: collation, 364 DataType: dataType, 365 ColumnType: columnType, 366 IsPK: isPK, 367 IsGenerated: isGenerated, 368 }) 369 } 370 colInfoMap[td.Name] = colInfo 371 } 372 return colInfoMap, nil 373 } 374 375 // Same as readSettings, but stores some of the results on this vr. 376 func (vr *vreplicator) loadSettings(ctx context.Context, dbClient *vdbClient) (settings binlogplayer.VRSettings, numTablesToCopy int64, err error) { 377 settings, numTablesToCopy, err = vr.readSettings(ctx, dbClient) 378 if err == nil { 379 vr.WorkflowType = int32(settings.WorkflowType) 380 vr.WorkflowName = settings.WorkflowName 381 } 382 return settings, numTablesToCopy, err 383 } 384 385 func (vr *vreplicator) readSettings(ctx context.Context, dbClient *vdbClient) (settings binlogplayer.VRSettings, numTablesToCopy int64, err error) { 386 settings, err = binlogplayer.ReadVRSettings(dbClient, vr.id) 387 if err != nil { 388 return settings, numTablesToCopy, fmt.Errorf("error reading VReplication settings: %v", err) 389 } 390 391 query := fmt.Sprintf("select count(distinct table_name) from _vt.copy_state where vrepl_id=%d", vr.id) 392 qr, err := vr.dbClient.ExecuteFetch(query, maxRows) 393 if err != nil { 394 return settings, numTablesToCopy, err 395 } 396 if len(qr.Rows) == 0 || len(qr.Rows[0]) == 0 { 397 return settings, numTablesToCopy, fmt.Errorf("unexpected result from %s: %v", query, qr) 398 } 399 numTablesToCopy, err = evalengine.ToInt64(qr.Rows[0][0]) 400 if err != nil { 401 return settings, numTablesToCopy, err 402 } 403 return settings, numTablesToCopy, nil 404 } 405 406 func (vr *vreplicator) setMessage(message string) error { 407 message = binlogplayer.MessageTruncate(message) 408 vr.stats.History.Add(&binlogplayer.StatsHistoryRecord{ 409 Time: time.Now(), 410 Message: message, 411 }) 412 buf := sqlparser.NewTrackedBuffer(nil) 413 buf.Myprintf("update _vt.vreplication set message=%s where id=%s", encodeString(message), strconv.Itoa(int(vr.id))) 414 query := buf.ParsedQuery().Query 415 if _, err := vr.dbClient.Execute(query); err != nil { 416 return fmt.Errorf("could not set message: %v: %v", query, err) 417 } 418 if err := insertLog(vr.dbClient, LogMessage, vr.id, vr.state, message); err != nil { 419 return err 420 } 421 return nil 422 } 423 424 func (vr *vreplicator) insertLog(typ, message string) error { 425 return insertLog(vr.dbClient, typ, vr.id, vr.state, message) 426 } 427 428 func (vr *vreplicator) setState(state, message string) error { 429 if message != "" { 430 vr.stats.History.Add(&binlogplayer.StatsHistoryRecord{ 431 Time: time.Now(), 432 Message: message, 433 }) 434 } 435 vr.stats.State.Set(state) 436 query := fmt.Sprintf("update _vt.vreplication set state='%v', message=%v where id=%v", state, encodeString(binlogplayer.MessageTruncate(message)), vr.id) 437 if _, err := vr.dbClient.ExecuteFetch(query, 1); err != nil { 438 return fmt.Errorf("could not set state: %v: %v", query, err) 439 } 440 if state == vr.state { 441 return nil 442 } 443 if err := insertLog(vr.dbClient, LogStateChange, vr.id, state, message); err != nil { 444 return err 445 } 446 vr.state = state 447 448 return nil 449 } 450 451 func encodeString(in string) string { 452 var buf strings.Builder 453 sqltypes.NewVarChar(in).EncodeSQL(&buf) 454 return buf.String() 455 } 456 457 func (vr *vreplicator) getSettingFKCheck() error { 458 qr, err := vr.dbClient.Execute("select @@foreign_key_checks;") 459 if err != nil { 460 return err 461 } 462 if len(qr.Rows) != 1 || len(qr.Fields) != 1 { 463 return fmt.Errorf("unable to select @@foreign_key_checks") 464 } 465 vr.originalFKCheckSetting, err = evalengine.ToInt64(qr.Rows[0][0]) 466 if err != nil { 467 return err 468 } 469 return nil 470 } 471 472 func (vr *vreplicator) resetFKCheckAfterCopy(dbClient *vdbClient) error { 473 _, err := dbClient.Execute(fmt.Sprintf("set foreign_key_checks=%d;", vr.originalFKCheckSetting)) 474 return err 475 } 476 477 func (vr *vreplicator) setSQLMode(ctx context.Context, dbClient *vdbClient) (func(), error) { 478 resetFunc := func() {} 479 // First save the original SQL mode if we have not already done so 480 if vr.originalSQLMode == "" { 481 res, err := dbClient.Execute(getSQLModeQuery) 482 if err != nil || len(res.Rows) != 1 { 483 return resetFunc, fmt.Errorf("could not get the original sql_mode on target: %v", err) 484 } 485 vr.originalSQLMode = res.Named().Row().AsString("sql_mode", "") 486 } 487 488 // Create a callback function for resetting the original 489 // SQL mode back at the end of the vreplication operation. 490 // You should defer this callback wherever you call setSQLMode() 491 resetFunc = func() { 492 query := fmt.Sprintf(setSQLModeQueryf, vr.originalSQLMode) 493 _, err := dbClient.Execute(query) 494 if err != nil { 495 log.Warningf("Could not reset sql_mode on target using %s: %v", query, err) 496 } 497 } 498 vreplicationSQLMode := SQLMode 499 settings, _, err := vr.readSettings(ctx, dbClient) 500 if err != nil { 501 return resetFunc, err 502 } 503 if settings.WorkflowType == int32(binlogdatapb.VReplicationWorkflowType_OnlineDDL) { 504 vreplicationSQLMode = StrictSQLMode 505 } 506 507 // Now set it to a permissive mode that will allow us to recreate 508 // any database object that exists on the source in full on the 509 // target 510 query := fmt.Sprintf(setSQLModeQueryf, vreplicationSQLMode) 511 if _, err := dbClient.Execute(query); err != nil { 512 return resetFunc, fmt.Errorf("could not set the permissive sql_mode on target using %s: %v", query, err) 513 } 514 515 return resetFunc, nil 516 } 517 518 // throttlerAppName returns the app name to be used by throttlerClient for this particular workflow 519 // example results: 520 // - "vreplication" for most flows 521 // - "vreplication:online-ddl" for online ddl flows. 522 // Note that with such name, it's possible to throttle 523 // the worflow by either /throttler/throttle-app?app=vreplication and/or /throttler/throttle-app?app=online-ddl 524 // This is useful when we want to throttle all migrations. We throttle "online-ddl" and that applies to both vreplication 525 // migrations as well as gh-ost migrations. 526 func (vr *vreplicator) throttlerAppName() string { 527 names := []string{vr.WorkflowName, throttlerVReplicationAppName} 528 if vr.WorkflowType == int32(binlogdatapb.VReplicationWorkflowType_OnlineDDL) { 529 names = append(names, throttlerOnlineDDLAppName) 530 } 531 return strings.Join(names, ":") 532 } 533 534 func (vr *vreplicator) updateTimeThrottled(componentThrottled ComponentName) error { 535 err := vr.throttleUpdatesRateLimiter.Do(func() error { 536 tm := time.Now().Unix() 537 update, err := binlogplayer.GenerateUpdateTimeThrottled(vr.id, tm, string(componentThrottled)) 538 if err != nil { 539 return err 540 } 541 if _, err := vr.dbClient.ExecuteFetch(update, maxRows); err != nil { 542 return fmt.Errorf("error %v updating time throttled", err) 543 } 544 return nil 545 }) 546 return err 547 } 548 549 func (vr *vreplicator) updateHeartbeatTime(tm int64) error { 550 update, err := binlogplayer.GenerateUpdateHeartbeat(vr.id, tm) 551 if err != nil { 552 return err 553 } 554 if _, err := vr.dbClient.ExecuteFetch(update, maxRows); err != nil { 555 return fmt.Errorf("error %v updating time", err) 556 } 557 return nil 558 } 559 560 func (vr *vreplicator) clearFKCheck(dbClient *vdbClient) error { 561 _, err := dbClient.Execute("set foreign_key_checks=0;") 562 return err 563 } 564 565 func recalculatePKColsInfoByColumnNames(uniqueKeyColumnNames []string, colInfos []*ColumnInfo) (pkColInfos []*ColumnInfo) { 566 pkColInfos = colInfos[:] 567 columnOrderMap := map[string]int64{} 568 for _, colInfo := range pkColInfos { 569 columnOrderMap[colInfo.Name] = math.MaxInt64 570 } 571 572 isPKMap := map[string]bool{} 573 for i, colName := range uniqueKeyColumnNames { 574 columnOrderMap[colName] = int64(i) 575 isPKMap[colName] = true 576 } 577 sort.SliceStable(pkColInfos, func(i, j int) bool { return columnOrderMap[pkColInfos[i].Name] < columnOrderMap[pkColInfos[j].Name] }) 578 for i := range pkColInfos { 579 pkColInfos[i].IsPK = isPKMap[pkColInfos[i].Name] 580 } 581 return pkColInfos 582 } 583 584 // stashSecondaryKeys temporarily DROPs all secondary keys from the table schema 585 // and stashes an ALTER TABLE statement that will be used to recreate them at the 586 // end of the copy phase. 587 func (vr *vreplicator) stashSecondaryKeys(ctx context.Context, tableName string) error { 588 if !vr.supportsDeferredSecondaryKeys() { 589 return fmt.Errorf("deferring secondary key creation is not supported for %s workflows", 590 binlogdatapb.VReplicationWorkflowType_name[vr.WorkflowType]) 591 } 592 secondaryKeys, err := vr.getTableSecondaryKeys(ctx, tableName) 593 if err != nil { 594 return err 595 } 596 if len(secondaryKeys) > 0 { 597 alterDrop := &sqlparser.AlterTable{ 598 Table: sqlparser.TableName{ 599 Qualifier: sqlparser.NewIdentifierCS(vr.dbClient.DBName()), 600 Name: sqlparser.NewIdentifierCS(tableName), 601 }, 602 } 603 alterReAdd := &sqlparser.AlterTable{ 604 Table: sqlparser.TableName{ 605 Qualifier: sqlparser.NewIdentifierCS(vr.dbClient.DBName()), 606 Name: sqlparser.NewIdentifierCS(tableName), 607 }, 608 } 609 for _, secondaryKey := range secondaryKeys { 610 // Primary should never happen. Fulltext keys are 611 // not supported for deferral and retained during 612 // the copy phase as they have some unique 613 // behaviors and constraints: 614 // - Adding a fulltext key requires a full table 615 // rebuild to add the internal FTS_DOC_ID field 616 // to each record. 617 // - You can not add/remove multiple fulltext keys 618 // in a single ALTER statement. 619 if secondaryKey.Info.Primary || secondaryKey.Info.Fulltext { 620 continue 621 } 622 alterDrop.AlterOptions = append(alterDrop.AlterOptions, 623 &sqlparser.DropKey{ 624 Name: secondaryKey.Info.Name, 625 Type: sqlparser.NormalKeyType, 626 }, 627 ) 628 alterReAdd.AlterOptions = append(alterReAdd.AlterOptions, 629 &sqlparser.AddIndexDefinition{ 630 IndexDefinition: secondaryKey, 631 }, 632 ) 633 } 634 action, err := json.Marshal(PostCopyAction{ 635 Type: PostCopyActionSQL, 636 Task: sqlparser.String(alterReAdd), 637 }) 638 if err != nil { 639 return err 640 } 641 insert, err := sqlparser.ParseAndBind(sqlCreatePostCopyAction, sqltypes.Uint32BindVariable(vr.id), 642 sqltypes.StringBindVariable(tableName), sqltypes.StringBindVariable(string(action))) 643 if err != nil { 644 return err 645 } 646 // Use a new DB client to avoid interfering with open transactions 647 // in the shared client as DDL includes an implied commit. 648 // We're also NOT using a DBA connection here because we want to 649 // be sure that the commit fails if the instance is somehow in 650 // READ-ONLY mode. 651 dbClient, err := vr.newClientConnection(ctx) 652 if err != nil { 653 log.Errorf("Unable to connect to the database when saving secondary keys for deferred creation on the %q table in the %q VReplication workflow: %v", 654 tableName, vr.WorkflowName, err) 655 return vterrors.Wrap(err, "unable to connect to the database when saving secondary keys for deferred creation") 656 } 657 defer dbClient.Close() 658 if _, err := dbClient.ExecuteFetch(insert, 1); err != nil { 659 return err 660 } 661 if _, err := dbClient.ExecuteFetch(sqlparser.String(alterDrop), 1); err != nil { 662 // If they've already been dropped, e.g. by another controller running on the tablet 663 // when doing a shard merge, then we can ignore the error. 664 if sqlErr, ok := err.(*mysql.SQLError); ok && sqlErr.Num == mysql.ERCantDropFieldOrKey { 665 secondaryKeys, err := vr.getTableSecondaryKeys(ctx, tableName) 666 if err == nil && len(secondaryKeys) == 0 { 667 return nil 668 } 669 } 670 return err 671 } 672 } 673 674 return nil 675 } 676 677 func (vr *vreplicator) getTableSecondaryKeys(ctx context.Context, tableName string) ([]*sqlparser.IndexDefinition, error) { 678 req := &tabletmanagerdatapb.GetSchemaRequest{Tables: []string{tableName}} 679 schema, err := vr.mysqld.GetSchema(ctx, vr.dbClient.DBName(), req) 680 if err != nil { 681 return nil, err 682 } 683 // schema should never be nil, but check to be extra safe. 684 if schema == nil || len(schema.TableDefinitions) != 1 { 685 return nil, fmt.Errorf("unexpected number of table definitions returned from GetSchema call for table %q: %d", 686 tableName, len(schema.TableDefinitions)) 687 } 688 tableSchema := schema.TableDefinitions[0].Schema 689 var secondaryKeys []*sqlparser.IndexDefinition 690 parsedDDL, err := sqlparser.ParseStrictDDL(tableSchema) 691 if err != nil { 692 return secondaryKeys, err 693 } 694 createTable, ok := parsedDDL.(*sqlparser.CreateTable) 695 // createTable or createTable.TableSpec should never be nil 696 // if it was a valid cast, but check to be extra safe. 697 if !ok || createTable == nil || createTable.GetTableSpec() == nil { 698 return nil, fmt.Errorf("could not determine CREATE TABLE statement from table schema %q", tableSchema) 699 } 700 701 for _, index := range createTable.GetTableSpec().Indexes { 702 if !index.Info.Primary { 703 secondaryKeys = append(secondaryKeys, index) 704 } 705 } 706 return secondaryKeys, err 707 } 708 709 func (vr *vreplicator) execPostCopyActions(ctx context.Context, tableName string) error { 710 defer vr.stats.PhaseTimings.Record("postCopyActions", time.Now()) 711 712 // Use a new DB client to avoid interfering with open transactions 713 // in the shared client as DDL includes an implied commit. 714 // We're also NOT using a DBA connection here because we want to be 715 // sure that the work fails if the instance is somehow in READ-ONLY 716 // mode. 717 dbClient, err := vr.newClientConnection(ctx) 718 if err != nil { 719 log.Errorf("Unable to connect to the database when executing post copy actions on the %q table in the %q VReplication workflow: %v", 720 tableName, vr.WorkflowName, err) 721 return vterrors.Wrap(err, "unable to connect to the database when executing post copy actions") 722 } 723 defer dbClient.Close() 724 725 query, err := sqlparser.ParseAndBind(sqlGetPostCopyActions, sqltypes.Uint32BindVariable(vr.id), 726 sqltypes.StringBindVariable(tableName)) 727 if err != nil { 728 return err 729 } 730 qr, err := dbClient.ExecuteFetch(query, -1) 731 if err != nil { 732 return err 733 } 734 // qr should never be nil, but check anyway to be extra safe. 735 if qr == nil || len(qr.Rows) == 0 { 736 return nil 737 } 738 739 if err := vr.insertLog(LogCopyStart, fmt.Sprintf("Executing %d post copy action(s) for %s table", 740 len(qr.Rows), tableName)); err != nil { 741 return err 742 } 743 744 // Save our connection ID so we can use it to easily KILL any 745 // running SQL action we may perform later if needed. 746 idqr, err := dbClient.ExecuteFetch("select connection_id()", 1) 747 if err != nil { 748 return err 749 } 750 // qr should never be nil, but check anyway to be extra safe. 751 if idqr == nil || len(idqr.Rows) != 1 { 752 return fmt.Errorf("unexpected number of rows returned (%d) from connection_id() query", len(idqr.Rows)) 753 } 754 connID, err := idqr.Rows[0][0].ToUint64() 755 if err != nil || connID == 0 { 756 return fmt.Errorf("unexpected result (%d) from connection_id() query, error: %v", connID, err) 757 } 758 759 deleteAction := func(dbc *vdbClient, id int64, vid uint32, tn string) error { 760 delq, err := sqlparser.ParseAndBind(sqlDeletePostCopyAction, sqltypes.Uint32BindVariable(vid), 761 sqltypes.StringBindVariable(tn), sqltypes.Int64BindVariable(id)) 762 if err != nil { 763 return err 764 } 765 if _, err := dbc.ExecuteFetch(delq, 1); err != nil { 766 return fmt.Errorf("failed to delete post copy action for the %q table with id %d: %v", 767 tableName, id, err) 768 } 769 return nil 770 } 771 772 // This could take hours so we start a monitoring goroutine to 773 // listen for context cancellation, which would indicate that 774 // the controller is stopping due to engine shutdown (tablet 775 // shutdown or transition). If that happens we attempt to KILL 776 // the running ALTER statement using a DBA connection. 777 // If we don't do this then we could e.g. cause a PRS to fail as 778 // the running ALTER will block setting [super_]read_only. 779 // A failed/killed ALTER will be tried again when the copy 780 // phase starts up again on the (new) PRIMARY. 781 var action PostCopyAction 782 done := make(chan struct{}) 783 defer close(done) 784 killAction := func(ak PostCopyAction) error { 785 // If we're executing an SQL query then KILL the 786 // connection being used to execute it. 787 if ak.Type == PostCopyActionSQL { 788 if connID < 1 { 789 return fmt.Errorf("invalid connection ID found (%d) when attempting to kill %q", connID, ak.Task) 790 } 791 killdbc := vr.vre.dbClientFactoryDba() 792 if err := killdbc.Connect(); err != nil { 793 return fmt.Errorf("unable to connect to the database when attempting to kill %q: %v", ak.Task, err) 794 } 795 defer killdbc.Close() 796 _, err = killdbc.ExecuteFetch(fmt.Sprintf("kill %d", connID), 1) 797 return err 798 } 799 // We may support non-SQL actions in the future. 800 return nil 801 } 802 go func() { 803 select { 804 // Only cancel an ongoing ALTER if the engine is closing. 805 case <-vr.vre.ctx.Done(): 806 log.Infof("Copy of the %q table stopped when performing the following post copy action in the %q VReplication workflow: %+v", 807 tableName, vr.WorkflowName, action) 808 if err := killAction(action); err != nil { 809 log.Errorf("Failed to kill post copy action on the %q table in the %q VReplication workflow: %v", 810 tableName, vr.WorkflowName, err) 811 } 812 return 813 case <-done: 814 // We're done, so no longer need to listen for cancellation. 815 return 816 } 817 }() 818 819 for _, row := range qr.Named().Rows { 820 select { 821 // Stop any further actions if the vreplicator's context is 822 // cancelled -- most likely due to hitting the 823 // vreplication_copy_phase_duration 824 case <-ctx.Done(): 825 return vterrors.Errorf(vtrpcpb.Code_CANCELED, "context has expired") 826 default: 827 } 828 id, err := row["id"].ToInt64() 829 if err != nil { 830 return err 831 } 832 action = PostCopyAction{} 833 actionBytes, err := row["action"].ToBytes() 834 if err != nil { 835 return err 836 } 837 if err := json.Unmarshal(actionBytes, &action); err != nil { 838 return err 839 } 840 841 // There can be multiple vreplicators/controllers running on 842 // the same tablet for the same table e.g. when doing shard 843 // merges. Let's check for that and if there are still others 844 // that have not finished the copy phase for the table, with 845 // the same action, then we skip executing it as an individual 846 // action on a table should only be done by the last vreplicator 847 // to finish. We use a transaction because we select matching 848 // rows with FOR UPDATE in order to serialize the execution of 849 // the post copy actions for the same workflow and table. 850 // This ensures that the actions are only performed once after 851 // all streams have completed the copy phase for the table. 852 redundant := false 853 _, err = dbClient.ExecuteFetch("start transaction", 1) 854 if err != nil { 855 return err 856 } 857 vrsq, err := sqlparser.ParseAndBind(sqlGetAndLockPostCopyActionsForTable, sqltypes.StringBindVariable(tableName)) 858 if err != nil { 859 return err 860 } 861 vrsres, err := dbClient.ExecuteFetch(vrsq, -1) 862 if err != nil { 863 return fmt.Errorf("failed to get post copy actions for the %q table: %v", tableName, err) 864 } 865 if vrsres != nil && len(vrsres.Rows) > 1 { 866 // We have more than one planned post copy action on the table. 867 for _, row := range vrsres.Named().Rows { 868 vrid, err := row["vrepl_id"].ToUint64() 869 if err != nil { 870 return err 871 } 872 ctlaction := row["action"].ToString() 873 // Let's make sure that it's a different controller/vreplicator 874 // and that the action is the same. 875 if uint32(vrid) != vr.id && strings.EqualFold(ctlaction, string(actionBytes)) { 876 // We know that there's another controller/vreplicator yet 877 // to finish its copy phase for the table and it will perform 878 // the same action on the same table when it completes, so we 879 // skip doing the action and simply delete our action record 880 // to mark this controller/vreplicator's post copy action work 881 // as being done for the table before it finishes the copy 882 // phase for the table. 883 if err := deleteAction(dbClient, id, vr.id, tableName); err != nil { 884 return err 885 } 886 redundant = true 887 break 888 } 889 } 890 } 891 _, err = dbClient.ExecuteFetch("commit", 1) 892 if err != nil { 893 return err 894 } 895 if redundant { 896 // Skip this action as it will be executed by a later vreplicator. 897 continue 898 } 899 900 switch action.Type { 901 case PostCopyActionSQL: 902 log.Infof("Executing post copy SQL action on the %q table in the %q VReplication workflow: %s", 903 tableName, vr.WorkflowName, action.Task) 904 // This will return an io.EOF / MySQL CRServerLost (errno 2013) 905 // error if it is killed by the monitoring goroutine. 906 if _, err := dbClient.ExecuteFetch(action.Task, -1); err != nil { 907 failedAlterErr := err 908 // It's possible that we previously executed the ALTER but 909 // the subsequent DELETE of the post_copy_action record failed. 910 // For example, the context could be cancelled in-between. 911 // It's also possible that the user has modified the schema on 912 // the target side. 913 // If we get a duplicate key/index error then let's see if the 914 // index definitions that we would have added already exist in 915 // the table schema and if so move forward and delete the 916 // post_copy_action record. 917 if sqlErr, ok := err.(*mysql.SQLError); ok && sqlErr.Number() == mysql.ERDupKeyName { 918 stmt, err := sqlparser.ParseStrictDDL(action.Task) 919 if err != nil { 920 return failedAlterErr 921 } 922 alterStmt, ok := stmt.(*sqlparser.AlterTable) 923 if !ok { 924 return failedAlterErr 925 } 926 currentSKs, err := vr.getTableSecondaryKeys(ctx, tableName) 927 if err != nil { 928 return failedAlterErr 929 } 930 if len(currentSKs) < len(alterStmt.AlterOptions) { 931 return failedAlterErr 932 } 933 for _, alterOption := range alterStmt.AlterOptions { 934 addKey, ok := alterOption.(*sqlparser.AddIndexDefinition) 935 if !ok { 936 return failedAlterErr 937 } 938 found := false 939 for _, currentSK := range currentSKs { 940 if sqlparser.Equals.RefOfIndexDefinition(addKey.IndexDefinition, currentSK) { 941 found = true 942 break 943 } 944 } 945 if !found { 946 return failedAlterErr 947 } 948 } 949 // All of the keys we wanted to add in the ALTER already 950 // exist in the live table schema. 951 } else { 952 return failedAlterErr 953 } 954 } 955 if err := deleteAction(dbClient, id, vr.id, tableName); err != nil { 956 return err 957 } 958 default: 959 return fmt.Errorf("unsupported post copy action type: %v", action.Type) 960 } 961 } 962 963 if err := vr.insertLog(LogCopyStart, fmt.Sprintf("Completed all post copy actions for %s table", 964 tableName)); err != nil { 965 return err 966 } 967 968 return nil 969 } 970 971 // supportsDeferredSecondaryKeys tells you if related work should be done 972 // for the workflow. Deferring secondary index generation is only supported 973 // with MoveTables, Migrate, and Reshard. 974 func (vr *vreplicator) supportsDeferredSecondaryKeys() bool { 975 return vr.WorkflowType == int32(binlogdatapb.VReplicationWorkflowType_MoveTables) || 976 vr.WorkflowType == int32(binlogdatapb.VReplicationWorkflowType_Migrate) || 977 vr.WorkflowType == int32(binlogdatapb.VReplicationWorkflowType_Reshard) 978 } 979 980 func (vr *vreplicator) newClientConnection(ctx context.Context) (*vdbClient, error) { 981 dbc := vr.vre.dbClientFactoryFiltered() 982 if err := dbc.Connect(); err != nil { 983 return nil, vterrors.Wrap(err, "can't connect to database") 984 } 985 dbClient := newVDBClient(dbc, vr.stats) 986 if _, err := vr.setSQLMode(ctx, dbClient); err != nil { 987 return nil, vterrors.Wrap(err, "failed to set sql_mode") 988 } 989 if err := vr.clearFKCheck(dbClient); err != nil { 990 return nil, vterrors.Wrap(err, "failed to clear foreign key check") 991 } 992 return dbClient, nil 993 }