Перейти к основному содержимому

Поддержка сжатия данных при записи на диск

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

Сведения

Функциональность доступна только для редакций Enterprise и Enterprise для ERP-систем.

Функциональность решает задачи:

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

Описание

Сжатие данных на уровне страниц (блоков) в СУБД – это метод сжатия содержимого файлов отношений (таблиц, материализованных представлений и индексов) путем применения к страницам алгоритма сжатия.

Применение данного подхода позволяет достичь следующих целей:

  • экономить место на диске;
  • снизить нагрузку на подсистему ввода/вывода;
  • снизить расход RAM под страничный кеш ОС.

Основной принцип механизма сжатия на уровне страниц состоит в следующем:

  1. При записи страницы на диск данные, содержащиеся в этой странице, сжимаются и сохраняются в файле сжатых данных (PCD - page compressed data), а место хранения каждой страницы в файле сжатых данных записывается в файл адресов сжатых данных (PCA - page compressed address).
  2. При чтении страницы сначала производится поиск соответствующего места хранения страницы в адресном файле (PCA), затем считываются необходимые данные из файла сжатых данных (PCD) с последующей декомпрессией для восстановления исходной страницы.

Настройка

Для использования функциональности сжатия необходимо активировать параметр enable_page_compression.

Название параметраТипЗначение по умолчаниюДопустимые значенияОписание
enable_page_compressionbooloffon/offАктивирует функциональность сжатия

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

  • для standalone-конфигурации — в файле $PGDATA/postgresql.conf;
  • для кластерной конфигурации — в файле /etc/pangolin-manager/postgres.yml.

В случае кластерной конфигурации следует устанавливать параметр в секцию bootstrap Pangolin Manager через команду:

pangolin-manager-ctl -c /etc/pangolin-manager/postgres.yml edit-config $CLNAME

Для применения параметра необходимо выполнить перезагрузку кластера (restart).

Управление

Сжатие можно настроить как для определенных отношений, так и для конкретных табличных пространств (ТП).

Сжатие поддерживается для следующих типов объектов БД:

  • обычные таблицы;
  • партиционированные таблицы;
  • материализованные представления;
  • индексы, встроенные в ядро Pangolin: B-Tree, Hash, GiST, SP-GiST и GIN.

Чтобы включить сжатие для объекта БД, необходимо при его создании установить параметры:

Параметр

Описание

Значения

compresstype

Алгоритм сжатия

pglz, lz4, zstd

compresslevel

Уровень сжатия. Параметр применим только для алгоритма zstd.

Диапазон значений - [0-19]. 0 - установить значение по умолчанию для текущего алгоритма сжатия (в библиотеке zstd v1.4.4 уровень сжатия по умолчанию равен 3)

compress_chunk_size

Размер фрагмента в байтах

512, 1024, 2048, 4096

compress_prealloc_chunks

Число предварительно выделенных фрагментов для хранения сжатых данных одной страницы

Максимальное значение ограничивается числом BLCKSZ/compress_chunk_size - 1

Параметр compresstype является обязательным для включения сжатия. Если параметр compress_chunk_size не задан, его значение по умолчанию будет равно 4096. Уровень сжатия compresslevel и число предустановленных фрагментов compress_prealloc_chunks, если не указано иное, будут равны 0.

Рекомендации по выбору параметров сжатия

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

При выборе параметров сжатия учитывайте следующие аспекты:

Аспект

Влияние

Множество таблиц имеют размер меньше, чем размер PCA-файла для выбранного размера фрагмента

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

Таблица содержит поля размером более 2 КБ

Данные таких полей могут выноситься в TOAST-раздел, где уже применяется сжатие, поэтому эффект от постраничного сжатия может быть ниже ожидаемого

Используются алгоритмы pglz или lz4

Если требуется более высокая степень сжатия, предпочтительно использовать zstd

Используются максимальные значения уровня сжатия

Сжатие выполняется на уровне страниц размером 8192 байта, поэтому повышение уровня сжатия не всегда дает заметный выигрыш

Функция relation_estimate_compression_ratio() дает результат, заметно отличающийся от фактического

Проверьте, что аргумент in_chunks задан в значении TRUE, если требуется оценить реальный объем данных на диске

Функция relation_estimate_compression_ratio() работает слишком долго

Для приблизительной оценки используйте только часть страниц объекта с помощью аргументов first_block и last_block

Создания объектов БД с параметрами сжатия

Примеры создания объектов БД с параметрами сжатия:

CREATE TABLE compressed_table_1(id int PRIMARY KEY, c1 text) WITH (compresstype=pglz); -- первичный ключ compressed_table_1_pkey без параметров сжатия.
CREATE TABLE compressed_table_2(id int PRIMARY KEY WITH(compresstype=pglz, compress_chunk_size=1024), c1 text) WITH (compresstype=pglz, compress_chunk_size=512); -- первичный ключ compressed_table_2_pkey с параметрами сжатия.
CREATE TABLE compressed_table_3(id int PRIMARY KEY, c1 text) WITH (compresstype=pglz, compresslevel=0, compress_chunk_size=1024);
CREATE TABLE compressed_table_4(id int PRIMARY KEY, c1 text) WITH (compresstype=pglz, compresslevel=1, compress_chunk_size=2048, compress_prealloc_chunks=0);

CREATE MATERIALIZED VIEW compressed_mv_1 WITH (compresstype=pglz, compresslevel=0, compress_chunk_size=1024) AS SELECT id, c1 FROM compressed_table_1 WHERE id <= 10;

CREATE INDEX compressed_table_1_idx_1 on compressed_table_1 USING btree(c1) WITH(compresstype=pglz, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
CREATE INDEX compressed_table_1_idx_2 on compressed_table_1 USING hash(c1) WITH(compresstype=pglz, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
CREATE INDEX compressed_table_1_idx_3 on compressed_table_1 USING gin((ARRAY[id])) WITH(compresstype=pglz, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
CREATE INDEX compressed_table_1_idx_4 on compressed_table_1 USING gist((point(id,id))) WITH(compresstype=pglz, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
CREATE INDEX compressed_table_1_idx_5 on compressed_table_1 USING spgist(c1) WITH(compresstype=pglz, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);

Создание ТП с параметрами сжатия и таблиц в сжатом ТП

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

Примеры создания табличных пространств (ТП) с параметрами сжатия и таблиц в сжатом табличном пространстве:

CREATE TABLESPACE compressed_tablespace_1 LOCATION ''  WITH (compresstype=pglz);
CREATE TABLESPACE compressed_tablespace_2 LOCATION '' WITH (compresstype=pglz, compress_chunk_size=512);
CREATE TABLESPACE compressed_tablespace_3 LOCATION '' WITH (compresstype=pglz, compresslevel=0, compress_chunk_size=1024);
CREATE TABLESPACE compressed_tablespace_4 LOCATION '' WITH (compresstype=pglz, compresslevel=1, compress_chunk_size=2048, compress_prealloc_chunks=0);

Таблица uncompressed_table не наследует опции сжатия табличного пространства compressed_tablespace_1, поэтому остается не в сжатом виде:

CREATE TABLE uncompressed_table(id int, c1 text) WITH (compresstype=none) TABLESPACE compressed_tablespace_1;

Таблица compressed_table_1 наследует опции сжатия табличного пространства compressed_tablespace_1:

CREATE TABLE compressed_table_1(id int, c1 text) TABLESPACE compressed_tablespace_1;

Таблица compressed_table_2 не наследует опции сжатия табличного пространства compressed_tablespace_1, так как для нее уже заданы опции сжатия:

CREATE TABLE compressed_table_2(id int, c1 text) WITH (compresstype=pglz, compresslevel=0, compress_chunk_size=1024) TABLESPACE compressed_tablespace_1;

Материализованное представление uncompressed_mv не наследует опции сжатия табличного пространства compressed_tablespace_1, поэтому остается не в сжатом виде:

CREATE MATERIALIZED VIEW uncompressed_mv WITH (compresstype=none) TABLESPACE compressed_tablespace_1 AS SELECT id, c1 FROM uncompressed_table WHERE id <= 10;

Материализованное представление compressed_mv_1 наследует опции сжатия табличного пространства compressed_tablespace_1:

CREATE MATERIALIZED VIEW compressed_mv_1 TABLESPACE compressed_tablespace_1 AS SELECT id, c1 FROM uncompressed_table WHERE id <= 10;

Материализованное представление compressed_mv_2 не наследует опции сжатия табличного пространства compressed_tablespace_1, так как для него уже заданы опции сжатия:

CREATE MATERIALIZED VIEW compressed_mv_2 WITH (compresstype=pglz, compresslevel=0, compress_chunk_size=1024) TABLESPACE compressed_tablespace_1 AS SELECT id, c1 FROM uncompressed_table WHERE id <= 10;

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

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

CREATE TABLE uncompressed_table AS SELECT * FROM compressed_table;
CREATE TABLE new_compressed_table WITH (compresstype=pglz, compresslevel=0, compress_chunk_size=1024) AS SELECT * FROM compressed_table;

Оценка эффективности сжатия размера объекта БД

Функция relation_estimate_compression_ratio()

Чтобы оценить, насколько уменьшится размер объекта БД после сжатия, можно воспользоваться функцией relation_estimate_compression_ratio(), доступной в расширении pageinspect. Результат этой функции поможет решить, стоит ли тратить ресурсы процессора на сжатие объекта.

Функция имеет следующее определение:

CREATE FUNCTION relation_estimate_compression_ratio(
relation regclass, -- имя объекта БД
algorithm text, -- алгоритм сжатия
level integer, -- уровень сжатия
chunk_size integer, -- размер фрагмента
in_chunks boolean, -- выдать результат с учетом размещения сжатых данных по выделенным фрагментам
first_block integer, -- номер первой страницы, с которой начнется анализ
last_block integer -- номер последней страницы (включительно), на которой анализ закончится
) RETURNS double precision
AS 'MODULE_PATHNAME', 'relation_estimate_compression_ratio'
LANGUAGE C PARALLEL SAFE;

Первые два аргумента relation и algorithm являются обязательными. Остальные, если не указаны, принимают следующие значения по умолчанию:

  • level = 0;
  • chunk_size = 4096;
  • in_chunks = FALSE;
  • first_block = 0;
  • last_block = 10 (или количество страниц, выделенных для хранения объекта БД, если оно меньше 10).

Функция анализирует страницы объекта БД в диапазоне номеров [first_block, last_block] и пытается их сжать. Затем функция вычисляет среднее значение коэффициента сжатия. Например, если функция вернет значение 2.9, это будет означать, что сжатый объект БД будет занимать примерно в 3 раза меньше места, чем исходный. Если аргумент in_chunks имеет значение FALSE, то результатом будет оценка эффективности сжатия определенного алгоритма для набора данных в выбранном диапазоне номеров страниц. Если же требуется оценить реальный размер сжатых данных на диске, то следует установить значение in_chunks в TRUE. В этом случае будет учитываться значение аргумента chunk_size: чем меньше значение chunk_size (чем выше гранулярность сжатых данных), тем больше вероятность, что сжатые данные займут меньше места на диске.

Примеры запросов вызова функции relation_estimate_compression_ratio:

  • без учета размещения сжатых данных по выделенным фрагментам:

    SELECT relation_estimate_compression_ratio('test_data_table', 'pglz', 0, 1024, FALSE, 0, 0);

    relation_estimate_compression_ratio
    -------------------------------------
    2.6080865966252786
    (1 row)
  • с учетом размещения сжатых данных по выделенным фрагментам

    SELECT relation_estimate_compression_ratio('test_data_table', 'pglz', 0, 1024, TRUE, 0, 0);

    relation_estimate_compression_ratio
    -------------------------------------
    2
    (1 row)

Функция page_compress()

Расширение pageinspect также предоставляет функцию сжатия одной страницы page_compress(), которую тоже можно использовать для оценки эффективности сжатия, но менее удобным способом.

Синтаксис функции:

CREATE FUNCTION page_compress(
IN page bytea, -- бинарное содержимое страницы
IN algorithm text, -- алгоритм сжатия
IN level integer, -- уровень сжатия
IN chunk_size integer -- размер фрагмента
) RETURNS bytea
AS 'MODULE_PATHNAME', 'page_compress'
LANGUAGE C STRICT PARALLEL SAFE;

Пример использования функции page_compress без учета размещения сжатых данных по выделенным фрагментам:

SELECT 8192. / octet_length(page_compress(get_raw_page('test_data_table', 'main', 0), 'pglz', 0, 1024)) AS compression_ratio;

compression_ratio
--------------------
2.6080865966252786
(1 row)

Результат совпадает с результатом в первом примере использования relation_estimate_compression_ratio().

Формат хранения несжатых данных отношений

Несжатое отношение хранится в отдельном файле, именуемом по номеру файлового узла, который содержится в pg_class.relfilenode (NNNN). Этот файл называется главным или основным слоем. Помимо главного файла у каждого отношения есть файл с картой свободного пространства с суффиксом _fsm (NNNN_fsm) и файл с картой видимости с суффиксом _vm (NNNN_vm). Нежурналируемые отношения имеют файл инициализации, имя которого содержит суффикс _init (NNNN_init). Когда размер отношения превышает 1 Гбайт, главный файл делится на сегменты размером в 1 Гбайт. Файл первого сегмента именуется по номеру файлового узла (NNNN), а последующие сегменты получают имена NNNN.1, NNNN.2 и так далее.

Все страницы сегмента одинакового размера (по 8 Кбайт) и на диске располагаются в фиксированных позициях по порядку.

Формат хранения сжатых данных отношений

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

  • файл с суффиксом _pcd (NNNN_pcd) - файл сжатых данных (PCD-файл, page compressed data);
  • файл с суффиксом _pca (NNNN_pca) - файл адресов сжатых данных (PCA-файл, page compressed address).

Описание PCD-файла

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

Одна сжатая страница (PageCompressData) состоит из следующих частей:

ПолеРазмерОписание
page_header24 байтаДанные заголовка страницы
size2 байтаРазмер сжатых данных
dataНе фиксированСжатые данные

Основной единицей хранения в сжатом файле данных является фрагмент (chunk). Сжатая страница хранится в виде одного или нескольких фрагментов. Незанятое пространство фрагмента заполняется нулями. Размер каждого фрагмента настраивается на уровне отношений и может составлять 1/16, 1/8, 1/4 или 1/2 от BLCKSZ (размер блока - 8 Кбайт). Чем меньше размер фрагмента, тем выше степень сжатия, но и выше риск фрагментации.

Каждый фрагмент состоит из двух частей: заголовка и данных. Структура заголовка (PageCompressChunk) представлена в таблице:

ПолеРазмерОписание
blockno4 байтаНомер страницы, хранящейся в этом фрагменте
chunkseq1 байтПоследовательный номер фрагмента, начиная с 1, для проверки, что сжатая страница собирается из фрагментов в правильном порядке
WITHdata1 байтФлаг, указывающий на наличие данных в данном фрагменте
checksum2 байтаКонтрольная сумма фрагмента

В части данных фрагмента хранится сжатая страница:

  • chunk header N.1 – заголовок первого фрагмента N-ой страницы сегмента, chunk header N.2 - заголовок второго фрагмента N-ой страницы сегмента и так далее;
  • page header N – заголовок N-ой страницы сегмента;
  • size – размер сжатой страницы как сумма размеров сжатых данных во всех фрагментах страницы. Например, для нулевой страницы это сумма размеров данных, расположенных в полях compressed data 0.1, compressed data 0.2, compressed data 0.3 и compressed data 0.4;
  • compressed data N.1 – часть сжатой N-ой страницы, помещенной в первый фрагмент, compressed data N.2 - часть сжатой N-ой страницы, помещенной во второй фрагмент и так далее.

Доступ к PCD-файлам осуществляется с помощью системных вызовов read() и write().

Описание PCA-файла

PCA-файл используется для хранения информации о распределении фрагментов в PCD-файле. Его содержимое включает заголовок и несколько адресов. В заголовке (PageCompressHeader) содержится информация, представленная в таблице:

ПолеРазмерОписание
nblocks4 байтаОбщее количество страниц (блоков)
allocated_chunks4 байтаОбщее количество фрагментов, выделенных в PCD-файле
algorithm1 байтАлгоритм сжатия: 1 - pglz, 2 - lz4, 3 - zstd
level1 байтУровень сжатия. Имеет эффект только при выборе алгоритма сжатия zstd.

Диапазон значений зависит от версии библиотеки zstd и определяется путем вызова библиотечных функций ZSTD_minCLevel(), ZSTD_maxCLevel().

Версия zstd v1.4.4 позволяет установить значения в диапазоне [1-19].

Чтобы узнать, какой диапазон значений поддерживает версия библиотеки zstd в текущем окружении, необходимо выполнить команду в терминале:
$ zstd 2>&1 | grep level
chunks_per_block1 байтЧисло фрагментов для хранения сжатых данных одной страницы
prealloc_chunks1 байтЧисло предварительно выделенных фрагментов для хранения сжатых данных одной страницы

Каждая адресная запись соответствует одной странице. В таблице ниже показана информация, содержащаяся в адресной записи:

ПолеРазмерОписание
nchunks1 байтКоличество фрагментов, фактически используемых для хранения сжатой страницы
allocated_chunks1 байтКоличество фрагментов, выделенных для хранения сжатой страницы
chunknos4 * (2, 4, 8 или 16) + 4 байтовМассив из номеров фрагментов

Поскольку однажды выделенный фрагмент для сжатой страницы закрепляется за ней, то значения nchunks и allocated_chunks могут не совпадать, например, в следующих случаях:

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

Размер массива номеров фрагментов рассчитывается как BLCKSZ/(fragment_size) + 1, то есть максимальный размер одной сжатой страницы может занимать пространство BLCKSZ + (fragment_size):

  • для размера фрагмента 1/16 от BLCKSZ (512 байт) – 17 фрагментов (8704 байт);
  • для размера фрагмента 1/8 от BLCKSZ (1024 байт) – 9 фрагментов (9216 байт);
  • для размера фрагмента 1/4 от BLCKSZ (2048 байт) – 5 фрагментов (10240 байт);
  • для размера фрагмента 1/2 от BLCKSZ (4096 байт) – 3 фрагмента (12288 байт).

Достижение этого максимального размера может быть вызвано ошибкой сжатия или его неэффективным применением, приводящим к низкой степени компрессии, например, при сжатии бинарных данных с высокой энтропией: преобразование данных или уже сжатой информации (JPEG, H.264 и прочие). В этих случаях страница сохраняется в PCD-файл без сжатия, чтобы избежать лишних накладных расходов на разжатие данных при чтении с диска.

примечание

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

Внимание!

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

Для работы с PCA-файлами применяется метод отображения ввода-вывода в память, который осуществляется с помощью системного вызова mmap(). Непосредственная работа с памятью вместо использования системных вызовов read() и write() позволяет упростить логику межпроцессного взаимодействия и автоматизировать файловый ввод-вывод. Взаимодействие между процессами происходит благодаря тому, что несколько процессов СУБД Pangolin при обращении к страницам одного и того же сегмента отношения создают общие отображения одного и того же PCA-файла. В результате этого отображаемые страницы физической памяти становятся общими для этих процессов. Изменения в отображении, сделанные одним процессом, становятся видимыми для других процессов. Процедура отображения PCA-файла в память может быть пропущена, если она уже была выполнена и указатель на память отображения хранится в LRU кеше.

Изменения содержимого отображения ядро Linux автоматически возвращает обратно в PCA-файл, но по умолчанию нет никаких гарантий относительно того, когда именно произойдет такая синхронизация. Для обеспечения сохранности данных в файле процессы СУБД Pangolin явно синхронизируют изменения отображения с диском, используя системный вызов msync(). Это происходит каждый раз, когда выделяется определенное количество фрагментов, общий размер которых составляет 32 МБ. Эта мера необходима, так как изменения в PCA-файле не журналируются.

Размер в 32 МБ был выбран в качестве оптимального значения для обеспечения баланса между снижением производительности системы и предотвращением ситуации, когда после сбоя потребуется восстанавливать слишком много адресных записей в PCA-файле.

При использовании подхода с отображением ввода-вывода в память, размер PCA-файла на диске должен быть максимально возможным, чтобы в нем поместились все адресные записи сегмента отношения. Размер файла определяется количеством фрагментов, необходимых для хранения сжатых данных страницы (чем больше фрагментов, тем больше размер файла):

  • для 16 фрагментов (размер фрагмента 512 байт) - размер файла 9 МБ;
  • для 8 фрагментов (размер фрагмента 1024 байт) - размер файла 5.5 МБ;
  • для 4 фрагментов (размер фрагмента 2048 байт) - размер файла 3 МБ;
  • для 2 фрагментов (размер фрагмента 4096 байт) - размер файла 2 МБ.

Принимая во внимание объем занимаемого PCA-файлами места на диске, сжатие данных на уровне страниц не является подходящим решением для таблиц небольшого размера.

Анализ информации, хранящейся в PCA-файле

Для анализа информации, хранящейся в PCA-файле, в расширении pageinspect представлены следующие функции:

  1. Анализ содержимого заголовка PCA-файла – функция get_compress_address_header().

    Синтаксис функции:

    CREATE FUNCTION get_compress_address_header(
    IN relname text, -- имя объекта БД
    IN segno integer, -- номер сегмента файла данных (для каждого сегмента создается отдельный PCA-файл)
    OUT nblocks integer, -- общее количество страниц (блоков)
    OUT allocated_chunks integer, -- общее количество фрагментов, выделенных в PCD-файле
    OUT chunk_size integer, -- размер фрагмента
    OUT algorithm integer, -- алгоритм сжатия
    OUT last_synced_nblocks integer, -- общее количество страниц, при котором выполнена синхронизация отображения PCA-файла с диском
    OUT last_synced_allocated_chunks integer -- общее количество фрагментов, при котором выполнена синхронизация отображения PCA-файла с диском
    ) RETURNS record
    AS 'MODULE_PATHNAME', 'get_compress_address_header'
    LANGUAGE C STRICT PARALLEL SAFE;

    Пример запроса:

    SELECT * FROM get_compress_address_header('test_data_table', 0);

    nblocks | allocated_chunks | chunk_size | algorithm | last_synced_nblocks | last_synced_allocated_chunks
    ---------+------------------+------------+-----------+---------------------+------------------------------
    2 | 2 | 1024 | 1 | 2 | 2
    (1 row)
  2. Анализ содержимого адресной записи в PCA-файле – функция get_compress_address_items().

    Синтаксис функции:

    CREATE FUNCTION get_compress_address_items(
    IN relname text, -- имя объекта БД
    IN segno integer, -- номер сегмента файла данных (для каждого сегмента создается отдельный PCA-файл)
    OUT blkno integer, -- номер страницы, хранящейся в этом фрагменте
    OUT nchunks integer, -- количество фрагментов, фактически используемых для хранения сжатой страницы
    OUT allocated_chunks integer, -- количество фрагментов, выделенных для хранения сжатой страницы
    OUT chunknos integer[] -- массив из номеров фрагментов
    ) RETURNS SETOF record
    AS 'MODULE_PATHNAME', 'get_compress_address_items'
    LANGUAGE C STRICT PARALLEL SAFE;

    Пример запроса:

    SELECT * FROM get_compress_address_items('test_data_table', 0);
     blkno | nchunks | allocated_chunks | chunknos
    -------+---------+------------------+----------
    0 | 1 | 1 | {1}
    1 | 1 | 1 | {2}
    (2 rows)

Диагностика

В рамках функциональность существует запрет изменения параметров сжатия для объектов БД.

При попытке изменения

ALTER TABLE test_data_table SET(compresstype=none);

Сжатие таблицы завершается с ошибкой:

ERROR:  Unsupported feature
DETAIL: Don't allow ALTER an option "compresstype"

Аналогично при попытке сброса параметров сжатия таблицы:

ALTER TABLE test_data_table RESET(compresstype);
ERROR:  Unsupported feature
DETAIL: Don't allow ALTER an option "compresstype"

Ограничения

Функциональность имеет следующие ограничения:

  • Функциональность по умолчанию выключена.
  • Параметр full_page_writes должен быть включен. В таких сценариях, как сбой сервера или оперативное резервное копирование, страницы сжатых объектов БД с большей вероятностью будут находиться в несогласованном состоянии, чем обычные несжатые объекты, и их необходимо восстанавливать с помощью полностраничной записи.
  • Не поддерживается сжатие таблиц системного каталога, временных таблиц, последовательностей, индекса Brin и глобального индекса. Признак сжатия определяется на уровне объекта.
  • Не рекомендуется использовать постраничное сжатие данных отношений, расположенных в преобразованном табличном пространстве (TDE). В настоящее время сжатие происходит после защитного преобразования данных, что снижает эффективность этого процесса.
  • В процессе очистки (VACUUM) не поддерживается удаление пустых страниц в конце сегмента сжатого отношения. Возможно только усечение всего сегмента. Поскольку свободные страницы в конце сегмента могут быть представлены не в виде непрерывных фрагментов в конце PCD-файла, сокращение этих страниц может не привести к значительной экономии места в хранилище, а также может вызвать несогласованность содержимого файла сжатых данных (PCD) и файла сжатых адресов (PCA).
  • Постраничное сжатие не подходит для сжатия отношений небольшого размера. PCA-файл должен иметь определенный фиксированный размер на диске, который зависит от размера фрагмента: чем меньше размер фрагмента, тем больше размер файла. Например, при размере фрагмента 512 байт он составит около 9 МБ, поэтому для слишком маленьких отношений общее пространство, занимаемое файлами сжатых данных (PCA + PCD), может оказаться больше, чем без сжатия.
  • Не поддерживается изменение параметров сжатия.
  • Команды, которые полностью пересоздают файлы данных (VACUUM FULL, ALTER TABLE), могут привести к увеличению размера базы данных, если выполняются над сжатыми таблицами. Это обусловлено тем, что невозможно обнулить размер старого файла сжатых адресов (PCA), поскольку он может быть отображен в память одного из процессов и использоваться, например, для отложенной записи блоков данных. В настоящее время не существует эффективного способа проверки валидности отображенной памяти, поэтому старые файлы сжатых адресов (PCA) будут сохраняться до тех пор, пока процесс контрольной точки их не удалит.
  • Использование pg_repack для объектов со сжатием данных на уровне страниц не поддерживается.

Сценарии использования

Хранение данных отношений на диске в сжатом формате

В качестве примера отношения приведен B-tree индекс.

  1. Установите параметр enable_page_compression в значение true секцию bootstrap Pangolin Manager через команду:

    pangolin-manager-ctl -c /etc/pangolin-manager/postgres.yml edit-config $CLNAME
  2. Выполните перезагрузку кластера:

    pangolin-manager-ctl -c /etc/pangolin-manager/postgres.yml restart clustername
  3. Создайте сжатую таблицу:

    CREATE TABLE test_data_table(
    id bigint,
    col_btree text,
    col_gin text, col_gin_tsv tsvector,
    col_hash text,
    col_gist text, col_gist_tsv tsvector,
    col_spgist text )
    WITH (compresstype=pglz, compresslevel=1, compress_chunk_size=2048, compress_prealloc_chunks=0);
  4. Добавьте в таблицу тестовые данные:

    INSERT INTO test_data_table(id, col_btree, col_gin, col_hash, col_gist, col_spgist)
    values(1,
    'test_data_btree',
    'test_data_gin: Can a sheet slitter slit sheets?',
    'test_data_hash',
    'test_data_gist: Can a sheet slitter slit sheets?',
    'test_data_spgist.data0');
  5. Создайте индекс B-tree по таблице в сжатом виде:

    CREATE INDEX test_data_table_btree_index on test_data_table(col_btree)
    WITH (compresstype=pglz, compresslevel=1, compress_chunk_size=2048, compress_prealloc_chunks=0);
  6. Добавьте тестовые данные в поля таблицы, по которым построен индекс B-tree:

    INSERT INTO test_data_table(id, col_btree)
    SELECT s.id,
    (SELECT 'test_data_btree_' || string_agg(x, '')
    FROM (SELECT chr(ascii('A') + ((random() * id * 25)::integer % 26)::integer)
    FROM generate_series(1, 2000)) AS y(x))
    FROM generate_series(2, 10) AS s(id);
  7. Проверьте, что на мастере и реплике поиск данных в таблице выполняется по индексу B-tree:

    SELECT id, col_btree FROM test_data_table WHERE col_btree = 'test_data_btree';

    Ожидаемый результат: Поиск данных в таблице успешно выполняется по индексу B-tree:

                               QUERY PLAN
    -----------------------------------------------------------------
    Index Scan using test_data_table_btree_index on test_data_table
    Index Cond: (col_btree = 'test_data_btree'::text)
    (2 rows)
  8. Выполните контрольную точку на мастере, чтобы страницы из буферного кеша записались на диск:

    CHECKPOINT;

    Ожидание синхронизации мастера и реплики:

    do $$
    declare
    lag bigint;
    begin
    loop
    select pg_wal_lsn_diff(pg_current_wal_insert_lsn(), replay_lsn)
    from pg_stat_replication
    into lag;

    if lag = 0 then
    exit;
    end if;
    end loop;
    end
    $$;
  9. Создайте точку перезагрузки на реплике, чтобы страницы из буферного кеша записались на диск:

    CHECKPOINT;
  10. Проверьте, что на мастере и реплике созданы адресный файл (PCA) и файл сжатых данных (PCD) для сжатой таблицы:

    SELECT pg_relation_filepath('test_data_table') AS test_data_table_path
    \gset
    SELECT format('\! ls -l $PGDATA/%s*', :'test_data_table_path') AS  show_files
    \gset

    :show_files

    Для сжатой таблицы успешно созданы адресный файл (PCA) и файл сжатых данных (PCD):

    -rw------- 1 postgres postgres       0 Oct 21 10:41 /pgdata/06/data/base/5/16403
    -rw------- 1 postgres postgres 24576 Oct 21 10:49 /pgdata/06/data/base/5/16403_fsm
    -rw------- 1 postgres postgres 3196168 Oct 21 11:07 /pgdata/06/data/base/5/16403_pca
    -rw------- 1 postgres postgres 4096 Oct 21 11:07 /pgdata/06/data/base/5/16403_pcd
  11. Проверьте, что на мастере и реплике созданы адресный файл (PCA) и файл сжатых данных (PCD) для сжатого индекса B-tree:

    SELECT pg_relation_filepath('test_data_table_btree_index') AS test_data_table_btree_index_path
    \gset
    SELECT format('\! ls -l $PGDATA/%s*', :'test_data_table_btree_index_path') AS show_files
    \gset

    :show_files

    Для сжатого индекса B-tree успешно созданы адресный файл (PCA) и файл сжатых данных (PCD):

    -rw------- 1 postgres postgres       0 Oct 21 10:46 /pgdata/06/data/base/5/16408
    -rw------- 1 postgres postgres 3196168 Oct 21 11:07 /pgdata/06/data/base/5/16408_pca
    -rw------- 1 postgres postgres 114688 Oct 21 11:07 /pgdata/06/data/base/5/16408_pcd

Исследование содержимого файла адресов сжатых данных (PCA) на низком уровне

  1. Выполните действия сценария использования «Хранение данных отношений на диске в сжатом формате».

  2. Активируйте расширение pageinspect:

    CREATE EXTENSION pageinspect SCHEMA ext;
  3. Получите содержимое заголовка PCA-файла для таблицы на мастере и реплике:

    SELECT * FROM get_compress_address_header('test_data_table', 0);

    Ожидаемый результат: Данные заголовка PCA-файла для таблицы успешно получены:

    nblocks  | allocated_chunks | chunk_size | algorithm | last_synced_nblocks | last_synced_allocated_chunks
    ---------+------------------+------------+-----------+---------------------+------------------------------
    2 | 2 | 2048 | 1 | 2 | 2
    (1 row)

    Для хранения данных таблицы было выделено 2 страницы (nblocks) по 8 Кбайт, сжатое содержимое которых поместилось в двух фрагментах (allocated_chunks) на диске размером по 2 Кбайта (chunk_size).

  4. Получите информацию об адресах сжатых данных для таблицы на мастере и реплике:

    SELECT * FROM get_compress_address_items('test_data_table', 0);

    Ожидаемый результат: Информация об адресах сжатых данных успешно получен.

    blkno | nchunks | allocated_chunks | chunknos
    -------+---------+------------------+----------
    0 | 1 | 1 | {1}
    1 | 1 | 1 | {2}
    (2 rows)

    Видно, что для обеих страниц (blkno) на диске распределены по одному фрагменту (allocated_chunks), что равняется фактическому числу используемых фрагментов (nchunks). Нужно иметь в виду, что nchunks может быть меньше, либо равно allocated_chunks.

    Поле chunknos определяет месторасположение сжатых данных страницы в PCD-файле.

  5. На мастере и реплике получите число страниц таблицы через интерфейс мониторинга размера БД и убедитесь, что результат совпадает с числом страниц в заголовке PCA-файла:

    SELECT main FROM get_nblocks('test_data_table');

    Ожидаемый результат: Число страниц успешно получено. Результат совпадает с числом страниц в заголовке PCA-файла:

    main
    ------
    2
    (1 row)

Оценка степени сжатия содержимого объекта БД

  1. Активируйте расширение pageinspect:

    CREATE EXTENSION pageinspect SCHEMA ext;
  2. Создайте и заполните несжатую таблицу тестовыми данными:

    CREATE TABLE uncompressed_test_data_table(a int, b int, c text);
    INSERT INTO uncompressed_test_data_table select id, id, id::text FROM generate_series(1, 10000) id;
  3. Получите число страниц несжатой таблицы:

    SELECT main AS nblocks FROM get_nblocks('uncompressed_test_data_table')
    \gset
    SELECT :nblocks;

    Число страниц несжатой таблицы успешно получено:

    ?column?
    ----------
    55
    (1 row)
  4. Оцените степень сжатия данных таблицы без учета раскладки данных по фрагментам в PCD-файле:

    SELECT relation_estimate_compression_ratio('uncompressed_test_data_table', 'pglz', 0, 512, FALSE, 0, :nblocks-1);
    SELECT relation_estimate_compression_ratio('uncompressed_test_data_table', 'pglz', 0, 1024, FALSE, 0, :nblocks-1);
    SELECT relation_estimate_compression_ratio('uncompressed_test_data_table', 'pglz', 0, 2048, FALSE, 0, :nblocks-1);
    SELECT relation_estimate_compression_ratio('uncompressed_test_data_table', 'pglz', 0, 4096, FALSE, 0, :nblocks-1);

    Ожидаемый результат: Степень сжатия для всех вариантов выполнения функции одинаковая и является предельной для заданного алгоритма сжатия:

    relation_estimate_compression_ratio
    -------------------------------------
    2.3646974849897133
    (1 row)
  5. Оцените степень сжатия данных таблицы с учетом раскладки данных по фрагментам в PCD-файле:

    SELECT relation_estimate_compression_ratio('uncompressed_test_data_table', 'pglz', 0, 512, TRUE, 0, :nblocks-1);

    relation_estimate_compression_ratio
    -------------------------------------
    2.2916666666666665
    (1 row)

    SELECT relation_estimate_compression_ratio('uncompressed_test_data_table', 'pglz', 0, 1024, TRUE, 0, :nblocks-1);

    relation_estimate_compression_ratio
    -------------------------------------
    2.0276497695852536
    (1 row)

    SELECT relation_estimate_compression_ratio('uncompressed_test_data_table', 'pglz', 0, 2048, TRUE, 0, :nblocks-1);

    relation_estimate_compression_ratio
    -------------------------------------
    2.018348623853211
    (1 row)

    SELECT relation_estimate_compression_ratio('uncompressed_test_data_table', 'pglz', 0, 4096, TRUE, 0, :nblocks-1);

    relation_estimate_compression_ratio
    -------------------------------------
    2
    (1 row)

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

  6. Создайте сжатую таблицу с алгоритмом сжатия pglz, размером фрагмента 512 байт и включите в нее данные из несжатой таблицы:

    CREATE TABLE compressed_test_data_table WITH (compresstype=pglz, compress_chunk_size=512)
    AS SELECT * FROM uncompressed_test_data_table;
  7. Выполните контрольную точку на мастере, чтобы страницы из буферного кеша записались на диск:

    CHECKPOINT;

    Ожидание синхронизации мастера и реплики:

    do $$
    declare
    lag bigint;
    begin
    loop
    select pg_wal_lsn_diff(pg_current_wal_insert_lsn(), replay_lsn)
    from pg_stat_replication
    into lag;

    if lag = 0 then
    exit;
    end if;
    end loop;
    end
    $$;
  8. Получите число страниц сжатой таблицы:

    SELECT main AS nblocks FROM get_nblocks('compressed_test_data_table')
    \gset
    SELECT :nblocks;

    Число страниц сжатой таблицы успешно получено:

    ?column?
    ----------
    55
    (1 row)
  9. Оцените степень сжатия данных сжатой таблицы с учетом раскладки данных по фрагментам в PCD-файле:

    SELECT relation_estimate_compression_ratio('compressed_test_data_table', 'pglz', 0, 512, TRUE, 0, :nblocks-1);
    relation_estimate_compression_ratio
    -------------------------------------
    2.2916666666666665
    (1 row)
  10. Получите размер PCD-файла сжатой таблицы:

SELECT pg_relation_filepath('compressed_test_data_table') AS compressed_test_data_table_path
\gset
SELECT format('\! stat --printf="%%s\n" $PGDATA/%s*_pcd', :'compressed_test_data_table_path') AS show_pcd_size
\gset

:show_pcd_size

Ожидаемый результат: Размер PCD-файла в байтах сжатой таблицы успешно получен.

196608
  1. Рассчитайте коэффициент сжатия, где делителем является размер PCD-файла, полученный на предыдущем шаге:

    SELECT :nblocks * 8192 / 196608.;

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

      ?column?
    --------------------
    2.2916666666666667
    (1 row)