Ask a team whether their MySQL replicas are crash-safe and you usually get one of two answers: "yes, it's MySQL 8, that's default now" or a war story about a replica that came back from an OOM kill with a duplicate-key error and had to be rebuilt from backup. Both answers are earned. MySQL 8.x replicas are dramatically more crash-safe than their ancestors, and it is still entirely possible to configure — or inherit — a replica that does not survive an unclean restart.
Crash safety for a replica means one specific thing: after the process dies at an arbitrary instant — power loss, OOM killer, kill -9 — it comes back, works out exactly where it was, and resumes replication with no duplicate transactions, no missed transactions, and no human involved. That property is the product of three mechanisms layered over a decade: transactional position tracking, relay log recovery, and GTID auto-positioning. Understanding all three is worth it, because the failure modes of each are what you inherit when one is misconfigured.
Why replicas used to break: position files
The original design tracked replication progress in flat files — master.info and relay-log.info — updated after events were applied. The applier would commit a transaction to InnoDB, then update the position file as a separate, non-atomic step. Crash between those two actions and the file says the transaction was never applied; on restart the replica re-executes it, and you wake up to a duplicate-key error, or worse, a silent double-apply on a table with no unique constraint to object.
The fix, which arrived in 5.6 and became the default in 8.0, was to move the bookkeeping into InnoDB tables — mysql.slave_relay_log_info and mysql.slave_master_info — so that with relay_log_info_repository=TABLE, the position update commits inside the same transaction as the replicated data itself. Applied-and-recorded became atomic: whatever instant the process dies, the recorded position and the actual data state cannot disagree.
The reason to know this history in 2026 is configuration archaeology. relay_log_info_repository and master_info_repository defaulted to TABLE in 8.0, were deprecated in 8.0.23, and are removed in 8.4, where TABLE is simply how it works. But a config file that has survived several upgrades can still carry relay_log_info_repository=FILE, quietly reintroducing the pre-5.6 failure mode on any 8.0 server that honors it. Grep your configs; delete the lines; let the safe default stand.
relay_log_recovery: distrust the relay log after a crash
Transactional position tracking protects the applied position. It does nothing for the relay log itself — the receiver thread's on-disk buffer of events fetched from the source. Relay logs are not synced with the same paranoia as data files; a crash can leave the newest relay log file truncated mid-event or subtly corrupt, and the receiver position recorded for it may point past what actually survived.
relay_log_recovery=ON resolves this with a blunt and correct policy: on startup, throw the unprocessed relay logs away. The replica discards everything after the applier's last committed position, resets the receiver to that same point, and re-fetches from the source. The relay log is treated as what it is — a disposable cache — and the source's binlog is treated as the durable truth.
It is OFF by default. Turn it on. On every replica. The only structural cost is re-downloading some already-fetched events after a crash, and the cases where that matters (the source has since purged those binlogs, or the source is gone too) are cases where you have a bigger incident anyway. One genuine caveat: with a multi-threaded applier, a replica that stopped uncleanly can hold gaps — later transactions committed while an earlier one was not — and relay log recovery cannot proceed from a gapped state. The documented remedy is START REPLICA UNTIL SQL_AFTER_MTS_GAPS to let the applier close the gaps, then restart cleanly. GTID auto-positioning, next, makes this whole class of problem mostly disappear.
-- The crash-safety posture, checked live:
SELECT @@GLOBAL.relay_log_recovery AS relay_log_recovery,
@@GLOBAL.gtid_mode AS gtid_mode,
@@GLOBAL.enforce_gtid_consistency AS enforce_gtid,
@@GLOBAL.sync_binlog AS sync_binlog,
@@GLOBAL.innodb_flush_log_at_trx_commit AS trx_flush;
-- Is this channel using auto-positioning?
SELECT channel_name, host, auto_position
FROM performance_schema.replication_connection_configuration;
GTID auto-position: recovery by identity, not coordinates
File-and-position replication makes crash recovery a coordinates problem: the replica must know exactly which byte offset in which binlog file to resume from, and every mechanism above exists to keep those coordinates honest. GTID auto-positioning dissolves the problem. With gtid_mode=ON and a channel configured with SOURCE_AUTO_POSITION=1, the replica reconnects by sending its gtid_executed set — the set of transaction identities it has durably applied, maintained transactionally in the mysql.gtid_executed table and the data itself — and the source computes and streams everything the replica is missing.
-- Converting a replica to GTID auto-positioning (gtid_mode=ON everywhere first):
STOP REPLICA;
CHANGE REPLICATION SOURCE TO
SOURCE_HOST = 'source.internal',
SOURCE_USER = 'repl',
SOURCE_AUTO_POSITION = 1,
SOURCE_SSL = 1;
START REPLICA;
There are no coordinates left to corrupt. A crash, a restore from a day-old snapshot, a failover to a different source — in every case the replica's opening move is "here is what I have," and the topology works out the rest. This is also what makes GTID the foundation for sane failover automation, and it composes with relay log recovery rather than replacing it: recovery still cleans up the damaged relay log cache, and auto-positioning guarantees the re-fetch resumes at exactly the right transactions. The identity model, and how it compares with PostgreSQL's LSN-plus-replication-slot approach, is explored in GTID vs replication slots.
Two flanking requirements keep the guarantee honest. Durability settings on the replica matter — sync_binlog=1 and innodb_flush_log_at_trx_commit=1 make what gtid_executed claims match what the disk holds after power loss; relaxed settings trade that certainty for speed, which is a legitimate choice on a rebuildable replica but should be a choice. And replicas should run super_read_only=ON so crash recovery is never entangled with local writes that do not belong there.
Crash replicas on purpose
Configuration review tells you what should happen. Only a crash tells you what does. The teams I trust on this run replica crash tests the way they run backup restore tests: on a schedule, against production-like hosts, with pass criteria written down.
The test is simple. Put realistic write load on the source. kill -9 the replica's mysqld mid-stream — not a graceful stop, which exercises none of this machinery. Bring it back, and verify three things: replication resumes without human action (both receiver and applier threads reach ON); no errors in Last_IO_Error or Last_SQL_Error and no duplicate-key noise in the log; and data integrity holds, checked with a table checksum comparison against the source rather than optimism. Then repeat the kill during heavy parallel apply, because the multi-threaded gap scenario is precisely the one that behaves differently under load. If your failover tooling — Orchestrator or otherwise — depends on replicas surviving restarts during topology changes, and it does, this test is also a failover test; the Orchestrator vs Patroni comparison covers how promotion machinery leans on these same guarantees.
Run it quarterly and after any change to replication or durability settings. A replica restart that has been rehearsed is an event; one that has not is an incident.
The checklist
The whole article compressed: gtid_mode=ON with enforce_gtid_consistency=ON, channels on SOURCE_AUTO_POSITION=1, relay_log_recovery=ON, no FILE repository settings surviving in configs, sync_binlog and innodb_flush_log_at_trx_commit set deliberately per replica tier, super_read_only=ON, and a standing kill -9 test with checksum verification. None of these are exotic; the work is making sure all of them are true on every replica, including the ones built by hand during the last incident.
Where MonPG fits
Being straight about it: MonPG is a PostgreSQL monitoring platform today, and MySQL support is coming — see MySQL monitoring (coming soon). Replica crash-safety posture checks — the settings audit above, drift detection across a fleet, replication thread and error monitoring around restarts — are the kind of always-on evidence we are designing the MySQL side around. Until it ships, the queries in this article and a config management assertion get you there. If you also run PostgreSQL, you can start there today, where we already track the standby and recovery signals on the Postgres side.