vitess.io/vitess@v0.16.2/go/vt/mysqlctl/replication.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 /* 18 Handle creating replicas and setting up the replication streams. 19 */ 20 21 package mysqlctl 22 23 import ( 24 "errors" 25 "fmt" 26 "net" 27 "strconv" 28 "strings" 29 "time" 30 31 "vitess.io/vitess/go/vt/vtgate/evalengine" 32 33 "context" 34 35 "vitess.io/vitess/go/mysql" 36 "vitess.io/vitess/go/netutil" 37 "vitess.io/vitess/go/vt/hook" 38 "vitess.io/vitess/go/vt/log" 39 ) 40 41 // WaitForReplicationStart waits until the deadline for replication to start. 42 // This validates the current primary is correct and can be connected to. 43 func WaitForReplicationStart(mysqld MysqlDaemon, replicaStartDeadline int) error { 44 var rowMap map[string]string 45 for replicaWait := 0; replicaWait < replicaStartDeadline; replicaWait++ { 46 status, err := mysqld.ReplicationStatus() 47 if err != nil { 48 return err 49 } 50 51 if status.Running() { 52 return nil 53 } 54 time.Sleep(time.Second) 55 } 56 57 errorKeys := []string{"Last_Error", "Last_IO_Error", "Last_SQL_Error"} 58 errs := make([]string, 0, len(errorKeys)) 59 for _, key := range errorKeys { 60 if rowMap[key] != "" { 61 errs = append(errs, key+": "+rowMap[key]) 62 } 63 } 64 if len(errs) != 0 { 65 return errors.New(strings.Join(errs, ", ")) 66 } 67 return nil 68 } 69 70 // StartReplication starts replication. 71 func (mysqld *Mysqld) StartReplication(hookExtraEnv map[string]string) error { 72 ctx := context.TODO() 73 conn, err := getPoolReconnect(ctx, mysqld.dbaPool) 74 if err != nil { 75 return err 76 } 77 defer conn.Recycle() 78 79 if err := mysqld.executeSuperQueryListConn(ctx, conn, []string{conn.StartReplicationCommand()}); err != nil { 80 return err 81 } 82 83 h := hook.NewSimpleHook("postflight_start_slave") 84 h.ExtraEnv = hookExtraEnv 85 return h.ExecuteOptional() 86 } 87 88 // StartReplicationUntilAfter starts replication until replication has come to `targetPos`, then it stops replication 89 func (mysqld *Mysqld) StartReplicationUntilAfter(ctx context.Context, targetPos mysql.Position) error { 90 conn, err := getPoolReconnect(ctx, mysqld.dbaPool) 91 if err != nil { 92 return err 93 } 94 defer conn.Recycle() 95 96 queries := []string{conn.StartReplicationUntilAfterCommand(targetPos)} 97 98 return mysqld.executeSuperQueryListConn(ctx, conn, queries) 99 } 100 101 // StartSQLThreadUntilAfter starts replication's SQL thread(s) until replication has come to `targetPos`, then it stops it 102 func (mysqld *Mysqld) StartSQLThreadUntilAfter(ctx context.Context, targetPos mysql.Position) error { 103 conn, err := getPoolReconnect(ctx, mysqld.dbaPool) 104 if err != nil { 105 return err 106 } 107 defer conn.Recycle() 108 109 queries := []string{conn.StartSQLThreadUntilAfterCommand(targetPos)} 110 111 return mysqld.executeSuperQueryListConn(ctx, conn, queries) 112 } 113 114 // StopReplication stops replication. 115 func (mysqld *Mysqld) StopReplication(hookExtraEnv map[string]string) error { 116 h := hook.NewSimpleHook("preflight_stop_slave") 117 h.ExtraEnv = hookExtraEnv 118 if err := h.ExecuteOptional(); err != nil { 119 return err 120 } 121 ctx := context.TODO() 122 conn, err := getPoolReconnect(ctx, mysqld.dbaPool) 123 if err != nil { 124 return err 125 } 126 defer conn.Recycle() 127 128 return mysqld.executeSuperQueryListConn(ctx, conn, []string{conn.StopReplicationCommand()}) 129 } 130 131 // StopIOThread stops a replica's IO thread only. 132 func (mysqld *Mysqld) StopIOThread(ctx context.Context) error { 133 conn, err := getPoolReconnect(ctx, mysqld.dbaPool) 134 if err != nil { 135 return err 136 } 137 defer conn.Recycle() 138 139 return mysqld.executeSuperQueryListConn(ctx, conn, []string{conn.StopIOThreadCommand()}) 140 } 141 142 // StopSQLThread stops a replica's SQL thread(s) only. 143 func (mysqld *Mysqld) StopSQLThread(ctx context.Context) error { 144 conn, err := getPoolReconnect(ctx, mysqld.dbaPool) 145 if err != nil { 146 return err 147 } 148 defer conn.Recycle() 149 150 return mysqld.executeSuperQueryListConn(ctx, conn, []string{conn.StopSQLThreadCommand()}) 151 } 152 153 // RestartReplication stops, resets and starts replication. 154 func (mysqld *Mysqld) RestartReplication(hookExtraEnv map[string]string) error { 155 h := hook.NewSimpleHook("preflight_stop_slave") 156 h.ExtraEnv = hookExtraEnv 157 if err := h.ExecuteOptional(); err != nil { 158 return err 159 } 160 ctx := context.TODO() 161 conn, err := getPoolReconnect(ctx, mysqld.dbaPool) 162 if err != nil { 163 return err 164 } 165 defer conn.Recycle() 166 167 if err := mysqld.executeSuperQueryListConn(ctx, conn, conn.RestartReplicationCommands()); err != nil { 168 return err 169 } 170 171 h = hook.NewSimpleHook("postflight_start_slave") 172 h.ExtraEnv = hookExtraEnv 173 return h.ExecuteOptional() 174 } 175 176 // GetMysqlPort returns mysql port 177 func (mysqld *Mysqld) GetMysqlPort() (int32, error) { 178 qr, err := mysqld.FetchSuperQuery(context.TODO(), "SHOW VARIABLES LIKE 'port'") 179 if err != nil { 180 return 0, err 181 } 182 if len(qr.Rows) != 1 { 183 return 0, errors.New("no port variable in mysql") 184 } 185 utemp, err := evalengine.ToUint64(qr.Rows[0][1]) 186 if err != nil { 187 return 0, err 188 } 189 return int32(utemp), nil 190 } 191 192 // GetServerID returns mysql server id 193 func (mysqld *Mysqld) GetServerID(ctx context.Context) (uint32, error) { 194 qr, err := mysqld.FetchSuperQuery(ctx, "select @@global.server_id") 195 if err != nil { 196 return 0, err 197 } 198 if len(qr.Rows) != 1 { 199 return 0, errors.New("no server_id in mysql") 200 } 201 utemp, err := evalengine.ToUint64(qr.Rows[0][0]) 202 if err != nil { 203 return 0, err 204 } 205 return uint32(utemp), nil 206 } 207 208 // GetServerUUID returns mysql server uuid 209 func (mysqld *Mysqld) GetServerUUID(ctx context.Context) (string, error) { 210 conn, err := getPoolReconnect(ctx, mysqld.dbaPool) 211 if err != nil { 212 return "", err 213 } 214 defer conn.Recycle() 215 216 return conn.GetServerUUID() 217 } 218 219 // IsReadOnly return true if the instance is read only 220 func (mysqld *Mysqld) IsReadOnly() (bool, error) { 221 qr, err := mysqld.FetchSuperQuery(context.TODO(), "SHOW VARIABLES LIKE 'read_only'") 222 if err != nil { 223 return true, err 224 } 225 if len(qr.Rows) != 1 { 226 return true, errors.New("no read_only variable in mysql") 227 } 228 if qr.Rows[0][1].ToString() == "ON" { 229 return true, nil 230 } 231 return false, nil 232 } 233 234 // SetReadOnly set/unset the read_only flag 235 func (mysqld *Mysqld) SetReadOnly(on bool) error { 236 // temp logging, to be removed in v17 237 var newState string 238 switch on { 239 case false: 240 newState = "ReadWrite" 241 case true: 242 newState = "ReadOnly" 243 } 244 log.Infof("SetReadOnly setting connection setting of %s:%d to : %s", 245 mysqld.dbcfgs.Host, mysqld.dbcfgs.Port, newState) 246 247 query := "SET GLOBAL read_only = " 248 if on { 249 query += "ON" 250 } else { 251 query += "OFF" 252 } 253 return mysqld.ExecuteSuperQuery(context.TODO(), query) 254 } 255 256 // SetSuperReadOnly set/unset the super_read_only flag 257 func (mysqld *Mysqld) SetSuperReadOnly(on bool) error { 258 query := "SET GLOBAL super_read_only = " 259 if on { 260 query += "ON" 261 } else { 262 query += "OFF" 263 } 264 return mysqld.ExecuteSuperQuery(context.TODO(), query) 265 } 266 267 // WaitSourcePos lets replicas wait to given replication position 268 func (mysqld *Mysqld) WaitSourcePos(ctx context.Context, targetPos mysql.Position) error { 269 // Get a connection. 270 conn, err := getPoolReconnect(ctx, mysqld.dbaPool) 271 if err != nil { 272 return err 273 } 274 defer conn.Recycle() 275 276 // First check if filePos flavored Position was passed in. If so, we can't defer to the flavor in the connection, 277 // unless that flavor is also filePos. 278 waitCommandName := "WaitUntilPositionCommand" 279 var query string 280 if targetPos.MatchesFlavor(mysql.FilePosFlavorID) { 281 // If we are the primary, WaitUntilFilePositionCommand will fail. 282 // But position is most likely reached. So, check the position 283 // first. 284 mpos, err := conn.PrimaryFilePosition() 285 if err != nil { 286 return fmt.Errorf("WaitSourcePos: PrimaryFilePosition failed: %v", err) 287 } 288 if mpos.AtLeast(targetPos) { 289 return nil 290 } 291 292 // Find the query to run, run it. 293 query, err = conn.WaitUntilFilePositionCommand(ctx, targetPos) 294 if err != nil { 295 return err 296 } 297 waitCommandName = "WaitUntilFilePositionCommand" 298 } else { 299 // If we are the primary, WaitUntilPositionCommand will fail. 300 // But position is most likely reached. So, check the position 301 // first. 302 mpos, err := conn.PrimaryPosition() 303 if err != nil { 304 return fmt.Errorf("WaitSourcePos: PrimaryPosition failed: %v", err) 305 } 306 if mpos.AtLeast(targetPos) { 307 return nil 308 } 309 310 // Find the query to run, run it. 311 query, err = conn.WaitUntilPositionCommand(ctx, targetPos) 312 if err != nil { 313 return err 314 } 315 } 316 317 qr, err := mysqld.FetchSuperQuery(ctx, query) 318 if err != nil { 319 return fmt.Errorf("%v(%v) failed: %v", waitCommandName, query, err) 320 } 321 322 if len(qr.Rows) != 1 || len(qr.Rows[0]) != 1 { 323 return fmt.Errorf("unexpected result format from %v(%v): %#v", waitCommandName, query, qr) 324 } 325 result := qr.Rows[0][0] 326 if result.IsNull() { 327 return fmt.Errorf("%v(%v) failed: replication is probably stopped", waitCommandName, query) 328 } 329 if result.ToString() == "-1" { 330 return fmt.Errorf("timed out waiting for position %v", targetPos) 331 } 332 return nil 333 } 334 335 // ReplicationStatus returns the server replication status 336 func (mysqld *Mysqld) ReplicationStatus() (mysql.ReplicationStatus, error) { 337 conn, err := getPoolReconnect(context.TODO(), mysqld.dbaPool) 338 if err != nil { 339 return mysql.ReplicationStatus{}, err 340 } 341 defer conn.Recycle() 342 343 return conn.ShowReplicationStatus() 344 } 345 346 // PrimaryStatus returns the primary replication statuses 347 func (mysqld *Mysqld) PrimaryStatus(ctx context.Context) (mysql.PrimaryStatus, error) { 348 conn, err := getPoolReconnect(ctx, mysqld.dbaPool) 349 if err != nil { 350 return mysql.PrimaryStatus{}, err 351 } 352 defer conn.Recycle() 353 354 return conn.ShowPrimaryStatus() 355 } 356 357 // GetGTIDPurged returns the gtid purged statuses 358 func (mysqld *Mysqld) GetGTIDPurged(ctx context.Context) (mysql.Position, error) { 359 conn, err := getPoolReconnect(ctx, mysqld.dbaPool) 360 if err != nil { 361 return mysql.Position{}, err 362 } 363 defer conn.Recycle() 364 365 return conn.GetGTIDPurged() 366 } 367 368 // PrimaryPosition returns the primary replication position. 369 func (mysqld *Mysqld) PrimaryPosition() (mysql.Position, error) { 370 conn, err := getPoolReconnect(context.TODO(), mysqld.dbaPool) 371 if err != nil { 372 return mysql.Position{}, err 373 } 374 defer conn.Recycle() 375 376 return conn.PrimaryPosition() 377 } 378 379 // SetReplicationPosition sets the replication position at which the replica will resume 380 // when its replication is started. 381 func (mysqld *Mysqld) SetReplicationPosition(ctx context.Context, pos mysql.Position) error { 382 conn, err := getPoolReconnect(ctx, mysqld.dbaPool) 383 if err != nil { 384 return err 385 } 386 defer conn.Recycle() 387 388 cmds := conn.SetReplicationPositionCommands(pos) 389 log.Infof("Executing commands to set replication position: %v", cmds) 390 return mysqld.executeSuperQueryListConn(ctx, conn, cmds) 391 } 392 393 // SetReplicationSource makes the provided host / port the primary. It optionally 394 // stops replication before, and starts it after. 395 func (mysqld *Mysqld) SetReplicationSource(ctx context.Context, host string, port int, replicationStopBefore bool, replicationStartAfter bool) error { 396 params, err := mysqld.dbcfgs.ReplConnector().MysqlParams() 397 if err != nil { 398 return err 399 } 400 conn, err := getPoolReconnect(ctx, mysqld.dbaPool) 401 if err != nil { 402 return err 403 } 404 defer conn.Recycle() 405 406 cmds := []string{} 407 if replicationStopBefore { 408 cmds = append(cmds, conn.StopReplicationCommand()) 409 } 410 // Reset replication parameters commands makes the instance forget the source host port 411 // This is required because sometimes MySQL gets stuck due to improper initialization of 412 // master info structure or related failures and throws errors like 413 // ERROR 1201 (HY000): Could not initialize master info structure; more error messages can be found in the MySQL error log 414 // These errors can only be resolved by resetting the replication parameters, otherwise START SLAVE fails. 415 // Therefore, we have elected to always reset the replication parameters whenever we try to set the source host port 416 // Since there is no real overhead, but it makes this function robust enough to also handle failures like these. 417 cmds = append(cmds, conn.ResetReplicationParametersCommands()...) 418 smc := conn.SetReplicationSourceCommand(params, host, port, int(replicationConnectRetry.Seconds())) 419 cmds = append(cmds, smc) 420 if replicationStartAfter { 421 cmds = append(cmds, conn.StartReplicationCommand()) 422 } 423 return mysqld.executeSuperQueryListConn(ctx, conn, cmds) 424 } 425 426 // ResetReplication resets all replication for this host. 427 func (mysqld *Mysqld) ResetReplication(ctx context.Context) error { 428 conn, connErr := getPoolReconnect(ctx, mysqld.dbaPool) 429 if connErr != nil { 430 return connErr 431 } 432 defer conn.Recycle() 433 434 cmds := conn.ResetReplicationCommands() 435 return mysqld.executeSuperQueryListConn(ctx, conn, cmds) 436 } 437 438 // ResetReplicationParameters resets the replica replication parameters for this host. 439 func (mysqld *Mysqld) ResetReplicationParameters(ctx context.Context) error { 440 conn, connErr := getPoolReconnect(ctx, mysqld.dbaPool) 441 if connErr != nil { 442 return connErr 443 } 444 defer conn.Recycle() 445 446 cmds := conn.ResetReplicationParametersCommands() 447 return mysqld.executeSuperQueryListConn(ctx, conn, cmds) 448 } 449 450 // +------+---------+---------------------+------+-------------+------+----------------------------------------------------------------+------------------+ 451 // | Id | User | Host | db | Command | Time | State | Info | 452 // +------+---------+---------------------+------+-------------+------+----------------------------------------------------------------+------------------+ 453 // | 9792 | vt_repl | host:port | NULL | Binlog Dump | 54 | Has sent all binlog to slave; waiting for binlog to be updated | NULL | 454 // | 9797 | vt_dba | localhost | NULL | Query | 0 | NULL | show processlist | 455 // +------+---------+---------------------+------+-------------+------+----------------------------------------------------------------+------------------+ 456 // 457 // Array indices for the results of SHOW PROCESSLIST. 458 const ( 459 colConnectionID = iota //nolint 460 colUsername //nolint 461 colClientAddr 462 colDbName //nolint 463 colCommand 464 ) 465 466 const ( 467 // this is the command used by mysql replicas 468 binlogDumpCommand = "Binlog Dump" 469 ) 470 471 // FindReplicas gets IP addresses for all currently connected replicas. 472 func FindReplicas(mysqld MysqlDaemon) ([]string, error) { 473 qr, err := mysqld.FetchSuperQuery(context.TODO(), "SHOW PROCESSLIST") 474 if err != nil { 475 return nil, err 476 } 477 addrs := make([]string, 0, 32) 478 for _, row := range qr.Rows { 479 // Check for prefix, since it could be "Binlog Dump GTID". 480 if strings.HasPrefix(row[colCommand].ToString(), binlogDumpCommand) { 481 host := row[colClientAddr].ToString() 482 if host == "localhost" { 483 // If we have a local binlog streamer, it will 484 // show up as being connected 485 // from 'localhost' through the local 486 // socket. Ignore it. 487 continue 488 } 489 host, _, err = netutil.SplitHostPort(host) 490 if err != nil { 491 return nil, fmt.Errorf("FindReplicas: malformed addr %v", err) 492 } 493 var ips []string 494 ips, err = net.LookupHost(host) 495 if err != nil { 496 return nil, fmt.Errorf("FindReplicas: LookupHost failed %v", err) 497 } 498 addrs = append(addrs, ips...) 499 } 500 } 501 502 return addrs, nil 503 } 504 505 // EnableBinlogPlayback prepares the server to play back events from a binlog stream. 506 // Whatever it does for a given flavor, it must be idempotent. 507 func (mysqld *Mysqld) EnableBinlogPlayback() error { 508 // Get a connection. 509 conn, err := getPoolReconnect(context.TODO(), mysqld.dbaPool) 510 if err != nil { 511 return err 512 } 513 defer conn.Recycle() 514 515 // See if we have a command to run, and run it. 516 cmd := conn.EnableBinlogPlaybackCommand() 517 if cmd == "" { 518 return nil 519 } 520 if err := mysqld.ExecuteSuperQuery(context.TODO(), cmd); err != nil { 521 log.Errorf("EnableBinlogPlayback: cannot run query '%v': %v", cmd, err) 522 return fmt.Errorf("EnableBinlogPlayback: cannot run query '%v': %v", cmd, err) 523 } 524 525 log.Info("EnableBinlogPlayback: successfully ran %v", cmd) 526 return nil 527 } 528 529 // DisableBinlogPlayback returns the server to the normal state after streaming. 530 // Whatever it does for a given flavor, it must be idempotent. 531 func (mysqld *Mysqld) DisableBinlogPlayback() error { 532 // Get a connection. 533 conn, err := getPoolReconnect(context.TODO(), mysqld.dbaPool) 534 if err != nil { 535 return err 536 } 537 defer conn.Recycle() 538 539 // See if we have a command to run, and run it. 540 cmd := conn.DisableBinlogPlaybackCommand() 541 if cmd == "" { 542 return nil 543 } 544 if err := mysqld.ExecuteSuperQuery(context.TODO(), cmd); err != nil { 545 log.Errorf("DisableBinlogPlayback: cannot run query '%v': %v", cmd, err) 546 return fmt.Errorf("DisableBinlogPlayback: cannot run query '%v': %v", cmd, err) 547 } 548 549 log.Info("DisableBinlogPlayback: successfully ran '%v'", cmd) 550 return nil 551 } 552 553 // GetBinlogInformation gets the binlog format, whether binlog is enabled and if updates on replica logging is enabled. 554 func (mysqld *Mysqld) GetBinlogInformation(ctx context.Context) (string, bool, bool, string, error) { 555 qr, err := mysqld.FetchSuperQuery(ctx, "select @@global.binlog_format, @@global.log_bin, @@global.log_slave_updates, @@global.binlog_row_image") 556 if err != nil { 557 return "", false, false, "", err 558 } 559 if len(qr.Rows) != 1 { 560 return "", false, false, "", errors.New("unable to read global variables binlog_format, log_bin, log_slave_updates, gtid_mode, binlog_rowge") 561 } 562 res := qr.Named().Row() 563 binlogFormat, err := res.ToString("@@global.binlog_format") 564 if err != nil { 565 return "", false, false, "", err 566 } 567 logBin, err := res.ToInt64("@@global.log_bin") 568 if err != nil { 569 return "", false, false, "", err 570 } 571 logReplicaUpdates, err := res.ToInt64("@@global.log_slave_updates") 572 if err != nil { 573 return "", false, false, "", err 574 } 575 binlogRowImage, err := res.ToString("@@global.binlog_row_image") 576 if err != nil { 577 return "", false, false, "", err 578 } 579 return binlogFormat, logBin == 1, logReplicaUpdates == 1, binlogRowImage, nil 580 } 581 582 // GetGTIDMode gets the GTID mode for the server 583 func (mysqld *Mysqld) GetGTIDMode(ctx context.Context) (string, error) { 584 conn, err := getPoolReconnect(ctx, mysqld.dbaPool) 585 if err != nil { 586 return "", err 587 } 588 defer conn.Recycle() 589 590 return conn.GetGTIDMode() 591 } 592 593 // FlushBinaryLogs is part of the MysqlDaemon interface. 594 func (mysqld *Mysqld) FlushBinaryLogs(ctx context.Context) (err error) { 595 _, err = mysqld.FetchSuperQuery(ctx, "FLUSH BINARY LOGS") 596 return err 597 } 598 599 // GetBinaryLogs is part of the MysqlDaemon interface. 600 func (mysqld *Mysqld) GetBinaryLogs(ctx context.Context) (binaryLogs []string, err error) { 601 qr, err := mysqld.FetchSuperQuery(ctx, "SHOW BINARY LOGS") 602 if err != nil { 603 return binaryLogs, err 604 } 605 for _, row := range qr.Rows { 606 binaryLogs = append(binaryLogs, row[0].ToString()) 607 } 608 return binaryLogs, err 609 } 610 611 // GetPreviousGTIDs is part of the MysqlDaemon interface. 612 func (mysqld *Mysqld) GetPreviousGTIDs(ctx context.Context, binlog string) (previousGtids string, err error) { 613 query := fmt.Sprintf("SHOW BINLOG EVENTS IN '%s' LIMIT 2", binlog) 614 qr, err := mysqld.FetchSuperQuery(ctx, query) 615 if err != nil { 616 return previousGtids, err 617 } 618 previousGtidsFound := false 619 for _, row := range qr.Named().Rows { 620 if row.AsString("Event_type", "") == "Previous_gtids" { 621 previousGtids = row.AsString("Info", "") 622 previousGtidsFound = true 623 } 624 } 625 if !previousGtidsFound { 626 return previousGtids, fmt.Errorf("GetPreviousGTIDs: previous GTIDs not found") 627 } 628 return previousGtids, nil 629 } 630 631 // SetSemiSyncEnabled enables or disables semi-sync replication for 632 // primary and/or replica mode. 633 func (mysqld *Mysqld) SetSemiSyncEnabled(primary, replica bool) error { 634 log.Infof("Setting semi-sync mode: primary=%v, replica=%v", primary, replica) 635 636 // Convert bool to int. 637 var p, s int 638 if primary { 639 p = 1 640 } 641 if replica { 642 s = 1 643 } 644 645 err := mysqld.ExecuteSuperQuery(context.TODO(), fmt.Sprintf( 646 "SET GLOBAL rpl_semi_sync_master_enabled = %v, GLOBAL rpl_semi_sync_slave_enabled = %v", 647 p, s)) 648 if err != nil { 649 return fmt.Errorf("can't set semi-sync mode: %v; make sure plugins are loaded in my.cnf", err) 650 } 651 return nil 652 } 653 654 // SemiSyncEnabled returns whether semi-sync is enabled for primary or replica. 655 // If the semi-sync plugin is not loaded, we assume semi-sync is disabled. 656 func (mysqld *Mysqld) SemiSyncEnabled() (primary, replica bool) { 657 vars, err := mysqld.fetchVariables(context.TODO(), "rpl_semi_sync_%_enabled") 658 if err != nil { 659 return false, false 660 } 661 primary = (vars["rpl_semi_sync_master_enabled"] == "ON") 662 replica = (vars["rpl_semi_sync_slave_enabled"] == "ON") 663 return primary, replica 664 } 665 666 // SemiSyncStatus returns the current status of semi-sync for primary and replica. 667 func (mysqld *Mysqld) SemiSyncStatus() (primary, replica bool) { 668 vars, err := mysqld.fetchStatuses(context.TODO(), "Rpl_semi_sync_%_status") 669 if err != nil { 670 return false, false 671 } 672 primary = vars["Rpl_semi_sync_master_status"] == "ON" 673 replica = vars["Rpl_semi_sync_slave_status"] == "ON" 674 return primary, replica 675 } 676 677 // SemiSyncClients returns the number of semi-sync clients for the primary. 678 func (mysqld *Mysqld) SemiSyncClients() uint32 { 679 qr, err := mysqld.FetchSuperQuery(context.TODO(), "SHOW STATUS LIKE 'Rpl_semi_sync_master_clients'") 680 if err != nil { 681 return 0 682 } 683 if len(qr.Rows) != 1 { 684 return 0 685 } 686 countStr := qr.Rows[0][1].ToString() 687 count, _ := strconv.ParseUint(countStr, 10, 0) 688 return uint32(count) 689 } 690 691 // SemiSyncSettings returns the settings of semi-sync which includes the timeout and the number of replicas to wait for. 692 func (mysqld *Mysqld) SemiSyncSettings() (timeout uint64, numReplicas uint32) { 693 vars, err := mysqld.fetchVariables(context.TODO(), "rpl_semi_sync_%") 694 if err != nil { 695 return 0, 0 696 } 697 timeout, _ = strconv.ParseUint(vars["rpl_semi_sync_master_timeout"], 10, 0) 698 numReplicasUint, _ := strconv.ParseUint(vars["rpl_semi_sync_master_wait_for_slave_count"], 10, 0) 699 return timeout, uint32(numReplicasUint) 700 } 701 702 // SemiSyncReplicationStatus returns whether semi-sync is currently used by replication. 703 func (mysqld *Mysqld) SemiSyncReplicationStatus() (bool, error) { 704 qr, err := mysqld.FetchSuperQuery(context.TODO(), "SHOW STATUS LIKE 'rpl_semi_sync_slave_status'") 705 if err != nil { 706 return false, err 707 } 708 if len(qr.Rows) != 1 { 709 return false, errors.New("no rpl_semi_sync_slave_status variable in mysql") 710 } 711 if qr.Rows[0][1].ToString() == "ON" { 712 return true, nil 713 } 714 return false, nil 715 }