Контекст инцидента
Пятница, 22:30. На вторичной реплике MySQL 5.7 график Seconds_Behind_Master внезапно улетает в стратосферу — 14 000 секунд и продолжает расти. Read-only сервисы начинают отдавать устаревшие данные профилей, кэширующие слои прогреваются до критических температур, а мониторинг заливается алертами.
Диагностика и снятие дампов
Первым делом снимаем состояние репликации и треды:
mysql -e "SHOW SLAVE STATUS\G"
mysql -e "SHOW FULL PROCESSLIST;"
В выводе SHOW SLAVE STATUS:
Slave_IO_Running: YesSlave_SQL_Running: YesSeconds_Behind_Master: 14210Last_Error: ""
SQL-тред не упал с ошибкой, он просто намертво завис в статусе:
System lock | UPDATE account_balances SET last_audit_ts = NOW() WHERE audit_batch = 4821;
Разработчики запустили ночной фоновый джоб без разбивки на чанки. Один транзакционный пакет изменил 2.4 миллиона строк. На мастере стояли быстрые NVMe в RAID10 и многопоточная запись, поэтому мастер проглотил транзакцию за 18 секунд. Но реплика выполняла её строго в один поток (в MySQL 5.7 schema-based multi-threaded replication не распараллеливает транзакции внутри одной базы).
Смотрим дисковую подсистему через iostat -xz 1:
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%
Утилизация диска упёрлась в 100%, при этом w_await скакнул до 50мс.
Ловушка параметров sync_binlog и doublewrite
На реплике были включены:
sync_binlog = 1
innodb_flush_log_at_trx_commit = 1
innodb_doublewrite = 1
При попытке сбросить гигантский объем грязных страниц (dirty pages) InnoDB упёрся в сброс буфера doublewrite и бесконечные системные вызовы fsync() на каждый флаш binlog-файла реплики (log_slave_updates был включен для каскадной репликации).
Снимаем стектрейс зависшего потока через gdb -p $(pidof mysqld):
#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 ()
Как погасили пожар
1. На время догона реплики временно снизили строгость записи:
SET GLOBAL sync_binlog = 0;
SET GLOBAL innodb_flush_log_at_trx_commit = 2;
2. Разблокировали I/O троттлинг:
SET GLOBAL innodb_io_capacity = 4000;
SET GLOBAL innodb_io_capacity_max = 8000;
Реплика переварила накопленный буфер за 24 минуты и догнала мастер.
Системные выводы для продакшена
- Пакетная обработка: любые миграции и массовые обновления обязаны быть разбиты на чанки по 2 000 — 5 000 строк с явным
sleep(0.05). - MySQL 8.0 и Writeset Replication: переход на
binlog_transaction_dependency_tracking = WRITESET(сняло ограничение однопоточной репликации по схемам). - Репликам без каскада строго отключать
log_slave_updates.