Сжатие данных с помощью таблиц столбцов в Azure Cosmos DB для PostgreSQL

ПРИМЕНИМО К: Azure Cosmos DB для PostgreSQL (на базе расширения базы данных Citus для PostgreSQL)

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

Создание таблицы

Чтобы использовать хранилище столбцов, укажите USING columnar при создании таблицы:

CREATE TABLE contestant (
    handle TEXT,
    birthdate DATE,
    rating INT,
    percentile FLOAT,
    country CHAR(3),
    achievements TEXT[]
) USING columnar;

Azure Cosmos DB для PostgreSQL преобразует строки в хранилище столбцов в полосах во время вставки. Каждая полоса содержит данные одной транзакции, но не более 150 000 строк. (Размер полосы и другие параметры таблицы столбцов можно изменить с помощью функции alter_columnar_table_set.)

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

-- insert these values into a single columnar stripe

INSERT INTO contestant VALUES
  ('a','1990-01-10',2090,97.1,'XA','{a}'),
  ('b','1990-11-01',2203,98.1,'XA','{a,b}'),
  ('c','1988-11-01',2907,99.4,'XB','{w,y}'),
  ('d','1985-05-05',2314,98.3,'XB','{}'),
  ('e','1995-05-05',2236,98.2,'XC','{a}');

По возможности рекомендуется создавать большие полосы, так как Azure Cosmos DB для PostgreSQL сжимает данные столбцов отдельно на полосу. Мы можем просмотреть сведения о таблице столбцов, например коэффициент сжатия, число полос и среднее количество строк на полосу, используя VACUUM VERBOSE:

VACUUM VERBOSE contestant;
INFO:  statistics for "contestant":
storage id: 10000000000
total file size: 24576, total data size: 248
compression rate: 1.31x
total row count: 5, stripe count: 1, average rows per stripe: 5
chunk count: 6, containing data for dropped columns: 0, zstd compressed: 6

Выходные данные показывают, что Azure Cosmos DB для PostgreSQL использует алгоритм сжатия zstd для получения сжатия данных 1,31x. Коэффициент сжатия сравнивает размер данных, вставленных в память, с размером этих данных в итоговой полосе.

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

Измерение сжатия

Давайте создадим новый пример с дополнительными данными, чтобы протестировать экономию при сжатии.

-- first a wide table using row storage
CREATE TABLE perf_row(
  c00 int8, c01 int8, c02 int8, c03 int8, c04 int8, c05 int8, c06 int8, c07 int8, c08 int8, c09 int8,
  c10 int8, c11 int8, c12 int8, c13 int8, c14 int8, c15 int8, c16 int8, c17 int8, c18 int8, c19 int8,
  c20 int8, c21 int8, c22 int8, c23 int8, c24 int8, c25 int8, c26 int8, c27 int8, c28 int8, c29 int8,
  c30 int8, c31 int8, c32 int8, c33 int8, c34 int8, c35 int8, c36 int8, c37 int8, c38 int8, c39 int8,
  c40 int8, c41 int8, c42 int8, c43 int8, c44 int8, c45 int8, c46 int8, c47 int8, c48 int8, c49 int8,
  c50 int8, c51 int8, c52 int8, c53 int8, c54 int8, c55 int8, c56 int8, c57 int8, c58 int8, c59 int8,
  c60 int8, c61 int8, c62 int8, c63 int8, c64 int8, c65 int8, c66 int8, c67 int8, c68 int8, c69 int8,
  c70 int8, c71 int8, c72 int8, c73 int8, c74 int8, c75 int8, c76 int8, c77 int8, c78 int8, c79 int8,
  c80 int8, c81 int8, c82 int8, c83 int8, c84 int8, c85 int8, c86 int8, c87 int8, c88 int8, c89 int8,
  c90 int8, c91 int8, c92 int8, c93 int8, c94 int8, c95 int8, c96 int8, c97 int8, c98 int8, c99 int8
);

-- next a table with identical columns using columnar storage
CREATE TABLE perf_columnar(LIKE perf_row) USING COLUMNAR;

Заполните обе таблицы одним и тем же большим набором данных:

INSERT INTO perf_row
  SELECT
    g % 00500, g % 01000, g % 01500, g % 02000, g % 02500, g % 03000, g % 03500, g % 04000, g % 04500, g % 05000,
    g % 05500, g % 06000, g % 06500, g % 07000, g % 07500, g % 08000, g % 08500, g % 09000, g % 09500, g % 10000,
    g % 10500, g % 11000, g % 11500, g % 12000, g % 12500, g % 13000, g % 13500, g % 14000, g % 14500, g % 15000,
    g % 15500, g % 16000, g % 16500, g % 17000, g % 17500, g % 18000, g % 18500, g % 19000, g % 19500, g % 20000,
    g % 20500, g % 21000, g % 21500, g % 22000, g % 22500, g % 23000, g % 23500, g % 24000, g % 24500, g % 25000,
    g % 25500, g % 26000, g % 26500, g % 27000, g % 27500, g % 28000, g % 28500, g % 29000, g % 29500, g % 30000,
    g % 30500, g % 31000, g % 31500, g % 32000, g % 32500, g % 33000, g % 33500, g % 34000, g % 34500, g % 35000,
    g % 35500, g % 36000, g % 36500, g % 37000, g % 37500, g % 38000, g % 38500, g % 39000, g % 39500, g % 40000,
    g % 40500, g % 41000, g % 41500, g % 42000, g % 42500, g % 43000, g % 43500, g % 44000, g % 44500, g % 45000,
    g % 45500, g % 46000, g % 46500, g % 47000, g % 47500, g % 48000, g % 48500, g % 49000, g % 49500, g % 50000
  FROM generate_series(1,50000000) g;

INSERT INTO perf_columnar
  SELECT
    g % 00500, g % 01000, g % 01500, g % 02000, g % 02500, g % 03000, g % 03500, g % 04000, g % 04500, g % 05000,
    g % 05500, g % 06000, g % 06500, g % 07000, g % 07500, g % 08000, g % 08500, g % 09000, g % 09500, g % 10000,
    g % 10500, g % 11000, g % 11500, g % 12000, g % 12500, g % 13000, g % 13500, g % 14000, g % 14500, g % 15000,
    g % 15500, g % 16000, g % 16500, g % 17000, g % 17500, g % 18000, g % 18500, g % 19000, g % 19500, g % 20000,
    g % 20500, g % 21000, g % 21500, g % 22000, g % 22500, g % 23000, g % 23500, g % 24000, g % 24500, g % 25000,
    g % 25500, g % 26000, g % 26500, g % 27000, g % 27500, g % 28000, g % 28500, g % 29000, g % 29500, g % 30000,
    g % 30500, g % 31000, g % 31500, g % 32000, g % 32500, g % 33000, g % 33500, g % 34000, g % 34500, g % 35000,
    g % 35500, g % 36000, g % 36500, g % 37000, g % 37500, g % 38000, g % 38500, g % 39000, g % 39500, g % 40000,
    g % 40500, g % 41000, g % 41500, g % 42000, g % 42500, g % 43000, g % 43500, g % 44000, g % 44500, g % 45000,
    g % 45500, g % 46000, g % 46500, g % 47000, g % 47500, g % 48000, g % 48500, g % 49000, g % 49500, g % 50000
  FROM generate_series(1,50000000) g;

VACUUM (FREEZE, ANALYZE) perf_row;
VACUUM (FREEZE, ANALYZE) perf_columnar;

Для этих данных в таблице столбцов можно увидеть коэффициент сжатия выше 8x.

SELECT pg_total_relation_size('perf_row')::numeric/
       pg_total_relation_size('perf_columnar') AS compression_ratio;
 compression_ratio
--------------------
 8.0196135873627944
(1 row)

Пример

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

Подводные камни

  • Хранилище столбцов выполняет сжатие по полосам. Полосы создаются для каждой транзакции, поэтому при вставке одной строки на транзакцию отдельные строки помещаются в собственные полосы. Сжатие и производительность однострочных полос будут хуже, чем у таблицы строк. Всегда вставляйте в таблицу столбцов большие объемы данных.
  • Если вы будете работать с группой мини-полос, производительность будет крайне низкой. Чтобы исправить ситуацию, создайте новую таблицу столбцов и скопируйте данные из оригинала в одну транзакцию:
    BEGIN;
    CREATE TABLE foo_compacted (LIKE foo) USING columnar;
    INSERT INTO foo_compacted SELECT * FROM foo;
    DROP TABLE foo;
    ALTER TABLE foo_compacted RENAME TO foo;
    COMMIT;
    
  • Несжимаемые данные могут быть проблемой, хотя хранилище столбцов по-прежнему будет полезно, если выбирать конкретные столбцы. Не нужно загружать остальные столбцы в память.
  • Для секционированной таблицы с сочетанием секций из строк и столбцов необходимо тщательно настраивать обновления, чтобы они затрагивали только секции со строками.
    • Если операция нацелена на определенную секцию строк (например, UPDATE p2 SET i = i + 1), она будет выполнена успешно; если на указанную секцию столбцов (например, UPDATE p1 SET i = i + 1) — она завершится ошибкой.
    • Если операция нацелена на секционированную таблицу и содержит предложение WHERE, исключающее все секции столбцов (например, UPDATE parent SET i = i + 1 WHERE timestamp = '2020-03-15'), она будет выполнено успешно.
    • Если операция нацелена на секционированную таблицу, но не выполняет фильтрацию по ключевым столбцам секции, она завершится ошибкой. Даже если существуют предложения WHERE, совпадающие со строками только в секциях столбцов, этого недостаточно — необходимо также отфильтровать ключ секции.

Ограничения

Эта функция по-прежнему имеет значительные ограничения:

  • Сжатие выполняется на диске, а не в памяти
  • Только добавление (без поддержки обновления и удаления)
  • Отсутствие реорганизации пространства (например, для отката транзакций может по-прежнему использоваться место на диске)
  • Отсутствие поддержки индексов, сканирования индексов или сканирования индексов растровых изображений
  • Отсутствие сканирования ИД транзакций
  • Отсутствие сканирования образцов
  • Отсутствие поддержки всплывающих уведомлений (поддержка больших значений)
  • Отсутствие поддержки операторов ON CONFLICT (кроме действий DO NOTHING без указания цели).
  • Отсутствие поддержки блокировок кортежей (SELECT ... FOR SHARE, SELECT ... FOR UPDATE)
  • Отсутствие поддержки упорядочиваемого уровня изоляции
  • Поддержка только для серверов PostgreSQL версии 12+
  • Отсутствие поддержки внешних ключей, уникальных ограничений или ограничений исключений
  • Отсутствие поддержки логического декодирования
  • Отсутствие поддержки для параллельного сканирования внутри узла
  • Отсутствие поддержки триггеров AFTER ... FOR EACH ROW
  • Отсутствие столбчатых таблиц UNLOGGED
  • Отсутствие столбчатых таблиц TEMPORARY

Дальнейшие действия