29.2. Подписка #
Подписка — это принимающая сторона логической репликации. Узел, на котором определяется подписка, называется подписчиком. В свойствах подписки определяется подключение к другой базе данных и набор публикаций (из одной или нескольких), на которые подписчик хочет подписаться.
База данных подписчика работает так же, как и экземпляр любой другой базы Postgres Pro, и может быть публикующей для других баз, если в ней определены собственные подписки.
Узел подписчика может подписываться на несколько подписок, если требуется. В одной паре публикующий сервер/подписчик могут быть определены несколько подписок, но при этом нужно позаботиться о том, чтобы публикуемые объекты в разных подписках не перекрывались.
Изменения в каждой подписке будут приходить через один слот репликации (см. Подраздел 25.2.6). Дополнительные слоты репликации могут потребоваться для начальной синхронизации данных, уже существующих в таблицах, но будут удалены в конце синхронизации.
Подписка логической репликации может представлять собой ведомый узел для синхронной репликации (см. Подраздел 25.2.8). В этом случае именем ведомого узла по умолчанию будет имя подписки. Другое имя можно задать в свойстве application_name
в строке подключения для данной подписки.
Подписки могут быть выгружены командой pg_dump
, если её выполняет суперпользователь. В противном случае выдаётся предупреждение и подписки пропускаются, так как обычным пользователям не разрешено читать всю информацию о подписках из каталога pg_subscription
.
Подписки добавляются командой CREATE SUBSCRIPTION
и могут быть остановлены/возобновлены в любой момент командой ALTER SUBSCRIPTION
, а также удалены командой DROP SUBSCRIPTION
.
Когда подписка удаляется и пересоздаётся, информация о синхронизации теряется. Это означает, что после этого данные необходимо синхронизировать заново.
Определения схемы не реплицируются, а публикуемые таблицы должны существовать в базе подписчика. Объектами репликации могут быть только обычные таблицы. Так, например, нельзя произвести репликацию в представление.
Таблицы публикации сопоставляются с таблицами подписчика по полностью заданным именам таблиц. Репликация в таблицы с другими именами на стороне подписчика не поддерживается.
Столбцы таблиц также сопоставляются по именам. Порядок столбцов в таблице подписчика может отличаться от порядка столбцов в публикации. Также могут не совпадать типы столбцов; достаточно только возможности преобразования текстового представления данных в целевой тип. Например, данные столбца типа integer
могут реплицироваться в столбец типа bigint
. Целевая таблица может также содержать дополнительные столбцы, отсутствующие в публикуемой таблице. Такие столбцы будут заполнены значениями по умолчанию, заданными в определении целевой таблицы. Однако логическая репликация в двоичном формате имеет более строгие ограничения. За подробностями обратитесь к описанию параметра binary
команды CREATE SUBSCRIPTION
.
29.2.1. Управление слотами репликации #
Как упоминалось ранее, каждая (активная) подписка получает изменения из слота репликации на удалённой (публикующей) стороне.
Дополнительные слоты синхронизации таблиц обычно существуют временно, создаются только для выполнения начальной синхронизации таблиц и автоматически удаляются, когда становятся не нужны. Эти слоты синхронизации таблиц называются так: «pg_%u_sync_%u_%llu
» (параметры: oid
подписки, relid
таблицы, системный идентификатор sysid
).
Обычно удалённый слот репликации создаётся автоматически, когда подписка создаётся командой CREATE SUBSCRIPTION
, и удаляется автоматически, когда подписка удаляется командой DROP SUBSCRIPTION
. Однако в некоторых ситуациях может быть полезно или даже необходимо манипулировать подпиской и нижележащим слотом по отдельности. Например, возможны такие сценарии:
При создании подписки слот репликации может уже существовать. В этом случае подписку можно создать с параметром
create_slot = false
, чтобы она была связана с существующим слотом.При создании подписки удалённый узел может быть недоступен или находиться в нерабочем состоянии. В этом случае подписку можно создать с указанием
connect = false
. При этом подключение к удалённому узлу не будет устанавливаться. Этот вариант использует pg_dump. Чтобы активировать такую подписку впоследствии, удалённый слот репликации нужно будет создать вручную.При ликвидации публикации может потребоваться сохранить слот репликации. Например, это полезно, когда нужно перенести базу данных подписчика на другой узел и активировать её там. В этом случае разорвите связь подписки со слотом, используя команду
ALTER SUBSCRIPTION
, прежде чем удалять подписку.При ликвидации подписки удалённый узел может быть недоступен. В этом случае разорвите связь подписки со слотом, используя команду
ALTER SUBSCRIPTION
, прежде чем пытаться удалять подписку. Если удалённый экземпляр базы данных прекратил существование, больше никакие действия не требуются. Если же экземпляр удалённой базы данных просто оказался недоступным, слот репликации (и все оставшиеся слоты синхронизации таблиц) нужно будет удалить вручную; в противном случае публикующий сервер(ы) продолжит сохранять WAL и может в конце концов заполнить всё место на диске. Такие случаи заслуживают самого серьёзного разбирательства.
29.2.2. Примеры: настройка логической репликации #
Создайте несколько тестовых таблиц на публикующем сервере.
test_pub=# CREATE TABLE t1(a int, b text, PRIMARY KEY(a)); CREATE TABLE test_pub=# CREATE TABLE t2(c int, d text, PRIMARY KEY(c)); CREATE TABLE test_pub=# CREATE TABLE t3(e int, f text, PRIMARY KEY(e)); CREATE TABLE
Создайте такие же таблицы на стороне подписчика.
test_sub=# CREATE TABLE t1(a int, b text, PRIMARY KEY(a)); CREATE TABLE test_sub=# CREATE TABLE t2(c int, d text, PRIMARY KEY(c)); CREATE TABLE test_sub=# CREATE TABLE t3(e int, f text, PRIMARY KEY(e)); CREATE TABLE
Вставьте данные в таблицы на стороне публикующего сервера.
test_pub=# INSERT INTO t1 VALUES (1, 'one'), (2, 'two'), (3, 'three'); INSERT 0 3 test_pub=# INSERT INTO t2 VALUES (1, 'A'), (2, 'B'), (3, 'C'); INSERT 0 3 test_pub=# INSERT INTO t3 VALUES (1, 'i'), (2, 'ii'), (3, 'iii'); INSERT 0 3
Создайте публикации для таблиц. Для публикаций pub2
и pub3a
параметром publish
отключаются некоторые операции, а публикация pub3b
создаётся с фильтром строк (см. Раздел 29.3).
test_pub=# CREATE PUBLICATION pub1 FOR TABLE t1; CREATE PUBLICATION test_pub=# CREATE PUBLICATION pub2 FOR TABLE t2 WITH (publish = 'truncate'); CREATE PUBLICATION test_pub=# CREATE PUBLICATION pub3a FOR TABLE t3 WITH (publish = 'truncate'); CREATE PUBLICATION test_pub=# CREATE PUBLICATION pub3b FOR TABLE t3 WHERE (e > 5); CREATE PUBLICATION
Создайте подписки на публикации. Подписка sub3
оформляется сразу на две публикации, pub3a
и pub3b
. Для всех подписок будут по умолчанию скопированы начальные данные.
test_sub=# CREATE SUBSCRIPTION sub1 test_sub-# CONNECTION 'host=localhost dbname=test_pub application_name=sub1' test_sub-# PUBLICATION pub1; CREATE SUBSCRIPTION test_sub=# CREATE SUBSCRIPTION sub2 test_sub-# CONNECTION 'host=localhost dbname=test_pub application_name=sub2' test_sub-# PUBLICATION pub2; CREATE SUBSCRIPTION test_sub=# CREATE SUBSCRIPTION sub3 test_sub-# CONNECTION 'host=localhost dbname=test_pub application_name=sub3' test_sub-# PUBLICATION pub3a, pub3b; CREATE SUBSCRIPTION
Вы можете убедиться в том, что начальные данные таблицы копируются независимо от того, какие операции исключены параметром publish
.
test_sub=# SELECT * FROM t1; a | b ---+------- 1 | one 2 | two 3 | three (3 rows) test_sub=# SELECT * FROM t2; c | d ---+--- 1 | A 2 | B 3 | C (3 rows)
Кроме того, поскольку при копировании исходных данных игнорируется исключение операций, определяемое в publish
, и у публикации pub3a
нет фильтра строк, скопированная таблица t3
содержит все строки, при том что они не соответствуют фильтру строк публикации pub3b
.
test_sub=# SELECT * FROM t3; e | f ---+----- 1 | i 2 | ii 3 | iii (3 rows)
Вставьте дополнительные данные в таблицы на стороне публикации.
test_pub=# INSERT INTO t1 VALUES (4, 'four'), (5, 'five'), (6, 'six'); INSERT 0 3 test_pub=# INSERT INTO t2 VALUES (4, 'D'), (5, 'E'), (6, 'F'); INSERT 0 3 test_pub=# INSERT INTO t3 VALUES (4, 'iv'), (5, 'v'), (6, 'vi'); INSERT 0 3
Теперь на стороне публикации данные выглядят так:
test_pub=# SELECT * FROM t1; a | b ---+------- 1 | one 2 | two 3 | three 4 | four 5 | five 6 | six (6 rows) test_pub=# SELECT * FROM t2; c | d ---+--- 1 | A 2 | B 3 | C 4 | D 5 | E 6 | F (6 rows) test_pub=# SELECT * FROM t3; e | f ---+----- 1 | i 2 | ii 3 | iii 4 | iv 5 | v 6 | vi (6 rows)
Вы можете заметить, что в процессе обычной репликации учитывается список операций, определённый в publish
. Вследствие этого через публикации pub2
и pub3a
не реплицируются операции INSERT
. Кроме того, для публикации pub3b
реплицируются только данные, соответствующие фильтру строк pub3b
. Теперь данные на стороне подписчика выглядят так:
test_sub=# SELECT * FROM t1; a | b ---+------- 1 | one 2 | two 3 | three 4 | four 5 | five 6 | six (6 rows) test_sub=# SELECT * FROM t2; c | d ---+--- 1 | A 2 | B 3 | C (3 rows) test_sub=# SELECT * FROM t3; e | f ---+----- 1 | i 2 | ii 3 | iii 6 | vi (4 rows)
29.2.3. Примеры: создание слота отложенной репликации #
В некоторых случаях (например, Подраздел 29.2.1), если слот удалённой репликации не был создан автоматически, пользователю необходимо создать его вручную, прежде чем подписку можно будет активировать. Шаги по созданию слота и активации подписки показаны в следующих примерах. В этих примерах указан стандартный модуль вывода логического декодирования (pgoutput
), который используется встроенной логической репликацией.
Сначала создайте публикацию, которая будет использоваться для примеров.
test_pub=# CREATE PUBLICATION pub1 FOR ALL TABLES; CREATE PUBLICATION
Пример 1: В подписке указано connect = false
.
Создайте подписку.
test_sub=# CREATE SUBSCRIPTION sub1 test_sub-# CONNECTION 'host=localhost dbname=test_pub' test_sub-# PUBLICATION pub1 test_sub-# WITH (connect=false); ПРЕДУПРЕЖДЕНИЕ: подписка создана, но не подключена ПОДСКАЗКА: Чтобы начать репликацию, вы должны вручную создать слот репликации, включить подписку, а затем обновить её. CREATE SUBSCRIPTION
На публикующем сервере вручную создайте слот. Поскольку имя не было указано при выполнении
CREATE SUBSCRIPTION
, имя создаваемого слота совпадает с именем подписки, например «sub1».test_pub=# SELECT * FROM pg_create_logical_replication_slot('sub1', 'pgoutput'); slot_name | lsn -----------+----------- sub1 | 0/19404D0 (1 row)
На подписчике выполните активацию подписки. После этого таблицы
pub1
начнут реплицироваться.test_sub=# ALTER SUBSCRIPTION sub1 ENABLE; ALTER SUBSCRIPTION test_sub=# ALTER SUBSCRIPTION sub1 REFRESH PUBLICATION; ALTER SUBSCRIPTION
Пример 2: В подписке указано connect = false
, но также указан параметр slot_name
.
Создайте подписку.
test_sub=# CREATE SUBSCRIPTION sub1 test_sub-# CONNECTION 'host=localhost dbname=test_pub' test_sub-# PUBLICATION pub1 test_sub-# WITH (connect=false, slot_name='myslot'); ПРЕДУПРЕЖДЕНИЕ: подписка создана, но не подключена ПОДСКАЗКА: Чтобы начать репликацию, вы должны вручную создать слот репликации, включить подписку, а затем обновить её. CREATE SUBSCRIPTION
На публикующем сервере вручную создайте слот с тем же именем, которое использовалось при выполнении
CREATE SUBSCRIPTION
, например «myslot».test_pub=# SELECT * FROM pg_create_logical_replication_slot('myslot', 'pgoutput'); slot_name | lsn -----------+----------- myslot | 0/19059A0 (1 row)
На подписчике остальные шаги активации подписки такие же, как указано выше.
test_sub=# ALTER SUBSCRIPTION sub1 ENABLE; ALTER SUBSCRIPTION test_sub=# ALTER SUBSCRIPTION sub1 REFRESH PUBLICATION; ALTER SUBSCRIPTION
Пример 3: В подписке указано slot_name = NONE
Создайте подписку. Если
slot_name = NONE
, также необходимо указатьenabled = false
иcreate_slot = false
.test_sub=# CREATE SUBSCRIPTION sub1 test_sub-# CONNECTION 'host=localhost dbname=test_pub' test_sub-# PUBLICATION pub1 test_sub-# WITH (slot_name=NONE, enabled=false, create_slot=false); CREATE SUBSCRIPTION
На публикующем сервере вручную создайте слот с любым именем, например «myslot».
test_pub=# SELECT * FROM pg_create_logical_replication_slot('myslot', 'pgoutput'); slot_name | lsn -----------+----------- myslot | 0/1905930 (1 row)
На подписчике свяжите подписку с только что созданным именем слота.
test_sub=# ALTER SUBSCRIPTION sub1 SET (slot_name='myslot'); ALTER SUBSCRIPTION
Остальные шаги активации подписки такие же, как указано выше.
test_sub=# ALTER SUBSCRIPTION sub1 ENABLE; ALTER SUBSCRIPTION test_sub=# ALTER SUBSCRIPTION sub1 REFRESH PUBLICATION; ALTER SUBSCRIPTION