Что такое replication lag в PostgreSQL и почему «секунды» обманывают
Replication lag — это разница между тем, что уже зафиксировано на primary, и тем, что успела получить и/или применить реплика. В PostgreSQL почти всегда речь о потоке WAL (Write-Ahead Log): primary генерирует WAL-записи, реплика их получает (receive) и проигрывает (replay). Если любой этап замедляется — растёт lag.
В быту lag часто измеряют «в секундах», но это не универсальная метрика. «Секунды» в статистике — это производная от временных отметок и подтверждений, а не ответ на вопрос «сколько времени нужно, чтобы догнать при текущей скорости». На всплесках нагрузки lag может расти рывками, а затем быстро схлопываться.
Правильная схема: сначала понять, на каком участке появляется отставание (отправка/сеть, запись WAL на диске, replay на standby), затем подтвердить это метриками, и только после этого менять параметры вроде max_wal_size или «крутить» autovacuum.
Быстрая модель: где появляется lag
Для потоковой репликации (streaming replication) конвейер выглядит так:
Primary генерирует WAL при любом изменении данных.
WAL sender отправляет WAL по сети на standby.
Standby принимает WAL (процесс walreceiver) и пишет его в WAL-сегменты.
Standby проигрывает WAL (recovery/replay), применяя изменения к данным.
На практике полезно держать в голове два «класса» отставания:
Send/Network lag: standby не успевает получить WAL от primary.
Replay/Apply lag: WAL получен, но standby медленно применяет его (обычно I/O на данных, CPU, блокировки или конфликты hot standby).

Диагностика шаг 1: измеряем lag через pg_stat_replication (primary)
На primary самый информативный источник — pg_stat_replication. Он показывает LSN на разных стадиях и оценку lag по времени для write/flush/replay.
SELECT
pid,
application_name,
client_addr,
state,
sync_state,
write_lag,
flush_lag,
replay_lag,
sent_lsn,
write_lsn,
flush_lsn,
replay_lsn
FROM pg_stat_replication
ORDER BY application_name;
Как читать ключевые поля:
sent_lsn— сколько primary уже отправил реплике.write_lsn— сколько реплика приняла и записала в WAL (запись могла быть без fsync).flush_lsn— сколько реплика гарантированно сбросила на диск (fsync).replay_lsn— сколько реплика уже применила к данным (replay).write_lag,flush_lag,replay_lag— оценка «времени отставания» по стадиям (может быть NULL).
Быстрая интерпретация:
Если
sent_lsnзаметно впередиwrite_lsn— проблема на пути «primary → сеть → walreceiver → запись WAL на standby».Если
write_lsnблизко кsent_lsn, ноreplay_lsnсильно позади — WAL «доезжает», но реплика медленно применяет изменения (replay bottleneck).
Переводим LSN в «байты отставания»
Для инцидентов и мониторинга байты часто полезнее «секунд»: сразу видно, копится ли очередь WAL и какого она размера.
SELECT
application_name,
pg_size_pretty(pg_wal_lsn_diff(sent_lsn, replay_lsn)) AS behind_replay,
pg_size_pretty(pg_wal_lsn_diff(sent_lsn, write_lsn)) AS behind_write
FROM pg_stat_replication;
Если на primary идёт «заливка» данных, lag в секундах может выглядеть страшно. А LSN diff в байтах покажет, что реплика догоняет приемлемой скоростью (или наоборот — очередь растёт).
Диагностика шаг 2: проверяем состояние на standby
На реплике важно отличить «не получаем WAL» от «получаем, но не успеваем проигрывать». Начните с двух проверок: recovery-режим и живость replay.
SELECT
pg_is_in_recovery() AS is_standby,
now() - pg_last_xact_replay_timestamp() AS replay_delay;
Если pg_last_xact_replay_timestamp() возвращает NULL, это означает, что транзакции ещё не проигрывались. Причины бывают нормальные (свежий standby, нет транзакций) и проблемные (WAL не поступает или replay не движется).
Затем посмотрите walreceiver:
SELECT
status,
receive_start_lsn,
written_lsn,
flushed_lsn,
latest_end_lsn,
latest_end_time
FROM pg_stat_wal_receiver;
Если статус streaming и latest_end_time обновляется, канал жив. Тогда фокус смещается на replay (диск/CPU/конфликты).
Причина №1: WAL, чекпоинты и удержание сегментов
WAL — главный «конвейер» репликации. Он же источник типовых сюрпризов: волнообразный lag, внезапный рост I/O и накопление сегментов на primary.
Чекпоинты и «пилообразная» нагрузка
Слишком частые или тяжёлые чекпоинты вызывают всплески записи на диск. Это может замедлять и генерацию/отправку WAL на primary, и replay на standby (диск занят фоновой записью и fsync).
Проверьте статистику чекпоинтов:
SELECT
checkpoints_timed,
checkpoints_req,
checkpoint_write_time,
checkpoint_sync_time,
buffers_checkpoint,
buffers_clean,
maxwritten_clean
FROM pg_stat_bgwriter;
Если checkpoints_req быстро растёт — чекпоинты часто «вынужденные» (например, упираетесь в max_wal_size). В таком режиме lag действительно может расти «волнами».
Роль max_wal_size в lag
max_wal_size не «ускоряет репликацию» напрямую. Он задаёт верхнюю границу объёма WAL между чекпоинтами. Слишком малое значение приводит к частым forced checkpoints и лишнему I/O, что снижает пропускную способность всего конвейера.
Имеет смысл думать про увеличение max_wal_size, если одновременно видите:
частые forced checkpoints (рост
checkpoints_req);пики write latency на диске;
рост lag синхронно с чекпоинтами (по логам и метрикам).
Replication slots и накопление WAL
Если lag большой или реплика недоступна, на primary может начать копиться WAL (особенно при использовании replication slots). Контролируйте удержание:
SELECT
slot_name,
slot_type,
active,
restart_lsn,
confirmed_flush_lsn,
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained
FROM pg_replication_slots
ORDER BY retained DESC;
Большое значение retained — сигнал, что primary вынужден хранить много WAL из‑за слота (часто потому что реплика «умерла» или сильно отстала).
Причина №2: I/O — узкое место и на primary, и на standby
Replication lag часто оказывается не «про PostgreSQL», а про диск. Причём профили нагрузки различаются:
Primary: последовательная запись WAL + fsync, плюс фоновые записи грязных страниц (checkpointer/bgwriter).
Standby: запись WAL + заметный random I/O при replay (применение изменений к данным и индексам), иногда одновременно с чтением (hot standby).

Признаки I/O bottleneck
lag растёт на массовых UPDATE/DELETE/INSERT на primary;
на standby растёт
replay_lag, при этомwrite_lsnпочти догналsent_lsn;в системе растёт iowait и latency диска (и это коррелирует по времени с ростом LSN diff).
Какие метрики в PostgreSQL смотреть
pg_stat_io(в новых версиях) — подробная телеметрия I/O по типам операций;pg_stat_bgwriter— косвенно показывает давление чекпоинтов;log_checkpoints— быстро показывает, сколько реально «стоит» чекпоинт по времени и объёму записи.
На уровне ОС смотрите iostat/pidstat/iotop. Главное — не «вообще высокая нагрузка», а совпадение по времени: рост lag и рост latency на диске.
Причина №3: сеть и поведение TCP
Сеть чаще виновата, когда отстаёт стадия receive: sent_lsn заметно впереди write_lsn, а на standby walreceiver либо получает медленно, либо реконнектится.
Что проверить в первую очередь
потери пакетов и ретрансмиты (обычно «плавающий» lag);
проблемы MTU/PMTU (чаще встречается в туннелях и при смешанных сетях);
конкуренцию по полосе (например, бэкапы или параллельная репликация).
Если сеть нестабильна, репликация может выглядеть «почти нормальной» по среднему lag, но при сбоях резко накапливать WAL и потом долго догонять. Поэтому важно смотреть не только текущие значения, но и динамику на графиках.
Причина №4: hot standby конфликты и длинные запросы на реплике
Hot standby позволяет читать с реплики, пока она применяет WAL. Но чтение на реплике может мешать replay. Типичный сценарий: на реплике выполняется длинный SELECT, а primary в это время активно делает UPDATE/DELETE или VACUUM. Для применения WAL standby нужно удалить/почистить версии строк, а запрос удерживает старый snapshot — возникает конфликт восстановления (recovery conflict).
Как это проявляется
replay_lagрастёт, при этомwrite_lsn/flush_lsnблизки кsent_lsn;в логах standby появляются сообщения про recovery conflict и отмену запросов;
или наоборот: запросы не отменяются, но replay «ждёт» (зависит от настроек задержек).
Какие настройки влияют
hot_standby_feedback— снижает конфликты, но может усиливать bloat на primary (включайте осознанно);max_standby_streaming_delayиmax_standby_archive_delay— сколько standby готов терпеть конфликт до отмены запроса.
Практика: если реплика нужна и для HA (минимальный lag), и для тяжёлой аналитики, лучше разделять роли (отдельная read-only реплика под отчёты) или жёстко ограничивать длительность запросов на HA-реплике.
Причина №5: autovacuum и побочные эффекты для репликации
autovacuum редко является «первопричиной lag», но часто усиливает нагрузку: влияет на объём WAL и I/O, и иногда становится триггером конфликтов на hot standby.
В активных таблицах autovacuum может:
генерировать дополнительный WAL (особенно при freeze/anti-wraparound);
занимать I/O и CPU, конкурируя с replay на standby;
провоцировать recovery conflicts при длинных чтениях на реплике.
Как понять, что autovacuum участвует
Сначала посмотрите, где больше всего мёртвых строк и когда был последний vacuum:
SELECT
relname,
n_dead_tup,
last_autovacuum,
last_vacuum,
last_autoanalyze,
vacuum_count,
autovacuum_count
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC
LIMIT 20;
Если n_dead_tup стабильно большое, autovacuum не справляется. Это почти всегда приводит к росту нагрузки при UPDATE/DELETE, а значит — косвенно увеличивает и replication lag (WAL и I/O).
Дальше имеет смысл проверить, не упираетесь ли вы в лимиты фоновых процессов:
autovacuum_max_workersautovacuum_vacuum_cost_limitиautovacuum_vacuum_cost_delayтабличные storage parameters для крупных «горячих» таблиц
Практический чек-лист: как локализовать причину lag за 15 минут
1) На primary: определяем тип отставания
Снимите
pg_stat_replication(LSN и lag-поля).Посчитайте отставание в байтах через
pg_wal_lsn_diff.Проверьте слоты (
pg_replication_slots) и удержание WAL.
2) На standby: отличаем receive от replay
pg_stat_wal_receiver: статусstreaming, обновляется лиlatest_end_time?now() - pg_last_xact_replay_timestamp(): replay реально «живой»?
3) Сопоставляем с системой
Если отстаёт receive: сеть, лимиты канала, запись WAL на диск standby.
Если отстаёт replay: диск с данными на standby, hot standby конфликты, конкуренция с запросами, CPU.
Если lag растёт «волнами»: чекпоинты,
max_wal_size, бурсты I/O.
Что менять в настройках и в каком порядке (без «магических» твиков)
1) Уберите очевидные I/O проблемы
Проверьте, что primary и standby не делят один и тот же медленный том с соседями по I/O.
Если standby используется для чтения, убедитесь, что запросы не «выжимают» диск в моменты, когда нужно быстро применять WAL.
2) Нормализуйте чекпоинты и WAL-режим
Включите
log_checkpointsи измерьте реальное время checkpoint.Оцените уместность увеличения
max_wal_size, если forced checkpoints частые.
3) Разграничьте роли реплики
Одна из самых частых причин «необъяснимого» lag — попытка использовать одну реплику и как HA-standby, и как отчётную read-replica. Если нужен стабильный минимальный lag — выделяйте отдельную реплику под тяжёлые отчёты или ограничивайте длительность запросов на HA-реплике.
4) Autovacuum: лечим причину, а не симптом
Если autovacuum не успевает, начните с выявления «плохих» таблиц и паттернов нагрузки. Тюнинг autovacuum без понимания, что именно тормозит, иногда даёт обратный эффект: больше конкуренции за I/O и ещё больше WAL.
Типовые сценарии и быстрые выводы
Сценарий A: write_lsn далеко позади sent_lsn
Чаще всего это сеть или запись WAL на standby. Проверьте стабильность канала и скорость диска именно там, где лежит WAL у реплики.
Сценарий B: write_lsn ≈ sent_lsn, но replay_lsn сильно позади
Чаще всего реплика упирается в I/O на данных или в hot standby конфликты. Смотрите логи standby на recovery conflict и профиль нагрузки от чтения.
Сценарий C: lag растёт волнами и совпадает с пиками записи
Чаще всего это чекпоинты и ограничение по WAL (max_wal_size слишком мал для текущей нагрузки) в сочетании с медленным диском. Подтверждайте через pg_stat_bgwriter и log_checkpoints.
Дополнительно: логическая репликация и миграции
Если вы используете логическую репликацию (например, для миграций или частичной репликации таблиц), лаг и узкие места измеряются по-другому: там появляется очередь на уровне слотов/подписок и apply workers. В таком случае полезно свериться с отдельным разбором: логическая репликация PostgreSQL для миграций.
Заключение
PostgreSQL replication lag почти всегда имеет измеримую причину: либо WAL не успевает «доехать» (сеть/receive), либо не успевает «проиграться» (replay/I/O/конфликты hot standby), либо конвейер периодически «затыкают» чекпоинты и фоновые процессы. Начинайте с pg_stat_replication, переводите LSN в байты, отличайте receive и replay — и только потом принимайте решения про max_wal_size, политику чтения с реплики и настройку autovacuum.
Для стабильной работы реплик критично, чтобы под PostgreSQL было предсказуемое I/O. Если вам нужна изолированная производительность под базу, выбирайте VDS с гарантированными ресурсами и отдельными дисками под данные и WAL.


