Репликация
Логическая репликация
16
Авторские права
© Postgres Professional, 2017–2025
Авторы: Егор Рогов, Павел Лузанов, Илья Баштанов, Алексей Береснев
Фото: Олег Бартунов (монастырь Пху и пик Бхрикути, Непал)
Использование материалов курса
Некоммерческое использование материалов курса (презентации,
демонстрации) разрешается без ограничений. Коммерческое
использование возможно только с письменного разрешения компании
Postgres Professional. Запрещается внесение изменений в материалы
курса.
Обратная связь
Отзывы, замечания и предложения направляйте по адресу:
edu@postgrespro.ru
Отказ от ответственности
Компания Postgres Professional не несет никакой ответственности за
любые повреждения и убытки, включая потерю дохода, нанесенные
прямым или непрямым, специальным или случайным использованием
материалов курса. Компания Postgres Professional не предоставляет
каких-либо гарантий на материалы курса. Материалы курса
предоставляются на основе принципа «как есть» и компания Postgres
Professional не обязана предоставлять сопровождение, поддержку,
обновления, расширения и изменения.
2
Темы
Сравнение логической и физической репликации
Публикации и подписки
Начальная синхронизация
Идентификация и фильтрация строк
Логическое декодирование
Большие транзакции
Конфликты
Источники репликации
3
Сравнение
Физическая
мастер-реплика: поток данных только в одну сторону
передача потока журнальных записей или файлов журнала
требуется двоичная совместимость серверов
реплицируется только весь кластер
Логическая
публикация-подписки: у сервера нет выделенной роли
передача изменений табличных строк
требуется совместимость на уровне протокола
возможна выборочная репликация отдельных таблиц
При физической репликации серверы имеют назначенные роли: мастер
и реплика. Мастер передает журнальные записи на реплику, которая
применяет эти записи к своим файлам данных. Применение происходит
чисто механически, без «понимания смысла» изменений, поэтому
важна двоичная совместимость между серверами (одинаковые
платформы и основные версии PostgreSQL). Поскольку журнал общий
для всего кластера, то и реплицировать можно только кластер целиком.
При логической репликации на одном сервере создается публикация,
другие серверы могут на нее подписаться. У сервера нет выделенной
роли: один и тот же сервер может как публиковать изменения, так и
подписываться на другие (или даже свои) публикации.
По умолчанию информация об изменениях строк в таблицах
передается подписке в текстовом, независимом от платформы виде,
в котором двоичная совместимость не требуется.
4
max_wal_senders = 10 max_logical_replication_workers = 4
max_replication_slots = 10 max_worker_processes = 8
Схема репликации
публикующий
сервер
select, insert
update, delete
подписчик
wal sender
сегменты WAL
select, insert
update, delete
сегменты WAL
logical
replication
apply worker
logical
replication
launcher
Логическая репликация использует модель «публикация–подписка».
При старте экземпляра запускается фоновый процесс logical
replication launcher, периодически проверяющий таблицу
pg_subscription системного каталога на предмет появления новой
подписки. Как только подписка появляется, для нее запускается
фоновый процесс logical replication apply worker. Он соединяется
с сервером публикации, на котором, как при любом подключении по
протоколу репликации, запускается процесс wal sender. Этот процесс
читает журнал предзаписи, отбирает нужные и преобразует их
в платформонезависимые сообщения протокола. При этом обязательно
используется слот логической репликации.
На стороне подписки рабочий процесс принимает сообщения и
применяет изменения. В это же время сервер-подписчик может
принимать обычные запросы на чтение и запись.
Обратите внимание, что на публикующем сервере может быть
запущено много процессов wal sender — по одному на каждую
подписку. Значения параметров max_wal_senders и
max_replication_slots должны быть не меньше этого количества.
На сервере подписки необходимо установить параметры
max_logical_replication_workers (для процессов, принимающих
изменения по подписке) и в целом max_worker_processes (как минимум
на единицу больше, учитывая logical replication launcher, но этот пул
используется и для других нужд).
5
Публикация
Объект базы данных
выдает изменения данных построчно в порядке фиксации транзакций
только базовые и секционированные таблицы
wal_level = logical
Публикуются операции
insert, update, delete, truncate
Права
создать публикацию — привилегия CREATE на базу данных
добавить таблицу — владелец или суперпользователь
добавить все таблицы схемы или базы данных — суперпользователь
На одном сервере создается публикация, выделяющая изменения
в одной или нескольких таблицах (базовых или секционированных)
одной базы данных. Чтобы создать публикацию, нужна привилегия
CREATE на базу данных. Для репликации из нескольких баз данных
потребуется создать несколько публикаций.
Включить в публикацию одну или несколько таблиц может их владелец
или суперпользователь. Все таблицы схемы или базы данных может
опубликовать только суперпользователь.
Для работы логической репликации в журнале публикующего сервера
необходима дополнительная информация (параметр wal_level = logical).
Публикация включает в себя изменения строк, происходящие в
таблицах в результате выполнения команд DML. Команда MERGE,
выполненная на публикующем сервере, применяется как INSERT,
UPDATE или DELETE.
По умолчанию изменения передаются не сразу, а только при фиксации
транзакции.
6
Подписка
Объект базы данных
получает сообщения от одной или нескольких публикаций
возможна подписка в двоичном формате
применяет изменения построчно
таблицы и столбцы сопоставляются по полным именам
Права
создание подписки — роль pg_create_subscription и суперпользователи
Определив в базе данных подписку на публикации, можно получать
и применять изменения. По сути, подписка — это подключение
к публикующему серверу, созданное для репликации данных.
Создавать подписки могут члены роли pg_create_subscription и
суперпользователи.
Применение изменений всегда происходит построчно. Хотя каждое
изменение не требует разбора и планирования запроса, массовые
изменения из-за этого могут выполняться медленно.
Таблицы идентифицируются по полным именам (включая схему),
столбцы также идентифицируются по именам. Это позволяет подписке
использовать отличающуюся схему данных (например, иметь в таблице
дополнительные столбцы).
При создании подписки можно указать параметр binary,
устанавливающий передачу данных в двоичном формате. Двоичный
формат может ускорить обработку если определены функции двоичного
получения и отправки для требуемых типов данных. Однако при
использовании двоичного формата важна совместимость машинного
представления данных на разных платформах.
7
S2
S1
P2
P1
P2
P1
Публикации и подписки
публикующий
сервер
подписчик
wal sender
logical repl.
apply worker
S1
P2
P1
подписчик
wal sender
logical repl.
apply worker
wal sender
logical repl.
apply worker
pg_stat_replication
pg_replication_slots
pg_stat_subscription
pg_stat_subscription_stats
На публикующем сервере можно распределять объекты по
публикациям любым образом. Можно включить все таблицы в одну
публикацию, можно разделить их между несколькими публикациями —
это не влияет на производительность.
Однако каждая подписка создает отдельное подключение
к публикующему серверу, нагружая его лишней работой. На
публикующем сервере в таком случае будет запущено несколько
процессов wal sender, каждый из которых будет самостоятельно читать
WAL и выполнять логическое декодирование (рассматривается ниже)
в своей локальной памяти. Поэтому если необходимо подписаться на
несколько публикаций, это лучше сделать в одной общей подписке.
Напомним, что публикации и подписки — объекты базы данных,
поэтому для каждой базы потребуется отдельное соединение.
Мониторинг логической репликации на публикующем сервере
идентичен мониторингу физической репликации. Здесь важнейшие
инструменты — представления pg_stat_replication и pg_replication_slots.
Информация о состоянии подписок находится в pg_subscription.
Данные о применении изменений в представлении pg_stat_subscription.
Данные об ошибках в процессе репликации в pg_stat_subscription_stats.
9
Ограничения
Не реплицируются
последовательности
материализованные представления
временные и нежурналируемые таблицы
внешние таблицы
большие объекты
DDL
Требование к TRUNCATE
таблицы, связанные внешними ключами, должны быть в одной
подписке
Реплицируются только изменения содержимого базовых и
секционированных таблиц, вызванные командами DML.
Не реплицируются содержимое остальных объектов, объединяемых
термином «отношение»: последовательностей, материализованных
представлений, временных и нежурналируемых таблиц, внешних
таблиц. Не реплицируются большие объекты.
Команды DDL также не реплицируются, поэтому нужно предварительно
создать таблицы на стороне подписки.
Команда TRUNCATE реплицируется успешно только в случае, если все
таблицы подписчика, внешние ключи которых ссылаются на
опустошаемые таблицы, включены в подписку.
10
Один процесс синхронизации для каждой таблицы
но не более max_sync_workers_per_subscription = 2
Начальная синхронизация
публикующий
сервер
select, insert
update, delete
подписчик
wal sender
сегменты WAL
select, insert
update, delete
сегменты WAL
T1 sync
worker
T2 sync
worker
logical repl.
apply worker
logical
replication
launcher
pg_subscription_rel
По умолчанию (если не задано значение параметра copy_data = false)
при создании подписки выполняется начальная синхронизация
содержимого таблиц. Она происходит бесшовно благодаря
использованию механизма экспорта снимка данных.
Начальную синхронизацию желательно завершить как можно скорее,
чтобы не удерживать открытым снимком горизонт очистки дольше
необходимого. Поэтому синхронизация распараллеливается: процесс
apply worker просматривает список реплицируемых таблиц и запускает
дополнительные рабочие процессы, которые и выполняют
синхронизацию. Синхронизация одной таблицы всегда выполняется
одним процессом.
Максимальное количество параллельных процессов синхронизации
ограничено параметром max_sync_workers_per_subscription
(по умолчанию — два процесса).
Состояние репликации для каждой таблицы в подписке показывает
представление pg_subscription_rel.
Если для подписки указать параметр binary, то в двоичном формате
будет выполняться не только репликация, но и начальная
синхронизация.
12
Идентификация строк
Для операций изменения и удаления необходимо выбрать
подходящую строку
Логический идентификатор строк
ALTER TABLE ... REPLICA IDENTITY ...
DEFAULT столбцы первичного ключа (по умолчанию)
USING INDEX столбцы уникального индекса с ограничением NOT NULL
FULL все столбцы
NOTHING без идентификации
(по умолчанию для системного каталога)
Вставка новых строк на стороне подписки происходит однозначно.
Сложнее при изменениях и удалениях — в этих случаях надо
определить, к какой строке на подписчике применять операцию.
Поэтому для каждой таблицы определяется логический
идентификатор строк. По умолчанию строки идентифицируются
столбцами первичного ключа, но идентификатор можно изменить
командой ALTER TABLE … REPLICA IDENTITY.
Строки можно идентифицировать по другому уникальному индексу,
в котором все столбцы имеют ограничение NOT NULL.
Еще один вариант — идентификация по всем столбцам. Начиная
с версии 16, для поиска строк в этом случае может использоваться
полный индекс на основе B-дерева, первое поле которого должно быть
столбцом из публикации. Версии 17+ могут использовать и хеш-
индексы. Если подходящего индекса нет, выполняется полное
сканирование, что крайне неэффективно для больших таблиц.
Можно вообще отказаться от поддержки репликации для некоторых
таблиц (по умолчанию так настроены таблицы системного каталога).
При изменении или удалении в WAL уровня replica не попадают
значения столбцов удаляемой версии строки — это одна из причин, по
которой на публикующем сервере необходим уровень журнала logical.
На этом уровне в журнал дополнительно записываются старые
значения столбцов, входящих в логический идентификатор. По этой
причине при идентификации по всем столбцам объем журнала может
существенно увеличиться по сравнению с уровнем replica.
13
Фильтрация строк
Фильтр в публикации
Возможна фильтрация
insert по любым публикуемым столбцам
delete по столбцам логического идентификатора
update по столбцам логического идентификатора;
в результате может получиться insert или delete
truncate фильтр игнорируется
Только простые выражения
По умолчанию публикующий сервер отправляет все строки
опубликованных таблиц.
В PostgreSQL 15 появилась возможность указать выражение фильтра;
строки, для которых выражение имеет значение false или NULL,
опубликованы не будут.
Фильтрация не распространяется на операцию truncate. Для операций
insert в выражение фильтра можно включать любые публикуемые
столбцы, а для update и delete — только столбцы, входящие в
логический идентификатор строк.
Для операции update выражение фильтра вычисляется и для старой,
и для новой версий строки. Если для обеих версий выражение фильтра
дает false или NULL, строка не реплицируется. Если старая строка не
соответствует фильтру, а новая — соответствует, то выполняется
вставка новой строки. Если, наоборот, старая версия соответствует,
а новая — нет, строка удаляется. Если обе соответствуют условию
фильтра, выполняется обновление.
Фильтровать можно лишь с помощью простых выражений: запрещены
пользовательские функции, операторы, типы и правила сортировки;
нельзя ссылаться на системные столбцы; нельзя обращаться
ко встроенным функциям с категориями изменчивости volatile и stable.
15
Логическое декодирование
логические
операции
записи WAL
переупорядочивающий
буфер
логическое
декодирование
модуль
вывода
logical_decoding_work_mem = 64MB
сообщения
протокола
репликации
слот логической репликации
зафиксированные
транзакции
wal sender
При потоковой физической репликации процесс wal sender читает
записи WAL и передает их реплике в неизменном виде.
При логической репликации записи преобразуются. Модуль
логического декодирования отбирает из них те, которые соответствуют
публикуемым операциям и таблицам, учитывая столбцы и условия
фильтров. Отобранные записи преобразуется в информацию
о логических операциях, направляются в переупорядочивающий буфер
и сохраняются в нем в соответствии с номерами транзакций.
В журнал на уровне logical дополнительно записывается информация,
необходимая для логического декодирования, в частности:
●
для update — новые значения всех столбцов, а не только
измененных;
●
для update и delete — старые значения столбцов, входящих
в логический идентификатор;
●
для commit — OID базы данных.
Переупорядочивающий буфер находится в локальной памяти процесса
wal sender. Если объем данных в нем приблизится к значению
параметра logical_decoding_work_mem, самая большая транзакция
сбрасывается в слот репликации (файл в PGDATA/pg_replslots/имя).
При фиксации транзакции все ее операции направляются в модуль
вывода, который преобразует их в сообщения протокола репликации.
17
Большие транзакции
сегменты WAL
select, insert
update, delete
apply worker
apply worker
apply worker
apply parallel
worker
apply parallel
worker
streaming =
parallel
streaming = on
streaming = off
wal sender
wal sender
врем.
файл
врем.
файл
врем.
файл
wal sender
max_parallel_apply_workers_per_subscription = 2
По умолчанию (параметр подписки streaming = off) изменения
накапливаются в переупорядочивающем буфере и отправляются
подписчику после фиксации транзакции.
Если транзакция изменяет много данных, могут возникать проблемы:
●
расход памяти wal sender на переупорядочивающий буфер;
●
запись файлов при превышении памяти, выделенной под
переупорядочивающий буфер (logical_decoding_work_mem);
●
отставание реплики.
Если задать streaming = on, публикующий сервер будет передавать
изменяемые данные подписчику во время работы транзакции.
Когда общий объем изменений превышает значение параметра
logical_decoding_work_mem, накопленные изменения самой большой
по объему транзакции не сбрасываются в файл, а сразу передаются
в модуль вывода. При этом подписчик сохраняет такие изменения
в своих временных файлах и применяет при фиксации транзакции.
Начиная с PostgreSQL 16, если задано streaming = parallel и есть
доступные рабочие процессы, изменения на подписчике не
сохраняются, а сразу же применяются дополнительными процессами.
Число таких процессов для одной подписки ограничено параметром
max_parallel_apply_workers_per_subscription.
18
Конфликты
Приводят к ошибке
нарушение ограничений целостности,
недостаточные права на целевую таблицу или политика защиты строк
репликация выдает ошибку,
подписка с параметром disable_on_error отключается
можно вручную исправить данные на стороне подписки
или пропустить конфликтующую транзакцию
Игнорируются
обновляемая или удаляемая строка отсутствует на подписчике
репликация продолжается, но согласованность может быть нарушена
Поскольку таблицы на публикующем сервере и на подписчике могут
изменяться независимо друг от друга, при выполнении операции,
поступившей от подписки, возможен конфликт с существующими
данными.
Часто конфликтом называют ситуацию, когда операция не может быть
выполнена, обычно из-за нарушения ограничения целостности, или
отсутствия прав у владельца подписки на целевую таблицу, или
ограничений на уровне строк (RLS, row level security).
В этом случае применение операции завершается ошибкой. Подписчик
периодически повторяет попытки, но до разрешения конфликта они
будут неудачными. Конфликты можно разрешать только вручную,
автоматическое разрешение пока не реализовано. Есть два варианта:
исправить данные на подписчике или пропустить конфликтующую
транзакцию. Во втором случае нужно отключить репликацию, вызвать
функцию pg_replication_origin_advance или выполнить команду ALTER
SUBSCRIPTION ... SKIP ..., и снова включить репликацию. Можно также
заранее настроить автоматическое отключение репликации, указав
параметр подписки disable_on_error.
Если строка, которую необходимо изменить или удалить, отсутствует на
подписчике, операция не будет выполнена. В этом случае ошибка не
возникает, но такие игнорируемые конфликты говорят о том, что
согласованность данных между публикующим сервером и подписчиком
нарушена.
20
Параметр подписки origin
any — передавать все изменения (по умолчанию)
none — передавать только локальные изменения
Источник репликации
select, insert
update, delete
сегменты WAL
select, insert
update, delete
сегменты WAL
wal sender
logical repl.
worker
logical repl.
worker
wal sender
O1
O1 O2
источник
транзакции
O1 O2
pg_replication_origin
pg_replication_origin_status
Если определить на одном сервере и публикацию, и подписку, и
передавать все изменения, то реплицированные изменения будут
реплицироваться и возникнет «снежный ком».
Начиная с PostgreSQL 16, публикующий сервер в начале транзакции,
которая была получена с другого узла, передает по протоколу
сообщение об источнике этой транзакции. Это также важно при
каскадной репликации.
По умолчанию подписка имеет параметр origin = any и публикация
передает ей все транзакции: и локальные, и полученные с другого узла.
В топологиях, где могут появиться циклы, например, при
двунаправленной репликации, следует устанавливать origin = none.
Тогда публикация будет передавать только локальные изменения.
Информацию об источниках можно увидеть в pg_replication_origin.
Текущая позиция воспроизведения для конкретного источника
репликации выводится в представлении pg_replication_origin_status.
22
Итоги
Логическая репликация использует модель
«публикация—подписка»
Передаются изменения табличных строк
Строки сопоставляются по логическим идентификаторам
Несовпадение данных на серверах может приводить
к конфликтам
Передача информации об источнике позволяет отслеживать
ход каскадной репликации и исключить зацикливание
23
Практика
1. Создайте две базы данных на одном сервере.
В первой базе данных создайте таблицу с первичным
ключом и добавьте в нее несколько строк. Настройте
логическую репликацию таблицы из первой базы во вторую,
проверьте работу репликации и удалите подписку.
2. Настройте логическую репликацию таблицы с сервера alpha
на сервер beta. Выполните транзакцию, изменяющую
значительный объем данных; перед ее фиксацией убедитесь,
что данные помещены на диск. Повторите то же самое, но
в потоковом режиме. Сохранялись ли данные на диске?
1. Воспользуйтесь утилитой pg_dump с ключом --schema-only.
Если попробовать выполнить это обычным образом, команда создания
подписки «повиснет» из-за того, что она должна дождаться завершения
активных транзакций на публикующем сервере, то есть и самой себя
в том числе. В таком случае необходимо заранее создать слот
логической репликации, как описано в документации:
2. Чтобы публикация не попала в логическую копию, используйте ключ
--no-publications.
Проверьте значение параметра logical_decoding_work_mem,
определяющего максимальный размер переупорядочивающего буфера.
При превышении этого значения данные будут вытесняться на диск.
24
Практика+
1. Создайте и опубликуйте на сервере alpha таблицу
со столбцом типа bytea. Вставьте в таблицу несколько
миллионов строк. Сравните время начальной синхронизации
для подписки в обычном и двоичном режимах, используя
для этого сервер beta.
2. Включите для подписки параллельную синхронизацию.
Добавляя в таблицу строки, изучите процессы,
обслуживающие подписку.
1. Оценить время начальной синхронизации можно с помощью
мониторинга таблицы pg_subscription_rel. Его можно организовать
на стороне клиента с помощью метакоманды \watch или на стороне
сервера с помощью функции, написанной, например, на PL/pgSQL.
2. Задайте подходящее значение параметра подписки streaming.
Для мониторинга процессов подписки используйте представление
pg_stat_subscription.