Идея в одну строку
Primary уже пишет всё, что меняет данные, в журнал предзаписи (WAL, см. wal). Раз журнала достаточно, чтобы поднять кластер после сбоя, его достаточно и чтобы держать вторую копию в актуальном состоянии. Standby просто не прекращает recovery: он бесконечно проигрывает приходящий WAL.
Поэтому физическая репликация - это не «копирование строк», а «проигрывание журнала на втором сервере». Копия получается побайтовая: те же файлы, те же relfilenode, тот же системный идентификатор кластера.
Кто что делает
primary standby
┌──────────────┐ ┌──────────────┐
│ backends │ пишут WAL │ walreceiver │ принимает поток
│ ↓ │ │ ↓ │
│ wal на диск │ ──walsender──▶ │ wal на диск │
│ │ (по TCP) │ ↓ │
└──────────────┘ │ startup │ проигрывает (replay)
└──────────────┘
- walsender на primary читает WAL и шлёт его по сети.
- walreceiver на standby принимает поток и сохраняет на диск.
- startup на standby проигрывает записи в том же порядке.
Standby при этом доступен на чтение (hot_standby = on), но любая
попытка записи отвергается: кластер в режиме recovery.
Три LSN и лаг
LSN (Log Sequence Number) - это позиция в журнале, монотонно растущее
число. Лаг репликации - это разница LSN между тем, что primary уже
записал, и тем, на какой позиции standby. pg_stat_replication на
primary показывает по каждому standby три рубежа:
sent_lsn- досюда primary отправил;flush_lsn- досюда standby сбросил на диск;replay_lsn- досюда standby реально проиграл (и видно на чтении).
Разрыв между flush_lsn и replay_lsn - это «WAL принят, но ещё не
применён». Под нагрузкой replay отстаёт первым: запись на диск дёшева,
а проигрывание конкурирует с читающими запросами.
SELECT application_name, state,
pg_wal_lsn_diff(sent_lsn, replay_lsn) AS replay_bytes_behind,
replay_lag
FROM pg_stat_replication;
Слоты репликации
Без слота primary не знает, докуда standby дочитал, и удаляет старый WAL
по своим правилам (max_wal_size, checkpoint, см. checkpoint). Если
standby отстал
больше, чем primary хранил, нужного сегмента уже нет - репликация
рвётся с «requested WAL segment has already been removed».
Слот (pg_create_physical_replication_slot) чинит это: primary держит
WAL ровно до restart_lsn слота и не удаляет раньше. Цена - обратная:
забытый неактивный слот удержит WAL навсегда и забьёт диск primary.
Поэтому слоты надо мониторить через pg_replication_slots.active.
Синхронность
По умолчанию репликация асинхронная: COMMIT на primary возвращается, не
дожидаясь standby. Это быстро, но при падении primary последние
транзакции могут не успеть уехать. synchronous_standby_names +
synchronous_commit = on заставят COMMIT ждать подтверждения standby -
ноль потерь ценой задержки каждого COMMIT.
Связано с hot-standby-feedback (как чтение на standby влияет на vacuum мастера) и logical-replication (репликация по строкам, а не по байтам журнала).