Распространение и изменение таблиц в Azure Cosmos DB для PostgreSQL

Область применения: Azure Cosmos DB для PostgreSQL (на базе расширения базы данных Citus до PostgreSQL)

Распространение таблиц

Чтобы создать распределенную таблицу, необходимо сначала определить схему таблицы. Сделать это можно с помощью оператора CREATE TABLE так же, как и для обычной таблицы PostgreSQL.

CREATE TABLE github_events
(
    event_id bigint,
    event_type text,
    event_public boolean,
    repo_id bigint,
    payload jsonb,
    repo jsonb,
    actor jsonb,
    org jsonb,
    created_at timestamp
);

Затем с помощью функции create_distributed_table() можно указать столбец распределения таблицы и создать сегменты рабочей роли.

SELECT create_distributed_table('github_events', 'repo_id');

Вызов функции сообщает Azure Cosmos DB для PostgreSQL, что таблица github_events должна распространяться в столбце repo_id (путем хэширования значения столбца).

Всего она по умолчанию создает 32 сегмента, каждый из которых владеет частью хэш-пространства и реплицируется по заданному по умолчанию значению конфигурации citus.shard_replication_factor. Схема таблицы, индексы и определения ограничений реплик сегментов, создаваемых на рабочем узле, такие же, как у таблицы в координаторе. После создания реплик функция сохраняет все распределенные метаданные на координаторе.

Каждому созданному сегменту назначается уникальный идентификатор, и все его реплики получают одинаковый идентификатор сегмента. Сегменты представлены на рабочем узле как обычные таблицы PostgreSQL с именем tablename_shardid, где tablename — это имя распределенной таблицы, а shardid — присвоенный уникальный идентификатор. К экземплярам postgres, находящимся на рабочих узлах можно подключаться, чтобы просматривать или выполнять команды в отдельных сегментах.

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

Справочные таблицы

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

К распространенным кандидатам для ссылочных таблиц относятся:

  • Небольшие таблицы, которые необходимо объединить с большими распределенными таблицами.
  • Таблицы в приложениях с несколькими клиентами, в которых отсутствует столбец идентификатора клиента или которые не связаны с клиентом. (Или, во время миграции, даже для некоторых таблиц, связанных с клиентом.)
  • Таблицы, где нужны уникальные ограничения для нескольких столбцов, которые при этом достаточно малы.

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

-- a reference table

CREATE TABLE states (
  code char(2) PRIMARY KEY,
  full_name text NOT NULL,
  general_sales_tax numeric(4,3)
);

-- distribute it to all workers

SELECT create_reference_table('states');

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

Помимо распространения таблицы в виде одного реплицированного сегмента, create_reference_table UDF помечает ее как эталонную таблицу в таблицах метаданных Azure Cosmos DB для PostgreSQL. Azure Cosmos DB для PostgreSQL автоматически выполняет двухфазные фиксации (2PC) для изменений таблиц, помеченных таким образом, что обеспечивает надежные гарантии согласованности.

Еще один пример использования ссылочных таблиц см. в руководстве по работе с многопользовательскими базами данных.

Распределение данных координатора

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

Функция create_distributed_table, описанная выше, работает как с пустыми, так и с непустыми таблицами, а для последних она автоматически распределяет строки таблицы по всему кластеру. Вы узнаете, копирует ли она данные по наличию сообщения, «ОБРАТИТЕ ВНИМАНИЕ: копирование данных из локальной таблицы...» Например:

CREATE TABLE series AS SELECT i FROM generate_series(1,1000000) i;
SELECT create_distributed_table('series', 'i');
NOTICE:  Copying data from local table...
 create_distributed_table
 --------------------------

 (1 row)

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

При распределении таблиц A и B, где A имеет внешний ключ к B, сначала распределите целевую таблицу ключа B. Выполнение этого действия в неправильном порядке приведет к ошибке:

ERROR:  cannot create foreign key constraint
DETAIL:  Referenced table must be a distributed table or a reference table.

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

При миграции данных из внешней базы данных, например из Amazon RDS в Azure Cosmos DB для PostgreSQL, сначала создайте распределенные таблицы create_distributed_tableAzure Cosmos DB для PostgreSQL, а затем скопируйте данные в таблицу. Копирование в распределенные таблицы позволяет избежать нехватки пространства на узле-координаторе.

Совместное размещение таблиц

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

Таблицы размещаются совместно в группах. Чтобы вручную управлять назначением группы совместного размещения для таблицы, используйте необязательный параметр colocate_with для create_distributed_table. Если вам не нужно совместное размещение таблиц, этот параметр следует опустить. По умолчанию он имеет значение 'default', которое группирует таблицу с любой другой таблицей совместного размещения по умолчанию, имеющей тот же тип столбца распределения, количество сегментов и фактор репликации. Если требуется прервать или изменить это неявное совместное размещение, можно воспользоваться update_distributed_table_colocation().

-- these tables are implicitly co-located by using the same
-- distribution column type and shard count with the default
-- co-location group

SELECT create_distributed_table('A', 'some_int_col');
SELECT create_distributed_table('B', 'other_int_col');

Если новая таблица не связана с другими из возможной группы совместного размещения, укажите colocated_with => 'none'.

-- not co-located with other tables

SELECT create_distributed_table('A', 'foo', colocate_with => 'none');

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

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

Чтобы явным образом совместно разместить несколько таблиц, распределите одну из них, а затем поместите остальные в ее группу совместного размещения. Например:

-- distribute stores
SELECT create_distributed_table('stores', 'store_id');

-- add to the same group as stores
SELECT create_distributed_table('orders', 'store_id', colocate_with => 'stores');
SELECT create_distributed_table('products', 'store_id', colocate_with => 'stores');

Сведения о группах совместного размещения хранятся в таблице pg_dist_colocation, а в таблице pg_dist_partition указано, к каким группам относятся те или иные таблицы.

Удаление таблиц

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

DROP TABLE github_events;

Изменение таблиц

Azure Cosmos DB для PostgreSQL автоматически распространяет множество типов инструкций DDL. При изменении распределенной таблицы на узле-координаторе также обновляются сегменты на рабочих узлах. Для других инструкций DDL требуется распространение вручную, а некоторые из них запрещены, например те, которые изменяют столбец распределения. При попытке выполнить инструкцию DDL, которая не подходит для автоматического распространения, произойдет ошибка, а таблицы на узле-координаторе останутся без изменений.

Ниже приведен справочник категорий инструкций DDL, которые выполняют распространение.

Добавление и изменение столбцов

Azure Cosmos DB для PostgreSQL автоматически распространяет большинство команд ALTER TABLE . Добавление столбцов или изменение их значений по умолчанию работает так же, как в базе данных PostgreSQL с одним компьютером:

-- Adding a column

ALTER TABLE products ADD COLUMN description text;

-- Changing default value

ALTER TABLE products ALTER COLUMN price SET DEFAULT 7.77;

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

Попытка выполнить такое изменение вызывает ошибку:

-- assumining store_id is the distribution column
-- for products, and that it has type integer

ALTER TABLE products
ALTER COLUMN store_id TYPE text;

/*
ERROR:  XX000: cannot execute ALTER TABLE command involving partition column
LOCATION:  ErrorIfUnsupportedAlterTableStmt, multi_utility.c:2150
*/

Добавление/удаление ограничений

Использование Azure Cosmos DB для PostgreSQL позволяет продолжать пользоваться безопасностью реляционной базы данных, включая ограничения базы данных (см. документы PostgreSQL). Из-за характера распределенных систем Azure Cosmos DB для PostgreSQL не будет перекрестно ссылаться на ограничения уникальности или ссылочной целостности между рабочими узлами.

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

Внешние ключи могут создаваться в следующих ситуациях:

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

Внешние ключи из ссылочных таблиц в распределенные таблицы не поддерживаются.

Примечание.

Первичные ключи и ограничения уникальности должны содержать столбец распределения. Если добавить их в любой другой столбец (а не в столбец распределения), будет выдаваться ошибка

В этом примере показано, как создавать первичные и внешние ключи в распределенных таблицах.

--
-- Adding a primary key
-- --------------------

-- We'll distribute these tables on the account_id. The ads and clicks
-- tables must use compound keys that include account_id.

ALTER TABLE accounts ADD PRIMARY KEY (id);
ALTER TABLE ads ADD PRIMARY KEY (account_id, id);
ALTER TABLE clicks ADD PRIMARY KEY (account_id, id);

-- Next distribute the tables

SELECT create_distributed_table('accounts', 'id');
SELECT create_distributed_table('ads',      'account_id');
SELECT create_distributed_table('clicks',   'account_id');

--
-- Adding foreign keys
-- -------------------

-- Note that this can happen before or after distribution, as long as
-- there exists a uniqueness constraint on the target column(s) which
-- can only be enforced before distribution.

ALTER TABLE ads ADD CONSTRAINT ads_account_fk
  FOREIGN KEY (account_id) REFERENCES accounts (id);
ALTER TABLE clicks ADD CONSTRAINT clicks_ad_fk
  FOREIGN KEY (account_id, ad_id) REFERENCES ads (account_id, id);

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

-- Suppose we want every ad to use a unique image. Notice we can
-- enforce it only per account when we distribute by account id.

ALTER TABLE ads ADD CONSTRAINT ads_unique_image
  UNIQUE (account_id, image_url);

Ограничения, отличающиеся от null, могут применяться к любому столбцу (распространения или нет), так как они не нуждаются в поиске между рабочими узлами.

ALTER TABLE ads ALTER COLUMN image_url SET NOT NULL;

Использование ограничений NOT VALID

В некоторых ситуациях может быть полезно применять ограничения для новых строк, оставляя при этом существующие не соответствующие ограничениям строки без изменений. Azure Cosmos DB для PostgreSQL поддерживает эту функцию для ограничений CHECK и внешних ключей с помощью обозначения ограничения PostgreSQL "NOT VALID".

Например, рассмотрим приложение, которое хранит профили пользователей в ссылочной таблице.

-- we're using the "text" column type here, but a real application
-- might use "citext" which is available in a postgres contrib module

CREATE TABLE users ( email text PRIMARY KEY );
SELECT create_reference_table('users');

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

INSERT INTO users VALUES
   ('foo@example.com'), ('hacker12@aol.com'), ('lol');

Мы хотели бы проверить адреса, но PostgreSQL не позволяет добавить ограничение CHECK, которое завершается ошибкой для существующих строк. Однако он разрешает ограничение, помеченное как недопустимое:

ALTER TABLE users
ADD CONSTRAINT syntactic_email
CHECK (email ~
   '^[a-zA-Z0-9.!#$%&''*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$'
) NOT VALID;

Теперь новые строки защищены.

INSERT INTO users VALUES ('fake');

/*
ERROR:  new row for relation "users_102010" violates
        check constraint "syntactic_email_102010"
DETAIL:  Failing row contains (fake).
*/

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

-- later, attempt to validate all rows
ALTER TABLE users
VALIDATE CONSTRAINT syntactic_email;

В документации PostgreSQL содержатся дополнительные сведения о NOT VALID и VALIDATE CONSTRAINT в разделе Инструкция ALTER TABLE.

Добавление и удаление индексов

Azure Cosmos DB для PostgreSQL поддерживает добавление и удаление индексов:

-- Adding an index

CREATE INDEX clicked_at_idx ON clicks USING BRIN (clicked_at);

-- Removing an index

DROP INDEX clicked_at_idx;

При добавлении индекса выполняется блокировка записи, которая может быть нежелательной в многопользовательской «системе записей». Чтобы сократить время простоя приложения, вместо этого создайте индекс одновременно. Общий объем работы при использовании этого метода больше, чем при построении стандартного индекса. Однако, поскольку штатная работа при этом может продолжаться во время построения индекса, этот метод удобен для добавления новых индексов в рабочей среде.

-- Adding an index without locking table writes

CREATE INDEX CONCURRENTLY clicked_at_idx ON clicks USING BRIN (clicked_at);

Типы и функции

Создание настраиваемых типов SQL и определяемых пользователем функций выполняется на рабочих узлах. Однако создание таких объектов баз данных в транзакции с распределенными операциями предполагает компромиссы.

Azure Cosmos DB для PostgreSQL параллелизирует операции, такие как create_distributed_table() между сегментами, с помощью нескольких подключений на рабочую роль. В то время как при создании объекта базы данных Azure Cosmos DB для PostgreSQL распространяет его на рабочие узлы с помощью одного подключения на рабочую роль. Объединение двух операций в одной транзакции может вызвать проблемы, так как параллельные подключения не смогут увидеть объект, созданный через одно соединение, но еще не зафиксированный.

Рассмотрим блок транзакций, который создает тип, таблицу, загружает данные и распределяет таблицу:

BEGIN;

-- type creation over a single connection:
CREATE TYPE coordinates AS (x int, y int);
CREATE TABLE positions (object_id text primary key, position coordinates);

-- data loading thus goes over a single connection:
SELECT create_distributed_table(‘positions’, ‘object_id’);

SET client_encoding TO 'UTF8';
\COPY positions FROM ‘positions.csv’

COMMIT;

Citus до версии 11.0 откладывал создание типа на рабочих узлах и фиксировал его отдельно при создании распределенной таблицы. Это позволяло параллельно копировать данные в create_distributed_table(). Однако это также означало, что тип не всегда присутствовал на рабочих узлах Citus, или, в случае отката транзакции, тип оставался на рабочих узлах.

При использовании Citus версии 11.0 поведение по умолчанию изменяется — назначается приоритет согласованности схемы между координатором и рабочими узлами. У такого нового поведения есть обратная сторона: если распространение объекта происходит после параллельной команды в той же транзакции, то транзакция более не может быть завершена, что высвечивается как ОШИБКА в блоке кода, показанного ниже:

BEGIN;
CREATE TABLE items (key text, value text);
-- parallel data loading:
SELECT create_distributed_table(‘items’, ‘key’);
SET client_encoding TO 'UTF8';
\COPY items FROM ‘items.csv’
CREATE TYPE coordinates AS (x int, y int);

ERROR:  cannot run type command because there was a parallel operation on a distributed table in the transaction

При возникновении этой проблемы существует два простых обходных решения:

  1. Используйте набор citus.create_object_propagation на automatic, чтобы отложить создание типа в этой ситуации, и в этом случае может возникнуть некоторое несоответствие между тем, какие объекты базы данных существуют на разных узлах.
  2. Используйте набор citus.multi_shard_modify_mode на sequential, чтобы отключить параллелизм для каждого узла. Загрузка данных в одной транзакции может быть медленнее.

Следующие шаги