29.2. Подписка #

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

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

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

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

Подписка логической репликации может представлять собой ведомый узел для синхронной репликации (см. Подраздел 26.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.4).

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