29.2. Подписка#
29.2. Подписка #
Подписка - это сторона нижнего потока логической репликации. Узел, где определена подписка, называется абонентом. Подписка определяет соединение с другой базой данных и набор изданий (одно или несколько), на которые она хочет подписаться.
База данных абонента ведет себя так же, как и любой другой экземпляр PostgreSQL, и может использоваться в качестве издателя для других баз данных путем определения собственных публикаций.
У абонентского узла может быть несколько подписок, если это требуется. Возможно определение нескольких подписок между одной парой издатель-подписчик, в таком случае необходимо обеспечить, чтобы подписанные объекты публикации не перекрывались.
Каждая подписка будет получать изменения через один слот репликации (см. Раздел 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); WARNING: subscription was created, but is not connected HINT: To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription. 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'); WARNING: subscription was created, but is not connected HINT: To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription. 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