Репликация: потоковая и логическая

Репликация — это поддержание копии базы на другом сервере, чтобы пережить отказ основного узла и распределить нагрузку чтения.

Репликация — непрерывная передача изменений с основного сервера (primary) на одну или несколько копий (standby). Standby повторяет у себя те же операции и держит данные в актуальном состоянии.

Одна машина — это единая точка отказа: упал диск или сервер, и база недоступна. Репликация даёт запасной узел, готовый подхватить нагрузку, а заодно — копии для чтения, чтобы тяжёлые отчёты не мешали основной работе. В разделе про транзакции мы видели журнал упреждающей записи (WAL): именно он лежит в основе репликации PostgreSQL — реплика просто проигрывает у себя поток WAL-записей с primary.

Различают два вида репликации: физическую (потоковую), копирующую базу побайтово на уровне WAL, и логическую, передающую изменения как логические операции «вставить/обновить/удалить» по выбранным таблицам.

Зачем это на практике

Репликация решает сразу несколько задач эксплуатации:

  • Отказоустойчивость. Если primary умер, один из standby повышают до нового primary (failover) — простой измеряется минутами, а не часами восстановления из бэкапа.
  • Масштабирование чтения. Реплики в режиме hot standby обслуживают SELECT, снимая нагрузку с primary (этому посвящён следующий урок).
  • Отчёты и аналитика. Тяжёлые запросы гоняют по отдельной реплике, не замедляя транзакции пользователей.
  • Логическая выборка. Логическая репликация умеет передавать только нужные таблицы — например, в отдельный аналитический кластер или между версиями PostgreSQL.

Потоковая (физическая) репликация

При потоковой репликации standby подключается к primary и непрерывно получает поток WAL — тех самых записей об изменениях. Standby применяет их к своей копии, оставаясь точным побайтовым клоном primary. Настройка в общих чертах сводится к нескольким параметрам на primary и команде клонирования на реплике.

# на PRIMARY: postgresql.conf
wal_level = replica            # достаточный уровень детализации WAL
max_wal_senders = 10           # сколько процессов-отправителей WAL разрешить

# на PRIMARY: pg_hba.conf — разрешаем реплике подключаться к псевдо-БД replication
host  replication  repl_user  10.0.0.0/24  scram-sha-256

Сама реплика создаётся утилитой pg_basebackup: она снимает базовую копию с primary и сразу настраивает потоковое подключение.

# на STANDBY: снять базовый бэкап primary и подготовить standby
pg_basebackup -h primary.host -U repl_user -D /var/lib/postgresql/data \
              -R -X stream -C -S standby1

# флаг -R создаёт файл standby.signal и пишет параметры подключения,
# чтобы сервер стартовал сразу как реплика

После старта standby проверить состояние репликации можно с primary через системное представление:

SELECT client_addr, state, sync_state,
       sent_lsn, replay_lsn
FROM pg_stat_replication;

--  client_addr | state     | sync_state | sent_lsn   | replay_lsn
-- -------------+-----------+------------+------------+-----------
--  10.0.0.5    | streaming | async      | 0/3A001F8  | 0/3A001F8

Разница между sent_lsn (что отправлено) и replay_lsn (что уже применено на реплике) — это лаг репликации: насколько реплика отстаёт от primary.

Hot standby: реплика, читающая запросы

По умолчанию standby в PostgreSQL — hot standby: он не только догоняет primary, но и обслуживает SELECT-запросы. Писать в него нельзя — любой INSERT/UPDATE вернёт ошибку «cannot execute ... in a read-only transaction». Это превращает реплику в дешёвый ресурс для масштабирования чтения.

# на STANDBY: postgresql.conf
hot_standby = on               # разрешить читать с реплики (включено по умолчанию)

# проверить, что мы на реплике:
SELECT pg_is_in_recovery();    # вернёт true на standby, false на primary

Логическая репликация и publications

Логическая репликация работает на другом уровне: вместо копирования WAL-байтов она декодирует изменения в логические операции и передаёт их по конкретным таблицам. На primary создают publication (набор публикуемых таблиц), на реплике — subscription (подписку на неё).

-- на ИСТОЧНИКЕ: что публикуем
CREATE PUBLICATION sales_pub FOR TABLE orders, order_items;

-- на ПОЛУЧАТЕЛЕ: подписываемся на публикацию
CREATE SUBSCRIPTION sales_sub
    CONNECTION 'host=source.host dbname=shop user=repl_user'
    PUBLICATION sales_pub;

Так можно реплицировать часть базы, объединять данные из нескольких источников в один приёмник или переезжать между мажорными версиями PostgreSQL без простоя. Цена гибкости — логическая репликация требует wal_level = logical и не копирует DDL (изменения схемы переносят вручную).

Синхронная vs асинхронная

Это ключевой выбор для физической репликации — компромисс между скоростью и гарантией сохранности.

Асинхронная (по умолчанию)Синхронная
Когда COMMIT возвращает управлениесразу после записи WAL на primaryпосле подтверждения от standby
Скоростьбыстрее, без ожидания сетимедленнее на сетевую задержку
Риск потери данных при отказе primaryможно потерять последние транзакцииноль потерь: они уже на реплике
# на PRIMARY: включить синхронную репликацию для конкретной реплики
synchronous_standby_names = 'standby1'
# теперь COMMIT ждёт, пока standby1 подтвердит получение WAL

Асинхронная — выбор по умолчанию: дёшево и быстро, ценой возможной потери последних миллисекунд транзакций при внезапной смерти primary. Синхронная гарантирует ноль потерь, но каждый COMMIT ждёт сетевого подтверждения — и если синхронная реплика отстала или упала, запись на primary может встать.

Как это работает под капотом

Физическая репликация — это поток WAL. На primary процесс walsender читает журнал и отправляет его реплике; на standby процесс walreceiver принимает поток, а процесс восстановления (startup) проигрывает записи, повторяя те же изменения страниц, что произошли на primary. Прогресс отслеживается по LSN (Log Sequence Number) — позиции в журнале. Слот репликации (replication slot) — это «закладка» на primary, которая не даёт удалять WAL-сегменты, пока их не получила реплика: так реплика не отстанет безвозвратно, даже если ненадолго отключится.

Логическая репликация поверх того же WAL добавляет логическое декодирование: специальный плагин превращает низкоуровневые WAL-записи в понятные строки «в таблице orders вставлена такая-то запись», и уже их применяет apply-процесс на подписчике. Поэтому источник и приёмник могут иметь разную физическую раскладку и даже разные версии PostgreSQL.

Частые ошибки

  • Слот репликации без потребителя. Если создать слот, а реплика к нему не подключается (или удалена), PostgreSQL копит WAL «для неё» и диск primary молча заполняется до отказа.
  • Ожидать запись на hot standby. Реплика только для чтения; приложение должно направлять INSERT/UPDATE на primary, а на реплику — лишь SELECT.
  • Считать асинхронную реплику «нулём потерь». При внезапном отказе primary незаконченные передачей транзакции теряются — для критичных данных нужна синхронная репликация.
  • Одна синхронная реплика как единственная. Если она упадёт, COMMIT на primary начнёт ждать вечно. Держите запас синхронных реплик или настраивайте кворум.
  • Ждать DDL от логической репликации. Она переносит данные, но не ALTER TABLE — схему на подписчике меняют отдельно.

Итоги

  • Репликация держит копию базы на standby, опираясь на поток WAL с primary.
  • Физическая (потоковая) репликация — побайтовый клон через WAL; настраивается через pg_basebackup и pg_stat_replication для контроля лага.
  • Hot standby по умолчанию обслуживает SELECT; pg_is_in_recovery() отличает реплику от primary.
  • Логическая репликация передаёт изменения по таблицам через PUBLICATION/SUBSCRIPTION — можно реплицировать часть базы и переезжать между версиями.
  • Асинхронная репликация быстрее, но допускает потерю последних транзакций; синхронная даёт ноль потерь ценой ожидания подтверждения от реплики.
Проверьте себя
1. Чем логическая репликация отличается от потоковой (физической)?
AЛогическая копирует базу побайтово, а физическая — по отдельным таблицам
BЛогическая передаёт изменения как операции по выбранным таблицам (PUBLICATION/SUBSCRIPTION), а физическая копирует поток WAL целиком
CМежду ними нет разницы, это два названия одного механизма
DЛогическая работает только внутри одного сервера, без сети
2. Что гарантирует синхронная репликация по сравнению с асинхронной?
AЧто реплику можно использовать для записи
BЧто COMMIT возвращается быстрее, не дожидаясь сети
CЧто после COMMIT данные уже подтверждены репликой, то есть при отказе primary они не потеряются
DЧто DDL автоматически переносится на реплику
3. Зачем нужен слот репликации (replication slot) на primary?
AЧтобы primary не удалял WAL-сегменты, пока их не получила реплика
BЧтобы разрешить запись на hot standby
CЧтобы ускорить COMMIT, минуя запись в WAL
DЧтобы автоматически переносить изменения схемы (DDL)