vitess.io/vitess@v0.16.2/go/mysql/flavor_mysqlgr.go (about)

     1  /*
     2  Copyright 2021 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 mysql
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"math"
    23  
    24  	"vitess.io/vitess/go/vt/proto/vtrpc"
    25  
    26  	"vitess.io/vitess/go/vt/vterrors"
    27  
    28  	"vitess.io/vitess/go/sqltypes"
    29  )
    30  
    31  // GRFlavorID is the string identifier for the MysqlGR flavor.
    32  const GRFlavorID = "MysqlGR"
    33  
    34  // ErrNoGroupStatus means no status for group replication.
    35  var ErrNoGroupStatus = errors.New("no group status")
    36  
    37  // mysqlGRFlavor implements the Flavor interface for Mysql.
    38  type mysqlGRFlavor struct {
    39  	mysqlFlavor
    40  }
    41  
    42  // newMysqlGRFlavor creates a new mysqlGR flavor.
    43  func newMysqlGRFlavor() flavor {
    44  	return &mysqlGRFlavor{}
    45  }
    46  
    47  // startReplicationCommand returns the command to start the replication.
    48  // we return empty here since `START GROUP_REPLICATION` should be called by
    49  // the external orchestrator
    50  func (mysqlGRFlavor) startReplicationCommand() string {
    51  	return ""
    52  }
    53  
    54  // restartReplicationCommands is disabled in mysqlGRFlavor
    55  func (mysqlGRFlavor) restartReplicationCommands() []string {
    56  	return []string{}
    57  }
    58  
    59  // startReplicationUntilAfter is disabled in mysqlGRFlavor
    60  func (mysqlGRFlavor) startReplicationUntilAfter(pos Position) string {
    61  	return ""
    62  }
    63  
    64  // startSQLThreadUntilAfter is disabled in mysqlGRFlavor
    65  func (mysqlGRFlavor) startSQLThreadUntilAfter(pos Position) string {
    66  	return ""
    67  }
    68  
    69  // stopReplicationCommand returns the command to stop the replication.
    70  // we return empty here since `STOP GROUP_REPLICATION` should be called by
    71  // the external orchestrator
    72  func (mysqlGRFlavor) stopReplicationCommand() string {
    73  	return ""
    74  }
    75  
    76  // stopIOThreadCommand is disabled in mysqlGRFlavor
    77  func (mysqlGRFlavor) stopIOThreadCommand() string {
    78  	return ""
    79  }
    80  
    81  // stopSQLThreadCommand is disabled in mysqlGRFlavor
    82  func (mysqlGRFlavor) stopSQLThreadCommand() string {
    83  	return ""
    84  }
    85  
    86  // startSQLThreadCommand is disabled in mysqlGRFlavor
    87  func (mysqlGRFlavor) startSQLThreadCommand() string {
    88  	return ""
    89  }
    90  
    91  // resetReplicationCommands is disabled in mysqlGRFlavor
    92  func (mysqlGRFlavor) resetReplicationCommands(c *Conn) []string {
    93  	return []string{}
    94  }
    95  
    96  // resetReplicationParametersCommands is part of the Flavor interface.
    97  func (mysqlGRFlavor) resetReplicationParametersCommands(c *Conn) []string {
    98  	return []string{}
    99  }
   100  
   101  // setReplicationPositionCommands is disabled in mysqlGRFlavor
   102  func (mysqlGRFlavor) setReplicationPositionCommands(pos Position) []string {
   103  	return []string{}
   104  }
   105  
   106  // status returns the result of the appropriate status command,
   107  // with parsed replication position.
   108  //
   109  // Note: primary will skip this function, only replica will call it.
   110  // TODO: Right now the GR's lag is defined as the lag between a node processing a txn
   111  // and the time the txn was committed. We should consider reporting lag between current queueing txn timestamp
   112  // from replication_connection_status and the current processing txn's commit timestamp
   113  func (mysqlGRFlavor) status(c *Conn) (ReplicationStatus, error) {
   114  	res := ReplicationStatus{}
   115  	// Get primary node information
   116  	query := `SELECT
   117  		MEMBER_HOST,
   118  		MEMBER_PORT
   119  	FROM
   120  		performance_schema.replication_group_members
   121  	WHERE
   122  		MEMBER_ROLE='PRIMARY' AND MEMBER_STATE='ONLINE'`
   123  	err := fetchStatusForGroupReplication(c, query, func(values []sqltypes.Value) error {
   124  		parsePrimaryGroupMember(&res, values)
   125  		return nil
   126  	})
   127  	if err != nil {
   128  		return ReplicationStatus{}, err
   129  	}
   130  
   131  	query = `SELECT
   132  		MEMBER_STATE
   133  	FROM
   134  		performance_schema.replication_group_members
   135  	WHERE
   136  		MEMBER_HOST=convert(@@hostname using ascii) AND MEMBER_PORT=@@port`
   137  	var chanel string
   138  	err = fetchStatusForGroupReplication(c, query, func(values []sqltypes.Value) error {
   139  		state := values[0].ToString()
   140  		if state == "ONLINE" {
   141  			chanel = "group_replication_applier"
   142  		} else if state == "RECOVERING" {
   143  			chanel = "group_replication_recovery"
   144  		} else { // OFFLINE, ERROR, UNREACHABLE
   145  			// If the member is not in healthy state, use max int as lag
   146  			res.ReplicationLagSeconds = math.MaxUint32
   147  		}
   148  		return nil
   149  	})
   150  	if err != nil {
   151  		return ReplicationStatus{}, err
   152  	}
   153  	// if chanel is not set, it means the state is not ONLINE or RECOVERING
   154  	// return partial result early
   155  	if chanel == "" {
   156  		return res, nil
   157  	}
   158  
   159  	// Populate IOState from replication_connection_status
   160  	query = fmt.Sprintf(`SELECT SERVICE_STATE
   161  		FROM performance_schema.replication_connection_status
   162  		WHERE CHANNEL_NAME='%s'`, chanel)
   163  	var connectionState ReplicationState
   164  	err = fetchStatusForGroupReplication(c, query, func(values []sqltypes.Value) error {
   165  		connectionState = ReplicationStatusToState(values[0].ToString())
   166  		return nil
   167  	})
   168  	if err != nil {
   169  		return ReplicationStatus{}, err
   170  	}
   171  	res.IOState = connectionState
   172  	// Populate SQLState from replication_connection_status
   173  	var applierState ReplicationState
   174  	query = fmt.Sprintf(`SELECT SERVICE_STATE
   175  		FROM performance_schema.replication_applier_status_by_coordinator
   176  		WHERE CHANNEL_NAME='%s'`, chanel)
   177  	err = fetchStatusForGroupReplication(c, query, func(values []sqltypes.Value) error {
   178  		applierState = ReplicationStatusToState(values[0].ToString())
   179  		return nil
   180  	})
   181  	if err != nil {
   182  		return ReplicationStatus{}, err
   183  	}
   184  	res.SQLState = applierState
   185  
   186  	// Collect lag information
   187  	// we use the difference between the last processed transaction's commit time
   188  	// and the end buffer time as the proxy to the lag
   189  	query = fmt.Sprintf(`SELECT
   190  		TIMESTAMPDIFF(SECOND, LAST_PROCESSED_TRANSACTION_ORIGINAL_COMMIT_TIMESTAMP, LAST_PROCESSED_TRANSACTION_END_BUFFER_TIMESTAMP)
   191  	FROM
   192  		performance_schema.replication_applier_status_by_coordinator
   193  	WHERE
   194  		CHANNEL_NAME='%s'`, chanel)
   195  	err = fetchStatusForGroupReplication(c, query, func(values []sqltypes.Value) error {
   196  		parseReplicationApplierLag(&res, values)
   197  		return nil
   198  	})
   199  	if err != nil {
   200  		return ReplicationStatus{}, err
   201  	}
   202  	return res, nil
   203  }
   204  
   205  func parsePrimaryGroupMember(res *ReplicationStatus, row []sqltypes.Value) {
   206  	res.SourceHost = row[0].ToString() /* MEMBER_HOST */
   207  	memberPort, _ := row[1].ToInt64()  /* MEMBER_PORT */
   208  	res.SourcePort = int(memberPort)
   209  }
   210  
   211  func parseReplicationApplierLag(res *ReplicationStatus, row []sqltypes.Value) {
   212  	lagSec, err := row[0].ToInt64()
   213  	// if the error is not nil, ReplicationLagSeconds will remain to be MaxUint32
   214  	if err == nil {
   215  		// Only set where there is no error
   216  		// The value can be NULL when there is no replication applied yet
   217  		res.ReplicationLagSeconds = uint(lagSec)
   218  	}
   219  }
   220  
   221  func fetchStatusForGroupReplication(c *Conn, query string, onResult func([]sqltypes.Value) error) error {
   222  	qr, err := c.ExecuteFetch(query, 100, true /* wantfields */)
   223  	if err != nil {
   224  		return err
   225  	}
   226  	// if group replication related query returns 0 rows, it means the group replication is not set up
   227  	if len(qr.Rows) == 0 {
   228  		return ErrNoGroupStatus
   229  	}
   230  	if len(qr.Rows) > 1 {
   231  		return vterrors.Errorf(vtrpc.Code_INTERNAL, "unexpected results for %v: %v", query, qr.Rows)
   232  	}
   233  	return onResult(qr.Rows[0])
   234  }
   235  
   236  // primaryStatus returns the result of 'SHOW MASTER STATUS',
   237  // with parsed executed position.
   238  func (mysqlGRFlavor) primaryStatus(c *Conn) (PrimaryStatus, error) {
   239  	return mysqlFlavor{}.primaryStatus(c)
   240  }
   241  
   242  func (mysqlGRFlavor) baseShowTables() string {
   243  	return mysqlFlavor{}.baseShowTables()
   244  }
   245  
   246  func (mysqlGRFlavor) baseShowTablesWithSizes() string {
   247  	return TablesWithSize80
   248  }
   249  
   250  // supportsCapability is part of the Flavor interface.
   251  func (mysqlGRFlavor) supportsCapability(serverVersion string, capability FlavorCapability) (bool, error) {
   252  	switch capability {
   253  	case InstantDDLFlavorCapability,
   254  		InstantExpandEnumCapability,
   255  		InstantAddLastColumnFlavorCapability,
   256  		InstantAddDropVirtualColumnFlavorCapability,
   257  		InstantChangeColumnDefaultFlavorCapability:
   258  		return ServerVersionAtLeast(serverVersion, 8, 0, 0)
   259  	case InstantAddDropColumnFlavorCapability:
   260  		return ServerVersionAtLeast(serverVersion, 8, 0, 29)
   261  	case TransactionalGtidExecutedFlavorCapability:
   262  		return ServerVersionAtLeast(serverVersion, 8, 0, 17)
   263  	case FastDropTableFlavorCapability:
   264  		return ServerVersionAtLeast(serverVersion, 8, 0, 23)
   265  	case MySQLJSONFlavorCapability:
   266  		return ServerVersionAtLeast(serverVersion, 5, 7, 0)
   267  	case MySQLUpgradeInServerFlavorCapability:
   268  		return ServerVersionAtLeast(serverVersion, 8, 0, 16)
   269  	case DynamicRedoLogCapacityFlavorCapability:
   270  		return ServerVersionAtLeast(serverVersion, 8, 0, 30)
   271  	default:
   272  		return false, nil
   273  	}
   274  }
   275  
   276  func init() {
   277  	flavors[GRFlavorID] = newMysqlGRFlavor
   278  }