F.41. Прозрачная логическая репликация DDL (pgl_ddl_deploy)#

F.41. Прозрачная логическая репликация DDL (pgl_ddl_deploy)

F.41. Прозрачная логическая репликация DDL (pgl_ddl_deploy) #

F.41.1. О pgl_ddl_deploy #

Прозрачная репликация DDL для Postgres 9.5+ как для pglogical, так и для нативной логической репликации. Расширение pg_background должно быть установлено для работы pgl_ddl_deploy.

Версия: 2.2.1

GitHub

Авторское право 2017 Enova International, Inc.

F.41.2. Обзор #

С момента первоначального выпуска этого расширения, версия 2.0 вводит основное изменение в виде поддержки нативной логической репликации. Прочитайте оригинальное резюме выпуска здесь: https://innovation.enova.com/pursuing-postgres-ddl-replication/

F.41.2.1. Описание на высоком уровне #

С любой текущей технологией логической репликации для Postgres у нас обычно есть отличные способы репликации событий DML (INSERT, UPDATE, DELETE), но мы остаемся наедине с задачей распространения изменений DDL. То есть, когда мы создаем новые таблицы, изменяем таблицы и тому подобное, мы должны управлять этим отдельно в процессе развертывания нашего приложения, чтобы внести те же изменения на логические реплики и добавить такие таблицы в репликацию.

Начиная с Postgres 13, нет встроенного способа выполнять «прозрачную репликацию DDL» на другие кластеры Postgres вместе с любой логической технологией репликации, построенной на стандартном Postgres.

Этот проект является попыткой сделать именно это. Фреймворк построен на следующих концепциях: - Триггеры событий всегда срабатывают на DDL событиях, и таким образом дают нам немедленный доступ к тому, что нам нужно - Триггеры событий дают нам доступ (с версии 9.5+) к объектам, которые изменяются - Мы можем видеть, какой SQL выполняет клиент в рамках триггера события - Мы можем проверять и выбирать, распространять ли это SQL-выражение на подписчиков - Мы можем добавлять новые таблицы в репликацию на этапе создания, до выполнения любого DML

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

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

F.41.2.2. Особенности #

  • Любое DDL SQL-выражение может быть напрямую передано подписчикам, без необходимости для ваших разработчиков пересматривать процесс миграции или знать тонкости репликации.

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

  • Фильтрация по схеме (регулярное выражение) поддерживается. Это позволяет избирательно реплицировать только определенные схемы в рамках публикации/набора репликации.

  • Фильтрация по конкретному набору таблиц поддерживается, что наиболее полезно для репликации небольшого набора таблиц и поддержания таких вещей, как добавление/удаление столбцов

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

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

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

  • ALTER TABLE операторы могут быть отфильтрованы по тегам подкоманд. Например, если вы используете селективную репликацию и хотите игнорировать такие вещи, как DISABLE TRIGGER, которые могут не существовать на подписчике, это полезно для повышения надежности репликации DDL.

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

F.41.2.3. Полный пример #

Поскольку мы всегда ищем документацию по примерам, мы показываем это в первую очередь. Предполагая, что логическая репликация уже настроена с активной подпиской, и учитывая эти публикации/наборы репликации: - default - реплицировать каждое событие - insert_update - реплицировать только вставки и обновления

Поставщик:

CREATE EXTENSION pgl_ddl_deploy;
CREATE EXTENSION pg_background;

--Setup permissions
SELECT pgl_ddl_deploy.add_role(oid) FROM pg_roles WHERE rolname in('app_owner', 'replication_role'); 

--Setup configs
INSERT INTO pgl_ddl_deploy.set_configs
(set_name,
include_schema_regex,
lock_safe_deployment,
allow_multi_statements)
VALUES ('default',
  '.*',
  true,
  true),
  ('insert_update',
  '.*happy.*',
  true,
  true);

Подписчики (запускается как на default, так и на insert_update подписчиках):

CREATE EXTENSION pgl_ddl_deploy;

--Setup permissions for the same role on subscriber to have DDL permissions 
CREATE ROLE app_owner WITH NOLOGIN;
SELECT pgl_ddl_deploy.add_role(oid) FROM pg_roles WHERE rolname in('app_owner', 'replication_role');

--Be sure that on the subscriber, app_owner role has the following perms:
GRANT CREATE ON DATABASE :DBNAME TO app_owner;

--If schemas already exist on subscriber that you want the app_owner
--role to be able to modify with any DDL, then you must do this: 
ALTER TABLE foo OWNER TO app_owner; --...etc

Вот способ исправить владельца таблицы подписчика на основе таблиц, уже находящихся в репликации на поставщике для той же публикации/набора репликации и конфигурации ddl, которую вы только что настроили на поставщике (пример команды оболочки - pglogical):

PGSERVICE=provider_cluster psql provider_db << EOM | PGSERVICE=subscriber_cluster psql subscriber_db
COPY
(
SELECT 'ALTER TABLE '||quote_ident(n.nspname)||'.'||quote_ident(c.relname)||' OWNER TO app_owner;'
FROM pglogical.replication_set rs
INNER JOIN pgl_ddl_deploy.set_configs sc
  ON sc.set_name = rs.set_name
INNER JOIN pgl_ddl_deploy.rep_set_table_wrapper() rsr
  ON rsr.set_id = rs.set_id
INNER JOIN pg_class c ON c.oid = rsr.set_reloid
INNER JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE rs.set_name = 'insert_update'
  AND n.nspname ~* sc.include_schema_regex
  AND n.nspname !~* pgl_ddl_deploy.exclude_regex()
)
TO STDOUT;
EOM

Поставщик:

--Deploy DDL replication
SELECT pgl_ddl_deploy.deploy(set_name)
FROM pgl_ddl_deploy.set_configs;

Подписчик - требуется только при использовании native для добавления таблицы pgl_ddl_deploy.queue в репликацию.

ALTER SUBSCRIPTION default REFRESH PUBLICATION WITH (COPY_DATA = false);

Поставщик:

--App deployment role
SET ROLE app_owner;

--Let's make some data!
CREATE TABLE foo(id serial primary key);
ALTER TABLE foo ADD COLUMN bla INT;
INSERT INTO foo (bla) VALUES (1),(2),(3);

CREATE SCHEMA happy;
CREATE TABLE happy.foo(id serial primary key);
ALTER TABLE happy.foo ADD COLUMN bla INT;
INSERT INTO happy.foo (bla) VALUES (1),(2),(3);
DELETE FROM happy.foo WHERE bla = 3;

Подписчик на default:

SELECT * FROM foo;
 id | bla
----+-----
  1 | 1
  2 | 2
  3 | 3
(3 rows)

SELECT * FROM happy.foo;
 id | bla
----+-----
  1 | 1
  2 | 2
(3 rows)

Обратите внимание, что обе таблицы реплицируются на основе конфигурации, как и все события (вставки, обновления и удаления).

Подписчик на insert_update:

SELECT * FROM foo;
ERROR:  relation "foo" does not exist
LINE 1: SELECT * FROM foo;

SELECT * FROM happy.foo;
 id | bla
----+-----
  1 | 1
  2 | 2
  3 | 3
(3 rows)

Обратите внимание, что таблица foo (в схеме public) не была реплицирована. Также, поскольку мы не реплицируем удаления здесь, happy.foo все еще содержит все данные.

F.41.2.4. Установка #

Функциональность этого требует версии postgres 9.5+ и рабочей установки pglogical.

DEB доступен в официальном репозитории PGDG как postgresql-${PGSQL_VERSION}-pgl-ddl-deploy см. инструкцию по установке на https://wiki.postgresql.org/wiki/Apt

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

Это расширение требует установки pglogical перед тем, как вы сможете создать расширение в любой базе данных. Затем расширение может быть развернуто как любое расширение postgres:

CREATE EXTENSION pgl_ddl_deploy;

Это расширение должно быть установлено на поставщике и всех подписчиках. Начиная с версии 1.5.0, у вас должна быть одинаковая версия pgl_ddl_deploy как на поставщике, так и на подписчике (НЕ обязательно одинаковая версия Postgres).

Чтобы обновить pgl_ddl_deploy до версии 1.5 с предыдущей версии, установите последние версии пакетов на вашем сервере(ах), затем выполните в базе(ах) данных:

ALTER EXTENSION pgl_ddl_deploy UPDATE;

F.41.3. Настройка и развертывание #

F.41.3.1. Конфигурация #

Для нативной логической репликации репликация DDL настраивается на основе каждой публикации. Для pglogical репликация DDL настраивается на основе каждого набора репликации.

Существует три основных типа конфигурации: - include_only_repset_tables - Поддерживаются только таблицы, уже находящиеся в публикации/наборе репликации. Это означает, что реплицируются только операторы ALTER TABLE или COMMENT. - include_schema_regex - Предоставьте регулярное выражение для соответствия как текущим, так и будущим схемам, которые будут автоматически добавлены в репликацию. Это поддерживает все типы событий, за исключением таких, как GRANT, которые не предоставляют доступ к информации о том, в какой схеме существует объект. - include_everything - Распространяйте все события DDL независимо от схемы. Это для случаев, таких как GRANT, которые не предоставляют доступ к информации о том, в какой схеме существует объект

Вышеуказанные 3 опции являются взаимоисключающими. Однако вы можете использовать дополнительную опцию ddl_only_replication вместе с include_schema_regex или include_everything. Это означает, что таблицы не добавляются в репликацию автоматически. Это используется, если вы хотите поддерживать синхронизацию схемы двух систем, но не обязательно реплицировать данные для всех таблиц.

Добавьте строки в pgl_ddl_deploy.set_configs, чтобы настроить (но еще не развернуть) репликацию DDL для конкретной публикации/набора репликации. Обратите особое внимание на то, что driver управляет, для какой технологии репликации это предназначено, native или pglogical. Например:

--Only some options are shown.  See below for all options

--This type of configuration will DDL replicate all user schemas, and auto-add
--new matching schemas/tables to replication:
INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, driver)
VALUES ('default', '.*', 'native'::pgl_ddl_deploy.driver);

--This type of configuration will maintain only the specific set of tables
--in the given replication set for any `ALTER TABLE` statements:
INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_only_repset_tables, driver)
VALUES ('my_special_tables', TRUE, 'native'::pgl_ddl_deploy.driver);

Соответствующие настройки: - driver: native или pglogical (тип pgl_ddl_deploy.driver) - ПО УМОЛЧАНИЮ pglogical - set_name: имя публикации ИЛИ имя набора репликации pglogical - include_schema_regex: регулярное выражение для включения схем в репликацию DDL. Это может быть использовано для автоматического добавления новых таблиц в репликацию. Эта опция несовместима с include_only_repset_tables. - lock_safe_deployment: если true, DDL будет выполняться в цикле с низким lock_timeout на подписчике - allow_multi_statements: если true, несколько SQL-запросов, отправленных клиентом, могут быть распространены при определенных условиях. См. ниже для получения более подробной информации о предостережениях и крайних случаях. Если false, только один SQL-запрос (технически говоря - SQL-запрос с одним узлом parsetree) будет подходить для распространения. - include_only_repset_tables: если true, только таблицы, находящиеся в репликации, будут поддерживаться репликацией DDL. Таким образом, здесь разрешены только операторы ALTER TABLE. Эта опция несовместима с include_schema_regex. - queue_subscriber_failures: если true, DDL будет разрешено завершаться с ошибкой на подписчике без прерывания репликации, и будет поставлено в очередь для повторной попытки с использованием функции pgl_ddl_deploy.retry_all_subscriber_logs(). Это полезно, например, если вы реплицируете VIEW DDL, но не хотите, чтобы ошибки блокировали репликацию данных. Это НЕ рекомендуется использовать с любой репликацией TABLE, так как эти события могут нарушить репликацию данных. - create_tags: набор тегов команд, для которых будут срабатывать триггеры события создания. Изменяйте с осторожностью. Они по умолчанию установлены в соответствующий набор по умолчанию для либо include_schema_regex, либо include_only_repset_tables. - drop_tags: набор тегов команд, для которых будут срабатывать триггеры события удаления. Изменяйте с осторожностью. Эти по умолчанию установлены в соответствующий набор по умолчанию для либо include_schema_regex, либо include_only_repset_tables. - blacklisted_tags: это теги команд, которые никогда не разрешено распространять на подписчиков. Это настраиваемо, но по умолчанию это pgl_ddl_deploy.blacklisted_tags() - exclude_alter_table_subcommands: если вы хотите исключить определенные теги подкоманд ALTER TABLE, здесь это можно сделать. Стандартный список можно найти как функцию pgl_ddl_deploy.common_exclude_alter_table_subcommands(). Вы также можете просто выбрать только определенные теги из этого списка для исключения. - ddl_only_replication: для использования с include_schema_regex только. Позволяет вам только реплицировать схему без автоматического добавления таблиц в репликацию. Это полезно, в частности, если вы хотите полностью синхронизировать структуру двух систем, но не обязательно хотите реплицировать данные для всех таблиц. - include_everything: распространять все события DDL независимо от схемы. Это для случаев, таких как GRANT, которые не предоставляют доступ к информации о том, в какой схеме существует объект. - signal_blocking_subscriber_sessions: завершать процессы на подписчике, удерживающие любой вид блокировки на целевой таблице, которая бы препятствовала выполнению DDL. cancel будет использовать pg_cancel_backend, terminate будет использовать pg_terminate_backend. cancel_then_terminate попытается отменить и, если не удастся, перейдет к завершению. NULL отключает эту функцию. Завершенные сессии будут записаны в таблицу подписчика pgl_ddl_deploy.killed_blockers, которая имеет поля reported и reported_at, которые предназначены для мониторинга, где вы можете уведомлять пользователей о завершенных запросах, а затем отмечать эти запросы как сообщенные пользователям. ** ПРИМЕЧАНИЕ ** - в настоящее время мы не поддерживаем определение зависимых блокировок с нативным партиционированием. Вы можете пропустить завершение блокирующих процессов, когда используется нативное партиционирование. - subscriber_lock_timeout: только для использования с signal_blocking_subscriber_sessions. Это необязательный параметр для lock_timeout для выполнения DDL на подписчике в миллисекундах перед завершением блокирующих процессов. По умолчанию 3000 (3 секунды).

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

SELECT pgl_ddl_deploy.exclude_regex();

Вы можете использовать этот запрос, чтобы проверить, какие схемы будут включены в ваши текущие конфигурации, если вы используете include_schema_regex:

SELECT sc.set_name, n.nspname
FROM pg_namespace n
INNER JOIN pgl_ddl_deploy.set_configs sc
  ON nspname !~* pgl_ddl_deploy.exclude_regex()
  AND n.nspname ~* sc.include_schema_regex
ORDER BY sc.set_name, n.nspname;

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

SELECT n.nspname
FROM pg_namespace n
WHERE nspname !~* pgl_ddl_deploy.exclude_regex()
  AND n.nspname ~* 'test'
ORDER BY n.nspname;

Нет хранимых процедур для вставки/обновления set_configs, которые, как мы считаем, не добавят большой ценности на данный момент. Существуют ограничения проверки и триггеры, чтобы гарантировать, что регулярное выражение является допустимым и другие условия уникальности соблюдены для параметров конфигурации.

F.41.3.2. Разрешения #

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

SELECT pgl_ddl_deploy.add_role(oid)
FROM pg_roles
WHERE rolname IN('app_owner_role');

Обратите внимание, что при обновлении до версии 1.5 мы автоматически повторно применяем разрешения, которые вы уже предоставили с помощью add_role к новым таблицам в этой версии.

F.41.3.3. Развертывание автоматической репликации DDL #

Чтобы развернуть (то есть активировать) репликацию DDL для заданного набора репликации, выполните:

--Deploy any set_configs with given set_name:
SELECT pgl_ddl_deploy.deploy(set_name);

--Deploy only a single set_config_id:
SELECT pgl_ddl_deploy.deploy(set_config_id);
  • С этого момента триггеры событий активны и будут срабатывать на следующих событиях (по умолчанию, если вы не настроили create_tags или drop_tags):

   command_tag
-----------------
 ALTER FUNCTION
 ALTER SEQUENCE
 ALTER TABLE
 ALTER TYPE
 ALTER VIEW
 CREATE FUNCTION
 CREATE SCHEMA
 CREATE SEQUENCE
 CREATE TABLE
 CREATE TABLE AS
 CREATE TYPE
 CREATE VIEW
 DROP FUNCTION
 DROP SCHEMA
 DROP SEQUENCE
 DROP TABLE
 DROP TYPE
 DROP VIEW
 SELECT INTO
  • Не все эти события обрабатываются одинаково - см. Ограничения и ограничения ниже

  • Обратите внимание, что если, исходя из вашей конфигурации, у вас есть таблицы, которые должны быть добавлены в репликацию уже сейчас, но не добавлены, вам не будет разрешено развертывание. Это потому, что от репликации DDL следует ожидать автоматического добавления только новых таблиц в репликацию. Чтобы обойти это, добавьте таблицы в репликацию вручную и синхронизируйте их по мере необходимости.

Репликация DDL может быть отключена/включена (это отключит/включит триггеры событий):

--By set_name
SELECT pgl_ddl_deploy.disable(set_name);
SELECT pgl_ddl_deploy.enable(set_name);

--By set_config_id
SELECT pgl_ddl_deploy.disable(set_name);
SELECT pgl_ddl_deploy.enable(set_name);

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

SELECT pgl_ddl_deploy.undeploy(set_name);
SELECT pgl_ddl_deploy.undeploy(set_config_id);

Если вы хотите изменить конфигурацию в set_configs, вы можете повторно развернуть, снова запустив pgl_ddl_deploy.deploy на указанном set_name. В настоящее время нет принудительного выполнения/предупреждения, если вы изменили конфигурацию, но не развернули, но добавить такую функцию должно быть легко.

Обратите внимание, что вы можете полностью переопределить триггеры событий, например, если вы администратор, который хочет выполнять DDL и знаете, что не хотите, чтобы это распространялось на подписчиков. Вы можете сделать это с помощью SESSION_REPLICATION_ROLE, т.е.:

SET SESSION_REPLICATION_ROLE TO REPLICA;

-- I don't care to send this to subscribers (note that you can also exclude statements
-- like this by using exclude_alter_table_subcommands)
ALTER TABLE foo SET (autovacuum_vacuum_threshold = 1000);

RESET SESSION_REPLICATION_ROLE;

F.41.3.4. Мониторинг и Администрирование #

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

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

  • events - Журналы реплицированных событий DDL на поставщике

  • subscriber_logs - Логи реплицированных DDL событий, выполненных на подписчиках

  • commands - Записывает подробный вывод из pg_event_trigger_ddl_commands() и pg_event_trigger_dropped_objects()

  • unhandled - Любой DDL, который захвачен, но не может быть обработан этой системой (см. подробности ниже), регистрируется здесь.

  • exceptions - Любое неожиданное исключение, вызванное функциями триггера событий, регистрируется здесь

Есть поля resolved в таблицах unhandled и exceptions, которые можно пометить, чтобы мониторинг показывал только новые проблемы на основе этой таблицы, используя функции: - pgl_ddl_deploy.resolve_unhandled(unhandled_id INT, notes TEXT = NULL) - pgl_ddl_deploy.resolve_exception(exception_id INT, notes TEXT = NULL)

F.41.4. Ограничения и Рестрикции #

F.41.4.1. DDL, включающий несколько таблиц #

Один оператор DDL SQL, который изменяет как реплицированные, так и нереплицированные таблицы, не может быть поддержан. Например, если у меня есть include_schema_regex, который включает только регулярное выражение '^replicated.*', это не поддерживается:

DROP TABLE replicated.foo, notreplicated.bar;

Аналогично, следующее может быть проблематичным, если вы используете фильтрованную репликацию:

ALTER TABLE replicated.foo ADD COLUMN foo_id INT REFERENCES unreplicated.foo (id);

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

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

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

F.41.4.2. Неподдерживаемые команды #

CREATE TABLE AS и SELECT INTO не поддерживаются для репликации DDL из-за ограничений на транзакционную согласованность. То есть, если таблица создается из набора данных на стороне поставщика, выполнение того же SQL на стороне подписчика никоим образом не гарантирует согласованность данных. Например:

CREATE TABLE foo AS
SELECT field_1, field_2, now() AS refreshed_at
FROM table_1;

Не только возможно, что table_1 даже не существует на подписчике, даже если она существует, она может быть не полностью актуальной с провайдером, в этом случае данные, созданные в таблице на подписчике, не будут совпадать. Хуже того, функция now() практически гарантированно будет отличаться на подписчике.

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

CREATE TABLE foo (field_1 INT PRIMARY KEY, field_2 TEXT, refreshed_at TIMESTAMPTZ);
INSERT INTO foo (field_1, field_2, refreshed_at)
SELECT field_1, field_2, now() AS refreshed_at
FROM table_1;

Вышеперечисленное полностью поддерживается этой системой, учитывая некоторые крайние случаи с многострочными операторами. CREATE TABLE будет автоматически реплицирован этой системой, и таблица будет добавлена в репликацию, так как у нее есть первичный ключ. Затем INSERT будет реплицирован обычной логической репликацией.

ПРИМЕЧАНИЕ: временные таблицы не затрагиваются этим ограничением, так как временные объекты всегда исключаются из репликации DDL.

Чтобы устранить их, см. Устранение необработанных DDL.

F.41.4.3. Ограничения SQL клиента с несколькими инструкциями #

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

Сложности и ограничения возникают, когда клиент отправляет все SQL-запросы в виде одной строки в Postgres. Предположим, следующие SQL-запросы:

CREATE TABLE foo (id serial primary key, bla text);
INSERT INTO foo (bla) VALUES ('hello world');

Если бы это было в файле, который я вызвал через psql, он бы выполнялся как две отдельные строки SQL-команд.

Однако, если в python или ruby’s ActiveRecord я создаю одну строку, как указано выше, и выполняю ее, то она будет отправлена в Postgres как одна строка SQL-команды. В таком случае, эта структура осознает, что выполняется многооператорное выражение, используя парсер Postgres для получения тегов команд полного SQL-выражения. У вас есть небольшая свобода с опцией allow_multi_statements: - Если false, pgl_ddl_deploy будет только автоматически реплицировать клиентское SQL-выражение, которое содержит один тег команды, соответствующий тегу команды триггера события. Это действительно безопасно, но это означает, что у вас может быть гораздо больше необработанных развертываний. - Если true, pgl_ddl_deploy будет только автоматически реплицировать DDL, который содержит безопасные теги команд для распространения. Например, смешанный DDL и DML запрещен, потому что выполнение такого выражения фактически приведет к двойному выполнению DML как на поставщике, так и на подписчике. Однако, если у вас есть CREATE TABLE и ALTER TABLE в одной команде, при условии, что таблица должна быть включена в репликацию на основе вашей конфигурации, то такое выражение будет отправлено подписчикам. Но, конечно, мы не можем гарантировать, что все DDL-выражения в этой команде относятся к одной и той же таблице - так что могут быть крайние случаи.

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

Чтобы устранить их, см. Устранение необработанных DDL.

Набор регрессионных тестов в папке sql содержит примеры нескольких из этих случаев.

Таким образом, ограничения на многооператорный SQL в значительной степени зависят от того, как ваш клиент отправляет свои сообщения в SQL в Postgres, и, аналогично, как ваши разработчики склонны писать SQL. Хорошая новость заключается в том, что должно быть намного проще обучить разработчиков логически разделять SQL в их миграциях, в отличие от гораздо большего объема работы, которую нам нужно выполнять, когда у нас нет какого-либо прозрачного репликации DDL.

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

Таблицы unhandled и журналы WARNING предназначены для использования с мониторингом для создания оповещений о необходимости ручного вмешательства при изменениях DDL.

F.41.4.4. Поддерживаемые конфигурации нативной логической репликации #

Единственное известное ограничение конфигурации для нативной логической репликации DDL заключается в том, что только публикация, включающая репликацию вставок, может поддерживать репликацию DDL. Это связано с тем, как работает механизм очереди DDL. По умолчанию CREATE PUBLICATION в postgres включает публикацию вставок. Это также можно настроить специально с помощью WITH (publish = 'insert'). Для получения более подробной информации см. https://www.postgresql.org/docs/current/sql-createpublication.html.

F.41.5. Решение проблем репликации DDL #

F.41.5.1. Разрешение неудачных DDL на подписчиках #

В некоторых случаях вы можете распространять DDL, который не удается на подписчике, и репликация будет нарушена, если вы не включили queue_subscriber_failures. Вам нужно будет: - Вручную развернуть с тем же SQL-запросом, измененным таким образом, чтобы он исключал неудачную часть. Для приведенного выше примера добавления столбца и добавления внешнего ключа, предполагая, что у нас нет этой таблицы unreplicated, вы бы выполнили:

ALTER TABLE replicated.foo ADD COLUMN foo_id INT;
  • Потребляйте изменения в затронутом слоте репликации, используя pg_logical_slot_get_changes до конкретного LSN транзакции, которая включала оператор DDL, чтобы репликация снова заработала (это читаемо только для pglogical, не нативно).

  • Вы также можете «обмануть» DDL, чтобы оно применилось - например, создав фиктивный объект, который позволит применить DDL, даже если он вам не нужен, а затем удалить эти объекты, как только репликация снова заработает.

  • Повторно включить репликацию для затронутого(их) подписчика(ов)

Если вы используете queue_subscriber_failures, любые ошибки DDL для вашей данной конфигурации будут зарегистрированы как неудачные в pgl_ddl_deploy.subscriber_logs. Вы можете решить проблемы и попытаться повторно развернуть, используя функции:

--Retry all failed, in transactional order
SELECT pgl_ddl_deploy.retry_all_subscriber_logs();

--Retry only a single failed log
SELECT pgl_ddl_deploy.retry_subscriber_log(subscriber_log_id);

Когда вы повторно просматриваете журналы подписчика, создаются новые строки для повторных попыток, таким образом строки с succeeded = f все еще остаются.

Запросы, подобные этому, полезны:

SELECT id,
  LEFT(ddl_sql, 30) AS sql,
  origin_subscriber_log_id,
  next_subscriber_log_id,
  succeeded
FROM pgl_ddl_deploy.subscriber_logs
WHERE NOT succeeded;

Затем, чтобы проверить повторную попытку:

WITH old AS (
SELECT id,
  LEFT(ddl_sql, 30) AS sql,
  origin_subscriber_log_id,
  next_subscriber_log_id,
  succeeded
FROM pgl_ddl_deploy.subscriber_logs
WHERE NOT succeeded)

SELECT id,
  LEFT(ddl_sql, 30) AS sql,
  origin_subscriber_log_id,
  next_subscriber_log_id,
  succeeded
FROM pgl_ddl_deploy.subscriber_logs
WHERE id IN (SELECT next_subscriber_log_id FROM old);
F.41.5.1.1. Нет разрешения: Последний выход #

Следующее применимо только к нативной репликации.

Иногда случаются случаи, когда что-то ломается, и вы либо не можете понять, как это обойти, либо это может быть фактически невозможно обработать определенный набор операций в транзакции автоматически. Хотя мы приветствовали бы более правильную функцию для отключения репликации DDL на подписчике, вы можете сделать это вручную с помощью следующей команды на подписчике:

ALTER TABLE pgl_ddl_deploy.queue DISABLE TRIGGER execute_queued_ddl;

ПРИМЕЧАНИЕ: репликация DDL будет отключена до тех пор, пока вы это делаете, хотя вы увидите любые изменения в виде новых строк в таблице queue. УБЕДИТЕСЬ, что вы повторно включили триггер как REPLICA TRIGGER правильно после этого:

ALTER TABLE pgl_ddl_deploy.queue ENABLE REPLICA TRIGGER execute_queued_ddl;

F.41.5.2. Разрешение необработанных DDL #

В настоящее время необработанное развертывание DDL может не нарушить репликацию само по себе. Если оператор DDL, который не удалось развернуть, фактически не влияет на данные, которые реплицируются (например, добавляется новая таблица), то репликация продолжится. Однако ситуацию следует разрешить как можно скорее, поскольку предполагается, что любые таблицы, которые были задействованы в DDL, должны быть распространены на подписчиков. Также возможно, что репликация прервется немедленно. Например, если в таблицу добавляется столбец, и данные реплицируются, это немедленно приведет к сбою из-за несоответствия столбцов. В таких случаях вам нужно будет: - Вручную развернуть необработанный SQL-оператор, измененный таким образом, чтобы исключить необработанную часть. Для примера многооператорного SQL выше, вам нужно будет исключить часть INSERT SQL и выполнить:

CREATE TABLE foo (id serial primary key, bla text);
  • Если задействована новая таблица, в этом случае вам также потребуется вручную добавить таблицу в репликацию, используя ALTER PUBLICATION ADD TABLE или pglogical.replication_set_add_table в зависимости от драйвера.

  • Если задействована новая таблица, возможно, потребуется повторно синхронизировать таблицу, если данные для нее реплицировались

  • Если новая таблица НЕ задействована (например, ALTER TABLE ADD COLUMN), то репликация просто продолжится с того места, где она прервалась

  • Повторно включить репликацию для затронутого(их) подписчика(ов)

  • Отметьте ваши необработанные записи как решенные, используя функцию:

SELECT pgl_ddl_deploy.resolve_unhandled(unhandled_id INT, notes TEXT = NULL);

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

F.41.5.3. Отключить репликацию DDL на подписчике #

Следующее относится только к нативной логической репликации.

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

Отключите триггер на таблице очереди DDL, чтобы игнорировать всю репликацию DDL на подписчике:

ALTER TABLE pgl_ddl_deploy.queue DISABLE TRIGGER execute_queued_ddl;

Сделайте все необходимое для ручных исправлений. Если вы подозреваете ошибку, пожалуйста, сообщите об этом. Когда вы закончите, ВЫ ДОЛЖНЫ ВКЛЮЧИТЬ ТРИГГЕР ТОЛЬКО В РЕЖИМЕ РЕПЛИКАЦИИ:

ALTER TABLE pgl_ddl_deploy.queue ENABLE REPLICA TRIGGER execute_queued_ddl;