Логическая репликация

Логическая репликация строится на передаче информации о выполненных в конкретных таблицах командах DML (INSERT, UPDATE, DELETE, TRUNCATE). В отличие от физической репликации, в логической не используются понятия «мастер» и «реплика» — вместо этого взаимодействие организовано через публикации и подписки. На одном сервере могут существовать одновременно и публикации, и подписки, поэтому потоки данных могут быть разнонаправленными.
Ключевые объекты логической репликации:
- Публикация (Publication) — объект базы данных, создаваемый для одной или нескольких таблиц. Публикация извлекает из записей WAL команды DML, относящиеся к публикуемым таблицам, и передает декодированные данные подписчикам. При создании можно указать конкретные таблицы, выбрать требуемые столбцы или настроить фильтрацию строк;
- Подписка (Subscription) — объект базы данных, подключающийся к одной или нескольким публикациям. Подписка получает декодированные сообщения по протоколу репликации и применяет их к таблицам на стороне подписчика.

Принцип работы логической репликации:
- Поток логической репликации использует обычный протокол репликации, изначально спроектированный для физической репликации. Однако по протоколу передаются не сырые записи WAL, а декодированные команды DML;
- Процесс
wal senderна стороне публикации получает декодированные изменения из журнала WAL и отправляет их подписчику по сетевому протоколу репликации; - На стороне подписки процесс
logical replication launcherсообщаетpostmasterо необходимости породить процессlogical replication worker, который обслуживает подписку — получает декодированные команды и применяет их к целевым таблицам.
Подготовка сервера
Для логической репликации сервер публикатора должен записывать в журнал WAL информацию, достаточную для декодирования команд DML. Это обеспечивается параметром wal_level, которому необходимо присвоить значение logical. Кроме того, файл pg_hba.conf должен содержать правила аутентификации для подключений репликации.
Переключимся на сервер публикатора (порт 5432) и установим требуемое значение.
postgres@repbase=# \c - - - 5432
You are now connected to database "repbase" as user "postgres" via socket in "/tmp" at
port "5432".
Выполним изменение параметра командой ALTER SYSTEM, которая запишет значение в файл postgresql.auto.conf.
postgres@repbase=# ALTER SYSTEM SET wal_level TO logical;
ALTER SYSTEM
Проверим текущее значение параметра.
postgres@repbase=# \dconfig+ wal_level
List of configuration parameters
Parameter | Value | Type | Context | Access privileges
-----------+---------+------+------------+-------------------
wal_level | replica | enum | postmaster |
(1 row)
Текущее значение остается replica, поскольку параметр имеет контекст postmaster — изменение вступает в силу после перезапуска сервера.
Действия для перезапуска сервера
Выйти из сеанса psql командой \q.
postgres@repbase=# \q
Выйти из оболочки пользователя postgres командой exit, вернувшись к пользователю student.
[postgres@ServerName ~]$ exit
Перезапустить экземпляр PostgreSQL командой sudo systemctl restart postgresql (пользователь student имеет права sudo, пользователь postgres — нет, что соответствует требованиям безопасности).
[student@ServerName ~]$ sudo systemctl restart postgresql
Вернуться под пользователя postgres командой sudo -iu postgres и подключиться к базе repbase.
[student@ServerName ~]$ sudo -iu postgres
Теперь снова подключимся к серверу публикатору.
[postgres@ServerName ~]$ psql -d repbase -q
postgres@repbase=# \dconfig+ wal_level
List of configuration parameters
Parameter | Value | Type | Context | Access privileges
-----------+---------+------+------------+-------------------
wal_level | logical | enum | postmaster |
(1 row)
Сервер перезапущен. Подключение к базе repbase на порту 5432 подтверждает, что экземпляр работает с новым значением wal_level = logical.
Организация репликации
На этом этапе формируются объекты репликации. Сначала на сервере-источнике создается публикация, которая определяет, какие таблицы будут реплицироваться. Затем структура таблиц переносится на сервер-получатель, чтобы подписчик знал схему данных. После этого создается подписка — она устанавливает связь между серверами и запускает начальную синхронизацию данных.
Создание публикации
На сервере публикатора создадим таблицу для репликации и заполним ее тестовыми данными.
postgres@repbase=# CREATE TABLE info2pub(id integer PRIMARY KEY, msg text);
CREATE TABLE
Таблица, участвующая в логической репликации, должна иметь первичный ключ или настроенный параметр REPLICA IDENTITY. Без него обновления и удаления строк не будут реплицироваться корректно.
postgres@repbase=# INSERT INTO info2pub VALUES(1,'Это таблица'),(2,'для логической'),(3,'репликации'),(4,'.');
INSERT 0 4
Создадим публикацию с помощью команды CREATE PUBLICATION для таблицы info2pub.
postgres@repbase=# CREATE PUBLICATION pub_info FOR TABLE info2pub;
CREATE PUBLICATION
Команда CREATE PUBLICATION создает публикацию для одной или нескольких таблиц. Можно указать конкретные таблицы, все таблицы базы данных или всех схемы. В нашем случае публикация создана для одной таблицы без ограничений по строкам и столбцам.
Проверить параметры созданной публикации можно метакомандой \dRp.
postgres@repbase=# \dRp
List of publications
Name | Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
----------+----------+------------+---------+---------+---------+-----------+----------
pub_info | postgres | f | t | t | t | t | f
(1 row)
При создании публикации можно задать параметр publish, который позволяет ограничить набор реплицируемых команд DML (insert, update, delete, truncate). В нашем случае он указан не был, поэтому в списке публикаций для каждой команды DML стоит значение t (true).
Копирование структуры таблицы
Перед созданием подписки на сервере подписчика должна существовать таблица с такой же структурой, как на стороне публикатора. Скопируем структуру таблицы info2pub с помощью утилиты pg_dump.
[postgres@ServerName ~]$ pg_dump -d repbase -t info2pub --schema-only | psql -d repbase -p 6432
Разбор команды
pg_dump -d repbase— логическое резервное копирование базы данныхrepbase(подключение на порту по умолчанию 5432);- опция
-t info2pub— выбор конкретной таблицыinfo2pub; - опция
--schema-only— экспорт только структуры (команды DDL), без данных; - результат передается по каналу в
psql -d repbase -p 6432, который подключается к базеrepbaseна сервере подписчика (порт 6432) и выполняет полученные команды DDL.
Подключимся к серверу подписчика и проверим созданную таблицу.
[postgres@ServerName ~]$ psql -p 6432 -d repbase
psql (15.5)
Type "help" for help.
postgres@repbase=# \d info2pub
Table "public.info2pub"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
id | integer | | not null |
msg | text | | |
Indexes:
"info2pub_pkey" PRIMARY KEY, btree (id)
Таблица info2pub на стороне подписчика имеет ту же структуру, что и на публикаторе: те же столбцы, типы данных и первичный ключ. Данные пока отсутствуют — таблица пуста.
Создание подписки
Создадим подписку с помощью команды CREATE SUBSCRIPTION, которая подключится к публикации pub_info на сервере публикатора. Строка подключения CONNECTION указывает целевой сервер и базу данных, параметр PUBLICATION — перечень публикаций для подписки.
postgres@repbase=# CREATE SUBSCRIPTION sub_info CONNECTION 'dbname=repbase' PUBLICATION pub_info;
NOTICE: created replication slot "sub_info" on publisher
CREATE SUBSCRIPTION
Сведения о созданных подписках выводятся метакомандой \dRs.
postgres@repbase=# \x \dRs+ \x
Expanded display is on.
List of subscriptions
-[ RECORD 1 ]--------+---------------
Name | sub_info
Owner | postgres
Enabled |t
Publication | {pub_info}
Binary |f
Streaming |f
Two-phase commit |d
Disable on error |f
Synchronous commit | off
Conninfo | dbname=repbase
Skip LSN | 0/0
Expanded display is off.
Сообщение NOTICE: created replication slot "sub_info" on publisher подтверждает, что на стороне публикатора автоматически создан слот репликации sub_info.
Начальная синхронизация
Начальная загрузка данных выполняется по умолчанию — отключить это поведение можно параметром copy_data=false команды CREATE SUBSCRIPTION.
Проверим подключение к серверу подписчика (порт 6432) и данные таблицы info2pub.
postgres@repbase=# \conninfo
You are connected to database "repbase" as user "postgres" via socket in "/tmp" at port "6432".
Выполним выборку данных из таблицы info2pub.
postgres@repbase=# SELECT * FROM info2pub;
id | msg
----+----------------
1 | Это таблица
2 | для логической
3 | репликации
4 | .
(4 rows)
Таблица info2pub на подписчике заполнена четырьмя строками, которые были перенесены с публикатора автоматически при создании подписки.
Мониторинг репликации
Начальная загрузка данных подтверждает, что подписка создана и работает, однако для системного контроля репликации этого недостаточно. Чтобы обнаружить задержки, рассинхронизацию или сбои потока репликации, необходим мониторинг. Он осуществляется с помощью системных представлений, отражающих состояние подписки на стороне подписчика, а рабочие процессы репликации наблюдаются через представления на обоих серверах.
Проверка работы репликации
Подключимся к серверу публикатора и вставим новую строку в таблицу info2pub.
postgres@repbase=# \c - - - 5432
You are now connected to database "repbase" as user "postgres" via socket in "/tmp" at port "5432".
postgres@repbase=# INSERT INTO info2pub VALUES (5,'Проверка репликации.');
INSERT 0 1
Переключимся на сервер подписчика (порт 6432) и проверим, что новая строка была реплицирована.
postgres@repbase=# \c - - - 6432
You are now connected to database "repbase" as user "postgres" via socket in "/tmp" at port "6432".
postgres@repbase=# SELECT * FROM info2pub;
id | msg
----+----------------------
1 | Это таблица
2 | для логической
3 | репликации
4 | .
5 | Проверка репликации.
(5 rows)
Строка с id = 5, вставленная на стороне публикатора, успешно реплицирована на подписчика. Это подтверждает корректную работу логической репликации: изменения, примененные к публикуемой таблице, передаются подписчику по протоколу репликации в виде декодированных команд DML.
Проверка состояния подписки
Системное представление pg_stat_subscription содержит сведения о текущем состоянии подписки на стороне подписчика.
Проверим состояние подписки sub_info.
postgres@repbase=# SELECT * FROM pg_stat_subscription \gx
-[ RECORD 1 ]---------+------------------------------
subid | 24822
subname | sub_info
pid | 3492
relid |
received_lsn | 0/D6ED080
last_msg_send_time | 2024-11-26 07:43:45.649792+03
last_msg_receipt_time | 2024-11-26 07:43:45.649828+03
latest_end_lsn | 0/D6ED080
latest_end_time | 2024-11-26 07:43:45.649792+03
Ключевые поля представления pg_stat_subscription
subname— имя подписки;pid— PID рабочего процесса логической репликации на стороне подписчика. Если значение пустое, рабочий процесс не запущен;received_lsn— последний LSN (Log Sequence Number) полученного от публикатора сообщения;last_msg_send_time— время отправки последнего сообщения от подписчика к публикатору;last_msg_receipt_time— время получения последнего сообщения от публикатора;latest_end_lsn— LSN последней примененной транзакции;latest_end_time— время завершения применения последней транзакции.
Если pid не пустой, подписка активна и рабочий процесс (logical replication worker) обрабатывает изменения. Совпадение received_lsn и latest_end_lsn указывает на отсутствие задержек в применении данных.
Процессы публикации и подписки
Просмотрим дерево процессов PostgreSQL для анализа работы логической репликации на стороне подписчика и публикатора.
postgres@repbase=# \! ps f -C postgres
PID TTY STAT TIME COMMAND
3377 ? Ss 0:00 /usr/pangolin-{pangolin_version}/bin/postgres -D /pgdata/06/data
3401 ? Ss 0:00 \_ postgres: checkpointer
3402 ? Ss 0:00 \_ postgres: background writer
3404 ? Ss 0:00 \_ postgres: idle sessions terminator
3405 ? Ss 0:00 \_ postgres: walwriter
3406 ? Ss 0:00 \_ postgres: license checker
3407 ? Ss 0:00 \_ postgres: autovacuum launcher
3408 ? Ss 0:00 \_ postgres: autounite launcher
3409 ? Ss 0:00 \_ postgres: integrity check launcher
3410 ? Ss 0:00 \_ postgres: logical replication launcher
3493 ? Ss 0:00 \_ postgres: walsender postgres [local] START_REPLICATION
2961 ? Ss 0:00 /usr/pangolin-{pangolin_version}/bin/postgres -D /var/lib/postgres/rpl_pgdata
2963 ? Ss 0:00 \_ postgres: checkpointer
2964 ? Ss 0:00 \_ postgres: background writer
2966 ? Ss 0:00 \_ postgres: idle sessions terminator
2969 ? Ss 0:00 \_ postgres: integrity check launcher
2970 ? Ss 0:00 \_ postgres: license checker
2921 ? Ss 0:00 \_ postgres: walwriter
3022 ? Ss 0:00 \_ postgres: autovacuum launcher
3023 ? Ss 0:00 \_ postgres: autounite launcher
3024 ? Ss 0:00 \_ postgres: logical replication launcher
3492 ? Ss 0:00 \_ postgres: logical replication worker for subscription 24822
3516 ? Ss 0:00 \_ postgres: postgres repbase [local] idle
Ключевые процессы логической репликации
walsender postgres [local] START_REPLICATION(PID 3493) — процесс на стороне публикатора (-D /pgdata/06/data). Передает подписчику декодированные команды DML по протоколу репликации;logical replication worker for subscription 24822(PID 3492) — процесс на стороне подписчика (-D /var/lib/postgres/rpl_pgdata). Принимает декодированные изменения и применяет их к целевым таблицам. Номер подписки24822соответствует значениюsubidиз представленияpg_stat_subscription;logical replication launcher— процесс, присутствующий на обоих серверах. Следит за состоянием подписок и при необходимости создает рабочие процессыlogical replication worker.
Сопоставим PID рабочего процесса с данными из pg_stat_subscription — совпадение значений подтвердит, что представление корректно отражает фактическое состояние процессов сервера.
postgres@repbase=# SELECT * FROM pg_stat_subscription \gx
-[ RECORD 1]---------+------------------------------
subid | 34308
subname | sub_info
pid | 44153
relid |
received_lsn | 0/C774928
last_msg_send_time | 2024-11-04 20:30:08.097156+03
last_msg_receipt_time| 2024-11-04 20:30:08.097208+03
latest_end_lsn | 0/C774928
Поле pid (44153) совпадает с PID процесса logical replication worker в дереве процессов.
Удаление объектов репликации
При необходимости прекращения репликации объекты удаляются в строгой последовательности: сначала подписка на сервере подписчика, затем публикация на сервере публикатора. При удалении подписки автоматически очищается слот репликации на стороне публикатора, что освобождает удерживаемые WAL-сегменты. Таблицы на обоих серверах сохраняются и могут изменяться независимо.
Удаление подписки и публикации
Удалим подписку sub_info на сервере подписчика с помощью команды DROP SUBSCRIPTION.
postgres@repbase=# DROP SUBSCRIPTION sub_info;
NOTICE: dropped replication slot "sub_info" on publisher
DROP SUBSCRIPTION
При удалении подписки автоматически удаляется слот репликации на стороне публикатора, что подтверждается сообщением NOTICE: dropped replication slot "sub_info" on publisher. Это освобождает ресурсы и позволяет серверу архивировать сегменты WAL, ранее удерживаемые слотом.
Переключимся на сервер публикатора и удалим публикацию с помощью команды DROP PUBLICATION.
postgres@repbase=# \c - - - 5432
You are now connected to database "repbase" as user "postgres" via socket in "/tmp" at port "5432".
postgres@repbase=# DROP PUBLICATION pub_info;
DROP PUBLICATION
Таблицы на обоих серверах сохраняются. После удаления публикации и подписки данные в них могут изменяться независимо.
Итоги
- Публикация (
CREATE PUBLICATION) создается на сервере-источнике для одной или нескольких таблиц; - Таблица должна иметь первичный ключ или настроенный параметр
REPLICA IDENTITYдля корректной репликации обновлений и удалений; - Подписка (
CREATE SUBSCRIPTION) создается на сервере-получателе и подключается к публикации по строкеCONNECTION; - При создании подписки автоматически создается слот репликации на стороне публикатора;
- Начальная загрузка данных выполняется автоматически — отключается параметром
copy_data=false; - Состояние подписки проверяется через представление
pg_stat_subscription(поляreceived_lsn,latest_end_lsn,pid); - На подписчике работает процесс
logical replication worker, на публикаторе —walsenderс декодированными командами DML; - Параметр
publishвCREATE PUBLICATIONограничивает набор реплицируемых команд DML; - Удаление объектов: сначала
DROP SUBSCRIPTIONна подписчике (слот удаляется автоматически), затемDROP PUBLICATIONна публикаторе.
Самопроверка
Вопрос 1
Какой параметр wal_level требуется для логической репликации, и что нужно сделать после его изменения?
Вопрос 2
Какие требования к таблице, участвующей в логической репликации? Выберите все верные варианты.
Вопрос 3
Что делает опция --schema-only в команде pg_dump?
Вопрос 4
Что происходит при создании подписки (CREATE SUBSCRIPTION)? Выберите все верные варианты.
Вопрос 5
Какие ключевые поля содержит представление pg_stat_subscription на стороне подписчика? Выберите все верные варианты.
Вопрос 6
Какой процесс работает на стороне подписчика и применяет декодированные изменения к целевым таблицам?
Вопрос 7
В какой последовательности нужно удалять объекты логической репликации? Выберите все верные варианты.
Вопрос 8
Какой параметр отключает начальную загрузку данных при создании подписки?