Move MySQL back in time, decontaminate or un-split brain a MySQL server, restore it into replication chain.
Rewinding MySQL is a way to move MySQL back in time, so as to de-apply bad transactions, and set the server as a valid replica to some other server.
There are two use cases:
- A replica accidentally contaminated by DML, e.g. some
DELETEwas executed directly on replica. - A split brain master: a scenario where a failover process demoted master A and promoted master B, even as demoted master A continued to receive some traffic.
gh-mysql-rewindcan un-split brain on server A and restore it as a healthy replica under B or any of its healthy replicas.
- MySQL GTID replication (
gtid_mode=ON) binlog_format=ROWbinlog_row_image=FULL- Binary logs enabled
log_slave_updatesenabled- Tested on MySQL 5.7, should work with 5.6, 8.0
gh-mysql-rewind is developed internally at GitHub and released to the public under the MIT license, and in slightly modified form (removing GitHub-specific code).
As an internal tool, it uses some of our existing infrastructure. Users of this tool should take a few steps before executing on their environments:
- Install
orchestrator, or alternatively replace allorchestrator-clientcommands with their own implementation. - Place the MariaDB
mysqlbinlogbinary, version10.3.10or above, on the server to be rewinded - Setup MySQL credentials for the tool to use
- All required and suggested changes are indicated in the code. Look for
IMPLEMENTATION.
gh-mysql-rewind is an implementation that utilizes two technologies:
- Oracle/MySQL GTID
- MariaDB's
mysqlbinlog --flashback
With GTID, we are able to know what went wrong on a server by observing the errant transactions: GTID entries applied on a server but not on its master or would-be master. That is the general detection mechanism.
With mysqlbinlog --flashback we are able to generate the anti-chain-of-events in a binary log. Applying that onto a server effectively rewinds it back in time. Mostly.
The problem is that MariaDB is agnostic of MySQL-GTID. mysqlbinlog --flashback ignores any GTID info and generates no GTID info.
gh-mysql-rewind bridges the two technologies. It uses GTID to detect which binary logs contain the offending transactions, then uses flashback to de-apply those transactions, and finally does the math to fix executed_gtid_set, gtid_purged.
gh-mysql-rewind -m <[intermediate-]master-host> [-x] [-r]
Rewind errant transactions on local server and rewire to replicate from master-host
-m master-host, serves as GTID baseline
-x execute (default is noop)
-r auto-start replication upon rewiring
gh-mysql-rewindneeds to run on the corrupted server.- Needs to be executed by a user with
sudoprivileges. - Needs
orchestrator-clientto be available. master-hostmust be provided. This will be a "good" server in the same cluster as the corrupted server. Not necessarily amaster.gh-mysql-rewindwill usemaster-hostto infer the errant transactions, and the operated box will end up replicating frommaster-host.- Sanity/protection checks:
- Server must be
read-only, to avoid running on an active master. - Must have no replicas (
gh-mysql-rewindwill issue aRESET MASTER). - Must not be actively replicating.
- Must not use
SQL_DELAY. - Must have some errant GTID
- Server must be
The tool needs to:
- Identify which binary logs need to be reverted
- Actually revert those binlogs
- Keep accurate track of reverted GTID entries, reconfigure
gtid_purgedon server.
Flow breakdown:
- Sanity checks.
- Note down
executed_gtid_seton server. - Note down
executed_gtid_seton master. - Compute errant GTID on server.
- Sanity check: there actually is errant GTID.
- Identify which binary logs contain the errant GTID.
- Will revert the last
n(n >= 1) binary logs of the server. e.g. if binary logs aremysql-bin.001, mysql-bin.002, mysql-bin.003, mysql-bin.004, mysql-bin.005:- if
mysql-bin.005contains all errant transactions, then onlymysql-bin.005is reverted. - if
mysql-bin.003andmysql-bin.004contain all errant transactions, thenmysql-bin.005is reverted, thenmysql-bin.004, thenmysql-bin.003.
- if
- Calculate the entire GTID set contained by those binary logs, by manually parsing the binary logs
- Will revert the last
- Generate
flashbackfor the relevant binary logs.- Inject dummy GTID statements into
flashbackoutput (which is originally ignorant of GTID)
- Inject dummy GTID statements into
- Apply flashback onto MySQL
RESET MASTERset global gtid_purged=?, by subtracting: originalexecuted_gtid_set- reverted GTID set.- Clearing relay logs (existing relay logs are inconsistent with the position the server needs to replicate from).
- Reconfigure replication.
- Potentially resume replication (if
-ris provided).
-
DDL DANGER:
gh-mysql-rewindcannot undo DDLs. If aALTER TABLEtakes place,gh-mysql-rewindwill rewind MySQL back to the past across said DDL, but will not actually de-apply the DDL. As result, once the server resumes replication it is likely to break on the DDL (e.g. it won't be able to drop an index because the index is already dropped). Some DDLs will possibly just NotWork™. Like aDROP COLUMNorADD COLUMNclosely coupled with operations on the table. There would be a mismatch in the number of columns when reverting events. -
Does not support
JSON,POINTdata types and will break when trying to flashback a statement which includes tables with such columns. -
gh-mysql-rewindoperates on entire binlog files. This can be improved upon, but it simplifies the process. A complete binary log is the smallest amount of rewind. This means we probably rewind more than strictly necessary. The downside is that we spend time reverting events we don't need to revert, and then spend time reapplying those events. -
The operated server must have no replicas: the operation ends up with a
RESET MASTER. If multiple servers need to be rewinded, begin with leaf nodes and work your way up, one by one. Alternatively, rearrange the topology such that your operated server has no replicas (e.g. use.orc relocate-replicas <operated-server> below <some-other-server>)
Let's assume the worst scenario, a split brain. Before trouble began, the topology looked like this:
m-old
+ r1
+ r2
+ r3
+ m-new
+ r4
+ r5
+ r6
A network partition caused a failover and a splitting of the topology into:
m-old
+ r1
+ r2
+ r3
m-new
+ r4
+ r5
+ r6
Production traffic has been directed to m-new, the newly promoted master, and to r4, r5, r6, its replicas.
Unfortunately m-old was receiving writes from local apps even after the failover. This leaves m-old, r1, r2, r3 in a split brain state.
- We want to run
gh-mysql-rewindon all four boxes. - We cannot immediately start with
m-oldnor withr2because they have replicas. If we moved away their replicas then we'd be able to operate on them. - We can start with
r1andr3. - We can point them to any of
m-new, r4, r5, r6assuming, of course, there'slog-bin=1andlog-slave-updateson those servers. - For example, we'd login to
r3and run:gh-mysql-rewind -m r5 -x -r. If all goes well, this will lead to:
m-old
+ r1
+ r2
m-new
+ r4
+ r5
+ r3
+ r6
- For example, we can then login to
r2(which now does not have replicas) and run:gh-mysql-rewind -m m-new -x -r. If all goes well, this will lead to:
m-old
+ r1
m-new
+ r2
+ r4
+ r5
+ r3
+ r6
- And so forth until we've rewinded all corrupted servers.
Back to the split brain state in the above:
m-old
+ r1
+ r2
+ r3
- It's OK to rewind
r1andr3concurrently. It's OK to point both to samemaster-hostand it's OK to point them to different master hosts. r2cannot be rewinded as long asr3is replicating from it.- You may
.orc relocate r3 below m-old, to get:and then it's OK to rewind all threem-old + r1 + r2 + r3r1, r2, r3concurrently.
gh-mysql-rewind is tested internally at GitHub.
gh-mysql-rewindFOSDEM presentation video and slides- MariaDB flashback
- orchestrator project, binary releases.