Логическая репликация в PostgreSQL позволяет перенаправлять изменения на уровне строк и событий DML, а не байт-копий файлов. Это открывает путь к миграциям без простоя: можно заранее выгрузить данные на новый сервер, стримить изменения в реальном времени и переключить приложения за минуты (или секунды), сводя простой к смене строки подключения.
Когда выбирать logical replication
Логическая репликация уместна, когда нужно:
- Перенести базу на новый сервер или в другой регион без остановки записи (zero downtime migration).
- Обновиться между мажорными версиями PostgreSQL с контролем совместимости схемы и расширений.
- Выделить подмножество таблиц или схем, не копируя весь кластер.
- Выполнить миграцию между разными параметрами хранения, файловыми системами или железом.
- Передавать изменения во внешнюю систему (CDC), включая JSON-формат через wal2json.
Если нужен горячий резерв всего кластера с минимальным лагом и без выборочности — проще физическая репликация. Для селективных миграций и межверсионных сценариев логическая — удобнее и гибче.
Термины и компоненты
- Publication (публикация) — набор таблиц/событий на источнике, изменения из которых доступны подписчикам.
- Subscription (подписка) — конфигурация на целевой стороне, тянущая данные с публикации и применяющая их.
- Logical replication slot (логический слот) — маркер позиции в WAL для декодирования изменений; удерживает WAL до подтверждения подписчиком.
- Output plugin — формат декодирования: встроенный
pgoutputдля нативной логической репликации, стороннийwal2json— для JSON-стриминга.
Логическая репликация по умолчанию не реплицирует DDL и большие объекты. Изменения схемы и состояние последовательностей нужно синхронизировать отдельно.

Подготовка источника (publisher)
Настройки postgresql.conf
# Включаем логическое декодирование и задаем лимиты
wal_level = logical
max_wal_senders = 10
max_replication_slots = 10
max_slot_wal_keep_size = 10GB
wal_keep_size = 1024MB
logical_decoding_work_mem = 128MB
Примените изменения без перезапуска, где возможно:
SELECT pg_reload_conf();
Учетные записи и pg_hba.conf
Создайте пользователя с правом REPLICATION для логического стриминга и доступом на чтение таблиц (для начальной COPY-передачи):
CREATE ROLE repl LOGIN REPLICATION PASSWORD 'strong_password';
GRANT USAGE ON SCHEMA public TO repl;
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 добавьте правила для доступа к БД и репликации (пример для сети 10.0.0.0/24):
host appdb repl 10.0.0.0/24 scram-sha-256
host replication repl 10.0.0.0/24 scram-sha-256
Перезагрузите правила доступа:
SELECT pg_reload_conf();
Реплика-идентичность
Для корректной репликации UPDATE/DELETE таблицам нужны первичные ключи. Если без них никак, временно включите полную реплика-идентичность:
ALTER TABLE public.some_table REPLICA IDENTITY FULL;
Лучше добавить ключи: это ускорит декодирование и уменьшит размер WAL.
Подготовка приемника (subscriber)
Синхронизация схемы и ролей
Начните со схемы: роли, расширения, типы, функции, таблицы, индексы. Проще всего выгрузить только схему и накатить её на приемнике:
# На источнике
pg_dump -s -d appdb > schema.sql
# На приемнике
psql -d appdb -f schema.sql
Убедитесь, что расширения и версии совместимы. Права на объекты и владельцы должны соответствовать, иначе при применении изменений возникнут ошибки.
Вариант A: нативная logical replication (pgoutput) для миграций
Создаем публикацию
-- На источнике
CREATE PUBLICATION app_pub FOR ALL TABLES;
-- Опционально: публикация TRUNCATE и др. событий
ALTER PUBLICATION app_pub SET (publish = 'insert, update, delete, truncate');
Если нужна выборочная репликация, используйте FOR TABLE и перечислите объекты, либо публикации по схемам.
Создаем подписку
-- На приемнике
CREATE SUBSCRIPTION app_sub
CONNECTION 'host=10.0.0.1 port=5432 dbname=appdb user=repl password=strong_password'
PUBLICATION app_pub
WITH (
copy_data = true,
create_slot = true,
slot_name = app_mig,
streaming = on,
synchronous_commit = off
);
copy_data = true запустит начальную загрузку через серию COPY, а затем — поток изменений. streaming = on помогает с большими транзакциями, передавая их частями. synchronous_commit = off на подписке ускоряет применение (риск минимальной потери подтверждения при аварии приемника).
Мониторинг лога и лага
-- На приемнике: статус подписки
SELECT subname, status, received_lsn, latest_end_lsn, latest_end_time
FROM pg_stat_subscription;
-- Оценка «секунд позади» по времени последнего зафиксированного LSN
SELECT subname,
EXTRACT(EPOCH FROM now() - latest_end_time) AS seconds_behind
FROM pg_stat_subscription;
-- На источнике: удержание WAL слотом подписки
SELECT slot_name,
active,
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained
FROM pg_replication_slots
WHERE slot_name = 'app_mig';
Контролируйте рост удержанного WAL. Если подписчик остановится, слот продолжит держать WAL до лимита max_slot_wal_keep_size, после чего потребуется переинициализация подписки (часто — с полной перезагрузкой таблиц).
Для снижения нагрузки на коннекторы рассмотрите пулер: руководство по PgBouncer и пуллинг соединений. Для восстановления на таймлайн и проверки бэкапов пригодится PITR: бэкапы WAL и восстановление.
Cutover без простоя: порядок действий
- Дождитесь стабильно малого лага на подписке (секунды).
- Остановите запись на источнике: переведите приложение в режим обслуживания или временно заблокируйте операции записи в БД на уровне ролей/прав.
- Проверьте, что подписка догнала источник:
-- На приемнике SELECT subname, EXTRACT(EPOCH FROM now() - latest_end_time) AS seconds_behind FROM pg_stat_subscription; - Отключите подписку, чтобы зафиксировать финальное состояние и не держать слот:
ALTER SUBSCRIPTION app_sub DISABLE; - Переключите приложение на новый сервер БД.
- После проверки — удалите подписку и публикацию на старом хосте, чтобы снять слот и освободить WAL:
-- На приемнике DROP SUBSCRIPTION app_sub WITH (drop slot); -- На источнике DROP PUBLICATION app_pub;
Минимальный простой — лишь на время заморозки записи и смены строки подключения. Всё остальное — онлайн.
Вариант B: wal2json и сценарии CDC
wal2json — модуль логического декодирования, превращающий изменения в JSON-поток. Он полезен для CDC и интеграций или если миграция подразумевает промежуточную обработку событий.
Создание логического слота под wal2json (после установки модуля):
SELECT pg_create_logical_replication_slot('app_cdc', 'wal2json');
Чтение событий утилитой pg_recvlogical:
pg_recvlogical -d "host=10.0.0.1 dbname=appdb user=repl password=strong_password" --slot=app_cdc --plugin=wal2json --start -o pretty-print=1 -f -
Типовой путь миграции с wal2json — сделать начальную загрузку данными (COPY/pg_dump), затем реплицировать дельту событий JSON-стримом, применяя её на приемнике вашим консьюмером. Для Postgres→Postgres миграций нативные публикации/подписки обычно проще, быстрее и надежнее.

Выбор и управление слотами
Типы слотов
- Physical — для потоковой (физической) репликации; не подходит для logical.
- Logical — для логического декодирования: подписки и плагины (pgoutput, wal2json).
- Временные — логические слоты, создаваемые как временные; удаляются при завершении сессии. Хороши для разовых выгрузок.
Рекомендации по слотам в миграции
- Используйте отдельный именованный слот под миграцию. Не делите слот между консьюмерами.
- Включайте
max_slot_wal_keep_sizeи мониторинг, чтобы слот не съел весь диск при простое подписчика. - Удаляйте слот сразу после успешного cutover:
DROP SUBSCRIPTION ... WITH (drop slot)илиSELECT pg_drop_replication_slot(...). - Для разового чтения через
wal2jsonрассмотрите временный слот:SELECT pg_create_logical_replication_slot('app_tmp', 'wal2json', true);
Мониторинг и алерты по слотам
SELECT slot_name,
slot_type,
active,
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained,
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), confirmed_flush_lsn)) AS lag
FROM pg_replication_slots
WHERE slot_type = 'logical';
Алертируйте при удержании WAL выше безопасного порога или если слот долго неактивен.
Ограничения и подводные камни
- DDL и последовательности. По умолчанию DDL не реплицируется. Последовательности часто требуют ручной синхронизации значений на приемнике перед cutover.
- REPLICA IDENTITY. Отсутствие первичных ключей замедляет репликацию и увеличивает риски конфликтов. Добавляйте ключи или включайте
REPLICA IDENTITY FULLкак временную меру. - Длинные транзакции. Они держат большие объемы WAL и растят лаг. Разбивайте массовые DML на пачки.
- Расширения и типы. Не все расширения и типы данных однозначно мигрируют между версиями. Проверяйте совместимость заранее.
- Большие объекты (LO). Не реплицируются логически. Используйте отдельную стратегию переноса.
- Конфликты применений. Подписка остановится при ошибках. Следите за
pg_stat_subscriptionи логами воркера; пропуск проблемных LSN — только как крайняя мера.
Оптимизация производительности
- Параллелизм. Новые версии улучшают параллельное применение на подписчике. Чем больше ядер и I/O, тем выше пропускная способность.
- WAL и сеть. Увеличьте
wal_buffers, при необходимости включайтеwal_compression, следите за пропускной способностью сети и задержками. - Автовакуум. Не отключайте. Он убирает мертвые версии строк, удерживая размеры таблиц и индексов под контролем.
- Индексы на приемнике. При staged-миграции иногда выгодно загрузить данные без вторичных индексов, а затем создать их пакетно.
Практический чек-лист миграции без простоя
- Проверить версии PostgreSQL, расширения, роли, параметры WAL и лимиты по слотам.
- Подготовить схему на приемнике, включить
REPLICA IDENTITYгде нужно. - Создать публикацию на источнике, подписку на приемнике с отдельным слотом.
- Дождаться завершения начальной загрузки и стабилизировать лаг.
- Заморозить записи, дождаться нулевого лага, отключить подписку.
- Переключить приложение на новый сервер и выполнить smoke-тесты.
- Удалить подписку со слотом, снять публикацию, финализировать мониторинг.
Ответы на частые вопросы
Можно ли реплицировать только часть схемы?
Да. Создайте публикацию с перечислением таблиц или используйте несколько публикаций для разных наборов. На приемнике — одна или несколько подписок.
Как быть с последовательностями?
Перед cutover синхронизируйте значения вручную: поднимите счетчики на приемнике до максимума из источника. Это исключит конфликты ключей при старте записи.
Нужны ли суперправа подписчику?
На источнике учетная запись обычно требует атрибута REPLICATION для логического стриминга WAL и прав SELECT на таблицы для начальной передачи. На приемнике — права на запись в целевые таблицы.
Что если слот «съел» диск?
Остановите подписку, удалите слот, освободите место и переинициализируйте подписку с начальной загрузкой. На будущее — задайте max_slot_wal_keep_size и мониторинг.
Итоги
Логическая репликация PostgreSQL — мощный инструмент для миграций без простоя. Ключи успеха: корректная схема и реплика-идентичность, аккуратный выбор слотов и мониторинг удержания WAL, четкий план cutover и уборка артефактов. Для Postgres→Postgres используйте нативные публикации/подписки; wal2json оставьте для CDC и интеграций. Протестируйте процесс на стенде, замерьте лаги и пропускную способность — и «zero downtime» станет предсказуемой процедурой.


