Incident Context
Friday night, 22:30. On the MySQL 5.7 read replica, Seconds_Behind_Master skyrocketed past 14,000 seconds. Read-only microservices began serving stale profile states, database caches flooded, and on-call alerts escalated immediately.
Diagnostics and Thread Inspection
We inspected the replication worker state:
mysql -e "SHOW SLAVE STATUS\G"
mysql -e "SHOW FULL PROCESSLIST;"
Output showed the SQL thread blocked on a single massive unbatched statement:
System lock | UPDATE account_balances SET last_audit_ts = NOW() WHERE audit_batch = 4821;
Developers ran an unpaginated batch updating 2.4 million rows. While the master absorbed it on NVMe RAID10 across multiple threads, the replica executed it in a single thread due to MySQL 5.7 schema-based replication limitations.
Disk metrics via iostat -xz 1 revealed 99.4% disk utilization with 50ms write latency:
Device r/s w/s rMB/s wMB/s await r_await w_await %util
nvme0n1 12 450 0.1 85.2 48.2 1.2 49.8 99.4%
fsync & doublewrite Bottleneck
The replica was running with maximum durability:
sync_binlog = 1
innodb_flush_log_at_trx_commit = 1
innodb_doublewrite = 1
Flushing huge dirty page bursts forced serial fsync() syscalls in the doublewrite buffer. A gdb backtrace confirmed the block:
#0 0x00007f9c8d10b70b in fsync () from /lib/x86_64-linux-gnu/libc.so.6
#1 0x000055c1e2891f1a in os_file_flush_func ()
#2 0x000055c1e29e012c in fil_flush ()
#3 0x000055c1e27a988d in buf_dblwr_flush_buffered_writes ()
Resolution & Mitigations
- Temporarily relaxed durability settings during recovery:
sync_binlog = 0andinnodb_flush_log_at_trx_commit = 2. - Scaled
innodb_io_capacity = 4000to drain the queue. - Migrated to MySQL 8.0
WRITESETbased multi-threaded replication. - Mandated batch updates strictly capped at 5,000 rows.