Логическая репликация PostgreSQL передаёт не страницы WAL, а сами изменения строк, поэтому подписчик может быть другой версией сервера, другой архитектуры и с другим набором индексов. Это удобно для миграций, разгрузки отчётов и точечного переноса части таблиц. Но у механизма есть границы: схема не реплицируется, последовательности не переносятся, а ошибка в одной транзакции может остановить весь поток. Разберём настройку публикации и подписки, контроль отставания и действия при сбоях.
Когда логическая репликация уместна
Физическая репликация копирует кластер целиком и требует одинаковой мажорной версии. Логическая работает на уровне таблиц: можно публиковать пять таблиц из пятидесяти, реплицировать базу в другую версию PostgreSQL и собирать данные с нескольких источников. Она же лежит в основе миграции с минимальным простоем и обновления мажорной версии через промежуточный кластер.
Не стоит использовать её как замену резервному копированию: подписчик повторяет ошибки и удаления, а задержка может достигать минут. Для отказоустойчивости с автоматическим переключением нужны слоты репликации с поддержкой failover и внешний менеджер, а не один CREATE SUBSCRIPTION.
Настройка публикатора
На источнике включите логический уровень WAL и заведите запас слотов и отправителей. Параметр wal_level требует перезапуска, остальные применяются перезагрузкой конфигурации.
ALTER SYSTEM SET wal_level = 'logical';
ALTER SYSTEM SET max_replication_slots = 10;
ALTER SYSTEM SET max_wal_senders = 10;
ALTER SYSTEM SET max_slot_wal_keep_size = '20GB';
max_slot_wal_keep_size ограничивает WAL, который удерживает неактивный слот: без него забытая подписка однажды займёт весь диск. После перезапуска проверьте SHOW wal_level и создайте роль репликации с правом чтения публикуемых таблиц.
CREATE ROLE repl LOGIN REPLICATION PASSWORD 'CHANGE_ME_strong_password';
GRANT SELECT ON ALL TABLES IN SCHEMA public TO repl;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO repl;
В pg_hba.conf для подписки достаточно строки с обычным подключением к базе: логическая репликация подключается как клиент, а не через служебную базу replication. Вторая строка нужна, только если вы используете физическую репликацию или pg_basebackup.
host replication repl 10.0.0.0/24 scram-sha-256
host shopdb repl 10.0.0.0/24 scram-sha-256
Публикация
Публикация описывает набор таблиц и операции, которые попадут в поток. По умолчанию публикуются INSERT, UPDATE, DELETE и TRUNCATE, а DDL не публикуется никогда.
CREATE PUBLICATION shop_pub FOR TABLE customers, orders, order_items;
ALTER PUBLICATION shop_pub ADD TABLE payments;
ALTER PUBLICATION shop_pub SET (publish = 'insert, update, delete, truncate');
SELECT * FROM pg_publication_tables WHERE pubname = 'shop_pub';
Для секционированных таблиц публикуйте корень с publish_via_partition_root = true, иначе изменения придут от имени конкретных секций. Проверьте, что у всех таблиц есть первичный ключ: без него UPDATE и DELETE на подписчике не найдут строку, и придётся включать REPLICA IDENTITY FULL, увеличивая объём WAL.
Подписка и начальная синхронизация
Схему на подписчике создайте заранее через pg_dump --schema-only: логическая репликация копирует данные, но не структуру. Затем создайте подписку — она сама создаст слот и скопирует существующие строки.
CREATE SUBSCRIPTION shop_sub
CONNECTION 'host=publisher.db.local port=5432 dbname=shopdb user=repl password=...'
PUBLICATION shop_pub
WITH (copy_data = true, create_slot = true, enabled = true);
Если нужно сначала подготовить слот или создать подписку без немедленного подключения, используйте connect = false и отдельный ALTER SUBSCRIPTION shop_sub ENABLE. Начальная синхронизация читает таблицы обычными запросами, поэтому роль repl должна иметь SELECT, а сеть — выдерживать объём данных. Число параллельных работников задаётся max_sync_workers_per_subscription.
Мониторинг отставания
Главный показатель — не время, а объём WAL, который удерживает слот, и позиция подтверждения подписчика. Смотрите на публикаторе слоты, а на подписчике — статистику подписки.
SELECT slot_name, active, restart_lsn,
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained
FROM pg_replication_slots;
SELECT subname, pid, received_lsn, latest_end_lsn, last_msg_send_time
FROM pg_stat_subscription;
SELECT * FROM pg_stat_subscription_stats;
Растущий retained при active = false означает, что подписчик отключён или сломан, и WAL копится. apply_error_count и sync_error_count в pg_stat_subscription_stats показывают ошибки применения и начальной синхронизации. Настройте оповещение по этим полям, иначе диск закончится раньше, чем вы заметите проблему.
Ограничения и последовательности
Логическая репликация не переносит последовательности, большие объекты и изменения схемы. После начальной синхронизации и перед переключением приложения значения последовательностей нужно выровнять вручную, иначе вставки упрутся в конфликт первичного ключа.
SELECT setval('public.orders_id_seq',
COALESCE((SELECT max(id) FROM public.orders), 0) + 1, false);
SELECT last_value, is_called FROM public.orders_id_seq;
ALTER TABLE public.orders REPLICA IDENTITY FULL;
Индексы и ограничения на подписчике создаются отдельно; их отсутствие замедляет применение, а не ломает его. Ограничения внешних ключей на подписчике лучше не дублировать, если порядок применения таблиц не гарантирован. Патчи схемы выполняйте на обеих сторонах вручную и в совместимом порядке: сначала добавьте колонку на подписчике, потом на публикаторе.
Конфликты и восстановление
Типичный конфликт — дубликат первичного ключа, когда на подписчик уже записали строку с тем же id. В PostgreSQL 16 и новее подписку можно настроить так, чтобы ошибка отключала её, а не останавливала поток в неопределённом состоянии; пропуск транзакции выполняется при отключённой подписке. Перед этим обязательно выясните источник расхождения.
ALTER SUBSCRIPTION shop_sub SET (disable_on_error = true);
ALTER SUBSCRIPTION shop_sub DISABLE;
ALTER SUBSCRIPTION shop_sub SKIP (lsn = '0/16B6C50');
ALTER SUBSCRIPTION shop_sub ENABLE;
Если подписку нужно удалить, сначала отключите её и обнулите имя слота, иначе DROP SUBSCRIPTION попытается связаться с недоступным публикатором и зависнет. Слот на источнике после этого удаляется отдельно через pg_drop_replication_slot.
План переключения
- Остановите запись в источник или переведите приложение в режим только чтения.
- Дождитесь, пока
latest_end_lsnдогонит текущую позицию WAL публикатора. - Выровняйте последовательности и проверьте количество строк в ключевых таблицах.
- Отключите подписку, обнулите слот и удалите её.
- Переключите приложение на новый узел и включите запись.
Итог
Логическая репликация PostgreSQL — точный инструмент для переноса таблиц, миграций и разгрузки аналитики, но он требует дисциплины: wal_level = logical, отдельная роль, заранее созданная схема и контроль слотов. Помните о последовательностях, DDL и конфликтах, ограничивайте удержание WAL и проверяйте отставание по pg_stat_subscription и pg_replication_slots. Тогда переключение пройдёт по плану, а не по тревоге.