diff --git a/go.mod b/go.mod index 443027c6..870981fc 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/cenkalti/backoff/v4 v4.3.0 github.com/containerd/errdefs v1.0.0 github.com/cucumber/godog v0.15.1 - github.com/go-mysql-org/go-mysql v1.14.0 + github.com/go-mysql-org/go-mysql v1.15.0 github.com/go-sql-driver/mysql v1.10.0 github.com/go-zookeeper/zk v1.0.4 github.com/gofrs/flock v0.13.0 @@ -57,7 +57,7 @@ require ( github.com/moby/moby/client v0.4.1 github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect - github.com/pingcap/errors v0.11.5-0.20250523034308-74f78ae071ee // indirect + github.com/pingcap/errors v0.11.5-0.20260310054046-9c8b3586e4b2 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect diff --git a/go.sum b/go.sum index 5f3e1c94..a88f22c4 100644 --- a/go.sum +++ b/go.sum @@ -38,8 +38,8 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-mysql-org/go-mysql v1.14.0 h1:s/TJhtutMZ7UFrXMBnxc/kYxbmtKdSEuIWryKGHJkb8= -github.com/go-mysql-org/go-mysql v1.14.0/go.mod h1:zw81GjlfxR676zCnNotEghW3agjEmcQp1WBX8M65FFw= +github.com/go-mysql-org/go-mysql v1.15.0 h1:bZeRUc9yNVbFEyote79Q4j8SV+q8Ls32AYXRl2QjUoc= +github.com/go-mysql-org/go-mysql v1.15.0/go.mod h1:VjBTZTTDKL8OMXUAhNbg3VHaVVq9HOXJEBLpAKBFIfE= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= @@ -106,8 +106,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= -github.com/pingcap/errors v0.11.5-0.20250523034308-74f78ae071ee h1:/IDPbpzkzA97t1/Z1+C3KlxbevjMeaI6BQYxvivu4u8= -github.com/pingcap/errors v0.11.5-0.20250523034308-74f78ae071ee/go.mod h1:X2r9ueLEUZgtx2cIogM0v4Zj5uvvzhuuiu7Pn8HzMPg= +github.com/pingcap/errors v0.11.5-0.20260310054046-9c8b3586e4b2 h1:cLgCk5mwDG9lDH+dPK8TmEliTjyGJwwKN0qevWAl8IY= +github.com/pingcap/errors v0.11.5-0.20260310054046-9c8b3586e4b2/go.mod h1:ktAJCA9lxrHHjVyVl2pKJFvzBnq2eZbb+CUOjBRPlXo= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -136,12 +136,13 @@ github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3A github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA= @@ -165,15 +166,12 @@ go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfC go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= -go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= @@ -194,8 +192,6 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -204,7 +200,6 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/app/util_test.go b/internal/app/util_test.go index 3e319cba..77275ad9 100644 --- a/internal/app/util_test.go +++ b/internal/app/util_test.go @@ -8,6 +8,7 @@ import ( "github.com/stretchr/testify/require" 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" @@ -317,7 +318,7 @@ func TestIsSplitBrained(t *testing.T) { masterGTID := mustGTIDSet("6DBC0B04-4B09-43DC-86CC-9AF852DED919:1-100," + "09978591-5754-4710-BF67-062880ABE1B4:1-100," + "AA6890C8-69F8-4BC4-B3A5-5D3FEA8C28CF:1-100") - masterUUID := masterGTID.(*gomysql.MysqlGTIDSet).Sets["6dbc0b04-4b09-43dc-86cc-9af852ded919"].SID + masterUUID := uuid.MustParse("6dbc0b04-4b09-43dc-86cc-9af852ded919") // equal gtids slaveGTID := mustGTIDSet("6DBC0B04-4B09-43DC-86CC-9AF852DED919:1-100," + diff --git a/internal/mysql/gtids/utils.go b/internal/mysql/gtids/utils.go index b4ac98b1..4ea44f97 100644 --- a/internal/mysql/gtids/utils.go +++ b/internal/mysql/gtids/utils.go @@ -16,21 +16,29 @@ func IsSlaveAhead(slaveGtidSet, masterGtidSet GTIDSet) bool { func IsSplitBrained(slaveGtidSet, masterGtidSet GTIDSet, masterUUID uuid.UUID) bool { mysqlSlaveGtidSet := slaveGtidSet.(*gomysql.MysqlGTIDSet) mysqlMasterGtidSet := masterGtidSet.(*gomysql.MysqlGTIDSet) - for _, slaveSet := range mysqlSlaveGtidSet.Sets { - masterSet, ok := mysqlMasterGtidSet.Sets[slaveSet.SID.String()] + for slaveUUID := range *mysqlSlaveGtidSet { + masterTagMap, ok := (*mysqlMasterGtidSet)[slaveUUID] if !ok { return true } - if masterSet.Contain(slaveSet) { - continue - } + slaveTagMap := (*mysqlSlaveGtidSet)[slaveUUID] + for tag, slaveIntervals := range slaveTagMap { + masterIntervals, ok := masterTagMap[tag] + if !ok { + return true + } - if masterSet.SID == masterUUID { - continue - } + if masterIntervals.Contain(slaveIntervals) { + continue + } + + if slaveUUID == masterUUID { + continue + } - return true + return true + } } return false diff --git a/internal/mysql/gtids/wrapper.go b/internal/mysql/gtids/wrapper.go index d5647014..30766f20 100644 --- a/internal/mysql/gtids/wrapper.go +++ b/internal/mysql/gtids/wrapper.go @@ -16,22 +16,70 @@ func ParseGtidSet(gtidset string) GTIDSet { return parsed } +// intervalSliceMinus returns intervals that are in 'a' but not in 'b' (a \ b). +// Both slices must be normalized (sorted, non-overlapping). +func intervalSliceMinus(a, b mysql.IntervalSlice) mysql.IntervalSlice { + var result mysql.IntervalSlice + bi := 0 + for _, iv := range a { + cur := iv.Start + for cur < iv.Stop { + // advance b past intervals that end before cur + for bi < len(b) && b[bi].Stop <= cur { + bi++ + } + if bi >= len(b) || b[bi].Start >= iv.Stop { + // no more b intervals overlap [cur, iv.Stop) + result = append(result, mysql.Interval{Start: cur, Stop: iv.Stop}) + break + } + // b[bi] overlaps [cur, iv.Stop) + if b[bi].Start > cur { + result = append(result, mysql.Interval{Start: cur, Stop: b[bi].Start}) + } + cur = b[bi].Stop + } + } + return result +} + +// mysqlGTIDSetMinus returns a new MysqlGTIDSet containing intervals that are +// in 'a' but not in 'b' (i.e. a \ b). +func mysqlGTIDSetMinus(a, b *mysql.MysqlGTIDSet) *mysql.MysqlGTIDSet { + result := mysql.NewMysqlGTIDSet() + for uid, aTagMap := range *a { + bTagMap := (*b)[uid] + for tag, aIntervals := range aTagMap { + var diff mysql.IntervalSlice + if bTagMap == nil { + diff = aIntervals + } else { + bIntervals := bTagMap[tag] + if bIntervals == nil { + diff = aIntervals + } else { + diff = intervalSliceMinus(aIntervals, bIntervals) + } + } + if len(diff) > 0 { + if result[uid] == nil { + result[uid] = make(map[mysql.Tag]mysql.IntervalSlice) + } + result[uid][tag] = diff + } + } + } + return &result +} + func GTIDDiff(replicaGTIDSet, sourceGTIDSet mysql.GTIDSet) (string, error) { mysqlReplicaGTIDSet := replicaGTIDSet.(*mysql.MysqlGTIDSet) mysqlSourceGTIDSet := sourceGTIDSet.(*mysql.MysqlGTIDSet) - // check standard case - diffWithSource := mysqlSourceGTIDSet.Clone().(*mysql.MysqlGTIDSet) - err := diffWithSource.Minus(*mysqlReplicaGTIDSet) - if err != nil { - return "", err - } - // check reverse case - diffWithReplica := mysqlReplicaGTIDSet.Clone().(*mysql.MysqlGTIDSet) - err = diffWithReplica.Minus(*mysqlSourceGTIDSet) - if err != nil { - return "", err - } + // intervals in source but not in replica + diffWithSource := mysqlGTIDSetMinus(mysqlSourceGTIDSet, mysqlReplicaGTIDSet) + // intervals in replica but not in source + diffWithReplica := mysqlGTIDSetMinus(mysqlReplicaGTIDSet, mysqlSourceGTIDSet) if diffWithSource.String() == "" && diffWithReplica.String() == "" { return "replica gtid equal source", nil