vitess.io/vitess@v0.16.2/go/vt/binlog/binlogplayer/binlog_player.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 binlogplayer contains the code that plays a vreplication 18 // stream on a client database. It usually runs inside the destination primary 19 // vttablet process. 20 package binlogplayer 21 22 import ( 23 "bytes" 24 "compress/zlib" 25 "context" 26 "encoding/binary" 27 "encoding/hex" 28 "fmt" 29 "io" 30 "math" 31 "os" 32 "sync" 33 "time" 34 35 "github.com/spf13/pflag" 36 37 "google.golang.org/protobuf/proto" 38 39 "vitess.io/vitess/go/history" 40 "vitess.io/vitess/go/mysql" 41 "vitess.io/vitess/go/sqltypes" 42 "vitess.io/vitess/go/stats" 43 "vitess.io/vitess/go/sync2" 44 "vitess.io/vitess/go/vt/log" 45 binlogdatapb "vitess.io/vitess/go/vt/proto/binlogdata" 46 topodatapb "vitess.io/vitess/go/vt/proto/topodata" 47 "vitess.io/vitess/go/vt/servenv" 48 "vitess.io/vitess/go/vt/throttler" 49 ) 50 51 var ( 52 // SlowQueryThreshold will cause we logging anything that's higher than it. 53 SlowQueryThreshold = time.Duration(100 * time.Millisecond) 54 55 // keys for the stats map 56 57 // BlplQuery is the key for the stats map. 58 BlplQuery = "Query" 59 // BlplTransaction is the key for the stats map. 60 BlplTransaction = "Transaction" 61 62 // VReplicationInit is for the Init state. 63 VReplicationInit = "Init" 64 // VReplicationCopying is for the Copying state. 65 VReplicationCopying = "Copying" 66 // BlpRunning is for the Running state. 67 BlpRunning = "Running" 68 // BlpStopped is for the Stopped state. 69 BlpStopped = "Stopped" 70 // BlpError is for the Error state. 71 BlpError = "Error" 72 ) 73 74 // Stats is the internal stats of a player. It is a different 75 // structure that is passed in so stats can be collected over the life 76 // of multiple individual players. 77 type Stats struct { 78 // Stats about the player, keys used are BlplQuery and BlplTransaction 79 Timings *stats.Timings 80 Rates *stats.Rates 81 82 // Last saved status 83 lastPositionMutex sync.Mutex 84 lastPosition mysql.Position 85 86 heartbeatMutex sync.Mutex 87 heartbeat int64 88 89 ReplicationLagSeconds sync2.AtomicInt64 90 History *history.History 91 92 State sync2.AtomicString 93 94 PhaseTimings *stats.Timings 95 QueryTimings *stats.Timings 96 QueryCount *stats.CountersWithSingleLabel 97 CopyRowCount *stats.Counter 98 CopyLoopCount *stats.Counter 99 ErrorCounts *stats.CountersWithMultiLabels 100 NoopQueryCount *stats.CountersWithSingleLabel 101 102 VReplicationLags *stats.Timings 103 VReplicationLagRates *stats.Rates 104 105 TableCopyRowCounts *stats.CountersWithSingleLabel 106 TableCopyTimings *stats.Timings 107 } 108 109 // RecordHeartbeat updates the time the last heartbeat from vstreamer was seen 110 func (bps *Stats) RecordHeartbeat(tm int64) { 111 bps.heartbeatMutex.Lock() 112 defer bps.heartbeatMutex.Unlock() 113 bps.heartbeat = tm 114 } 115 116 // Heartbeat gets the time the last heartbeat from vstreamer was seen 117 func (bps *Stats) Heartbeat() int64 { 118 bps.heartbeatMutex.Lock() 119 defer bps.heartbeatMutex.Unlock() 120 return bps.heartbeat 121 } 122 123 // SetLastPosition sets the last replication position. 124 func (bps *Stats) SetLastPosition(pos mysql.Position) { 125 bps.lastPositionMutex.Lock() 126 defer bps.lastPositionMutex.Unlock() 127 bps.lastPosition = pos 128 } 129 130 // LastPosition gets the last replication position. 131 func (bps *Stats) LastPosition() mysql.Position { 132 bps.lastPositionMutex.Lock() 133 defer bps.lastPositionMutex.Unlock() 134 return bps.lastPosition 135 } 136 137 // MessageHistory gets all the messages, we store 3 at a time 138 func (bps *Stats) MessageHistory() []string { 139 strs := make([]string, 0, 3) 140 for _, h := range bps.History.Records() { 141 h1, _ := h.(*StatsHistoryRecord) 142 if h1 != nil { 143 strs = append(strs, h1.Message) 144 } 145 } 146 return strs 147 } 148 149 // NewStats creates a new Stats structure. 150 func NewStats() *Stats { 151 bps := &Stats{} 152 bps.Timings = stats.NewTimings("", "", "") 153 bps.Rates = stats.NewRates("", bps.Timings, 15*60/5, 5*time.Second) 154 bps.History = history.New(3) 155 bps.ReplicationLagSeconds.Set(math.MaxInt64) 156 bps.PhaseTimings = stats.NewTimings("", "", "Phase") 157 bps.QueryTimings = stats.NewTimings("", "", "Phase") 158 bps.QueryCount = stats.NewCountersWithSingleLabel("", "", "Phase", "") 159 bps.CopyRowCount = stats.NewCounter("", "") 160 bps.CopyLoopCount = stats.NewCounter("", "") 161 bps.ErrorCounts = stats.NewCountersWithMultiLabels("", "", []string{"type"}) 162 bps.NoopQueryCount = stats.NewCountersWithSingleLabel("", "", "Statement", "") 163 bps.VReplicationLags = stats.NewTimings("", "", "") 164 bps.VReplicationLagRates = stats.NewRates("", bps.VReplicationLags, 15*60/5, 5*time.Second) 165 bps.TableCopyRowCounts = stats.NewCountersWithSingleLabel("", "", "Table", "") 166 bps.TableCopyTimings = stats.NewTimings("", "", "Table") 167 return bps 168 } 169 170 // BinlogPlayer is for reading a stream of updates from BinlogServer. 171 type BinlogPlayer struct { 172 tablet *topodatapb.Tablet 173 dbClient DBClient 174 175 // for key range base requests 176 keyRange *topodatapb.KeyRange 177 178 // for table base requests 179 tables []string 180 181 // common to all 182 uid uint32 183 position mysql.Position 184 stopPosition mysql.Position 185 blplStats *Stats 186 defaultCharset *binlogdatapb.Charset 187 currentCharset *binlogdatapb.Charset 188 deadlockRetry time.Duration 189 } 190 191 // NewBinlogPlayerKeyRange returns a new BinlogPlayer pointing at the server 192 // replicating the provided keyrange and updating _vt.vreplication 193 // with uid=startPosition.Uid. 194 // If !stopPosition.IsZero(), it will stop when reaching that position. 195 func NewBinlogPlayerKeyRange(dbClient DBClient, tablet *topodatapb.Tablet, keyRange *topodatapb.KeyRange, uid uint32, blplStats *Stats) *BinlogPlayer { 196 result := &BinlogPlayer{ 197 tablet: tablet, 198 dbClient: dbClient, 199 keyRange: keyRange, 200 uid: uid, 201 blplStats: blplStats, 202 deadlockRetry: 1 * time.Second, 203 } 204 return result 205 } 206 207 // NewBinlogPlayerTables returns a new BinlogPlayer pointing at the server 208 // replicating the provided tables and updating _vt.vreplication 209 // with uid=startPosition.Uid. 210 // If !stopPosition.IsZero(), it will stop when reaching that position. 211 func NewBinlogPlayerTables(dbClient DBClient, tablet *topodatapb.Tablet, tables []string, uid uint32, blplStats *Stats) *BinlogPlayer { 212 result := &BinlogPlayer{ 213 tablet: tablet, 214 dbClient: dbClient, 215 tables: tables, 216 uid: uid, 217 blplStats: blplStats, 218 deadlockRetry: 1 * time.Second, 219 } 220 return result 221 } 222 223 // ApplyBinlogEvents makes an RPC request to BinlogServer 224 // and processes the events. It returns nil if the provided context 225 // was canceled, or if we reached the stopping point. 226 // If an error is encountered, it updates the vreplication state to "Error". 227 // If a stop position was specified, and reached, the state is updated to "Stopped". 228 func (blp *BinlogPlayer) ApplyBinlogEvents(ctx context.Context) error { 229 if err := blp.setVReplicationState(BlpRunning, ""); err != nil { 230 log.Errorf("Error writing Running state: %v", err) 231 } 232 233 if err := blp.applyEvents(ctx); err != nil { 234 if err := blp.setVReplicationState(BlpError, err.Error()); err != nil { 235 log.Errorf("Error writing stop state: %v", err) 236 } 237 return err 238 } 239 return nil 240 } 241 242 // applyEvents returns a recordable status message on termination or an error otherwise. 243 func (blp *BinlogPlayer) applyEvents(ctx context.Context) error { 244 // Read starting values for vreplication. 245 settings, err := ReadVRSettings(blp.dbClient, blp.uid) 246 if err != nil { 247 log.Error(err) 248 return err 249 } 250 251 blp.position = settings.StartPos 252 blp.stopPosition = settings.StopPos 253 t, err := throttler.NewThrottler( 254 fmt.Sprintf("BinlogPlayer/%d", blp.uid), 255 "transactions", 256 1, /* threadCount */ 257 settings.MaxTPS, 258 settings.MaxReplicationLag, 259 ) 260 if err != nil { 261 err := fmt.Errorf("failed to instantiate throttler: %v", err) 262 log.Error(err) 263 return err 264 } 265 defer t.Close() 266 267 // Log the mode of operation and when the player stops. 268 if len(blp.tables) > 0 { 269 log.Infof("BinlogPlayer client %v for tables %v starting @ '%v', server: %v", 270 blp.uid, 271 blp.tables, 272 blp.position, 273 blp.tablet, 274 ) 275 } else { 276 log.Infof("BinlogPlayer client %v for keyrange '%v-%v' starting @ '%v', server: %v", 277 blp.uid, 278 hex.EncodeToString(blp.keyRange.GetStart()), 279 hex.EncodeToString(blp.keyRange.GetEnd()), 280 blp.position, 281 blp.tablet, 282 ) 283 } 284 if !blp.stopPosition.IsZero() { 285 switch { 286 case blp.position.Equal(blp.stopPosition): 287 msg := fmt.Sprintf("not starting BinlogPlayer, we're already at the desired position %v", blp.stopPosition) 288 log.Info(msg) 289 if err := blp.setVReplicationState(BlpStopped, msg); err != nil { 290 log.Errorf("Error writing stop state: %v", err) 291 } 292 return nil 293 case blp.position.AtLeast(blp.stopPosition): 294 msg := fmt.Sprintf("starting point %v greater than stopping point %v", blp.position, blp.stopPosition) 295 log.Error(msg) 296 if err := blp.setVReplicationState(BlpStopped, msg); err != nil { 297 log.Errorf("Error writing stop state: %v", err) 298 } 299 // Don't return an error. Otherwise, it will keep retrying. 300 return nil 301 default: 302 log.Infof("Will stop player when reaching %v", blp.stopPosition) 303 } 304 } 305 306 clientFactory, ok := clientFactories[binlogPlayerProtocol] 307 if !ok { 308 return fmt.Errorf("no binlog player client factory named %v", binlogPlayerProtocol) 309 } 310 blplClient := clientFactory() 311 err = blplClient.Dial(blp.tablet) 312 if err != nil { 313 err := fmt.Errorf("error dialing binlog server: %v", err) 314 log.Error(err) 315 return err 316 } 317 defer blplClient.Close() 318 319 // Get the current charset of our connection, so we can ask the stream server 320 // to check that they match. The streamer will also only send per-statement 321 // charset data if that statement's charset is different from what we specify. 322 if dbClient, ok := blp.dbClient.(*dbClientImpl); ok { 323 blp.defaultCharset, err = mysql.GetCharset(dbClient.dbConn) 324 if err != nil { 325 return fmt.Errorf("can't get charset to request binlog stream: %v", err) 326 } 327 log.Infof("original charset: %v", blp.defaultCharset) 328 blp.currentCharset = blp.defaultCharset 329 // Restore original charset when we're done. 330 defer func() { 331 // If the connection has been closed, there's no need to restore 332 // this connection-specific setting. 333 if dbClient.dbConn == nil { 334 return 335 } 336 log.Infof("restoring original charset %v", blp.defaultCharset) 337 if csErr := mysql.SetCharset(dbClient.dbConn, blp.defaultCharset); csErr != nil { 338 log.Errorf("can't restore original charset %v: %v", blp.defaultCharset, csErr) 339 } 340 }() 341 } 342 343 var stream BinlogTransactionStream 344 if len(blp.tables) > 0 { 345 stream, err = blplClient.StreamTables(ctx, mysql.EncodePosition(blp.position), blp.tables, blp.defaultCharset) 346 } else { 347 stream, err = blplClient.StreamKeyRange(ctx, mysql.EncodePosition(blp.position), blp.keyRange, blp.defaultCharset) 348 } 349 if err != nil { 350 err := fmt.Errorf("error sending streaming query to binlog server: %v", err) 351 log.Error(err) 352 return err 353 } 354 355 for { 356 // Block if we are throttled. 357 for { 358 backoff := t.Throttle(0 /* threadID */) 359 if backoff == throttler.NotThrottled { 360 break 361 } 362 // We don't bother checking for context cancellation here because the 363 // sleep will block only up to 1 second. (Usually, backoff is 1s / rate 364 // e.g. a rate of 1000 TPS results into a backoff of 1 ms.) 365 time.Sleep(backoff) 366 } 367 368 // get the response 369 response, err := stream.Recv() 370 // Check context before checking error, because canceled 371 // contexts could be wrapped as regular errors. 372 select { 373 case <-ctx.Done(): 374 return nil 375 default: 376 } 377 if err != nil { 378 return fmt.Errorf("error received from Stream %v", err) 379 } 380 381 // process the transaction 382 for { 383 ok, err = blp.processTransaction(response) 384 if err != nil { 385 log.Infof("transaction failed: %v", err) 386 for _, stmt := range response.Statements { 387 log.Infof("statement: %q", stmt.Sql) 388 } 389 return fmt.Errorf("error in processing binlog event %v", err) 390 } 391 if ok { 392 if !blp.stopPosition.IsZero() { 393 if blp.position.AtLeast(blp.stopPosition) { 394 msg := "Reached stopping position, done playing logs" 395 log.Info(msg) 396 if err := blp.setVReplicationState(BlpStopped, msg); err != nil { 397 log.Errorf("Error writing stop state: %v", err) 398 } 399 return nil 400 } 401 } 402 break 403 } 404 log.Infof("Retrying txn in %v.", blp.deadlockRetry) 405 time.Sleep(blp.deadlockRetry) 406 } 407 } 408 } 409 410 func (blp *BinlogPlayer) processTransaction(tx *binlogdatapb.BinlogTransaction) (ok bool, err error) { 411 txnStartTime := time.Now() 412 if err = blp.dbClient.Begin(); err != nil { 413 return false, fmt.Errorf("failed query BEGIN, err: %s", err) 414 } 415 for i, stmt := range tx.Statements { 416 // Make sure the statement is replayed in the proper charset. 417 if dbClient, ok := blp.dbClient.(*dbClientImpl); ok { 418 var stmtCharset *binlogdatapb.Charset 419 if stmt.Charset != nil { 420 stmtCharset = stmt.Charset 421 } else { 422 // Streamer sends a nil Charset for statements that use the 423 // charset we specified in the request. 424 stmtCharset = blp.defaultCharset 425 } 426 if !proto.Equal(blp.currentCharset, stmtCharset) { 427 // In regular MySQL replication, the charset is silently adjusted as 428 // needed during event playback. Here we also adjust so that playback 429 // proceeds, but in Vitess-land this usually means a misconfigured 430 // server or a misbehaving client, so we spam the logs with warnings. 431 log.Warningf("BinlogPlayer changing charset from %v to %v for statement %d in transaction %v", blp.currentCharset, stmtCharset, i, tx) 432 err = mysql.SetCharset(dbClient.dbConn, stmtCharset) 433 if err != nil { 434 return false, fmt.Errorf("can't set charset for statement %d in transaction %v: %v", i, tx, err) 435 } 436 blp.currentCharset = stmtCharset 437 } 438 } 439 if _, err = blp.exec(string(stmt.Sql)); err == nil { 440 continue 441 } 442 if sqlErr, ok := err.(*mysql.SQLError); ok && sqlErr.Number() == mysql.ERLockDeadlock { 443 // Deadlock: ask for retry 444 log.Infof("Deadlock: %v", err) 445 if err = blp.dbClient.Rollback(); err != nil { 446 return false, err 447 } 448 return false, nil 449 } 450 _ = blp.dbClient.Rollback() 451 return false, err 452 } 453 // Update recovery position after successful replay. 454 // This also updates the blp's internal position. 455 if err = blp.writeRecoveryPosition(tx); err != nil { 456 _ = blp.dbClient.Rollback() 457 return false, err 458 } 459 if err = blp.dbClient.Commit(); err != nil { 460 return false, fmt.Errorf("failed query COMMIT, err: %s", err) 461 } 462 blp.blplStats.Timings.Record(BlplTransaction, txnStartTime) 463 return true, nil 464 } 465 466 func (blp *BinlogPlayer) exec(sql string) (*sqltypes.Result, error) { 467 queryStartTime := time.Now() 468 qr, err := blp.dbClient.ExecuteFetch(sql, 0) 469 blp.blplStats.Timings.Record(BlplQuery, queryStartTime) 470 if d := time.Since(queryStartTime); d > SlowQueryThreshold { 471 log.Infof("SLOW QUERY (took %.2fs) '%s'", d.Seconds(), sql) 472 } 473 return qr, err 474 } 475 476 // writeRecoveryPosition writes the current GTID as the recovery position 477 // for the next transaction. 478 // It also tries to get the timestamp for the transaction. Two cases: 479 // - we have statements, and they start with a SET TIMESTAMP that we 480 // can parse: then we update transaction_timestamp in vreplication 481 // with it, and set ReplicationLagSeconds to now() - transaction_timestamp 482 // - otherwise (the statements are probably filtered out), we leave 483 // transaction_timestamp alone (keeping the old value), and we don't 484 // change ReplicationLagSeconds 485 func (blp *BinlogPlayer) writeRecoveryPosition(tx *binlogdatapb.BinlogTransaction) error { 486 position, err := DecodePosition(tx.EventToken.Position) 487 if err != nil { 488 return err 489 } 490 491 now := time.Now().Unix() 492 updateRecovery := GenerateUpdatePos(blp.uid, position, now, tx.EventToken.Timestamp, blp.blplStats.CopyRowCount.Get(), false) 493 494 qr, err := blp.exec(updateRecovery) 495 if err != nil { 496 return fmt.Errorf("error %v in writing recovery info %v", err, updateRecovery) 497 } 498 if qr.RowsAffected != 1 { 499 return fmt.Errorf("cannot update vreplication table, affected %v rows", qr.RowsAffected) 500 } 501 502 // Update position after successful write. 503 blp.position = position 504 blp.blplStats.SetLastPosition(blp.position) 505 if tx.EventToken.Timestamp != 0 { 506 blp.blplStats.ReplicationLagSeconds.Set(now - tx.EventToken.Timestamp) 507 } 508 return nil 509 } 510 511 func (blp *BinlogPlayer) setVReplicationState(state, message string) error { 512 if message != "" { 513 blp.blplStats.History.Add(&StatsHistoryRecord{ 514 Time: time.Now(), 515 Message: message, 516 }) 517 } 518 blp.blplStats.State.Set(state) 519 query := fmt.Sprintf("update _vt.vreplication set state='%v', message=%v where id=%v", state, encodeString(MessageTruncate(message)), blp.uid) 520 if _, err := blp.dbClient.ExecuteFetch(query, 1); err != nil { 521 return fmt.Errorf("could not set state: %v: %v", query, err) 522 } 523 return nil 524 } 525 526 // VRSettings contains the settings of a vreplication table. 527 type VRSettings struct { 528 StartPos mysql.Position 529 StopPos mysql.Position 530 MaxTPS int64 531 MaxReplicationLag int64 532 State string 533 WorkflowType int32 534 WorkflowSubType int32 535 WorkflowName string 536 DeferSecondaryKeys bool 537 } 538 539 // ReadVRSettings retrieves the throttler settings for 540 // vreplication from the checkpoint table. 541 func ReadVRSettings(dbClient DBClient, uid uint32) (VRSettings, error) { 542 query := fmt.Sprintf("select pos, stop_pos, max_tps, max_replication_lag, state, workflow_type, workflow, workflow_sub_type, defer_secondary_keys from _vt.vreplication where id=%v", uid) 543 qr, err := dbClient.ExecuteFetch(query, 1) 544 if err != nil { 545 return VRSettings{}, fmt.Errorf("error %v in selecting vreplication settings %v", err, query) 546 } 547 548 if len(qr.Rows) != 1 { 549 return VRSettings{}, fmt.Errorf("checkpoint information not available in db for %v", uid) 550 } 551 vrRow := qr.Named().Row() 552 553 maxTPS, err := vrRow.ToInt64("max_tps") 554 if err != nil { 555 return VRSettings{}, fmt.Errorf("failed to parse max_tps column2: %v", err) 556 } 557 maxReplicationLag, err := vrRow.ToInt64("max_replication_lag") 558 if err != nil { 559 return VRSettings{}, fmt.Errorf("failed to parse max_replication_lag column: %v", err) 560 } 561 startPos, err := DecodePosition(vrRow.AsString("pos", "")) 562 if err != nil { 563 return VRSettings{}, fmt.Errorf("failed to parse pos column: %v", err) 564 } 565 stopPos, err := mysql.DecodePosition(vrRow.AsString("stop_pos", "")) 566 if err != nil { 567 return VRSettings{}, fmt.Errorf("failed to parse stop_pos column: %v", err) 568 } 569 workflowTypeTmp, err := vrRow.ToInt64("workflow_type") 570 workflowType := int32(workflowTypeTmp) 571 if err != nil { 572 return VRSettings{}, fmt.Errorf("failed to parse workflow_type column: %v", err) 573 } 574 workflowSubTypeTmp, err := vrRow.ToInt64("workflow_sub_type") 575 workflowSubType := int32(workflowSubTypeTmp) 576 if err != nil { 577 return VRSettings{}, fmt.Errorf("failed to parse workflow_sub_type column: %v", err) 578 } 579 deferSecondaryKeys, err := vrRow.ToBool("defer_secondary_keys") 580 if err != nil { 581 return VRSettings{}, fmt.Errorf("failed to parse defer_secondary_keys column: %v", err) 582 } 583 return VRSettings{ 584 StartPos: startPos, 585 StopPos: stopPos, 586 MaxTPS: maxTPS, 587 MaxReplicationLag: maxReplicationLag, 588 State: vrRow.AsString("state", ""), 589 WorkflowType: workflowType, 590 WorkflowName: vrRow.AsString("workflow", ""), 591 WorkflowSubType: workflowSubType, 592 DeferSecondaryKeys: deferSecondaryKeys, 593 }, nil 594 } 595 596 // CreateVReplication returns a statement to populate the first value into 597 // the _vt.vreplication table. 598 func CreateVReplication(workflow string, source *binlogdatapb.BinlogSource, position string, maxTPS, maxReplicationLag, timeUpdated int64, dbName string, 599 workflowType binlogdatapb.VReplicationWorkflowType, workflowSubType binlogdatapb.VReplicationWorkflowSubType, deferSecondaryKeys bool) string { 600 return fmt.Sprintf("insert into _vt.vreplication "+ 601 "(workflow, source, pos, max_tps, max_replication_lag, time_updated, transaction_timestamp, state, db_name, workflow_type, workflow_sub_type, defer_secondary_keys) "+ 602 "values (%v, %v, %v, %v, %v, %v, 0, '%v', %v, %v, %v, %v)", 603 encodeString(workflow), encodeString(source.String()), encodeString(position), maxTPS, maxReplicationLag, 604 timeUpdated, BlpRunning, encodeString(dbName), int64(workflowType), int64(workflowSubType), deferSecondaryKeys) 605 } 606 607 // CreateVReplicationState returns a statement to create a stopped vreplication. 608 func CreateVReplicationState(workflow string, source *binlogdatapb.BinlogSource, position, state string, dbName string, 609 workflowType binlogdatapb.VReplicationWorkflowType, workflowSubType binlogdatapb.VReplicationWorkflowSubType) string { 610 return fmt.Sprintf("insert into _vt.vreplication "+ 611 "(workflow, source, pos, max_tps, max_replication_lag, time_updated, transaction_timestamp, state, db_name, workflow_type, workflow_sub_type) "+ 612 "values (%v, %v, %v, %v, %v, %v, 0, '%v', %v, %v, %v)", 613 encodeString(workflow), encodeString(source.String()), encodeString(position), throttler.MaxRateModuleDisabled, 614 throttler.ReplicationLagModuleDisabled, time.Now().Unix(), state, encodeString(dbName), 615 int64(workflowType), int64(workflowSubType)) 616 } 617 618 // GenerateUpdatePos returns a statement to record the latest processed gtid in the _vt.vreplication table. 619 func GenerateUpdatePos(uid uint32, pos mysql.Position, timeUpdated int64, txTimestamp int64, rowsCopied int64, compress bool) string { 620 strGTID := encodeString(mysql.EncodePosition(pos)) 621 if compress { 622 strGTID = fmt.Sprintf("compress(%s)", strGTID) 623 } 624 if txTimestamp != 0 { 625 return fmt.Sprintf( 626 "update _vt.vreplication set pos=%v, time_updated=%v, transaction_timestamp=%v, rows_copied=%v, message='' where id=%v", 627 strGTID, timeUpdated, txTimestamp, rowsCopied, uid) 628 } 629 return fmt.Sprintf( 630 "update _vt.vreplication set pos=%v, time_updated=%v, rows_copied=%v, message='' where id=%v", strGTID, timeUpdated, rowsCopied, uid) 631 } 632 633 // GenerateUpdateRowsCopied returns a statement to update the rows_copied value in the _vt.vreplication table. 634 func GenerateUpdateRowsCopied(uid uint32, rowsCopied int64) string { 635 return fmt.Sprintf("update _vt.vreplication set rows_copied=%v where id=%v", rowsCopied, uid) 636 } 637 638 // GenerateUpdateHeartbeat returns a statement to record the latest heartbeat in the _vt.vreplication table. 639 func GenerateUpdateHeartbeat(uid uint32, timeUpdated int64) (string, error) { 640 if timeUpdated == 0 { 641 return "", fmt.Errorf("timeUpdated cannot be zero") 642 } 643 return fmt.Sprintf("update _vt.vreplication set time_updated=%v, time_heartbeat=%v where id=%v", timeUpdated, timeUpdated, uid), nil 644 } 645 646 // GenerateUpdateTimeThrottled returns a statement to record the latest throttle time in the _vt.vreplication table. 647 func GenerateUpdateTimeThrottled(uid uint32, timeThrottledUnix int64, componentThrottled string) (string, error) { 648 if timeThrottledUnix == 0 { 649 return "", fmt.Errorf("timeUpdated cannot be zero") 650 } 651 return fmt.Sprintf("update _vt.vreplication set time_updated=%v, time_throttled=%v, component_throttled='%v' where id=%v", timeThrottledUnix, timeThrottledUnix, componentThrottled, uid), nil 652 } 653 654 // StartVReplication returns a statement to start the replication. 655 func StartVReplication(uid uint32) string { 656 return fmt.Sprintf( 657 "update _vt.vreplication set state='%v', stop_pos=NULL where id=%v", 658 BlpRunning, uid) 659 } 660 661 // StartVReplicationUntil returns a statement to start the replication with a stop position. 662 func StartVReplicationUntil(uid uint32, pos string) string { 663 return fmt.Sprintf( 664 "update _vt.vreplication set state='%v', stop_pos=%v where id=%v", 665 BlpRunning, encodeString(pos), uid) 666 } 667 668 // StopVReplication returns a statement to stop the replication. 669 func StopVReplication(uid uint32, message string) string { 670 return fmt.Sprintf( 671 "update _vt.vreplication set state='%v', message=%v where id=%v", 672 BlpStopped, encodeString(MessageTruncate(message)), uid) 673 } 674 675 // DeleteVReplication returns a statement to delete the replication. 676 func DeleteVReplication(uid uint32) string { 677 return fmt.Sprintf("delete from _vt.vreplication where id=%v", uid) 678 } 679 680 // MessageTruncate truncates the message string to a safe length. 681 func MessageTruncate(msg string) string { 682 // message length is 1000 bytes. 683 return LimitString(msg, 950) 684 } 685 686 func encodeString(in string) string { 687 buf := bytes.NewBuffer(nil) 688 sqltypes.NewVarChar(in).EncodeSQL(buf) 689 return buf.String() 690 } 691 692 // ReadVReplicationPos returns a statement to query the gtid for a 693 // given stream from the _vt.vreplication table. 694 func ReadVReplicationPos(index uint32) string { 695 return fmt.Sprintf("select pos from _vt.vreplication where id=%v", index) 696 } 697 698 // ReadVReplicationStatus returns a statement to query the status fields for a 699 // given stream from the _vt.vreplication table. 700 func ReadVReplicationStatus(index uint32) string { 701 return fmt.Sprintf("select pos, state, message from _vt.vreplication where id=%v", index) 702 } 703 704 // MysqlUncompress will uncompress a binary string in the format stored by mysql's compress() function 705 // The first four bytes represent the size of the original string passed to compress() 706 // Remaining part is the compressed string using zlib, which we uncompress here using golang's zlib library 707 func MysqlUncompress(input string) []byte { 708 // consistency check 709 inputBytes := []byte(input) 710 if len(inputBytes) < 5 { 711 return nil 712 } 713 714 // determine length 715 dataLength := uint32(inputBytes[0]) + uint32(inputBytes[1])<<8 + uint32(inputBytes[2])<<16 + uint32(inputBytes[3])<<24 716 dataLengthBytes := make([]byte, 4) 717 binary.LittleEndian.PutUint32(dataLengthBytes, dataLength) 718 dataLength = binary.LittleEndian.Uint32(dataLengthBytes) 719 720 // uncompress using zlib 721 inputData := inputBytes[4:] 722 inputDataBuf := bytes.NewBuffer(inputData) 723 reader, err := zlib.NewReader(inputDataBuf) 724 if err != nil { 725 return nil 726 } 727 var outputBytes bytes.Buffer 728 if _, err := io.Copy(&outputBytes, reader); err != nil { 729 return nil 730 } 731 if outputBytes.Len() == 0 { 732 return nil 733 } 734 if dataLength != uint32(outputBytes.Len()) { // double check that the stored and uncompressed lengths match 735 return nil 736 } 737 return outputBytes.Bytes() 738 } 739 740 // DecodePosition attempts to uncompress the passed value first and if it fails tries to decode it as a valid GTID 741 func DecodePosition(gtid string) (mysql.Position, error) { 742 b := MysqlUncompress(gtid) 743 if b != nil { 744 gtid = string(b) 745 } 746 return mysql.DecodePosition(gtid) 747 } 748 749 // StatsHistoryRecord is used to store a Message with timestamp 750 type StatsHistoryRecord struct { 751 Time time.Time 752 Message string 753 } 754 755 // IsDuplicate implements history.Deduplicable 756 func (r *StatsHistoryRecord) IsDuplicate(other any) bool { 757 return false 758 } 759 760 const binlogPlayerProtocolFlagName = "binlog_player_protocol" 761 762 // SetProtocol is a helper function to set the binlogplayer --binlog_player_protocol 763 // flag value for tests. If successful, it returns a function that, when called, 764 // returns the flag to its previous value. 765 // 766 // Note that because this variable is bound to a flag, the effects of this 767 // function are global, not scoped to the calling test-case. Therefore, it should 768 // not be used in conjunction with t.Parallel. 769 func SetProtocol(name string, protocol string) (reset func()) { 770 var tmp []string 771 tmp, os.Args = os.Args[:], []string{name} 772 defer func() { os.Args = tmp }() 773 774 servenv.OnParseFor(name, func(fs *pflag.FlagSet) { 775 if fs.Lookup(binlogPlayerProtocolFlagName) != nil { 776 return 777 } 778 779 registerFlags(fs) 780 }) 781 servenv.ParseFlags(name) 782 783 switch oldVal, err := pflag.CommandLine.GetString(binlogPlayerProtocolFlagName); err { 784 case nil: 785 reset = func() { SetProtocol(name, oldVal) } 786 default: 787 log.Errorf("failed to get string value for flag %q: %v", binlogPlayerProtocolFlagName, err) 788 reset = func() {} 789 } 790 791 if err := pflag.Set(binlogPlayerProtocolFlagName, protocol); err != nil { 792 msg := "failed to set flag %q to %q: %v" 793 log.Errorf(msg, binlogPlayerProtocolFlagName, protocol, err) 794 reset = func() {} 795 } 796 797 return reset 798 }