vitess.io/vitess@v0.16.2/go/vt/vttablet/tabletmanager/vreplication/vcopier.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 "context" 21 "fmt" 22 "io" 23 "math" 24 "strconv" 25 "strings" 26 "time" 27 28 "google.golang.org/protobuf/encoding/prototext" 29 "google.golang.org/protobuf/proto" 30 31 "vitess.io/vitess/go/bytes2" 32 "vitess.io/vitess/go/mysql" 33 "vitess.io/vitess/go/pools" 34 "vitess.io/vitess/go/sqltypes" 35 "vitess.io/vitess/go/vt/binlog/binlogplayer" 36 "vitess.io/vitess/go/vt/log" 37 binlogdatapb "vitess.io/vitess/go/vt/proto/binlogdata" 38 querypb "vitess.io/vitess/go/vt/proto/query" 39 vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc" 40 "vitess.io/vitess/go/vt/sqlparser" 41 "vitess.io/vitess/go/vt/vterrors" 42 ) 43 44 type vcopier struct { 45 vr *vreplicator 46 throttlerAppName string 47 } 48 49 // vcopierCopyTask stores the args and lifecycle hooks of a copy task. 50 type vcopierCopyTask struct { 51 args *vcopierCopyTaskArgs 52 lifecycle *vcopierCopyTaskLifecycle 53 } 54 55 // vcopierCopyTaskArgs stores the input of a copy task. 56 type vcopierCopyTaskArgs struct { 57 lastpk *querypb.Row 58 rows []*querypb.Row 59 } 60 61 // vcopierCopyTaskHooks contains callback functions to be triggered as a copy 62 // task progresses through in-progress phases of its lifecycle. 63 type vcopierCopyTaskHooks struct { 64 fns []func(context.Context, *vcopierCopyTaskArgs) error 65 } 66 67 // vcopierCopyTaskLifecycle can be used to inject additional behaviors into the 68 // vcopierCopyTask execution. 69 // 70 // It contains two types of hooks. In-progress hooks (simply called "hooks") 71 // which can be registered before or after various phases of the copy task, 72 // such as "insert row", "commit", etc. Result hooks are used to register 73 // callbacks to be triggered when a task is "done" (= canceled, completed, 74 // failed). 75 type vcopierCopyTaskLifecycle struct { 76 hooks map[string]*vcopierCopyTaskHooks 77 resultHooks *vcopierCopyTaskResultHooks 78 } 79 80 // vcopierCopyTaskResult contains information about a task that is done. 81 type vcopierCopyTaskResult struct { 82 args *vcopierCopyTaskArgs 83 err error 84 startedAt time.Time 85 state vcopierCopyTaskState 86 } 87 88 // vcopierCopyTaskHooks contains callback functions to be triggered when a copy 89 // reaches a "done" state (= canceled, completed, failed). 90 type vcopierCopyTaskResultHooks struct { 91 fns []func(context.Context, *vcopierCopyTaskResult) 92 } 93 94 // vcopierCopyTaskState marks the states and sub-states that a copy task goes 95 // through. 96 // 97 // 1. Pending 98 // 2. Begin 99 // 3. Insert rows 100 // 4. Insert copy state 101 // 5. Commit 102 // 6. One of: 103 // - Complete 104 // - Cancel 105 // - Fail 106 type vcopierCopyTaskState int 107 108 const ( 109 vcopierCopyTaskPending vcopierCopyTaskState = iota 110 vcopierCopyTaskBegin 111 vcopierCopyTaskInsertRows 112 vcopierCopyTaskInsertCopyState 113 vcopierCopyTaskCommit 114 vcopierCopyTaskCancel 115 vcopierCopyTaskComplete 116 vcopierCopyTaskFail 117 ) 118 119 // vcopierCopyWorkQueue accepts tasks via Enqueue, and distributes those tasks 120 // concurrently (or synchronously) to internal workers. 121 type vcopierCopyWorkQueue struct { 122 concurrent bool 123 isOpen bool 124 maxDepth int 125 workerFactory func(context.Context) (*vcopierCopyWorker, error) 126 workerPool *pools.ResourcePool 127 } 128 129 // vcopierCopyWorker will Execute a single task at a time in the calling 130 // goroutine. 131 type vcopierCopyWorker struct { 132 *vdbClient 133 closeDbClient bool 134 copyStateInsert *sqlparser.ParsedQuery 135 isOpen bool 136 pkfields []*querypb.Field 137 sqlbuffer bytes2.Buffer 138 tablePlan *TablePlan 139 } 140 141 func newVCopier(vr *vreplicator) *vcopier { 142 return &vcopier{ 143 vr: vr, 144 throttlerAppName: vr.throttlerAppName(), 145 } 146 } 147 148 func newVCopierCopyTask(args *vcopierCopyTaskArgs) *vcopierCopyTask { 149 return &vcopierCopyTask{ 150 args: args, 151 lifecycle: newVCopierCopyTaskLifecycle(), 152 } 153 } 154 155 func newVCopierCopyTaskArgs(rows []*querypb.Row, lastpk *querypb.Row) *vcopierCopyTaskArgs { 156 return &vcopierCopyTaskArgs{ 157 rows: rows, 158 lastpk: lastpk, 159 } 160 } 161 162 func newVCopierCopyTaskHooks() *vcopierCopyTaskHooks { 163 return &vcopierCopyTaskHooks{ 164 fns: make([]func(context.Context, *vcopierCopyTaskArgs) error, 0), 165 } 166 } 167 168 func newVCopierCopyTaskLifecycle() *vcopierCopyTaskLifecycle { 169 return &vcopierCopyTaskLifecycle{ 170 hooks: make(map[string]*vcopierCopyTaskHooks), 171 resultHooks: newVCopierCopyTaskResultHooks(), 172 } 173 } 174 175 func newVCopierCopyTaskResult(args *vcopierCopyTaskArgs, startedAt time.Time, state vcopierCopyTaskState, err error) *vcopierCopyTaskResult { 176 return &vcopierCopyTaskResult{ 177 args: args, 178 err: err, 179 startedAt: startedAt, 180 state: state, 181 } 182 } 183 184 func newVCopierCopyTaskResultHooks() *vcopierCopyTaskResultHooks { 185 return &vcopierCopyTaskResultHooks{ 186 fns: make([]func(context.Context, *vcopierCopyTaskResult), 0), 187 } 188 } 189 190 func newVCopierCopyWorkQueue( 191 concurrent bool, 192 maxDepth int, 193 workerFactory func(ctx context.Context) (*vcopierCopyWorker, error), 194 ) *vcopierCopyWorkQueue { 195 maxDepth = int(math.Max(float64(maxDepth), 1)) 196 return &vcopierCopyWorkQueue{ 197 concurrent: concurrent, 198 maxDepth: maxDepth, 199 workerFactory: workerFactory, 200 } 201 } 202 203 func newVCopierCopyWorker( 204 closeDbClient bool, 205 vdbClient *vdbClient, 206 ) *vcopierCopyWorker { 207 return &vcopierCopyWorker{ 208 closeDbClient: closeDbClient, 209 vdbClient: vdbClient, 210 } 211 } 212 213 // initTablesForCopy (phase 1) identifies the list of tables to be copied and inserts 214 // them into copy_state. If there are no tables to copy, it explicitly stops 215 // the stream. Otherwise, the copy phase (phase 2) may think that all tables are copied. 216 // This will cause us to go into the replication phase (phase 3) without a starting position. 217 func (vc *vcopier) initTablesForCopy(ctx context.Context) error { 218 defer vc.vr.dbClient.Rollback() 219 220 plan, err := buildReplicatorPlan(vc.vr.source, vc.vr.colInfoMap, nil, vc.vr.stats) 221 if err != nil { 222 return err 223 } 224 if err := vc.vr.dbClient.Begin(); err != nil { 225 return err 226 } 227 // Insert the table list only if at least one table matches. 228 if len(plan.TargetTables) != 0 { 229 var buf strings.Builder 230 buf.WriteString("insert into _vt.copy_state(vrepl_id, table_name) values ") 231 prefix := "" 232 for name := range plan.TargetTables { 233 fmt.Fprintf(&buf, "%s(%d, %s)", prefix, vc.vr.id, encodeString(name)) 234 prefix = ", " 235 } 236 if _, err := vc.vr.dbClient.Execute(buf.String()); err != nil { 237 return err 238 } 239 if err := vc.vr.setState(binlogplayer.VReplicationCopying, ""); err != nil { 240 return err 241 } 242 if err := vc.vr.insertLog(LogCopyStart, fmt.Sprintf("Copy phase started for %d table(s)", 243 len(plan.TargetTables))); err != nil { 244 return err 245 } 246 247 if vc.vr.supportsDeferredSecondaryKeys() { 248 settings, err := binlogplayer.ReadVRSettings(vc.vr.dbClient, vc.vr.id) 249 if err != nil { 250 return err 251 } 252 if settings.DeferSecondaryKeys { 253 if err := vc.vr.insertLog(LogCopyStart, fmt.Sprintf("Copy phase temporarily dropping secondary keys for %d table(s)", 254 len(plan.TargetTables))); err != nil { 255 return err 256 } 257 for name := range plan.TargetTables { 258 if err := vc.vr.stashSecondaryKeys(ctx, name); err != nil { 259 return err 260 } 261 } 262 if err := vc.vr.insertLog(LogCopyStart, 263 fmt.Sprintf("Copy phase finished dropping secondary keys and saving post copy actions to restore them for %d table(s)", 264 len(plan.TargetTables))); err != nil { 265 return err 266 } 267 } 268 } 269 } else { 270 if err := vc.vr.setState(binlogplayer.BlpStopped, "There is nothing to replicate"); err != nil { 271 return err 272 } 273 } 274 return vc.vr.dbClient.Commit() 275 } 276 277 // copyNext performs a multi-step process on each iteration. 278 // Step 1: catchup: During this step, it replicates from the source from the last position. 279 // This is a partial replication: events are applied only to tables or subsets of tables 280 // that have already been copied. This goes on until replication catches up. 281 // Step 2: Start streaming. This returns the initial field info along with the GTID 282 // as of which the snapshot is being streamed. 283 // Step 3: fastForward: The target is fast-forwarded to the GTID obtained. This should 284 // be quick because we were mostly caught up as of step 1. This ensures that the 285 // snapshot of the rows are consistent with the position where the target stopped. 286 // Step 4: copy rows: Copy the next set of rows from the stream that was started in Step 2. 287 // This goes on until all rows are copied, or a timeout. In both cases, copyNext 288 // returns, and the replicator decides whether to invoke copyNext again, or to 289 // go to the next phase if all the copying is done. 290 // Steps 2, 3 and 4 are performed by copyTable. 291 // copyNext also builds the copyState metadata that contains the tables and their last 292 // primary key that was copied. A nil Result means that nothing has been copied. 293 // A table that was fully copied is removed from copyState. 294 func (vc *vcopier) copyNext(ctx context.Context, settings binlogplayer.VRSettings) error { 295 qr, err := vc.vr.dbClient.Execute(fmt.Sprintf("select table_name, lastpk from _vt.copy_state where vrepl_id = %d and id in (select max(id) from _vt.copy_state group by vrepl_id, table_name)", vc.vr.id)) 296 if err != nil { 297 return err 298 } 299 var tableToCopy string 300 copyState := make(map[string]*sqltypes.Result) 301 for _, row := range qr.Rows { 302 tableName := row[0].ToString() 303 lastpk := row[1].ToString() 304 if tableToCopy == "" { 305 tableToCopy = tableName 306 } 307 copyState[tableName] = nil 308 if lastpk != "" { 309 var r querypb.QueryResult 310 if err := prototext.Unmarshal([]byte(lastpk), &r); err != nil { 311 return err 312 } 313 copyState[tableName] = sqltypes.Proto3ToResult(&r) 314 } 315 } 316 if len(copyState) == 0 { 317 return fmt.Errorf("unexpected: there are no tables to copy") 318 } 319 if err := vc.catchup(ctx, copyState); err != nil { 320 return err 321 } 322 return vc.copyTable(ctx, tableToCopy, copyState) 323 } 324 325 // catchup replays events to the subset of the tables that have been copied 326 // until replication is caught up. In order to stop, the seconds behind primary has 327 // to fall below replicationLagTolerance. 328 func (vc *vcopier) catchup(ctx context.Context, copyState map[string]*sqltypes.Result) error { 329 ctx, cancel := context.WithCancel(ctx) 330 defer cancel() 331 defer vc.vr.stats.PhaseTimings.Record("catchup", time.Now()) 332 333 settings, err := binlogplayer.ReadVRSettings(vc.vr.dbClient, vc.vr.id) 334 if err != nil { 335 return err 336 } 337 // If there's no start position, it means we're copying the 338 // first table. So, there's nothing to catch up to. 339 if settings.StartPos.IsZero() { 340 return nil 341 } 342 343 // Start vreplication. 344 errch := make(chan error, 1) 345 go func() { 346 errch <- newVPlayer(vc.vr, settings, copyState, mysql.Position{}, "catchup").play(ctx) 347 }() 348 349 // Wait for catchup. 350 tkr := time.NewTicker(waitRetryTime) 351 defer tkr.Stop() 352 seconds := int64(replicaLagTolerance / time.Second) 353 for { 354 sbm := vc.vr.stats.ReplicationLagSeconds.Get() 355 if sbm < seconds { 356 cancel() 357 // Make sure vplayer returns before returning. 358 <-errch 359 return nil 360 } 361 select { 362 case err := <-errch: 363 if err != nil { 364 return err 365 } 366 return io.EOF 367 case <-ctx.Done(): 368 // Make sure vplayer returns before returning. 369 <-errch 370 return io.EOF 371 case <-tkr.C: 372 } 373 } 374 } 375 376 // copyTable performs the synchronized copy of the next set of rows from 377 // the current table being copied. Each packet received is transactionally 378 // committed with the lastpk. This allows for consistent resumability. 379 func (vc *vcopier) copyTable(ctx context.Context, tableName string, copyState map[string]*sqltypes.Result) error { 380 defer vc.vr.dbClient.Rollback() 381 defer vc.vr.stats.PhaseTimings.Record("copy", time.Now()) 382 defer vc.vr.stats.CopyLoopCount.Add(1) 383 384 log.Infof("Copying table %s, lastpk: %v", tableName, copyState[tableName]) 385 386 plan, err := buildReplicatorPlan(vc.vr.source, vc.vr.colInfoMap, nil, vc.vr.stats) 387 if err != nil { 388 return err 389 } 390 391 initialPlan, ok := plan.TargetTables[tableName] 392 if !ok { 393 return fmt.Errorf("plan not found for table: %s, current plans are: %#v", tableName, plan.TargetTables) 394 } 395 396 ctx, cancel := context.WithTimeout(ctx, copyPhaseDuration) 397 defer cancel() 398 399 var lastpkpb *querypb.QueryResult 400 if lastpkqr := copyState[tableName]; lastpkqr != nil { 401 lastpkpb = sqltypes.ResultToProto3(lastpkqr) 402 } 403 404 rowsCopiedTicker := time.NewTicker(rowsCopiedUpdateInterval) 405 defer rowsCopiedTicker.Stop() 406 copyStateGCTicker := time.NewTicker(copyStateGCInterval) 407 defer copyStateGCTicker.Stop() 408 409 parallelism := int(math.Max(1, float64(vreplicationParallelInsertWorkers))) 410 copyWorkerFactory := vc.newCopyWorkerFactory(parallelism) 411 copyWorkQueue := vc.newCopyWorkQueue(parallelism, copyWorkerFactory) 412 defer copyWorkQueue.close() 413 414 // Allocate a result channel to collect results from tasks. 415 resultCh := make(chan *vcopierCopyTaskResult, parallelism*4) 416 defer close(resultCh) 417 418 var lastpk *querypb.Row 419 var pkfields []*querypb.Field 420 421 // Use this for task sequencing. 422 var prevCh <-chan *vcopierCopyTaskResult 423 424 serr := vc.vr.sourceVStreamer.VStreamRows(ctx, initialPlan.SendRule.Filter, lastpkpb, func(rows *binlogdatapb.VStreamRowsResponse) error { 425 for { 426 select { 427 case <-rowsCopiedTicker.C: 428 update := binlogplayer.GenerateUpdateRowsCopied(vc.vr.id, vc.vr.stats.CopyRowCount.Get()) 429 _, _ = vc.vr.dbClient.Execute(update) 430 case <-ctx.Done(): 431 return io.EOF 432 default: 433 } 434 select { 435 case <-copyStateGCTicker.C: 436 // Garbage collect older copy_state rows: 437 // - Using a goroutine so that we are not blocking the copy flow 438 // - Using a new connection so that we do not change the transactional behavior of the copy itself 439 // This helps to ensure that the table does not grow too large and the 440 // number of rows does not have a big impact on the queries used for 441 // the workflow. 442 go func() { 443 gcQuery := fmt.Sprintf("delete from _vt.copy_state where vrepl_id = %d and table_name = %s and id < (select maxid from (select max(id) as maxid from _vt.copy_state where vrepl_id = %d and table_name = %s) as depsel)", 444 vc.vr.id, encodeString(tableName), vc.vr.id, encodeString(tableName)) 445 dbClient := vc.vr.vre.getDBClient(false) 446 if err := dbClient.Connect(); err != nil { 447 log.Errorf("Error while garbage collecting older copy_state rows, could not connect to database: %v", err) 448 return 449 } 450 defer dbClient.Close() 451 if _, err := dbClient.ExecuteFetch(gcQuery, -1); err != nil { 452 log.Errorf("Error while garbage collecting older copy_state rows with query %q: %v", gcQuery, err) 453 } 454 }() 455 case <-ctx.Done(): 456 return io.EOF 457 default: 458 } 459 if rows.Throttled { 460 _ = vc.vr.updateTimeThrottled(RowStreamerComponentName) 461 return nil 462 } 463 if rows.Heartbeat { 464 _ = vc.vr.updateHeartbeatTime(time.Now().Unix()) 465 return nil 466 } 467 // verify throttler is happy, otherwise keep looping 468 if vc.vr.vre.throttlerClient.ThrottleCheckOKOrWaitAppName(ctx, vc.throttlerAppName) { 469 break // out of 'for' loop 470 } else { // we're throttled 471 _ = vc.vr.updateTimeThrottled(VCopierComponentName) 472 } 473 } 474 if !copyWorkQueue.isOpen { 475 if len(rows.Fields) == 0 { 476 return fmt.Errorf("expecting field event first, got: %v", rows) 477 } 478 if err := vc.fastForward(ctx, copyState, rows.Gtid); err != nil { 479 return err 480 } 481 fieldEvent := &binlogdatapb.FieldEvent{ 482 TableName: initialPlan.SendRule.Match, 483 } 484 fieldEvent.Fields = append(fieldEvent.Fields, rows.Fields...) 485 tablePlan, err := plan.buildExecutionPlan(fieldEvent) 486 if err != nil { 487 return err 488 } 489 pkfields = append(pkfields, rows.Pkfields...) 490 buf := sqlparser.NewTrackedBuffer(nil) 491 buf.Myprintf( 492 "insert into _vt.copy_state (lastpk, vrepl_id, table_name) values (%a, %s, %s)", ":lastpk", 493 strconv.Itoa(int(vc.vr.id)), 494 encodeString(tableName)) 495 addLatestCopyState := buf.ParsedQuery() 496 copyWorkQueue.open(addLatestCopyState, pkfields, tablePlan) 497 } 498 if len(rows.Rows) == 0 { 499 return nil 500 } 501 502 // Clone rows, since pointer values will change while async work is 503 // happening. Can skip this when there's no parallelism. 504 if parallelism > 1 { 505 rows = proto.Clone(rows).(*binlogdatapb.VStreamRowsResponse) 506 } 507 508 // Prepare a vcopierCopyTask for the current batch of work. 509 // TODO(maxeng) see if using a pre-allocated pool will speed things up. 510 currCh := make(chan *vcopierCopyTaskResult, 1) 511 currT := newVCopierCopyTask(newVCopierCopyTaskArgs(rows.Rows, rows.Lastpk)) 512 513 // Send result to the global resultCh and currCh. resultCh is used by 514 // the loop to return results to VStreamRows. currCh will be used to 515 // sequence the start of the nextT. 516 currT.lifecycle.onResult().sendTo(currCh) 517 currT.lifecycle.onResult().sendTo(resultCh) 518 519 // Use prevCh to Sequence the prevT with the currT so that: 520 // * The prevT is completed before we begin updating 521 // _vt.copy_state for currT. 522 // * If prevT fails or is canceled, the current task is 523 // canceled. 524 // prevCh is nil only for the first task in the vcopier run. 525 if prevCh != nil { 526 // prevT publishes to prevCh, and currT is the only thing that can 527 // consume from prevCh. If prevT is already done, then prevCh will 528 // have a value in it. If prevT isn't yet done, then prevCh will 529 // have a value later. Either way, AwaitCompletion should 530 // eventually get a value, unless there is a context expiry. 531 currT.lifecycle.before(vcopierCopyTaskInsertCopyState).awaitCompletion(prevCh) 532 } 533 534 // Store currCh in prevCh. The nextT will use this for sequencing. 535 prevCh = currCh 536 537 // Update stats after task is done. 538 currT.lifecycle.onResult().do(func(_ context.Context, result *vcopierCopyTaskResult) { 539 if result.state == vcopierCopyTaskFail { 540 vc.vr.stats.ErrorCounts.Add([]string{"Copy"}, 1) 541 } 542 if result.state == vcopierCopyTaskComplete { 543 vc.vr.stats.CopyRowCount.Add(int64(len(result.args.rows))) 544 vc.vr.stats.QueryCount.Add("copy", 1) 545 vc.vr.stats.TableCopyRowCounts.Add(tableName, int64(len(result.args.rows))) 546 vc.vr.stats.TableCopyTimings.Add(tableName, time.Since(result.startedAt)) 547 } 548 }) 549 550 if err := copyWorkQueue.enqueue(ctx, currT); err != nil { 551 log.Warningf("failed to enqueue task in workflow %s: %s", vc.vr.WorkflowName, err.Error()) 552 return err 553 } 554 555 // When async execution is not enabled, a done task will be available 556 // in the resultCh after each Enqueue, unless there was a queue state 557 // error (e.g. couldn't obtain a worker from pool). 558 // 559 // When async execution is enabled, results will show up in the channel 560 // eventually, possibly in a subsequent VStreamRows loop. It's still 561 // a good idea to check this channel on every pass so that: 562 // 563 // * resultCh doesn't fill up. If it does fill up then tasks won't be 564 // able to add their results to the channel, and progress in this 565 // goroutine will be blocked. 566 // * We keep lastpk up-to-date. 567 select { 568 case result := <-resultCh: 569 if result != nil { 570 switch result.state { 571 case vcopierCopyTaskCancel: 572 log.Warningf("task was canceled in workflow %s: %v", vc.vr.WorkflowName, result.err) 573 return io.EOF 574 case vcopierCopyTaskComplete: 575 // Collect lastpk. Needed for logging at the end. 576 lastpk = result.args.lastpk 577 case vcopierCopyTaskFail: 578 return vterrors.Wrapf(result.err, "task error") 579 } 580 } else { 581 return io.EOF 582 } 583 default: 584 } 585 586 return nil 587 }) 588 589 // Close the work queue. This will prevent new tasks from being enqueued, 590 // and will wait until all workers are returned to the worker pool. 591 copyWorkQueue.close() 592 593 // When tasks are executed async, there may be tasks that complete (or fail) 594 // after the last VStreamRows callback exits. Get the lastpk from completed 595 // tasks, or errors from failed ones. 596 var empty bool 597 var terrs []error 598 for !empty { 599 select { 600 case result := <-resultCh: 601 switch result.state { 602 case vcopierCopyTaskCancel: 603 // A task cancelation probably indicates an expired context due 604 // to a PlannedReparentShard or elapsed copy phase duration, 605 // neither of which are error conditions. 606 case vcopierCopyTaskComplete: 607 // Get the latest lastpk, purely for logging purposes. 608 lastpk = result.args.lastpk 609 case vcopierCopyTaskFail: 610 // Aggregate non-nil errors. 611 terrs = append(terrs, result.err) 612 } 613 default: 614 empty = true 615 } 616 } 617 if len(terrs) > 0 { 618 terr := vterrors.Aggregate(terrs) 619 log.Warningf("task error in workflow %s: %v", vc.vr.WorkflowName, terr) 620 return vterrors.Wrapf(terr, "task error") 621 } 622 623 // Get the last committed pk into a loggable form. 624 lastpkbuf, merr := prototext.Marshal(&querypb.QueryResult{ 625 Fields: pkfields, 626 Rows: []*querypb.Row{lastpk}, 627 }) 628 if merr != nil { 629 return fmt.Errorf("failed to marshal pk fields and value into query result: %s", merr.Error()) 630 } 631 lastpkbv := map[string]*querypb.BindVariable{ 632 "lastpk": { 633 Type: sqltypes.VarBinary, 634 Value: lastpkbuf, 635 }, 636 } 637 638 // A context expiration was probably caused by a PlannedReparentShard or an 639 // elapsed copy phase duration. Those are normal, non-error interruptions 640 // of a copy phase. 641 select { 642 case <-ctx.Done(): 643 log.Infof("Copy of %v stopped at lastpk: %v", tableName, lastpkbv) 644 return nil 645 default: 646 } 647 if serr != nil { 648 return serr 649 } 650 651 // Perform any post copy actions 652 if err := vc.vr.execPostCopyActions(ctx, tableName); err != nil { 653 return vterrors.Wrapf(err, "failed to execute post copy actions for table %q", tableName) 654 } 655 656 log.Infof("Copy of %v finished at lastpk: %v", tableName, lastpkbv) 657 buf := sqlparser.NewTrackedBuffer(nil) 658 buf.Myprintf( 659 "delete cs, pca from %s as cs left join %s as pca on cs.vrepl_id=pca.vrepl_id and cs.table_name=pca.table_name where cs.vrepl_id=%d and cs.table_name=%s", 660 copyStateTableName, postCopyActionTableName, 661 vc.vr.id, encodeString(tableName), 662 ) 663 if _, err := vc.vr.dbClient.Execute(buf.String()); err != nil { 664 return err 665 } 666 667 return nil 668 } 669 670 func (vc *vcopier) fastForward(ctx context.Context, copyState map[string]*sqltypes.Result, gtid string) error { 671 defer vc.vr.stats.PhaseTimings.Record("fastforward", time.Now()) 672 pos, err := mysql.DecodePosition(gtid) 673 if err != nil { 674 return err 675 } 676 settings, err := binlogplayer.ReadVRSettings(vc.vr.dbClient, vc.vr.id) 677 if err != nil { 678 return err 679 } 680 if settings.StartPos.IsZero() { 681 update := binlogplayer.GenerateUpdatePos(vc.vr.id, pos, time.Now().Unix(), 0, vc.vr.stats.CopyRowCount.Get(), vreplicationStoreCompressedGTID) 682 _, err := vc.vr.dbClient.Execute(update) 683 return err 684 } 685 return newVPlayer(vc.vr, settings, copyState, pos, "fastforward").play(ctx) 686 } 687 688 func (vc *vcopier) newCopyWorkQueue( 689 parallelism int, 690 workerFactory func(context.Context) (*vcopierCopyWorker, error), 691 ) *vcopierCopyWorkQueue { 692 concurrent := parallelism > 1 693 return newVCopierCopyWorkQueue(concurrent, parallelism, workerFactory) 694 } 695 696 func (vc *vcopier) newCopyWorkerFactory(parallelism int) func(context.Context) (*vcopierCopyWorker, error) { 697 if parallelism > 1 { 698 return func(ctx context.Context) (*vcopierCopyWorker, error) { 699 dbClient, err := vc.vr.newClientConnection(ctx) 700 if err != nil { 701 return nil, fmt.Errorf("failed to create new db client: %s", err.Error()) 702 } 703 return newVCopierCopyWorker( 704 true, /* close db client */ 705 dbClient, 706 ), nil 707 } 708 } 709 return func(_ context.Context) (*vcopierCopyWorker, error) { 710 return newVCopierCopyWorker( 711 false, /* close db client */ 712 vc.vr.dbClient, 713 ), nil 714 } 715 } 716 717 // close waits for all workers to be returned to the worker pool. 718 func (vcq *vcopierCopyWorkQueue) close() { 719 if !vcq.isOpen { 720 return 721 } 722 vcq.isOpen = false 723 vcq.workerPool.Close() 724 } 725 726 // enqueue a new copy task. This will obtain a worker from the pool, execute 727 // the task with that worker, and afterwards return the worker to the pool. If 728 // vcopierCopyWorkQueue is configured to operate concurrently, the task will be 729 // executed in a separate goroutine. Otherwise the task will be executed in the 730 // calling goroutine. 731 func (vcq *vcopierCopyWorkQueue) enqueue(ctx context.Context, currT *vcopierCopyTask) error { 732 if !vcq.isOpen { 733 return fmt.Errorf("work queue is not open") 734 } 735 736 // Get a handle on an unused worker. 737 poolH, err := vcq.workerPool.Get(ctx, nil) 738 if err != nil { 739 return fmt.Errorf("failed to get a worker from pool: %s", err.Error()) 740 } 741 742 currW, ok := poolH.(*vcopierCopyWorker) 743 if !ok { 744 return fmt.Errorf("failed to cast pool resource to *vcopierCopyWorker") 745 } 746 747 execute := func(task *vcopierCopyTask) { 748 currW.execute(ctx, task) 749 vcq.workerPool.Put(poolH) 750 } 751 752 // If the work queue is configured to work concurrently, execute the task 753 // in a separate goroutine. Otherwise execute the task in the calling 754 // goroutine. 755 if vcq.concurrent { 756 go execute(currT) 757 } else { 758 execute(currT) 759 } 760 761 return nil 762 } 763 764 // open the work queue. The provided arguments are used to generate 765 // statements for inserting rows and copy state. 766 func (vcq *vcopierCopyWorkQueue) open( 767 copyStateInsert *sqlparser.ParsedQuery, 768 pkfields []*querypb.Field, 769 tablePlan *TablePlan, 770 ) { 771 if vcq.isOpen { 772 return 773 } 774 775 poolCapacity := int(math.Max(float64(vcq.maxDepth), 1)) 776 vcq.workerPool = pools.NewResourcePool( 777 /* factory */ 778 func(ctx context.Context) (pools.Resource, error) { 779 worker, err := vcq.workerFactory(ctx) 780 if err != nil { 781 return nil, fmt.Errorf( 782 "failed to create vcopier worker: %s", 783 err.Error(), 784 ) 785 } 786 worker.open(copyStateInsert, pkfields, tablePlan) 787 return worker, nil 788 }, 789 poolCapacity, /* initial capacity */ 790 poolCapacity, /* max capacity */ 791 0, /* idle timeout */ 792 0, /* max lifetime */ 793 nil, /* log wait */ 794 nil, /* refresh check */ 795 0, /* refresh interval */ 796 ) 797 798 vcq.isOpen = true 799 } 800 801 // after returns a vcopierCopyTaskHooks that can be used to register callbacks 802 // to be triggered after the specified vcopierCopyTaskState. 803 func (vtl *vcopierCopyTaskLifecycle) after(state vcopierCopyTaskState) *vcopierCopyTaskHooks { 804 key := "after:" + state.String() 805 if _, ok := vtl.hooks[key]; !ok { 806 vtl.hooks[key] = newVCopierCopyTaskHooks() 807 } 808 return vtl.hooks[key] 809 } 810 811 // before returns a vcopierCopyTaskHooks that can be used to register callbacks 812 // to be triggered before the the specified vcopierCopyTaskState. 813 func (vtl *vcopierCopyTaskLifecycle) before(state vcopierCopyTaskState) *vcopierCopyTaskHooks { 814 key := "before:" + state.String() 815 if _, ok := vtl.hooks[key]; !ok { 816 vtl.hooks[key] = newVCopierCopyTaskHooks() 817 } 818 return vtl.hooks[key] 819 } 820 821 // onResult returns a vcopierCopyTaskResultHooks that can be used to register 822 // callbacks to be triggered when a task reaches a "done" state (= canceled, 823 // completed, failed). 824 func (vtl *vcopierCopyTaskLifecycle) onResult() *vcopierCopyTaskResultHooks { 825 return vtl.resultHooks 826 } 827 828 // tryAdvance is a convenient way of wrapping up lifecycle hooks with task 829 // execution steps. E.g.: 830 // 831 // if _, err := task.lifecycle.before(nextState).notify(ctx, args); err != nil { 832 // return err 833 // } 834 // if _, err := fn(ctx, args); err != nil { 835 // return err 836 // } 837 // if _, err := task.lifecycle.after(nextState).notify(ctx, args); err != nil { 838 // return err 839 // } 840 // 841 // Is roughly equivalent to: 842 // 843 // if _, err := task.Lifecycle.tryAdvance(ctx, args, nextState, fn); err != nil { 844 // return err 845 // } 846 func (vtl *vcopierCopyTaskLifecycle) tryAdvance( 847 ctx context.Context, 848 args *vcopierCopyTaskArgs, 849 nextState vcopierCopyTaskState, 850 fn func(context.Context, *vcopierCopyTaskArgs) error, 851 ) (vcopierCopyTaskState, error) { 852 var err error 853 newState := nextState 854 855 if err = vtl.before(nextState).notify(ctx, args); err != nil { 856 goto END 857 } 858 if err = fn(ctx, args); err != nil { 859 goto END 860 } 861 if err = vtl.after(nextState).notify(ctx, args); err != nil { 862 goto END 863 } 864 865 END: 866 if err != nil { 867 newState = vcopierCopyTaskFail 868 if vterrors.Code(err) == vtrpcpb.Code_CANCELED { 869 newState = vcopierCopyTaskCancel 870 } 871 } 872 873 return newState, err 874 } 875 876 // do registers a callback with the vcopierCopyTaskResultHooks, to be triggered 877 // when a task reaches a "done" state (= canceled, completed, failed). 878 func (vrh *vcopierCopyTaskResultHooks) do(fn func(context.Context, *vcopierCopyTaskResult)) { 879 vrh.fns = append(vrh.fns, fn) 880 } 881 882 // notify triggers all callbacks registered with this vcopierCopyTaskResultHooks. 883 func (vrh *vcopierCopyTaskResultHooks) notify(ctx context.Context, result *vcopierCopyTaskResult) { 884 for _, fn := range vrh.fns { 885 fn(ctx, result) 886 } 887 } 888 889 // sendTo registers a hook that accepts a result and sends the result to the 890 // provided channel. E.g.: 891 // 892 // resultCh := make(chan *vcopierCopyTaskResult, 1) 893 // task.lifecycle.onResult().sendTo(resultCh) 894 // defer func() { 895 // result := <-resultCh 896 // }() 897 func (vrh *vcopierCopyTaskResultHooks) sendTo(ch chan<- *vcopierCopyTaskResult) { 898 vrh.do(func(ctx context.Context, result *vcopierCopyTaskResult) { 899 select { 900 case ch <- result: 901 case <-ctx.Done(): 902 // Failure to send the result to the consumer on the other side of 903 // the channel before context expires will have the following 904 // consequences: 905 // 906 // * Subsequent tasks waiting for this task to complete won't run. 907 // That's OK. They won't hang waiting on the channel because, 908 // like this task they respond to context expiration. 909 // * The outermost loop managing task execution may not know that 910 // this task failed or succeeded. 911 // - In the case that this task succeeded, statistics and logging 912 // will not indicate that this task completed. That's not great, 913 // but shouldn't negatively impact the integrity of data or the 914 // copy workflow because the current state has been persisted 915 // to the database. 916 // - In the case that this task failed, there should be no adverse 917 // impact: the outermost loop handles context expiration by 918 // stopping the copy phase without completing it. 919 } 920 }) 921 } 922 923 // awaitCompletion registers a callback that returns an error unless the 924 // provided chan produces a vcopierTaskResult in a complete state. 925 // 926 // This is useful for sequencing vcopierCopyTasks, e.g.: 927 // 928 // resultCh := make(chan *vcopierCopyTaskResult, 1) 929 // prevT.lifecycle.onResult().sendTo(resultCh) 930 // currT.Lifecycle.before(vcopierCopyTaskInsertCopyState).awaitCompletion(resultCh) 931 func (vth *vcopierCopyTaskHooks) awaitCompletion(resultCh <-chan *vcopierCopyTaskResult) { 932 vth.do(func(ctx context.Context, args *vcopierCopyTaskArgs) error { 933 select { 934 case result := <-resultCh: 935 if result == nil { 936 return fmt.Errorf("channel was closed before a result received") 937 } 938 if !vcopierCopyTaskStateIsDone(result.state) { 939 return fmt.Errorf("received result is not done") 940 } 941 if result.state != vcopierCopyTaskComplete { 942 return fmt.Errorf("received result is not complete") 943 } 944 return nil 945 case <-ctx.Done(): 946 // A context expiration probably indicates a PlannedReparentShard 947 // or an elapsed copy phase duration. Those aren't treated as error 948 // conditions, but we'll return the context error here anyway. 949 // 950 // Task execution will detect the presence of the error, mark this 951 // task canceled, and abort. Subsequent tasks won't execute because 952 // this task didn't complete. 953 return vterrors.Errorf(vtrpcpb.Code_CANCELED, "context has expired") 954 } 955 }) 956 } 957 958 // do registers a callback with the vcopierCopyTaskResultHooks, to be triggered 959 // before or after a user-specified state. 960 func (vth *vcopierCopyTaskHooks) do(fn func(context.Context, *vcopierCopyTaskArgs) error) { 961 vth.fns = append(vth.fns, fn) 962 } 963 964 // notify triggers all callbacks registered with this vcopierCopyTaskHooks. 965 func (vth *vcopierCopyTaskHooks) notify(ctx context.Context, args *vcopierCopyTaskArgs) error { 966 for _, fn := range vth.fns { 967 if err := fn(ctx, args); err != nil { 968 return err 969 } 970 } 971 return nil 972 } 973 974 func (vts vcopierCopyTaskState) String() string { 975 switch vts { 976 case vcopierCopyTaskPending: 977 return "pending" 978 case vcopierCopyTaskBegin: 979 return "begin" 980 case vcopierCopyTaskInsertRows: 981 return "insert-rows" 982 case vcopierCopyTaskInsertCopyState: 983 return "insert-copy-state" 984 case vcopierCopyTaskCommit: 985 return "commit" 986 case vcopierCopyTaskCancel: 987 return "done:cancel" 988 case vcopierCopyTaskComplete: 989 return "done:complete" 990 case vcopierCopyTaskFail: 991 return "done:fail" 992 } 993 994 return fmt.Sprintf("undefined(%d)", int(vts)) 995 } 996 997 // ApplySetting implements pools.Resource. 998 func (vbc *vcopierCopyWorker) ApplySetting(context.Context, *pools.Setting) error { 999 return vterrors.Errorf(vtrpcpb.Code_UNIMPLEMENTED, "[BUG] vcopierCopyWorker does not implement ApplySetting") 1000 } 1001 1002 // Close implements pool.Resource. 1003 func (vbc *vcopierCopyWorker) Close() { 1004 if !vbc.isOpen { 1005 return 1006 } 1007 1008 vbc.isOpen = false 1009 if vbc.closeDbClient { 1010 vbc.vdbClient.Close() 1011 } 1012 } 1013 1014 // Expired implements pools.Resource. 1015 func (vbc *vcopierCopyWorker) Expired(time.Duration) bool { 1016 return false 1017 } 1018 1019 // IsSameSetting implements pools.Resource. 1020 func (vbc *vcopierCopyWorker) IsSameSetting(string) bool { 1021 return true 1022 } 1023 1024 // IsSettingApplied implements pools.Resource. 1025 func (vbc *vcopierCopyWorker) IsSettingApplied() bool { 1026 return false 1027 } 1028 1029 // ResetSetting implements pools.Resource. 1030 func (vbc *vcopierCopyWorker) ResetSetting(context.Context) error { 1031 return vterrors.Errorf(vtrpcpb.Code_UNIMPLEMENTED, "[BUG] vcopierCopyWorker does not implement ResetSetting") 1032 } 1033 1034 // execute advances a task through each state until it is done (= canceled, 1035 // completed, failed). 1036 func (vbc *vcopierCopyWorker) execute(ctx context.Context, task *vcopierCopyTask) *vcopierCopyTaskResult { 1037 startedAt := time.Now() 1038 state := vcopierCopyTaskPending 1039 1040 var err error 1041 1042 // As long as the current state is not done, keep trying to advance to the 1043 // next state. 1044 for !vcopierCopyTaskStateIsDone(state) { 1045 // Get the next state that we want to advance to. 1046 nextState := vcopierCopyTaskGetNextState(state) 1047 1048 var advanceFn func(context.Context, *vcopierCopyTaskArgs) error 1049 1050 // Get the advanceFn to use to advance the task to the nextState. 1051 switch nextState { 1052 case vcopierCopyTaskBegin: 1053 advanceFn = func(context.Context, *vcopierCopyTaskArgs) error { 1054 // Rollback to make sure we're in a clean state. 1055 if err := vbc.vdbClient.Rollback(); err != nil { 1056 return vterrors.Wrapf(err, "failed to rollback") 1057 } 1058 // Begin transaction. 1059 if err := vbc.vdbClient.Begin(); err != nil { 1060 return vterrors.Wrapf(err, "failed to start transaction") 1061 } 1062 return nil 1063 } 1064 case vcopierCopyTaskInsertRows: 1065 advanceFn = func(ctx context.Context, args *vcopierCopyTaskArgs) error { 1066 if _, err := vbc.insertRows(ctx, args.rows); err != nil { 1067 return vterrors.Wrapf(err, "failed inserting rows") 1068 } 1069 return nil 1070 } 1071 case vcopierCopyTaskInsertCopyState: 1072 advanceFn = func(ctx context.Context, args *vcopierCopyTaskArgs) error { 1073 if err := vbc.insertCopyState(ctx, args.lastpk); err != nil { 1074 return vterrors.Wrapf(err, "error updating _vt.copy_state") 1075 } 1076 return nil 1077 } 1078 case vcopierCopyTaskCommit: 1079 advanceFn = func(context.Context, *vcopierCopyTaskArgs) error { 1080 // Commit. 1081 if err := vbc.vdbClient.Commit(); err != nil { 1082 return vterrors.Wrapf(err, "error commiting transaction") 1083 } 1084 return nil 1085 } 1086 case vcopierCopyTaskComplete: 1087 advanceFn = func(context.Context, *vcopierCopyTaskArgs) error { return nil } 1088 default: 1089 err = fmt.Errorf("don't know how to advance from %s to %s", state, nextState) 1090 state = vcopierCopyTaskFail 1091 goto END 1092 } 1093 1094 // tryAdvance tries to execute advanceFn, and also executes any 1095 // lifecycle hooks surrounding the provided state. 1096 // 1097 // If all lifecycle hooks and advanceFn are successfully executed, 1098 // tryAdvance will return state, which should be == the nextState that 1099 // we provided. 1100 // 1101 // If there was a failure executing lifecycle hooks or advanceFn, 1102 // tryAdvance will return a failure state (i.e. canceled or failed), 1103 // along with a diagnostic error. 1104 if state, err = task.lifecycle.tryAdvance(ctx, task.args, nextState, advanceFn); err != nil { 1105 goto END 1106 } 1107 } 1108 1109 END: 1110 // At this point, we're in a "done" state (= canceled, completed, failed). 1111 // Notify any onResult callbacks. 1112 result := newVCopierCopyTaskResult(task.args, startedAt, state, err) 1113 task.lifecycle.onResult().notify(ctx, result) 1114 1115 return result 1116 } 1117 1118 func (vbc *vcopierCopyWorker) insertCopyState(ctx context.Context, lastpk *querypb.Row) error { 1119 var buf []byte 1120 buf, err := prototext.Marshal(&querypb.QueryResult{ 1121 Fields: vbc.pkfields, 1122 Rows: []*querypb.Row{lastpk}, 1123 }) 1124 if err != nil { 1125 return err 1126 } 1127 bv := map[string]*querypb.BindVariable{ 1128 "lastpk": { 1129 Type: sqltypes.VarBinary, 1130 Value: buf, 1131 }, 1132 } 1133 copyStateInsert, err := vbc.copyStateInsert.GenerateQuery(bv, nil) 1134 if err != nil { 1135 return err 1136 } 1137 if _, err := vbc.vdbClient.Execute(copyStateInsert); err != nil { 1138 return err 1139 } 1140 return nil 1141 } 1142 1143 func (vbc *vcopierCopyWorker) insertRows(ctx context.Context, rows []*querypb.Row) (*sqltypes.Result, error) { 1144 return vbc.tablePlan.applyBulkInsert( 1145 &vbc.sqlbuffer, 1146 rows, 1147 func(sql string) (*sqltypes.Result, error) { 1148 return vbc.vdbClient.ExecuteWithRetry(ctx, sql) 1149 }, 1150 ) 1151 } 1152 1153 // open the vcopierCopyWorker. The provided arguments are used to generate 1154 // statements for inserting rows and copy state. 1155 func (vbc *vcopierCopyWorker) open( 1156 copyStateInsert *sqlparser.ParsedQuery, 1157 pkfields []*querypb.Field, 1158 tablePlan *TablePlan, 1159 ) { 1160 if vbc.isOpen { 1161 return 1162 } 1163 vbc.copyStateInsert = copyStateInsert 1164 vbc.isOpen = true 1165 vbc.pkfields = pkfields 1166 vbc.tablePlan = tablePlan 1167 } 1168 1169 // vcopierCopyTaskStateIsDone returns true if the provided state is in one of 1170 // these three states: 1171 // 1172 // * vcopierCopyTaskCancel 1173 // * vcopierCopyTaskComplete 1174 // * vcopierCopyTaskFail 1175 func vcopierCopyTaskStateIsDone(vts vcopierCopyTaskState) bool { 1176 return vts == vcopierCopyTaskCancel || 1177 vts == vcopierCopyTaskComplete || 1178 vts == vcopierCopyTaskFail 1179 } 1180 1181 // vcopierCopyTaskGetNextState returns the optimistic next state. The next 1182 // state after vcopierCopyTaskPending is vcopierCopyTaskInProgress, followed 1183 // by vcopierCopyTaskInsertRows, etc. 1184 func vcopierCopyTaskGetNextState(vts vcopierCopyTaskState) vcopierCopyTaskState { 1185 switch vts { 1186 case vcopierCopyTaskPending: 1187 return vcopierCopyTaskBegin 1188 case vcopierCopyTaskBegin: 1189 return vcopierCopyTaskInsertRows 1190 case vcopierCopyTaskInsertRows: 1191 return vcopierCopyTaskInsertCopyState 1192 case vcopierCopyTaskInsertCopyState: 1193 return vcopierCopyTaskCommit 1194 case vcopierCopyTaskCommit: 1195 return vcopierCopyTaskComplete 1196 } 1197 return vts 1198 }