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

Логическая репликация — это метод репликации объектов данных и изменений в них, основанный на их репликационных идентификаторах (обычно это первичный ключ). Мы используем термин «логическая» как противопоставление физической репликации, использующей точные адреса блоков и побайтовое копирование. QHB поддерживает одновременно оба механизма; см. главу Высокая доступность, балансировка нагрузки и репликация. Логическая репликация позволяет более детально управлять как репликацией данных, так и вопросами безопасности.

В логической репликации используется модель публикаций и подписок с одним или несколькими подписчиками на одну или несколько публикаций на узле-публикаторе. Подписчики извлекают данные из публикаций, на которые они подписаны, и затем могут повторно публиковать данные для обеспечения каскадной репликации или более сложных конфигураций.

Логическая репликация таблицы обычно начинается с создания снимка данных в публикующей базе данных и копирования его подписчику. После этого изменения на стороне публикатора передаются подписчику тогда, когда они происходят, в реальном времени. Подписчик применяет эти данные в том же порядке, что и публикатор, поэтому для публикаций в рамках одной подписки гарантируется транзакционная согласованность. Этот метод репликации данных иногда называется транзакционной репликацией.

Типичные сценарии использования логической репликации:

  • Передача подписчикам инкрементальных изменений в одной базе данных или подмножестве базы данных, когда они происходят.

  • Срабатывание триггеров для отдельных изменений, когда они поступают к подписчику.

  • Объединение нескольких баз данных в одну (например, в целях анализа).

  • Репликация между разными основными версиями QHB.

  • Репликация между экземплярами QHB на разных платформах.

  • Предоставление доступа к реплицированным данным разным группам пользователей.

  • Разделение подмножества базы данных между несколькими базами данных.

База-подписчик, ведет себя так же, как и любой другой экземпляр QHB, и может использоваться как публикатор для других баз данных, определив собственные публикации. Когда подписчик воспринимается приложением как исключительно читающий, никаких конфликтов с одной подпиской не будет. С другой стороны, конфликты могут возникнуть, если приложение или другие подписчики производят запись в тот же набор таблиц.



Публикация

Публикацию можно определить на любом основном сервере физической репликации. Узел, где определяется публикация, называется публикатором. Публикация — это набор изменений, сгенерированных в таблице или в группе таблиц, который также можно описать как просто набор изменений или набор репликации. Каждая публикация существует только в одной базе данных.

Публикации отличаются от схем и не влияют на доступ к таблице. При необходимости каждую таблицу можно добавить в несколько публикаций. В настоящее время публикации могут содержать только таблицы. Объекты в них нужно добавлять явно, за исключением случаев, когда публикация создана для всех таблиц (с указанием ALL TABLES).

Публикации могут предпочесть ограничить выдаваемые изменения любым сочетанием операций INSERT, UPDATE, DELETE и TRUNCATE подобно тому, как триггеры срабатывают для конкретных типов событий. По умолчанию реплицируются все типы операций.

Чтобы иметь возможность реплицировать операции UPDATE и DELETE, в публикуемой таблице должен быть сконфигурирован «репликационный идентификатор», чтобы на стороне подписчика можно было определить соответствующие строки для изменения или удаления. По умолчанию это первичный ключ, если таковой имеется. Кроме того, в качестве репликационного идентификатора можно установить другой уникальный индекс (с некоторыми дополнительными условиями). Если у таблицы нет подходящего ключа, в качестве репликационного идентификатора можно установить «full», что означает, что ключом станет вся строка. Однако это крайне неэффективно и должно использоваться только как запасной вариант, если другого решения нет. Если на стороне публикатора установлен репликационный идентификатор, отличный от «full», то идентификатор, состоящий из того же или меньшего количества столбцов, должен быть также определен на стороне подписчика. Более подробную информацию о том, как установить репликационный идентификатор, см. в описании параметра REPLICA IDENTITY. Если в публикацию, которая реплицирует операции UPDATE или DELETE, добавляется таблица без репликационного идентификатора, то последующие операции UPDATE и DELETE вызовут ошибку на стороне публикатора. Операции INSERT могут выполняться вне зависимости от репликационного идентификатора.

У каждой публикации может быть множество подписчиков.

Публикация создается с помощью команды CREATE PUBLICATION и впоследствии может быть изменена или удалена с помощью соответствующих команд.

С помощью команды ALTER PUBLICATION в публикацию можно динамически добавлять или удалять отдельные таблицы. Операции ADD TABLE и DROP TABLE являются транзакционными, поэтому репликация таблицы начнется и закончится с корректным снимком только после фиксации транзакции.



Подписка

Подписка — это принимающая сторона логической репликации. Узел, где определяется подписка, называется подписчиком. В подписке определяется подключение к другой базе данных и набор публикаций (из одной или нескольких), на которые подписчик хочет подписаться.

База-подписчик ведет себя так же, как и любой другой экземпляр QHB, и может использоваться как публикатор для других баз данных, определив собственные публикации.

Узел-подписчик при желании может иметь несколько подписок. В одной паре публикатор- подписчик можно определить несколько подписок, но в этом случае следует позаботиться о том, чтобы публикуемые объекты в разных подписках не перекрывались.

Каждая подписка будет получать изменения через один слот репликации (см. подраздел Слоты репликации). Для начальной синхронизации данных, уже существующих в таблицах, могут потребоваться дополнительные слоты репликации, но они будут удалены в конце синхронизации.

Подписка логической репликации может быть резервным сервером для синхронной репликации (см. подраздел Синхронная репликация). При этом по умолчанию именем резервного сервера будет имя подписки. Другое имя можно задать в параметре application_name в строке подключения этой подписки.

Подписки сбрасываются на диск командой qhb_dump, если текущий пользователь является суперпользователем. В противном случае выдается предупреждение и подписки пропускаются, потому что обычные пользователи не могут читать всю информацию о подписках из каталога pg_subscription.

Подписка добавляется с помощью команды CREATE SUBSCRIPTION и в любой момент может быть остановлена/возобновлена с помощью команды ALTER SUBSCRIPTION и удалена с помощью команды DROP SUBSCRIPTION.

Когда подписка удаляется и пересоздается, информация о синхронизации теряется. Это означает, что после этого данные нужно синхронизировать заново.

Определения схем не реплицируются, а публикуемые таблицы должны существовать на подписчике. Целью репликации могут быть только обычные таблицы. Например, нельзя реплицировать в представление.

Таблицы сопоставляются между публикатором и подписчиком по полностью уточненным именам. Репликация в таблицу с другим именем на стороне подписчика не поддерживается.

Столбцы таблиц тоже сопоставляются по именам. Порядок столбцов в таблице подписчика не обязан совпадать с порядком столбцов у публикатора. Типам данных столбцов тоже не обязательно совпадать, если текстовое представление данных можно преобразовать в целевой тип. К примеру, можно реплицировать из столбца типа integer в столбец типа bigint. Кроме того, целевая таблица может содержать дополнительные столбцы, отсутствующие в таблице публикатора. Все эти столбцы будут заполнены значениями по умолчанию, заданными в определении целевой таблицы.


Управление слотами репликации

Как уже упоминалось ранее, каждая (активная) подписка получает изменения из слота репликации на удаленной (публикующей) стороне.

Дополнительные слоты синхронизации таблиц обычно являются временными, создаваемыми внутренне для проведения начальной синхронизации таблиц и автоматически удаляются, когда становятся не нужны. У этих слотов синхронизации таблиц генерируются имена вида «pg_%u_sync_%u_%llu» (параметры: oid подписки, relid таблицы, системный идентификатор sysid).

Обычно удаленный слот репликации создается автоматически, когда подписка создается с помощью команды CREATE SUBSCRIPTION, и автоматически же удаляется при удалении подписки командой DROP SUBSCRIPTION. Однако в некоторых ситуациях может быть полезно или даже необходимо манипулировать подпиской и нижележащим слотом репликации по отдельности. Ниже приведены некоторые возможные сценарии:

  • При создании подписки слот репликации уже существует. В этом случае подписку можно создать с параметром create_slot = false, чтобы связать ее с существующим слотом.

  • При создании подписки удаленный хост недоступен или находится в неопределенном состоянии. В этом случае подписку можно создать с параметром connect = false. Тогда с удаленным хостом вообще не будет устанавливаться соединение. Этот вариант использует qhb_dump. Тогда для активации подписки удаленный слот репликации нужно будет создавать вручную.

  • При удалении подписки нужно сохранить слот репликации. Это может быть полезно, когда база-подписчик переносится на другой хост и будет активироваться оттуда. В этом случае перед удалением подписки следует отвязать от нее слот с помощью команды ALTER SUBSCRIPTION.

  • При удалении подписки удаленный хост недоступен. В этом случае перед удалением подписки следует отвязать от нее слот с помощью команды ALTER SUBSCRIPTION. Если удаленный экземпляр базы данных уже не существует, в дальнейших действиях нет необходимости. Однако если удаленный экземпляр базы данных просто недоступен, слот репликации (и все оставшиеся слоты синхронизации таблиц) нужно будет удалить вручную; в противном случае они продолжат сохранять WAL и в итоге могут вызвать переполнение диска. Такие случаи следует разбирать особенно тщательно.



Конфликты

Логическая репликация ведет себя подобно обычным операциям DML в том аспекте, что данные будут обновлены, даже если они были изменены локально на узле-подписчике. Если входящие данные нарушают какие-либо ограничения, репликация остановится. Это называется конфликтом. При репликации операций UPDATE или DELETE отсутствие данных не спровоцирует конфликт, и такие операции просто будут пропускаться.

Конфликт вызовет ошибку и остановит репликацию; разрешать его пользователю должен вручную. Подробную информацию о конфликте можно найти в журнале сервера-подписчика.

Разрешить конфликт можно либо изменив данные на подписчике, чтобы они не конфликтовали с входящими изменениями, либо пропустив транзакцию, конфликтующую с существующими данными. Транзакцию можно пропустить, вызвав функцию pg_replication_origin_advance()

с указанием имени_узла, соответствующего имени подписки, а также позиции. Текущую позицию источников можно посмотреть в системном представлении pg_replication_origin_status.



Ограничения

В настоящее время логическая репликация имеет следующие ограничения и недостатки функциональности. Они могут быть устранены в будущих выпусках.

  • Схема базы данных и команды DDL не реплицируются. Изначальную схему можно скопировать вручную командой pg_dump --schema-only. Последующие изменения схемы нужно будет синхронизировать вручную. (Однако обратите внимание, что схемам необязательно быть абсолютно идентичными на обеих сторонах.) Когда определения схемы меняются в активной базе данных, логическая репликация устойчива к ошибкам. Когда схема меняется на публикаторе, и реплицированные данные начинают прибывать на сторону подписчика, но не вписываются в схему таблицы, репликация будет выдавать ошибку, пока схема не обновится. Во многих случаях случайных ошибок можно избежать, применяя дополняющие изменения схемы сначала на подписчике.

  • Данные последовательностей не реплицируются. Данные в столбцах serial или столбцах идентификации, выдаваемые последовательностями, разумеется, будут реплицированы как часть таблицы, но сама последовательность на подписчике все равно будет демонстрировать исходное значение. Если подписчик используется как база данных только для чтения, обычно это не представляет проблемы. Однако если предполагается, что на базу-подписчик будут переключаться в работе или при отказе, то последовательности нужно обновить до текущих значений, либо скопировав текущие данные из публикатора (вероятно, с помощью pg_dump), либо выбрав достаточно большое значение из самих таблиц.

  • Репликация команд TRUNCATE поддерживается, но при опустошении групп таблиц, соединенных внешними ключами, следует соблюдать осторожность. При репликации действия опустошения подписчик опустошит ту же группу таблиц, которая был опустошена на публикаторе (явным указанием или неявно собрав их посредством CASCADE), за исключением таблиц, не входящих в подписку. Это сработает корректно, если все затронутые таблицы входят в одну подписку. Но если у некоторых опустошаемых таблиц на подписчике имеют связи по внешнему ключу с таблицами, не входящими в ту же (или любую другую) подписку, то применение действия опустошения на подписчике завершится ошибкой.

  • Большие объекты (см. главу Большие объекты) не реплицируются. Это никак нельзя обойти, кроме как хранить данные в обычных таблицах.

  • Репликация поддерживается только для таблиц, включая партиционированные. Попытки реплицировать другие типы отношений, например представления, материализованные представления или сторонние таблицы, приведут к ошибке.

  • При репликации между партиционированными таблицами по умолчанию фактическая репликация производится из листовых партиций на публикаторе, поэтому партиции с публикатора должны существовать и на подписчике, в виде допустимых целевых таблиц. (Они тоже могут быть листовыми партициями, могут дальше разбиваться на партиции или даже могут быть независимыми таблицами.) В публикациях также может быть указано, что изменения будут реплицироваться с использованием идентификаторов и схемы корневой партиционированной таблицы, а не отдельных листовых партиций, в которых фактически произошли изменения (см. справочную страницу команды CREATE PUBLICATION).



Архитектура

Логическая репликация начинается с копирования снимка данных в базе-публикаторе. После этого изменения на публикаторе передаются подписчику тогда, когда они происходят, в реальном времени. Подписчик применяет данные в том порядке, в котором на публикаторе происходят фиксации, поэтому для публикаций в рамках одной подписки гарантируется транзакционная согласованность.

Логическая репликация собирается с архитектурой, схожей с физической потоковой репликацией (см. подраздел Потоковая репликация). Это реализуется процессами «walsender» (передача WAL) и «apply» (применение). Процесс walsender запускает логическое декодирование (описанное в главе Логическое декодирование) WAL и загружает стандартный плагин логического декодирования (pgoutput). Этот плагин преобразует изменения, считанные из WAL, в протокол логической репликации (см. раздел Протокол логической потоковой репликации) и отфильтровывает данные согласно спецификации публикации. Затем данные последовательно передаются по протоколу потоковой репликации рабочему процессу применения, которые сопоставляет их с локальными таблицами и применяет отдельные изменения по мере их поступления, в правильном транзакционном порядке.

Процесс применения в базе-подписчике всегда выполняется с параметром session_replication_role, установленным в значение replica, что вызывает обычные эффекты для триггеров и ограничений.

В настоящее время процесс применения логической репликации запускает только триггеры уровня строк, но не триггеры уровня операторов. Однако начальная синхронизация таблицы реализуется как команда COPY и поэтому запускает для INSERT как триггеры уровня строк, так и триггеры уровня операторов.


Начальный снимок

Мгновенное состояние исходных данных существующих таблиц в подписке фиксируется в снимке, а затем они копируются в параллельном экземпляре процесса применения особого вида. Этот процесс создаст собственный слот репликации и скопирует существующие данные. Как только копирование завершится, содержимое таблицы станет видимым для других серверных процессов. Когда существующие данные будут скопированы, рабочий процесс перейдет в режим синхронизации, чтобы таблица пришла в синхронизированное состояние с основным процессом применения путем потоковой передачи всех изменений, произошедших во время начального копирования данных, используя стандартную логическую репликацию. Во время этой фазы синхронизации изменения применяются и фиксируются в том же порядке, в котором они произошли на публикаторе. По завершении синхронизации управление репликацией таблицы передается обратно основному процессу применения, в котором репликация продолжатся в обычном режиме.



Мониторинг

Поскольку логическая репликация основана на архитектуре, схожей с физической потоковой репликацией, мониторинг узла публикации похож на мониторинг основного сервера физической репликации (см. подраздел Мониторинг потоковой репликации).

Данные мониторинга о подписке можно увидеть в pg_stat_subscription. Это представление содержит по одной строке для каждого рабочего процесса подписки. В зависимости от состояния подписки, у нее может быть ноль или более активных рабочих процессов.

Обычно для включенной подписки выполняется один процесс применения. Для выключенной или поврежденной подписки в этом представлении будет содержаться ноль строк. Если выполняется начальная синхронизация данных для какой-либо таблицы, будут отображаться дополнительные рабочие процессы для синхронизируемых таблиц.



Безопасность

Пользователь, имеющий возможность модифицировать схему таблиц на стороне подписчика, может выполнить произвольный код как суперпользователь. Назначайте владение и право TRIGGER для таких таблиц только тем ролям, которым доверяют суперпользователи. Более того, если недоверенные пользователи могут создавать таблицы, используйте только те публикации, где явно перечислен список таблиц. Иначе говоря, создавайте подписку FOR ALL TABLES (для всех таблиц), только когда суперпользователи доверяют всем пользователям, которым разрешено создавать не временные таблицы на публикаторе или подписчике.

Роль, используемая для подключения репликации, должна иметь атрибут REPLICATION (или быть суперпользователем). Если у роли нет атрибутов SUPERUSER и BYPASSRLS, на публикаторе могут выполняться политики защиты строк. Если эта роль не доверяет всем владельцам таблиц, добавьте в строку подключения options= -crow_security=off; если впоследствии владелец таблицы добавит политику защиты строк, такое значение параметра остановит репликацию, но не даст политике выполниться. Доступ для этой роли следует сконфигурировать в qhb_hba.conf, и у нее должен быть атрибут LOGIN.

Чтобы иметь возможность скопировать исходные данные таблицы, роль, используемая для подключения репликации, должна иметь право SELECT для публикуемой таблицы (или быть суперпользователем).

Чтобы создать публикацию, пользователь должен иметь право CREATE в базе данных.

Чтобы добавлять в публикацию таблицы, пользователь должен иметь права владельца для этих таблиц. Создавать публикации, автоматически публикующие все таблицы, могут только суперпользователи.

Создавать подписки могут только суперпользователи.

Процесс применения подписки будет выполняться в локальной базе данных с правами суперпользователя.

Права проверяются только один раз при запуске подключения репликации. Они не перепроверяются при чтении каждой записи изменения с публикатора или при применении каждого изменения.



Параметры конфигурации

Для логической репликации нужно установить несколько параметров конфигурации.

На стороне публикатора следует установить для параметра wal_level значение logical, а для параметра max_replication_slots — число не меньше ожидаемого количества подключающихся подписок плюс некоторый резерв для синхронизации таблиц. А для параметра max_wal_senders следует установить число не меньше max_replication_slots плюс количество физических реплик, подключающихся одновременно.

На стороне подписчика тоже необходимо установить параметр max_replication_slots. В нем следует задать как минимум количество подписок, которые будут добавлены подписчику, плюс некоторый резерв для синхронизации таблиц. В параметре max_logical_replication_workers нужно задать как минимум количество подписок и, опять же, плюс некоторый резерв для синхронизации таблиц. Кроме того, может потребоваться скорректировать параметр max_worker_processes, чтобы это число включало рабочие процессы репликации, как минимум max_logical_replication_workers + 1. Обратите внимание, что некоторые расширения и параллельные запросы тоже занимают слоты рабочих процессов из числа max_worker_processes.



Быстрая настройка

Сначала установите параметры конфигурации в qhb.conf:

wal_level = logical

Другие необходимые параметры по умолчанию имеют значения, достаточные для базовой настройки.

Файл qhb_hba.conf нужно скорректировать, чтобы разрешить репликацию (здесь значения будут зависеть от фактической конфигурации вашей сети и пользователя, от имени которого вы желаете подключаться):

host     all     repuser     0.0.0.0/0     md5

Затем в базе-публикаторе выполните:

CREATE PUBLICATION mypub FOR TABLE users, departments;

А в базе-подписчике выполните:

CREATE SUBSCRIPTION mysub CONNECTION 'dbname=foo host=bar user=repuser' PUBLICATION mypub;

Вышеуказанная команда запустит процесс репликации, который сначала синхронизирует исходное содержимое таблиц users и departments, а затем начнет реплицировать в эти таблицы инкрементальные изменения.