30.2. Подписка#

30.2. Подписка

30.2. Подписка

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

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

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

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

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

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

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

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

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

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

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

30.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 и могут в конечном итоге заполнить диск. Такие случаи следует тщательно исследовать.

30.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 имеет фильтр строк (см. Раздел 30.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)