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 }