# Логическая репликация (publication/subscription) _Репликация · PostgreSQL Knowledge Base_ **TL;DR:** Логическая репликация шлёт не байты WAL, а декодированные изменения строк: INSERT/UPDATE/DELETE по конкретным таблицам. Источник объявляет PUBLICATION, приёмник создаёт SUBSCRIPTION. Требует wal_level = logical. Можно реплицировать часть таблиц между разными версиями и схемами. ## Чем отличается от физической Физическая репликация копирует кластер байт-в-байт через WAL. Всё или ничего: та же версия, та же схема, нельзя писать на копии. Логическая работает на уровне строк. Из того же WAL процесс логического декодирования восстанавливает «в таблице X строка с ключом K стала такой-то» и шлёт это подписчику как логическую операцию. Подписчик - обычный самостоятельный кластер: у него свои индексы, он принимает запись, может быть другой мажорной версии. ``` publisher subscriber ┌────────────────────────┐ ┌──────────────────┐ │ WAL (wal_level=logical) │ │ apply worker │ │ ↓ logical decoding │ ──строки──▶│ ↓ │ │ PUBLICATION (набор │ │ INSERT/UPDATE/ │ │ таблиц) │ │ DELETE локально │ └────────────────────────┘ └──────────────────┘ ``` ## Минимальная настройка На публикующем сервере нужен `wal_level = logical` (см. [wal-level](/courses/postgres/kb/wal-level.md); требует рестарта - это больше, чем `replica`, потому что в WAL добавляется информация для декодирования строк). ```sql -- на источнике CREATE PUBLICATION pub_flights FOR TABLE flights; -- на приёмнике (таблица flights должна уже существовать структурно) CREATE SUBSCRIPTION sub_flights CONNECTION 'host=primary dbname=lab user=rep password=...' PUBLICATION pub_flights; ``` При создании подписки по умолчанию идёт начальная копия данных (`copy_data = true`), затем поток догоняющих изменений. Прогресс видно в `pg_stat_subscription` на приёмнике и `pg_replication_slots` на источнике (под каждую подписку источник держит логический слот). ## Что НЕ едет автоматически Это главный источник сюрпризов. Логическая репликация везёт DML по перечисленным таблицам - и почти ничего больше: - **DDL не реплицируется**. `ALTER TABLE ... ADD COLUMN` на источнике не доедет; добавить колонку нужно руками на обеих сторонах, иначе apply встанет. - **Последовательности (sequences) не синхронизируются** - значение `serial`/identity на приёмнике своё. После переключения легко словить конфликт первичного ключа. - **TRUNCATE** едет, только если включён в публикацию. - Таблице нужна **replica identity** (обычно первичный ключ), иначе UPDATE/DELETE по ней не реплицируются. ## Конфликты Подписчик принимает запись и от своих клиентов тоже. Если прилетит изменение строки, которой локально уже нет (или есть с другим ключом), apply-воркер падает с конфликтом и встаёт - репликация по этой подписке стоит, пока конфликт не разрешат вручную. Поэтому «писать в обе стороны» без чёткого разделения ключей - прямая дорога к остановке (см. [distributed-pitfalls](/courses/postgres/kb/distributed-pitfalls.md)). Типичные применения: миграция между мажорными версиями без даунтайма, сбор нескольких баз в одно хранилище, репликация подмножества таблиц. Про сам журнал, из которого всё декодируется - [streaming-replication](/courses/postgres/kb/streaming-replication.md). ## Команды ```sql CREATE PUBLICATION pub_flights FOR TABLE flights; ``` Объявить набор таблиц для логической репликации на источнике ```sql SELECT * FROM pg_publication_tables; ``` Какие таблицы реально попали в публикации ```sql SELECT subname, received_lsn, latest_end_lsn FROM pg_stat_subscription; ``` На приёмнике: прогресс применения каждой подписки ```sql SHOW wal_level; ``` Логическая репликация требует wal_level = logical (рестарт сервера) ## См. также - [Физическая (потоковая) репликация](/courses/postgres/kb/streaming-replication.md) - [hot_standby_feedback и vacuum мастера](/courses/postgres/kb/hot-standby-feedback.md) - [Распределённые ловушки: 2PC, multi-master, CAP](/courses/postgres/kb/distributed-pitfalls.md) - [Уровни журнала: minimal, replica, logical](/courses/postgres/kb/wal-level.md)