vitess.io/vitess@v0.16.2/go/mysql/replication_status.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 mysql 18 19 import ( 20 "fmt" 21 22 replicationdatapb "vitess.io/vitess/go/vt/proto/replicationdata" 23 "vitess.io/vitess/go/vt/vterrors" 24 ) 25 26 // ReplicationStatus holds replication information from SHOW SLAVE STATUS. 27 type ReplicationStatus struct { 28 // Position is the current position of the replica. For GTID replication implementations 29 // it is the executed GTID set. For file replication implementation, it is same as 30 // FilePosition 31 Position Position 32 // RelayLogPosition is the Position that the replica would be at if it 33 // were to finish executing everything that's currently in its relay log. 34 // However, some MySQL flavors don't expose this information, 35 // in which case RelayLogPosition.IsZero() will be true. 36 // If ReplicationLagUnknown is true then we should not rely on the seconds 37 // behind value and we can instead try to calculate the lag ourselves when 38 // appropriate. For MySQL GTID replication implementation it is the union of 39 // executed GTID set and retrieved GTID set. For file replication implementation, 40 // it is same as RelayLogSourceBinlogEquivalentPosition 41 RelayLogPosition Position 42 // FilePosition stores the position of the source tablets binary log 43 // upto which the SQL thread of the replica has run. 44 FilePosition Position 45 // RelayLogSourceBinlogEquivalentPosition stores the position of the source tablets binary log 46 // upto which the IO thread has read and added to the relay log 47 RelayLogSourceBinlogEquivalentPosition Position 48 // RelayLogFilePosition stores the position in the relay log file 49 RelayLogFilePosition Position 50 SourceServerID uint 51 IOState ReplicationState 52 LastIOError string 53 SQLState ReplicationState 54 LastSQLError string 55 ReplicationLagSeconds uint 56 ReplicationLagUnknown bool 57 SourceHost string 58 SourcePort int 59 SourceUser string 60 ConnectRetry int 61 SourceUUID SID 62 SQLDelay uint 63 AutoPosition bool 64 UsingGTID bool 65 HasReplicationFilters bool 66 SSLAllowed bool 67 } 68 69 // Running returns true if both the IO and SQL threads are running. 70 func (s *ReplicationStatus) Running() bool { 71 return s.IOState == ReplicationStateRunning && s.SQLState == ReplicationStateRunning 72 } 73 74 // Healthy returns true if both the SQL IO components are healthy 75 func (s *ReplicationStatus) Healthy() bool { 76 return s.SQLHealthy() && s.IOHealthy() 77 } 78 79 // IOHealthy returns true if the IO thread is running OR, the 80 // IO thread is connecting AND there's no IO error from the last 81 // attempt to connect to the source. 82 func (s *ReplicationStatus) IOHealthy() bool { 83 return s.IOState == ReplicationStateRunning || 84 (s.IOState == ReplicationStateConnecting && s.LastIOError == "") 85 } 86 87 // SQLHealthy returns true if the SQLState is running. 88 // For consistency and to support altering this calculation in the future. 89 func (s *ReplicationStatus) SQLHealthy() bool { 90 return s.SQLState == ReplicationStateRunning 91 } 92 93 // ReplicationStatusToProto translates a Status to proto3. 94 func ReplicationStatusToProto(s ReplicationStatus) *replicationdatapb.Status { 95 replstatuspb := &replicationdatapb.Status{ 96 Position: EncodePosition(s.Position), 97 RelayLogPosition: EncodePosition(s.RelayLogPosition), 98 FilePosition: EncodePosition(s.FilePosition), 99 RelayLogSourceBinlogEquivalentPosition: EncodePosition(s.RelayLogSourceBinlogEquivalentPosition), 100 SourceServerId: uint32(s.SourceServerID), 101 ReplicationLagSeconds: uint32(s.ReplicationLagSeconds), 102 ReplicationLagUnknown: s.ReplicationLagUnknown, 103 SqlDelay: uint32(s.SQLDelay), 104 RelayLogFilePosition: EncodePosition(s.RelayLogFilePosition), 105 SourceHost: s.SourceHost, 106 SourceUser: s.SourceUser, 107 SourcePort: int32(s.SourcePort), 108 ConnectRetry: int32(s.ConnectRetry), 109 SourceUuid: s.SourceUUID.String(), 110 IoState: int32(s.IOState), 111 LastIoError: s.LastIOError, 112 SqlState: int32(s.SQLState), 113 LastSqlError: s.LastSQLError, 114 SslAllowed: s.SSLAllowed, 115 HasReplicationFilters: s.HasReplicationFilters, 116 AutoPosition: s.AutoPosition, 117 UsingGtid: s.UsingGTID, 118 } 119 return replstatuspb 120 } 121 122 // ProtoToReplicationStatus translates a proto Status, or panics. 123 func ProtoToReplicationStatus(s *replicationdatapb.Status) ReplicationStatus { 124 pos, err := DecodePosition(s.Position) 125 if err != nil { 126 panic(vterrors.Wrapf(err, "cannot decode Position")) 127 } 128 relayPos, err := DecodePosition(s.RelayLogPosition) 129 if err != nil { 130 panic(vterrors.Wrapf(err, "cannot decode RelayLogPosition")) 131 } 132 filePos, err := DecodePosition(s.FilePosition) 133 if err != nil { 134 panic(vterrors.Wrapf(err, "cannot decode FilePosition")) 135 } 136 fileRelayPos, err := DecodePosition(s.RelayLogSourceBinlogEquivalentPosition) 137 if err != nil { 138 panic(vterrors.Wrapf(err, "cannot decode RelayLogSourceBinlogEquivalentPosition")) 139 } 140 relayFilePos, err := DecodePosition(s.RelayLogFilePosition) 141 if err != nil { 142 panic(vterrors.Wrapf(err, "cannot decode RelayLogFilePosition")) 143 } 144 var sid SID 145 if s.SourceUuid != "" { 146 sid, err = ParseSID(s.SourceUuid) 147 if err != nil { 148 panic(vterrors.Wrapf(err, "cannot decode SourceUUID")) 149 } 150 } 151 replstatus := ReplicationStatus{ 152 Position: pos, 153 RelayLogPosition: relayPos, 154 FilePosition: filePos, 155 RelayLogSourceBinlogEquivalentPosition: fileRelayPos, 156 RelayLogFilePosition: relayFilePos, 157 SourceServerID: uint(s.SourceServerId), 158 ReplicationLagSeconds: uint(s.ReplicationLagSeconds), 159 ReplicationLagUnknown: s.ReplicationLagUnknown, 160 SQLDelay: uint(s.SqlDelay), 161 SourceHost: s.SourceHost, 162 SourceUser: s.SourceUser, 163 SourcePort: int(s.SourcePort), 164 ConnectRetry: int(s.ConnectRetry), 165 SourceUUID: sid, 166 IOState: ReplicationState(s.IoState), 167 LastIOError: s.LastIoError, 168 SQLState: ReplicationState(s.SqlState), 169 LastSQLError: s.LastSqlError, 170 SSLAllowed: s.SslAllowed, 171 HasReplicationFilters: s.HasReplicationFilters, 172 AutoPosition: s.AutoPosition, 173 UsingGTID: s.UsingGtid, 174 } 175 return replstatus 176 } 177 178 // FindErrantGTIDs can be used to find errant GTIDs in the receiver's relay log, by comparing it against all known replicas, 179 // provided as a list of ReplicationStatus's. This method only works if the flavor for all retrieved ReplicationStatus's is MySQL. 180 // The result is returned as a Mysql56GTIDSet, each of whose elements is a found errant GTID. 181 // This function is best effort in nature. If it marks something as errant, then it is for sure errant. But there may be cases of errant GTIDs, which aren't caught by this function. 182 func (s *ReplicationStatus) FindErrantGTIDs(otherReplicaStatuses []*ReplicationStatus) (Mysql56GTIDSet, error) { 183 if len(otherReplicaStatuses) == 0 { 184 // If there is nothing to compare this replica against, then we must assume that its GTID set is the correct one. 185 return nil, nil 186 } 187 188 relayLogSet, ok := s.RelayLogPosition.GTIDSet.(Mysql56GTIDSet) 189 if !ok { 190 return nil, fmt.Errorf("errant GTIDs can only be computed on the MySQL flavor") 191 } 192 193 otherSets := make([]Mysql56GTIDSet, 0, len(otherReplicaStatuses)) 194 for _, status := range otherReplicaStatuses { 195 otherSet, ok := status.RelayLogPosition.GTIDSet.(Mysql56GTIDSet) 196 if !ok { 197 panic("The receiver ReplicationStatus contained a Mysql56GTIDSet in its relay log, but a replica's ReplicationStatus is of another flavor. This should never happen.") 198 } 199 otherSets = append(otherSets, otherSet) 200 } 201 202 // Copy set for final diffSet so we don't mutate receiver. 203 diffSet := make(Mysql56GTIDSet, len(relayLogSet)) 204 for sid, intervals := range relayLogSet { 205 if sid == s.SourceUUID { 206 continue 207 } 208 diffSet[sid] = intervals 209 } 210 211 for _, otherSet := range otherSets { 212 diffSet = diffSet.Difference(otherSet) 213 } 214 215 if len(diffSet) == 0 { 216 // If diffSet is empty, then we have no errant GTIDs. 217 return nil, nil 218 } 219 220 return diffSet, nil 221 }