PostgreSQL logical replication: настройка, мониторинг и подводные камни
Логическая репликация в PostgreSQL — механизм выборочного копирования данных между серверами на уровне отдельных таблиц. В отличие от физической репликации, которая передаёт WAL-сегменты целиком, логическая декодирует изменения и применяет их на подписчике. Это открывает сценарии: частичная репликация, мажорные апгрейды без даунтайма, консолидация данных из нескольких кластеров.
Физическая vs логическая репликация
- Физическая — побайтовая копия WAL. Реплика идентична мастеру. Нельзя реплицировать отдельные таблицы. Реплика read-only.
- Логическая — декодирование WAL в логические операции (INSERT, UPDATE, DELETE). Подписчик может иметь свои индексы, дополнительные таблицы, другую мажорную версию PostgreSQL.
Типичные use cases:
- Миграция между мажорными версиями (PG 14 → PG 16) с минимальным даунтаймом
- Репликация подмножества таблиц в аналитический кластер
- Консолидация данных из нескольких источников
- Географически распределённые системы
Настройка publisher/subscriber пошагово
Два сервера: db-primary (publisher) и db-replica (subscriber). PostgreSQL 15+.
Конфигурация publisher
# postgresql.conf (publisher)
wal_level = logical
max_replication_slots = 10
max_wal_senders = 10
# pg_hba.conf
host mydb repl_user 10.0.1.0/24 scram-sha-256
Создаём пользователя и публикацию:
CREATE ROLE repl_user WITH REPLICATION LOGIN PASSWORD 'strong_pass_here';
GRANT SELECT ON ALL TABLES IN SCHEMA public TO repl_user;
-- Публикация всех таблиц
CREATE PUBLICATION my_pub FOR ALL TABLES;
-- Или выборочно
CREATE PUBLICATION orders_pub FOR TABLE orders, order_items, customers;
Конфигурация subscriber
На подписчике таблицы должны существовать заранее (DDL не реплицируется). Создаём структуру и подписку:
CREATE SUBSCRIPTION my_sub
CONNECTION 'host=db-primary port=5432 dbname=mydb user=repl_user password=strong_pass_here'
PUBLICATION my_pub;
PostgreSQL автоматически создаст replication slot на publisher, выполнит начальную синхронизацию и перейдёт в режим стриминга.
Проверка
-- На publisher: слоты
SELECT slot_name, active, restart_lsn FROM pg_replication_slots;
-- На subscriber: статус
SELECT * FROM pg_stat_subscription;
DDL-изменения и ограничения
Главное ограничение — DDL не реплицируется. Добавили колонку на publisher — подписчик не узнает. Порядок безопасного изменения:
- Добавить колонку на subscriber (с DEFAULT или NULL)
- Добавить колонку на publisher
- Репликация продолжит работать
Другие ограничения:
- Sequences не синхронизируются — обновляйте через
setval() - Large Objects не реплицируются
- Таблицы без PRIMARY KEY — UPDATE/DELETE не пройдут без REPLICA IDENTITY
-- Если нет PK, задаём REPLICA IDENTITY
ALTER TABLE events REPLICA IDENTITY USING INDEX events_unique_idx;
-- Крайний вариант (дорого):
ALTER TABLE events REPLICA IDENTITY FULL;
Мониторинг лага
-- На publisher: лаг по подписчикам
SELECT
client_addr,
application_name,
sent_lsn,
replay_lsn,
pg_wal_lsn_diff(sent_lsn, replay_lsn) AS replay_lag_bytes
FROM pg_stat_replication;
-- Размер удерживаемого WAL в слоте
SELECT
slot_name,
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS wal_retained
FROM pg_replication_slots;
Отслеживайте wal_retained — если растёт, подписчик не успевает или отключился.
Типичные проблемы
Slot bloat — разрастание WAL
Неактивный подписчик удерживает WAL от удаления. Диск publisher может закончиться:
-- Неактивные слоты
SELECT slot_name, pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained
FROM pg_replication_slots WHERE NOT active;
-- Удаляем мёртвый слот
SELECT pg_drop_replication_slot('dead_slot');
В PostgreSQL 13+ задайте max_slot_wal_keep_size — лимит WAL на слот. При превышении слот инвалидируется.
Конфликты на subscriber
Если на subscriber есть строка с тем же PK — возникнет конфликт. Решения:
- Удалить конфликтующую строку
- В PG 15+:
ALTER SUBSCRIPTION my_sub SKIP (lsn = '0/12345678'); - Отключить/включить подписку после ручного исправления
Failover publisher
При failover (Patroni, pg_auto_failover) replication slot теряется. Варианты:
- PostgreSQL 16+: failover slots переносятся автоматически
- До PG 16: пересоздать подписку с
copy_data = false
-- Пересоздание после failover (PG < 16)
DROP SUBSCRIPTION my_sub;
CREATE SUBSCRIPTION my_sub
CONNECTION 'host=new-primary port=5432 dbname=mydb user=repl_user password=...'
PUBLICATION my_pub
WITH (copy_data = false);
Рекомендации
- Всегда задавайте PRIMARY KEY или REPLICA IDENTITY
- Мониторьте
pg_replication_slots— неактивные слоты убивают диск - Настройте
max_slot_wal_keep_sizeкак страховку - DDL-миграции — сначала subscriber, потом publisher
- Тестируйте failover-сценарии заранее
Логическая репликация требует внимания к DDL-миграциям и мониторинга слотов. Но при грамотной настройке это надёжный инструмент для zero-downtime миграций и построения распределённых систем на PostgreSQL.