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  }