39.1 Сменить уровень: от байтов к строкам
Физическая репликация работает на уровне хранения: страница, смещение, байты. Логическая поднимается на уровень логической модели: таблица, строка, операция INSERT/UPDATE/DELETE.
Источник этих данных один и тот же - WAL. Но чтобы из журнала можно
было восстановить «какая строка какой таблицы как изменилась», в него
нужно дописать дополнительную информацию. Поэтому логическая
репликация требует wal_level = logical - это больше, чем replica:
в журнал кладётся достаточно, чтобы декодировать изменения строк, а
не только повторить запись страниц.
publisher (wal_level=logical) subscriber
┌────────────────────────┐ ┌──────────────────┐
│ WAL │ │ apply worker │
│ ↓ logical decoding │ ──строки──▶│ ↓ │
│ PUBLICATION (таблицы) │ │ INSERT/UPDATE/ │
│ │ │ DELETE локально │
└────────────────────────┘ └──────────────────┘
Смена уровня требует рестарта сервера: wal_level нельзя поменять на
лету. Это первая практическая засада - архитектуру под логическую
репликацию закладывают заранее.
39.2 Publication и subscription
Две стороны объявляются явно. Источник создаёт публикацию - набор таблиц, изменения которых он готов отдавать:
-- на источнике
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), а затем поток догоняющих изменений. Под каждую
подписку источник заводит логический слот репликации - тот же
механизм удержания WAL, что в физической репликации, со всеми его
рисками (забытый слот копит журнал).
Прогресс видно с обеих сторон: pg_stat_subscription на приёмнике,
pg_replication_slots и pg_stat_replication на источнике.
39.2.1 Копнуть глубже: replica identity
Чтобы реплицировать UPDATE или DELETE, приёмнику надо понять, какую строку менять. Эту роль играет replica identity - набор колонок, однозначно определяющий строку. По умолчанию это первичный ключ.
ALTER TABLE flights REPLICA IDENTITY FULL; -- если нет PK: вся строка как ключ
У таблицы без первичного ключа и без явно заданной replica identity INSERT реплицируется, а UPDATE и DELETE - нет (источнику нечем опознать строку). Это тихая потеря изменений, которую легко не заметить, пока данные на сторонах не разойдутся.
39.3 Что НЕ едет автоматически
Здесь живёт большинство сюрпризов. Логическая репликация везёт DML по перечисленным таблицам - и почти ничего сверх этого. Запомни этот список, он спасает от ночных инцидентов:
- DDL не реплицируется.
ALTER TABLE ... ADD COLUMNна источнике не доедет. Добавишь колонку только на источнике - apply на приёмнике встанет, не найдя её. Схему меняют согласованно руками на обеих сторонах. - Последовательности не синхронизируются. Значение serial / identity на приёмнике живёт своей жизнью. После переключения на приёмник легко словить конфликт первичного ключа: новая вставка возьмёт значение, которое уже занято реплицированными строками.
- TRUNCATE едет, только если он явно включён в публикацию.
- Объекты вне таблиц (последовательности как объекты, представления, функции) логическая репликация не переносит.
Правило простое: логическая репликация - про строки конкретных таблиц, а не про «всю базу целиком». Всё остальное - твоя забота.
39.4 Конфликты применения
Приёмник - самостоятельный кластер, и он принимает запись не только от репликации, но и от своих клиентов. Это создаёт конфликты, которых в физической репликации быть не может (там standby не пишет вообще).
Прилетела реплицированная вставка с ключом, который на приёмнике уже занят локальной строкой - apply-воркер не может её применить и останавливается. Репликация по этой подписке встаёт целиком, пока конфликт не разрешат вручную: либо убрать мешающую строку, либо пропустить проблемную транзакцию. В логе приёмника - ошибка нарушения уникальности с указанием подписки.
Ключевое отличие от физической репликации: там standby не может «не суметь» применить журнал, он всегда повторяет байты. Здесь apply выполняет настоящие SQL-операции, и любая из них может упереться в локальное состояние.
39.5 Подводный камень: самодельный multi-master
Логическую репликацию легко настроить в обе стороны: пусть оба сервера и публикуют, и подписываются друг на друга, и пишем в любой. На демонстрации это работает и выглядит как бесплатный multi-master.
В проде оно ломается на первом конфликте. Два узла независимо выдали одинаковый id разным строкам (последовательности-то не общие) - apply встал на нарушении ключа. Одну строку обновили на обоих узлах сразу - встроенного разрешения «кто прав» у PostgreSQL нет, репликация по подписке остановилась. Без аккуратной настройки origin изменение ходит по кругу.
Вывод не «нельзя», а «это полноценная распределённая система со своей политикой разрешения конфликтов, а не галочка в конфиге». Настоящий multi-master берут отдельным решением. Подробный разбор - в главе про распределённые ловушки и в distributed-pitfalls.
39.6 Когда логическая, когда физическая
Два механизма решают разные задачи, и выбор почти всегда однозначен, если задать правильный вопрос.
Бери физическую, когда нужна точная копия всего кластера: горячий резерв на failover, разгрузка чтения на полную копию, простой надёжный HA. Одна версия, всё или ничего, минимум сюрпризов.
Бери логическую, когда нужна гибкость хранения:
- миграция между мажорными версиями без даунтайма - подписчик на новой версии догоняет старого, потом переключение;
- репликация подмножества таблиц или баз в общее хранилище;
- разные схемы или индексы на источнике и приёмнике;
- запись на приёмнике в дополнение к репликации.
Если слышишь «нам нужна копия ради надёжности» - это физическая. Если «нам нужно перелить вот эти таблицы вон туда, возможно в другую версию» - логическая. Про сам журнал, из которого оба механизма берут изменения - глава о потоковой репликации и streaming-replication.
Уроки в sandbox
lab-39.1. Публикация и каталог логической репликации
Полноценную пару источник-приёмник поднимает отдельная топология (и требует wal_level = logical с рестартом). Здесь мы разберём источник-сторону, доступную на одном сервере: создадим публикацию, посмотрим, какие таблицы в неё реально попали, и проверим текущий wal_level. Сначала предскажешь, потом проверишь.
Проверь уровень журнала:
SHOW wal_level;. Предскажи, хватит ли его для запуска подписки (для публикации - да, для потока изменений нужен logical).Создай публикацию на одну таблицу:
CREATE PUBLICATION pub_flights FOR TABLE flights;.Посмотри, что попало:
SELECT pubname, tablename FROM pg_publication_tables;- должна быть строка flights.Добавь вторую таблицу:
ALTER PUBLICATION pub_flights ADD TABLE bookings;и снова посмотри pg_publication_tables - теперь две строки.Проверь replica identity таблицы:
SELECT relname, relreplident FROM pg_class WHERE relname IN ('flights','bookings');- 'd' означает default (первичный ключ), которого хватает для UPDATE/DELETE.Убери за собой:
DROP PUBLICATION pub_flights;.
sandbox с автопроверкой - открыть в песочнице
Резюме
- Логическая репликация работает на уровне строк (INSERT/UPDATE/DELETE по таблицам), а не байтов WAL; для декодирования нужен wal_level = logical и рестарт сервера.
- Источник объявляет PUBLICATION (набор таблиц), приёмник создаёт SUBSCRIPTION; приёмник - самостоятельный кластер, может быть другой версии и принимать запись.
- Под каждую подписку источник держит логический слот - тот же риск удержания WAL, что у физических слотов.
- Для UPDATE/DELETE нужна replica identity (обычно первичный ключ); без неё эти операции тихо не реплицируются.
- DDL и последовательности не реплицируются: схему меняют руками на обеих сторонах, значения identity на приёмнике свои.
- Приёмник пишет сам, поэтому apply может упереться в конфликт (например, дубль ключа) и остановить репликацию по подписке до ручного разрешения.
- Физическая - для точной копии всего кластера (HA, failover); логическая - для гибкости (миграция версий, подмножество таблиц, разные схемы).
Контрольные вопросы
Почему логическая репликация требует wal_level = logical, а физической хватает replica?
Показать ответ
Физической репликации нужно лишь повторить запись страниц - этого уровня replica достаточно. Логическая должна восстановить из журнала смысл: какая строка какой таблицы как изменилась. Для этого в WAL дописывается дополнительная информация (в том числе ради UPDATE/DELETE по replica identity), и этот объём включается только на уровне logical. Сменить wal_level можно лишь с рестартом сервера, поэтому под логическую репликацию закладываются заранее.
Что произойдёт, если на источнике сделать ALTER TABLE ADD COLUMN, а на приёмнике нет?
Показать ответ
DDL логическая репликация не везёт. На источнике колонка появится, в поток пойдут строки с новой колонкой, а приёмник, не зная о ней, не сможет применить изменение - apply-воркер по этой подписке остановится с ошибкой. Поэтому схему меняют согласованно: сначала совместимым образом на приёмнике, потом на источнике. Несогласованный ALTER - типовая причина «репликация встала ночью».
Почему таблице для логической репликации может понадобиться REPLICA IDENTITY FULL?
Показать ответ
Чтобы реплицировать UPDATE/DELETE, источник должен передать, какую именно строку менять, - это и есть replica identity, по умолчанию первичный ключ. Если у таблицы нет первичного ключа, без явной replica identity INSERT-ы реплицируются, а UPDATE/DELETE - нет. REPLICA IDENTITY FULL делает ключом всю строку, и тогда UPDATE/DELETE едут, ценой большего объёма в журнале.
Почему apply-воркер логической репликации может остановиться, а startup физической - нет?
Показать ответ
Startup в физической репликации просто повторяет байты WAL - у него нет «бизнес-логики», упереться не во что. Apply в логической выполняет настоящие SQL-операции на приёмнике, который вдобавок принимает собственную запись. Любая такая операция может нарушить ограничение (например, уникальность ключа) - тогда apply по этой подписке встаёт до ручного разрешения конфликта. Это плата за то, что приёмник - полноценный пишущий кластер, а не пассивная копия.
Как выбрать между физической и логической репликацией?
Показать ответ
Задай вопрос «мне нужна точная копия всего кластера или гибкая пересылка данных?». Точная копия ради надёжности (HA, failover, разгрузка чтения) - физическая: одна версия, всё или ничего, минимум сюрпризов. Гибкость (миграция между мажорными версиями без даунтайма, репликация части таблиц, разные схемы/индексы, запись на приёмнике) - логическая, со всеми её оговорками про DDL, последовательности и конфликты.