Репликация: потоковая и логическая
Репликация — это поддержание копии базы на другом сервере, чтобы пережить отказ основного узла и распределить нагрузку чтения.
Репликация — непрерывная передача изменений с основного сервера (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— можно реплицировать часть базы и переезжать между версиями. - Асинхронная репликация быстрее, но допускает потерю последних транзакций; синхронная даёт ноль потерь ценой ожидания подтверждения от реплики.