Skip to content

Commit

Permalink
fix: checking for split brain
Browse files Browse the repository at this point in the history
  • Loading branch information
Fizic committed Mar 19, 2024
1 parent e4dc375 commit d0108bf
Show file tree
Hide file tree
Showing 6 changed files with 50 additions and 6 deletions.
8 changes: 7 additions & 1 deletion internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -827,6 +827,12 @@ func (app *App) calcActiveNodes(clusterState, clusterStateDcs map[string]*NodeSt
app.logger.Warnf("failed to get master status %v", err)
return nil, err
}
muuid, err := masterNode.UUID()
if err != nil {
app.logger.Warnf("failed to get master uuid %v", err)
return nil, err
}

for host, node := range clusterState {
if host == master {
activeNodes = append(activeNodes, master)
Expand Down Expand Up @@ -869,7 +875,7 @@ func (app *App) calcActiveNodes(clusterState, clusterStateDcs map[string]*NodeSt
continue
}
sgtids := gtids.ParseGtidSet(sstatus.ExecutedGtidSet)
if !(sstatus.ReplicationState == mysql.ReplicationRunning && isGTIDLessOrEqual(sgtids, mgtids)) {
if sstatus.ReplicationState != mysql.ReplicationRunning || isSplitBrained(sgtids, mgtids, muuid) {
app.logger.Errorf("calc active nodes: %s is not replicating or splitbrained, deleting from active...", host)
continue
}
Expand Down
21 changes: 21 additions & 0 deletions internal/app/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"fmt"
"time"

gomysql "github.com/go-mysql-org/go-mysql/mysql"
"github.com/google/uuid"
"github.com/yandex/mysync/internal/log"
"github.com/yandex/mysync/internal/mysql"
"github.com/yandex/mysync/internal/mysql/gtids"
Expand Down Expand Up @@ -233,6 +235,25 @@ func isGTIDLessOrEqual(slaveGtidSet, masterGtidSet gtids.GTIDSet) bool {
return masterGtidSet.Contain(slaveGtidSet) || masterGtidSet.Equal(slaveGtidSet)
}

func isSplitBrained(slaveGtidSet, masterGtidSet *gomysql.MysqlGTIDSet, masterUUID uuid.UUID) bool {
for _, MasterSet := range masterGtidSet.Sets {
slaveSet, ok := slaveGtidSet.Sets[MasterSet.SID.String()]
if !ok {
continue
}
if MasterSet.Contain(slaveSet) || MasterSet == slaveSet {
continue
}

if MasterSet.SID == masterUUID {
continue
}

return false
}
return true
}

func validatePriority(priority *int64) error {
if priority == nil || *priority >= 0 {
return nil
Expand Down
4 changes: 4 additions & 0 deletions internal/mysql/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ type readOnlyResult struct {
SuperReadOnly int `db:"SuperReadOnly"`
}

type ServerUUIDResult struct {
ServerUUID string `db:"server_uuid"`
}

// CascadeNodeConfiguration is a dcs node configuration for cascade mysql replica
type CascadeNodeConfiguration struct {
// StreamFrom - is a host to stream from. Can be changed from CLI.
Expand Down
6 changes: 3 additions & 3 deletions internal/mysql/gtids/wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import (

type GTIDSet = mysql.GTIDSet

func ParseGtidSet(gtidset string) mysql.GTIDSet {
parsed, err := mysql.ParseGTIDSet(mysql.MySQLFlavor, gtidset)
func ParseGtidSet(gtidset string) *mysql.MysqlGTIDSet {
parsed, err := mysql.ParseMysqlGTIDSet(gtidset)
if err != nil {
panic(err)
}
return parsed
return parsed.(*mysql.MysqlGTIDSet)
}
15 changes: 13 additions & 2 deletions internal/mysql/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ import (
"syscall"
"time"

mysql2 "github.com/go-mysql-org/go-mysql/mysql"
"github.com/go-sql-driver/mysql"
"github.com/google/uuid"
"github.com/jmoiron/sqlx"
"github.com/shirou/gopsutil/v3/process"

"github.com/yandex/mysync/internal/config"
"github.com/yandex/mysync/internal/log"
"github.com/yandex/mysync/internal/mysql/gtids"
Expand Down Expand Up @@ -578,7 +579,7 @@ func (n *Node) GTIDExecuted() (*GTIDExecuted, error) {
}

// GTIDExecuted returns global transaction id executed
func (n *Node) GTIDExecutedParsed() (gtids.GTIDSet, error) {
func (n *Node) GTIDExecutedParsed() (*mysql2.MysqlGTIDSet, error) {
gtid, err := n.GTIDExecuted()
if err != nil {
return nil, err
Expand All @@ -602,6 +603,16 @@ func (n *Node) GetBinlogs() ([]Binlog, error) {
return binlogs, err
}

// UUID returns server_uuid
func (n *Node) UUID() (uuid.UUID, error) {
var r ServerUUIDResult
err := n.queryRow(queryGetUUID, nil, &r)
if err != nil {
return uuid.UUID{}, err
}
return uuid.Parse(r.ServerUUID)
}

// IsReadOnly returns (true, true) if MySQL Node in (read-only, super-read-only) mode
func (n *Node) IsReadOnly() (bool, bool, error) {
var ror readOnlyResult
Expand Down
2 changes: 2 additions & 0 deletions internal/mysql/queries.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const (
queryReplicaStatus = "replica_status"
queryGetVersion = "get_version"
queryGTIDExecuted = "gtid_executed"
queryGetUUID = "get_uuid"
queryShowBinaryLogs = "binary_logs"
queryReplicationLag = "replication_lag"
querySlaveHosts = "slave_hosts"
Expand Down Expand Up @@ -53,6 +54,7 @@ var DefaultQueries = map[string]string{
queryReplicaStatus: `SHOW REPLICA STATUS FOR CHANNEL :channel`,
queryGetVersion: `SELECT sys.version_major() AS MajorVersion, sys.version_minor() AS MinorVersion, sys.version_patch() AS PatchVersion`,
queryGTIDExecuted: `SELECT @@GLOBAL.gtid_executed as Executed_Gtid_Set`,
queryGetUUID: `SELECT @@server_uuid as server_uuid`,
queryShowBinaryLogs: `SHOW BINARY LOGS`,
querySlaveHosts: `SHOW SLAVE HOSTS`,
queryReplicationLag: ``,
Expand Down

0 comments on commit d0108bf

Please sign in to comment.