Как работает репликация: что значит «отставание реплики» в секундах
Если у вас есть мастер и реплика базы данных, рано или поздно вы упрётесь в число, которое сначала кажется абстрактным — «отставание реплики: 4 секунды». А потом это число ломает логику приложения: пользователь сохранил профиль, тут же открыл страницу профиля — а там старые данные. Дальше начинаются вопросы: почему вообще есть задержка, откуда она берётся, чем синхронная репликация отличается от асинхронной и как понять, что с лагом происходит прямо сейчас. Разберём механику по порядку, без магии.
Содержание
- Что физически происходит при репликации
- Почему отставание вообще возникает
- Синхронная и асинхронная репликация: разная цена за разную гарантию
- Риск read-after-write: почему только что записанные данные «пропадают»
- Как посмотреть текущий лаг репликации: PostgreSQL
- Как посмотреть текущий лаг репликации: MySQL
- Что мониторить постоянно, а не проверять руками
Что физически происходит при репликации
В основе почти любой репликации реляционной базы лежит один и тот же принцип: мастер не отправляет реплике готовые таблицы, он отправляет журнал изменений — тот же самый журнал, который использует сам для собственной надёжности.
В PostgreSQL это WAL (write-ahead log, журнал предзаписи): каждая транзакция сначала записывается в WAL, и только потом применяется к файлам данных. Потоковая репликация — это когда мастер передаёт реплике поток записей WAL по сети, а реплика проигрывает их у себя в том же порядке, в каком они были записаны на мастере.
В MySQL исторически используется binlog (binary log) — журнал, в который пишутся события изменения данных. Реплика читает binlog мастера (в современных версиях — через поток репликации, раньше — через отдельный I/O-поток) и применяет события к своей копии данных.
Важное следствие этой архитектуры: реплика — это не «второй мастер, который сам решает, что писать». Она вообще не выполняет исходные SQL-запросы заново (не всегда — есть и такая, statement-based, схема, но сейчас доминирует row-based репликация, где реплицируются готовые изменения строк, а не текст запроса). Она детерминированно проигрывает журнал. Это и делает данные на реплике почти точной копией мастера — с одной оговоркой: «почти» означает «с задержкой во времени».
Схематично цепочка выглядит так:
Мастер: транзакция → запись в WAL/binlog → фиксация (commit) → данные видны читателям мастера
│
▼ (передача по сети)
Реплика: получение записи журнала → запись на диск реплики → применение к данным → данные видны читателям реплики
Между «данные видны на мастере» и «данные видны на реплике» проходит время. Это время и называют отставанием (лагом) реплики. Оно почти никогда не равно нулю — вопрос только в масштабе: миллисекунды это или минуты.
Почему отставание вообще возникает
Причин у лага немного, и все они укладываются в три группы.
Сеть. Запись журнала должна физически долететь от мастера до реплики. Если реплика в другом дата-центре или в другой стране — это не тот же локальный линк, что между процессами на одной машине. Плюс сама пропускная способность канала: если на мастере идёт всплеск записи (массовая загрузка данных, миграция, batch-обновление), объём журнала, который нужно передать, резко растёт, и передача может не успевать за генерацией.
Нагрузка на реплику при применении изменений. Получить запись журнала и применить её к данным — не бесплатная операция. Реплика должна найти нужные страницы, обновить индексы, записать изменения на диск. Если на реплике одновременно идёт тяжёлая read-нагрузка (аналитические запросы, полные сканы таблиц), процесс применения журнала конкурирует с этими запросами за CPU, диск и блокировки — и начинает отставать. Это одна из самых частых причин лага на практике: реплику держат «для чтения», сажают на неё тяжёлую аналитику, и именно эта аналитика замедляет применение изменений, ради которых реплика вообще существует.
Долгие транзакции и блокировки на реплике. Отдельный частый случай для PostgreSQL: если на реплике выполняется долгий читающий запрос (например, отчёт на 10 минут) в режиме hot_standby, а мастер тем временем присылает изменение, конфликтующее с этим запросом (скажем, удаляет строки, которые запрос ещё читает), у PostgreSQL есть выбор — отменить читающий запрос либо задержать применение WAL до его завершения (регулируется параметром max_standby_streaming_delay). Чем дольше живут читающие транзакции на реплике, тем больше потенциальный лаг. В MySQL похожая проблема проявляется через блокировки строк и метаданных, которые применяющий поток репликации ждёт наравне с обычными сессиями.
Дополнительно: если диск реплики медленнее, чем на мастере (другой тип хранилища, другая загрузка I/O), применение журнала физически не может угнаться за скоростью его генерации — лаг будет расти монотонно, пока не изменится нагрузка или железо.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверСинхронная и асинхронная репликация: разная цена за разную гарантию
По умолчанию и PostgreSQL, и MySQL реплицируют асинхронно: мастер фиксирует транзакцию (отвечает клиенту «commit успешен»), не дожидаясь подтверждения от реплики, что запись журнала до неё дошла. Это быстро — задержка на запись определяется только самим мастером — но означает риск: если мастер упадёт в момент между локальным commit и доставкой записи на реплику, эти последние транзакции могут быть потеряны при переключении на реплику как на новый мастер.
Синхронная репликация меняет правило: мастер ждёт подтверждения от реплики (или от кворума реплик), прежде чем сообщить клиенту об успешном commit. В PostgreSQL это включается через synchronous_standby_names на мастере — можно указать конкретную реплику или кворумную схему (ANY n (node1, node2, node3)). В MySQL это semi-synchronous repl (rpl_semi_sync_master_enabled) или полноценный group replication.
| Асинхронная | Синхронная | |
|---|---|---|
| Задержка на запись (commit) | Минимальная, не зависит от реплики | Выше — мастер ждёт реплику |
| Риск потери данных при отказе мастера | Есть (последние транзакции) | Нет (для подтверждённых транзакций) |
| Что происходит при недоступности реплики | Мастер работает как обычно | Мастер может подвиснуть на commit, если не настроен приоритет/таймаут |
| Типичное применение | Реплики для чтения, географический резерв | Критичные данные (платежи, финансовые операции) на близких репликах |
На практике большинство инсталляций держат асинхронную схему для реплик, разнесённых географически (иначе задержка на запись станет неприемлемой из-за сетевого RTT), и синхронную — только для одной-двух реплик в том же дата-центре, где нужна гарантия нулевой потери данных. Групповая/кворумная репликация (Patroni + PostgreSQL, MySQL Group Replication, Galera) усложняет картину, но базовый компромисс «синхронность стоит задержки на запись» остаётся тем же.
Риск read-after-write: почему только что записанные данные «пропадают»
Это самая практическая проблема, с которой сталкивается любое приложение, читающее с реплик ради масштабирования. Сценарий типовой:
- Пользователь отправляет форму — приложение пишет в мастер, получает
commit successful. - Сразу после этого приложение (тот же запрос или следующий за ним) читает те же данные — но с реплики, потому что весь read-трафик по умолчанию идёт туда.
- Если запись ещё не долетела до реплики (а она физически не может лететь мгновенно), читающий запрос видит старую версию данных или вовсе не находит только что созданную запись.
Для пользователя это выглядит как баг: «я сохранил — а показывает, что не сохранилось». Особенно неприятно это на страницах вида «редактирование → редирект → просмотр», когда просмотр случайно попадает на реплику, которая ещё не догнала мастер.
Важно: это не баг репликации, а прямое следствие её асинхронной природы. Формально это называется eventual consistency (согласованность в конечном счёте) — данные обязательно станут одинаковыми на мастере и всех репликах, но не сразу, и не в момент коммита.
Практические способы смягчить проблему:
- Читать с мастера сразу после записи. Самый простой и надёжный паттерн: в рамках одного пользовательского запроса (или короткого окна после записи, или до конца текущей HTTP-сессии) направлять чтения на мастер, а не на реплику. Многие ORM и прокси для баз данных (ProxySQL, pgpool-II, встроенная логика Rails/Django) поддерживают это из коробки как read-your-writes routing.
- Sticky-сессии с отслеживанием LSN/GTID. Более точный вариант: после записи запомнить позицию в журнале (LSN в PostgreSQL, GTID в MySQL) и при чтении убедиться, что реплика уже применила изменения хотя бы до этой позиции — иначе читать с мастера или подождать. Сложнее в реализации, но не создаёт лишней нагрузки на мастер по сравнению с «читать всё с мастера N секунд».
- Кворумное чтение. Требовать подтверждения от большинства узлов можно в распределённых СУБД, но для обычной мастер-репличной схемы PostgreSQL/MySQL это недоступно без дополнительного слоя.
- Осознанно смириться там, где это не критично. Для ленты новостей, списка комментариев, аналитики задержка в секунды почти никогда не важна для пользователя. Прежде чем городить сложную маршрутизацию, стоит трезво оценить, где read-after-write реально критичен (профиль сразу после редактирования, статус заказа сразу после оплаты), а где нет.
Хорошая практика — не решать проблему глобально «одним рубильником», а явно помечать в коде те немногие места, где чтение обязано идти с мастера.
Как посмотреть текущий лаг репликации: PostgreSQL
На стороне мастера можно посмотреть состояние каждой подключённой реплики:
SELECT client_addr, state, sent_lsn, write_lsn, flush_lsn, replay_lsn,
write_lag, flush_lag, replay_lag
FROM pg_stat_replication;
Здесь replay_lag — это интервал времени между тем, когда транзакция была зафиксирована на мастере, и тем, когда она была применена (replay) на реплике. Именно это число обычно и имеют в виду, говоря «отставание реплики в секундах».
На стороне реплики самый простой способ — сравнить время последней применённой транзакции с текущим временем:
SELECT now() - pg_last_xact_replay_timestamp() AS replication_lag;
Если результат растёт со временем (а не колеблется вокруг небольшого значения), это признак, что реплика не успевает — стоит смотреть нагрузку на неё (CPU, диск, долгие запросы через pg_stat_activity) и состояние сети до мастера.
Проверить, что сервер вообще находится в режиме реплики, и увидеть позиции журнала:
SELECT pg_is_in_recovery();
SELECT pg_last_wal_receive_lsn(), pg_last_wal_replay_lsn();
Разница между receive и replay LSN показывает, сколько журнала уже получено, но ещё не применено — это отдельная метрика от сетевой задержки.
Как посмотреть текущий лаг репликации: MySQL
На реплике (в современных версиях MySQL 8.0.22+ команда называется SHOW REPLICA STATUS, в более старых и в некоторых форках MariaDB — SHOW SLAVE STATUS):
SHOW REPLICA STATUS\G
В выводе ищите поле Seconds_Behind_Source (или Seconds_Behind_Master в старом синтаксисе). Оно показывает разницу во времени между тем, что применяющий поток (SQL thread) сейчас обрабатывает, и текущим временем на реплике.
Оговорка: это поле известно тем, что может врать в отдельных ситуациях — например, если реплика была отключена от мастера и переподключилась, Seconds_Behind_Source может на короткое время показать NULL или некорректное значение. Для более надёжного измерения используют pt-heartbeat из Percona Toolkit: на мастере утилита раз в секунду пишет в служебную таблицу метку времени, а на реплике вычисляет реальный лаг как разницу между уже применённой меткой и текущим временем — это устраняет искажения встроенного счётчика.
На стороне мастера позицию в журнале показывает:
SHOW BINARY LOG STATUS\G
(в версиях до 8.2 — SHOW MASTER STATUS\G). Сравнивая позицию (или GTID-набор) мастера с тем, что реплика уже применила (Retrieved_Gtid_Set / Executed_Gtid_Set в выводе SHOW REPLICA STATUS), можно понять, сколько именно данных ещё не долетело и не применилось — отдельно от простого счётчика секунд.
Что мониторить постоянно, а не проверять руками
Разовая проверка командой полезна для диагностики конкретной проблемы, но лаг репликации стоит держать под постоянным наблюдением с алертом — потому что рост лага почти всегда сигнализирует о реальной проблеме (перегруженная реплика, зависшая транзакция, сетевые проблемы), которую лучше поймать до того, как она превратится в жалобы пользователей.
Для PostgreSQL и MySQL есть готовые экспортеры метрик для Prometheus (postgres_exporter, mysqld_exporter), которые отдают в том числе метрики лага, и типовые дашборды для Grafana поверх них. Если у вас уже есть база на VPS, вопрос «что именно смотреть в дашборде» подробно разобран в статье про мониторинг баз данных через Grafana.
Разумный порог алерта зависит от вашей нагрузки и от чувствительности приложения к read-after-write — единого «нормального» числа секунд не существует. Стоит замерить типичный лаг на своей паре мастер-реплика в спокойном режиме и ставить порог алерта заметно выше этого фонового уровня, а не ориентироваться на цифры из чужих статей.
Если репликация PostgreSQL ещё не настроена, порядок действий — от подготовки мастера до слотов репликации и первой проверки — описан в статье как установить и настроить репликацию PostgreSQL на VPS. А если репликация уже работает, но лаг растёт или реплика периодически отваливается, разбор типовых причин и решений — в статье репликация PostgreSQL на сервере: частые ошибки и решения. Если же растущий лаг сопровождается ещё и раздутым каталогом pg_wal на мастере — это отдельный частый симптом, разобранный в статье про рост размера WAL в PostgreSQL: обычно виноват именно застрявший из-за отставшей реплики слот репликации.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Можно ли сделать лаг репликации нулевым?
Полностью нулевым — нет, пока данные передаются по сети и применяются отдельным процессом. Синхронная репликация делает так, что мастер не подтверждает запись, пока реплика её не получила, но и это не «мгновенно» — просто пользователь ждёт дольше на этапе commit, а не после него.
Что произойдёт, если реплика отстанет очень сильно, а на мастере тем временем удалят нужный ей WAL/binlog?
В PostgreSQL для этого существуют слоты репликации (replication slots) — они не дают мастеру удалить WAL, пока его не заберёт реплика, но ценой риска: если реплика отвалилась и не переподключается, WAL будет копиться на мастере и может забить диск. В MySQL аналогичная защита — binlog_expire_logs_seconds и мониторинг того, что реплика успевает вычитывать binlog до его ротации/удаления.
Асинхронная репликация — это вообще безопасно для продакшена?
Да, это стандартная и самая распространённая схема — большинство продакшен-систем с репликами для чтения используют именно её. Синхронность нужна только там, где недопустима потеря даже последних нескольких транзакций при аварии мастера (типичный пример — платежи).
Почему на одной и той же паре мастер-реплика лаг то 0, то внезапно 30 секунд?
Обычно это означает периодическую нагрузку: ночной batch-джоб, тяжёлый отчёт, резервное копирование, которые на время конкурируют с применением журнала за ресурсы реплики. Стоит сопоставить график лага с графиком нагрузки на CPU/диск реплики — совпадение по времени почти всегда укажет на виновника.
Нужно ли реплике железо такое же, как у мастера?
Строго говоря, реплике достаточно успевать применять поток изменений с той же скоростью, с которой мастер его генерирует. На практике это означает, что диск реплики (особенно скорость записи) не должен быть заметно медленнее мастера — иначе лаг будет расти при любой нагрузке, даже без дополнительных read-запросов.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →