F.11. Колоночно-ориентированный формат (ORC) Citus Columnar#
F.11. Колоночно-ориентированный формат (ORC) Citus Columnar
- F.11.1. О хранилище Citus Columnar
- F.11.2. Введение
- F.11.3. Установка Сolumnar
- F.11.4. Основные принципы колоночного хранения данных
- F.11.5. Использование функциональности метода доступа к таблице
- F.11.6. Использование колоночного формата
- F.11.7. Микробенчмарк производительности
- F.11.8. Общие рекомендации
- F.11.9. Работа с Time Series-данными
F.11.1. О хранилище Citus Columnar
Версия: 12.1.5
Страница проекта www.citusdata.com
Copyright © Citus Data, Inc.
F.11.2. Введение
В традиционных аналитических системах (OLAP) размер и объем данных со временем увеличиваются, что может привести к различным проблемам с производительностью. Хранилище Columnar обеспечивает значительные преимущества в случаях, когда системы управления базами данных (СУБД) используются для аналитических задач, в которых пакетная загрузка является основным способом загрузки данных.
При использовании данного формата можно сжимать таблицы для значительного снижения потребления ресурсов и увеличения производительности. При этом нет необходимости переключения на СУБД с массово-параллельной архитектурой. Колоночная структура ORC в хранилище Columnar обеспечивает высокую производительность за счет чтения соответствующих данных с диска и их более эффективного сжатия.
Примечание
Хранилище Columnar не полностью заменяет СУБД с массивной параллельной обработкой, но исключает их использование в некоторых случаях. Решение о переходе на MPP СУБД зависит от конкретных объемов данных и требований к рабочей нагрузке. Хотя пороговое значение варьируется, обычно оно находится в пределах десятков терабайт.
F.11.3. Установка Сolumnar
Хранилище Columnar устанавливается как расширение. Чтобы добавить Columnar в вашу локальную базу данных Tantor SE, подключитесь с помощью psql и выполните:
CREATE EXTENSION pg_columnar;
Теперь вы можете создавать и использовать колоночные таблицы в вашей базе данных.
F.11.4. Основные принципы колоночного хранения данных
Стандартный метод хранения данных heap
в Tantor SE хорошо работает для нагрузки OLTP:
Поддержка операций
UPDATE
/DELETE
Эффективный поиск одиночных кортежей
Колоночные таблицы больше всего подходят для аналитических задач или хранения данных складских помещений, т.к. они предоставляют следующие преимущества по сравнению с методом хранения heap:
Сжатие
Не читаются ненужные столбцы
Эффективное использование VACUUM
F.11.5. Использование функциональности метода доступа к таблице
Преимущества Columnar над cstore_fdw:
Хранение в колонках основано на API-методе доступа к таблице. В большинстве случаев этот API позволяет выполнять аналогичные операции как над колоночными таблицами, так и над традиционными строковыми таблицами (таблицами heap).
Поддержка журнала предварительной записи (WAL)
Поддержка транзакций
ROLLBACK
;Поддержка физической репликации;
Поддержка восстановления, включая восстановление до определенного момента времени (PITR);
Поддержка
pg_dump
иpg_upgrade
без необходимости специальных операций или дополнительных шагов;Поддерживает большую часть функциональности, которая работает с обычными таблицами heap (строковыми таблицами)
Ограничения
Только для добавления (
UPDATE
/DELETE
операции не поддерживаются);Не освобождает пространство (например, откатившиеся транзакции могут все еще занимать дисковое пространство);
Поддержка сканирования битовых индексов отсутствует;
Не поддерживается tidscans;
Поддержка выборочного сканирования отсутствует;
Метод хранения TOAST не поддерживается (большие значения хранятся внутренне);
ON CONFLICT не поддерживается (кроме DO NOTHING без указания цели);
Уровень строковых блокировок не поддерживается (
SELECT ... FOR SHARE
,SELECT ... FOR UPDATE
);Уровень изоляции serializable не поддерживается.
Поддерживаются версии Tantor SE 14+;
Внешние ключи, ограничения уникальности или ограничение-исключения не поддерживаются.
Логическое декодирование не поддерживается;
Параллельные сканирования внутри узла не поддерживаются;
AFTER ... FOR EACH ROW
триггеры не поддерживаются;Непротоколируемые колоночные таблицы не поддерживаются.
Со временем, перечисленные выше ограничения будут постепенно сниматься.
F.11.6. Использование колоночного формата
Создайте колоночную таблицу, указав опцию
USING columnar
:
CREATE TABLE my_columnar_table ( id INT, i1 INT, i2 INT8, n NUMERIC, t TEXT ) USING columnar;
Вставьте данные в таблицу и выведите их как обычно (с учетом ограничений, перечисленных выше).
Чтобы просмотреть внутреннюю статистику для таблицы, используйте
VACUUM VERBOSE
. Обратите внимание, что
VACUUM
(без FULL
) гораздо
быстрее работает на колоночной таблице, потому что сканируются только
метаданные, а не фактические данные.
Установите параметры таблицы с помощью ALTER TABLE
:
ALTER TABLE my_columnar_table SET( columnar.compression = 'none', columnar.stripe_row_limit = 10000 );
Можно настроить следующие параметры:
columnar.compression:
['none'|'pglz'|'zstd'|'lz4'|'lz4hc']
- устанавливает тип сжатия для вновь вставленных данных. Существующие данные повторно не сжимаются и не распаковываются. Значение по умолчанию -'zstd'
(если поддержка данного алгоритма была указана при компиляции).columnar.compression_level:
<integer>
- устанавливает уровень сжатия. Допустимые значения от1
до19
. Если метод сжатия не поддерживает выбранный уровень, вместо него будет выбрано ближайшее к нему значение. Значение по умолчанию3
.columnar.stripe_row_limit:
<integer>
- максимальное количество строк в полосе для вновь вставленных данных. Существующие полосы данных не будут изменены и могут содержать больше строк, чем указанное максимальное значение. Допустимые значения от1000
до100000000
. Значение по умолчанию -150000
.columnar.chunk_group_row_limit:
<integer>
- максимальное количество строк в блоке для вновь вставленных данных. Существующие блоки данных не будут изменены и могут содержать больше строк, чем это максимальное значение. Допустимые значения от1000
до100000000
. Значение по умолчанию -10000
.
Можно просмотреть параметры всех таблиц, используя:
SELECT * from columnar.options;
Или конкретной таблицы:
SELECT * FROM columnar.options WHERE relation = 'my_columnar_table'::regclass;
Можно сбросить один или несколько параметров таблицы до их значений по умолчанию (или текущих значений, установленных командой SET
)
с помощью columnar.alter_columnar_table_reset
:
SELECT columnar.alter_columnar_table_reset( 'my_columnar_table', chunk_group_row_limit => true );
Установите общие параметры columnar
, используя SET
:
SET columnar.compression TO 'none'; SET columnar.enable_qual_pushdown TO false; SET columnar.compression TO default;
columnar.enable_custom_scan:
<boolean>
- включает использование настраиваемого сканирования для передачи проекций и квалификаторов непосредственно на уровень хранения. Значение по умолчанию -true
.columnar.enable_qual_pushdown:
<boolean>
- включает передачу квалификаторов в колоночный формат. Не сработает, еслиcolumnar.enable_custom_scan
неtrue
. Значение по умолчанию -true
.columnar.qual_pushdown_correlation_threshold:
<real>
- порог корреляции при достижении которого предпринимается попытка передать квалификатор, ссылающийся на заданную колонку. При установленном значении0
предпринимается попытка передать все квалификаторы, даже если колонка некоррелированная. Допустимые значения - от0.0
до1.0
. Значение по умолчанию -0.9
.columnar.max_custom_scan_paths:
<integer>
- максимальное количество путей настраиваемого сканирования, которые нужно сгенерировать для колоночной таблицы при планировании. Допустимые значения от1
до1024
. Значение по умолчанию -64
.columnar.planner_debug_level - уровень сообщений по планированию колоночного хранения в порядке увеличения детальности:
'log'
'warning'
'notice'
'info'
'debug'
'debug1'
'debug2'
'debug3'
'debug4'
'debug5'
Значение по умолчанию —
debug3
.Также можно настроить следующие параметры таблиц:
columnar.compression
columnar.compression_level
,columnar.stripe_row_limit
,columnar.chunk_group_row_limit
. Но изменения коснутся только вновь создаваемых таблиц, а не вновь создаваемых полос в существующих таблицах.
F.11.6.1. Преобразование между heap и columnar
Примечание
Убедитесь, что вы учитываете все дополнительные функции, которые могут
использоваться в таблице перед преобразованием формата (такие как
защита на уровне строк, параметры хранения, ограничения, наследование и т.д.) и
убедитесь, что они корректно воспроизводятся в новой таблице или секции. LIKE
, использованный ниже, является
сокращением, которое работает только в стандартных случаях.
CREATE TABLE my_table(i INT8 DEFAULT '7'); INSERT INTO my_table VALUES(1); -- convert to columnar SELECT columnar.alter_table_set_access_method('my_table', 'columnar'); -- back to row SELECT columnar.alter_table_set_access_method('my_table', 'heap');
F.11.6.2. Тип таблицы по умолчанию
Тип таблицы по умолчанию для базы данных по умолчанию -
heap
. Вы можете изменить тип таблицы по умолчанию, используя
default_table_access_method
:
SET default_table_access_method TO 'columnar';
Теперь для любой таблицы, которую вы создаете вручную с помощью
CREATE TABLE
, тип по умолчанию будет
columnar
.
F.11.6.3. Секционирование
Колоночные таблицы можно использовать как секции; и секционированная таблица может состоять из разных сочетаний секций, организованных как кучи или колонки.
CREATE TABLE parent(ts timestamptz, i int, n numeric, s text) PARTITION BY RANGE (ts); -- columnar partition CREATE TABLE p0 PARTITION OF parent FOR VALUES FROM ('2020-01-01') TO ('2020-02-01') USING COLUMNAR; -- columnar partition CREATE TABLE p1 PARTITION OF parent FOR VALUES FROM ('2020-02-01') TO ('2020-03-01') USING COLUMNAR; -- row partition CREATE TABLE p2 PARTITION OF parent FOR VALUES FROM ('2020-03-01') TO ('2020-04-01'); INSERT INTO parent VALUES ('2020-01-15', 10, 100, 'one thousand'); -- columnar INSERT INTO parent VALUES ('2020-02-15', 20, 200, 'two thousand'); -- columnar INSERT INTO parent VALUES ('2020-03-15', 30, 300, 'three thousand'); -- row
При выполнении операций с секционированной таблицей, состоящей
из строк и колонок, обратите внимание на следующие особенности
операций, которые поддерживаются для строк, но не поддерживаются для колонок
(например, UPDATE
,
DELETE
, блокировки строк и т.д.):
Если операция применяется на определенной таблице,в которой данные хранятся в кучах, (например,
UPDATE p2 SET i = i + 1
), она будет успешной; но если она выполняется на колоночной таблице (напримерUPDATE p1 SET i = i + 1
), она не пройдет.Если операция выполняется на секционированной таблице и содержит предложение WHERE, которое исключает все таблицы, сохраненные в виде колонок (например,
UPDATE parent SET i = i + 1 WHERE ts = '2020-03-15'
), она выполняется успешно.Если операция выполняется на секционированной таблице, но не исключает все ctrwbb колонок, она не пройдет; даже если обновляемые фактические данные затрагивают только таблицы heap (например,
UPDATE parent SET i = i + 1 WHERE n = 300
).
Обратите внимание, что Columnar поддерживает индексы btree
и
hash
(и ограничения, которые требуют их использования), но не поддерживает индексы gist
,
gin
, spgist
и
brin
. По этой причине, если некоторые
таблицы имеют колоночный формат и если индекс не
поддерживается, невозможно создать индексы непосредственно на
секционированной (родительской) таблице. В таком случае нужно создать
индекс на отдельных таблицах heap. То же самое касается
ограничений, которые требуют использование индексов, например:
CREATE INDEX p2_ts_idx ON p2 (ts); CREATE UNIQUE INDEX p2_i_unique ON p2 (i); ALTER TABLE p2 ADD UNIQUE (n);
F.11.7. Микробенчмарк производительности
Важно
Этот микробенчмарк не предназначен для имитации каких-либо реальных рабочих нагрузок. Степень сжатия, а следовательно, производительность, будет зависеть от конкретной рабочей нагрузки. Данный бенчмарк имитирует нагрузку с целью демонстрации преимуществ хранения данных в формате columnar.
Установите plpython3u перед запуском теста:
CREATE EXTENSION plpython3u;
Схема:
CREATE TABLE perf_row( id INT8, ts TIMESTAMPTZ, customer_id INT8, vendor_id INT8, name TEXT, description TEXT, value NUMERIC, quantity INT4 ); CREATE TABLE perf_columnar(LIKE perf_row) USING COLUMNAR;
Функции для генерации данных:
CREATE OR REPLACE FUNCTION random_words(n INT4) RETURNS TEXT LANGUAGE plpython3u AS $$ import random t = '' words = ['zero','one','two','three','four','five','six','seven','eight','nine','ten'] for i in range(0,n): if (i != 0): t += ' ' r = random.randint(0,len(words)-1) t += words[r] return t $$;
Вставьте данные, используя функцию random_words
:
INSERT INTO perf_row SELECT g, -- id '2020-01-01'::timestamptz + ('1 minute'::interval * g), -- ts (random() * 1000000)::INT4, -- customer_id (random() * 100)::INT4, -- vendor_id random_words(7), -- name random_words(100), -- description (random() * 100000)::INT4/100.0, -- value (random() * 100)::INT4 -- quantity FROM generate_series(1,75000000) g; INSERT INTO perf_columnar SELECT * FROM perf_row;
Проверьте уровень сжатия:
=> SELECT pg_total_relation_size('perf_row')::numeric/pg_total_relation_size('perf_columnar') AS compression_ratio; compression_ratio -------------------- 5.3958044063457513 (1 row)
Общий коэффициент сжатия колоночной таблицы по сравнению со сжатием тех же данных, хранящихся в куче, составляет 5.4X.
=> VACUUM VERBOSE perf_columnar; INFO: statistics for "perf_columnar": storage id: 10000000000 total file size: 8761368576, total data size: 8734266196 compression rate: 5.01x total row count: 75000000, stripe count: 500, average rows per stripe: 150000 chunk count: 60000, containing data for dropped columns: 0, zstd compressed: 60000
VACUUM VERBOSE
сообщает более низкий коэффициент сжатия, потому что он усредняет коэффициент сжатия отдельных блоков и учитывает экономию метаданных колоночного формата.
Характеристики системы:
Azure VM: Стандарт D2s v3 (2 виртуальных процессора, 8 ГиБ памяти)
Linux (ubuntu 18.04)
Дисковый накопитель данных: Стандартный HDD (512GB, максимум 500 IOPS, максимум 60 МБ/с)
PostgreSQL 13 (
--with-llvm
,--with-python
)shared_buffers = 128MB
max_parallel_workers_per_gather = 0
jit = on
Примечание
Поскольку использовалась система с достаточным объемом физической памяти для хранения большой части таблицы, преимущества колоночного ввода-вывода не демонстрируются в полной мере во время выполнения запроса, если размер данных значительно не увеличивается.
Запросы приложения:
-- OFFSET 1000 so that no rows are returned, and we collect only timings SELECT vendor_id, SUM(quantity) FROM perf_row GROUP BY vendor_id OFFSET 1000; SELECT vendor_id, SUM(quantity) FROM perf_row GROUP BY vendor_id OFFSET 1000; SELECT vendor_id, SUM(quantity) FROM perf_row GROUP BY vendor_id OFFSET 1000; SELECT vendor_id, SUM(quantity) FROM perf_columnar GROUP BY vendor_id OFFSET 1000; SELECT vendor_id, SUM(quantity) FROM perf_columnar GROUP BY vendor_id OFFSET 1000; SELECT vendor_id, SUM(quantity) FROM perf_columnar GROUP BY vendor_id OFFSET 1000;
- Timing (median of three runs):
строка: 436с
колоночный: 16s
ускорение: 27X
F.11.8. Общие рекомендации
F.11.8.1. Выбор размера полосы (Stripe Size) и размера «куска» (Chunk Size)
Определение оптимальных размеров полос и блоков для колоночных
таблиц
в Tantor SE зависит от
различных факторов, таких как тип данных, шаблоны запросов и характеристики оборудования.
Вот некоторые рекомендации:
Размер полосы: Оптимальный размер полосы может зависеть от типичного размера запросов. Если запросы обычно возвращают большое количество строк, больший размер полосы может быть более эффективным, так как он уменьшает количество операций ввода/вывода. С другой стороны, если запросы часто возвращают небольшой подмножество данных, предпочтительнее может быть меньший размер полосы.
Размер блока: Это определяет, сколько данных будет сжиматься за один раз. Меньший размер блока может привести к более высокому коэффициенту сжатия, но может увеличить накладные расходы на сжатие. Больший размер блока может уменьшить накладные расходы, но потенциально снизить коэффициент сжатия.
Тестирование и Настройка: Важно проводить тестирование с реальными данными и запросами, чтобы определить оптимальные настройки для вашей конкретной ситуации. Вы можете начать с настроек, рекомендованных по умолчанию, а затем экспериментировать с различными размерами полос и блоков, чтобы увидеть, как это влияет на производительность запросов и коэффициенты сжатия.
Аппаратные свойства: Также стоит учитывать характеристики вашего оборудования, такие как пропускная способность диска и процессора, так как это может повлиять на то, какие размеры полос и блоков будут наиболее эффективными.
В конечном итоге, оптимальные размеры полосы и блока зависят от уникальных характеристик вашей среды, данных и шаблонов запросов.
F.11.8.2. Алгоритмы сжатия
Выбор алгоритма сжатия для columnar
таблиц в Tantor SE
зависит от нескольких факторов, включая характер данных, требования к производительности,
и технические характеристики оборудования. Вот несколько рекомендаций, которые могут помочь вам принять решение:
none
: Этот тип сжатия не применяет никакого сжатия к данным. Он может быть полезен, если ваши данные уже сжаты, или если у вас очень высокие требования к производительности и достаточно дискового пространства.lz4
: обеспечивает быстрое сжатие и распаковку данных. Это может быть полезно, если у вас высокие требования к производительности, но вы все же хотите сэкономить немного дискового пространства.zstd
: предлагает более высокое соотношение сжатия по сравнению сLZ4
, но требует больше процессорного времени для сжатия и распаковки данных. Этот алгоритм может быть полезен, если у вас ограниченное дисковое пространство и вы готовы потратить немного больше процессорного времени на сжатие данных.
Важно отметить, что выбор алгоритма сжатия является компромиссом между производительностью (скоростью сжатия и распаковки) и дисковым пространством. Кроме того, эффективность каждого алгоритма сжатия может значительно зависеть от характера ваших данных. Поэтому рекомендуется провести некоторые тесты с реальными данными и запросами, чтобы определить наиболее подходящий алгоритм сжатия в вашей ситуации.
F.11.9. Работа с Time Series-данными
F.11.9.1. Создание таблиц
Time Series-данные характеризуются последовательной записью значений по возрастанию во времени. Для таких данных важна упорядоченность.
Создайте необходимые расширения:
create extension pg_columnar; create extension plpython3u;
Создайте тестовую таблицу:
CREATE TABLE perf_columnar( id INT8, ts TIMESTAMPTZ, customer_id INT8, vendor_id INT8, name TEXT, description TEXT, value NUMERIC, quantity INT4 ) USING columnar; ALTER TABLE public.perf_columnar SET( columnar.compression = 'lz4', columnar.stripe_row_limit = 100000, columnar.chunk_group_row_limit = 10000 );
Создайте функцию для генерации случайного текста:
CREATE OR REPLACE FUNCTION random_words(n INT4) RETURNS TEXT LANGUAGE plpython3u AS $$ import random t = '' words = ['zero','one','two','three','four','five','six','seven','eight','nine','ten'] for i in range(0, n): if (i != 0): t += ' ' r = random.randint(0, len(words) - 1) t += words[r] return t $$;
Сгенерируйте тестовые данные:
INSERT INTO perf_columnar SELECT g, -- id '2023-06-01'::timestamptz + ('1 minute'::interval * g), -- ts (random() * 1000000)::INT4, -- customer_id (random() * 100)::INT4, -- vendor_id random_words(7), -- name random_words(100), -- description (random() * 100000)::INT4/100.0, -- value (random() * 100)::INT4 -- quantity FROM generate_series(1,7500000) g;
Как видно из приведенного выше запроса, мы вставили данные, отсортированные по ts.
Соберите статистику с помощью VACUUM (ANALYZE, VERBOSE)
:
VACUUM ANALYZE perf_columnar;
Создайте копию таблицы с другими параметрами:
CREATE TABLE perf_columnar2(LIKE perf_columnar) USING COLUMNAR; ALTER TABLE public.perf_columnar2 SET( columnar.compression = 'zstd', columnar.stripe_row_limit = 10000, columnar.chunk_group_row_limit = 1000 ); INSERT INTO perf_columnar2 SELECT * FROM perf_columnar; VACUUM ANALYZE perf_columnar2;
Проверьте размер получившихся таблиц:
test_db=# \dt+ List of relations Schema | Name | Type | Owner | Persistence | Access method | Size | Description --------+----------------+-------+----------+-------------+---------------+---------+------------- public | perf_columnar | table | postgres | permanent | columnar | 1886 MB | public | perf_columnar2 | table | postgres | permanent | columnar | 850 MB | (2 rows)
Как видно, таблица perf_columnar
в два раза больше, чем perf_columnar2
.
Проверьте параметры таблиц:
test_db=# select * from columnar.options; relation | chunk_group_row_limit | stripe_row_limit | compression | compression_level ----------------+-----------------------+------------------+-------------+------------------- perf_columnar | 10000 | 100000 | lz4 | 3 perf_columnar2 | 1000 | 10000 | zstd | 3 (2 rows)
F.11.9.2. Сравнение производительности запросов
Выполните типичный запрос для чтения набора записей из таблицы perf_columnar
за определенный промежуток времени:
explain (analyze, verbose, buffers) select ts from perf_columnar where ts < '2023-06-01 10:00:00'::timestamp with time zone and ts > '2023-06-01 10:00:05'::timestamp with time zone; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------------------------- Custom Scan (ColumnarScan) on public.perf_columnar (cost=0.00..401.12 rows=4 width=8) (actual time=18.838..49.001 rows=4 loops=1) Output: ts Filter: ((perf_columnar.ts < '2023-06-01 10:00:00+03'::timestamp with time zone) AND (perf_columnar.ts > '2023-06-01 10:00:05+03'::timestamp with time zone)) Rows Removed by Filter: 9996 Columnar Projected Columns: ts Columnar Chunk Group Filters: ((ts < '2023-06-01 10:00:00+03'::timestamp with time zone) AND (ts > '2023-06-01 10:00:05+03'::timestamp with time zone)) Columnar Chunk Groups Removed by Filter: 749 <----------- Buffers: shared hit=3833 read=264 <----------- Query Identifier: 1607994334608619710 Planning: Buffers: shared hit=52 read=7 Planning Time: 12.789 ms Execution Time: 49.188 ms (13 rows)
Выполните типичный запрос для чтения набора записей из таблицы perf_columnar2
за определенный промежуток времени:
explain (analyze, verbose, buffers) select ts from perf_columnar2 where ts < '2020-01-01 10:00:00'::timestamp with time zone and ts > '2020-01-01 10:00:05'::timestamp with time zone; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------- Custom Scan (ColumnarScan) on public.perf_columnar2 (cost=0.00..17.95 rows=52 width=8) (actual time=5.175..58.532 rows=49 loops=1) Output: ts Filter: ((perf_columnar2.ts < '2020-01-01 10:00:00+03'::timestamp with time zone) AND (perf_columnar2.ts > '2020-01-01 10:00:05+03'::timestamp with time zone)) Rows Removed by Filter: 951 Columnar Projected Columns: ts Columnar Chunk Group Filters: ((ts < '2020-01-01 10:00:00+03'::timestamp with time zone) AND (ts > '2020-01-01 10:00:05+03'::timestamp with time zone)) Columnar Chunk Groups Removed by Filter: 7499 <----------- Buffers: shared hit=40824 read=1 <----------- Query Identifier: -801549076851693482 Planning: Buffers: shared hit=155 Planning Time: 1.086 ms Execution Time: 58.717 ms (13 rows)
Обратите внимание на Columnar Chunk Groups Removed by Filter: 749
в первом случае и 7499
во втором.
Однако, количество прочитанных буферов во втором запросе гораздо выше.
Терминология для понимания поведения этих запросов:
Stripe
: Этот тип не применяет никакого сжатия к данным. Он может быть полезен, если ваши данные уже сжаты, или если у вас очень высокие требования к производительности и достаточно дискового пространства.Chunk Group
: полосы разбиваются на группы блоков по 10 000 строк (по умолчанию).Chunk
: Каждая группа блоков состоит из одного блока для каждого столбца.Chunk
является единицей сжатия, и для каждого блока отслеживаются минимальные/максимальные значения, что позволяет фильтровать группы блоков (Chunk Group Filtering
).Chunk Group Filtering
: когда предложение запросаWHERE
не соответствует ни одному кортежу в блоке, и можно определить это по минимальному/максимальному значению для блока, тогда фильтрация группы блоков просто пропускает всю группу блоков без ее распаковки.
Как мы видим, были отфильтрованы группы блоков 749/7499
, что означает,
что строки 7490000/7499000
были отфильтрованы без необходимости извлечения или распаковки
данных. Только одна группа блоков (10 000 и 1 000 строк) требовала извлечения и
распаковки, поэтому запрос занял всего несколько миллисекунд.
Но, как видно из планов запросов, в первом случае было использовано 30 МБ, а во втором - в десять раз больше - 319 МБ.
F.11.9.3. Использование индексов
Columnar поддерживает индексы btree
и hash
(и ограничения, требующие их),
но не поддерживает типы индексов, такие как gist
, gin
,
spgist
и brin
.
Рассмотрите возможность создания индексов для таблиц perf_columnar
и
perf_columnar2
:
create index test on perf_columnar2 (ts); create index test_h on perf_columnar (ts);
Проверьте размер индексов:
test_db=# \di+ List of relations Schema | Name | Type | Owner | Table | Persistence | Access method | Size | Description --------+--------+-------+----------+----------------+-------------+---------------+--------+------------- public | test | index | postgres | perf_columnar2 | permanent | btree | 161 MB | public | test_h | index | postgres | perf_columnar | permanent | btree | 161 MB | (2 rows)
Размер индексов одинаковый.
Отключите принудительное использование Custom Scan
:
SET columnar.enable_custom_scan TO false;
Повторно выполните типичный запрос для чтения набора записей из таблицы perf_columnar
для определенного временного интервала:
explain (analyze, verbose, buffers) select ts from perf_columnar where ts < '2023-06-01 10:00:00'::timestamp with time zone and ts > '2023-06-01 10:00:05'::timestamp with time zone; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------------------------------- Index Scan using test_h on public.perf_columnar (cost=0.43..3217.48 rows=4 width=8) (actual time=402.144..402.204 rows=4 loops=1) Output: ts Index Cond: ((perf_columnar.ts < '2023-06-01 10:00:00+03'::timestamp with time zone) AND (perf_columnar.ts > '2023-06-01 10:00:05+03'::timestamp with time zone)) Buffers: shared hit=181 read=3232 <----------- Query Identifier: 1607994334608619710 Planning: Buffers: shared hit=20 read=5 Planning Time: 16.278 ms Execution Time: 402.386 ms (9 rows)
Почти такое же количество буферов было просканировано, как при использовании
Custom Scan
.
Повторно выполните типичный запрос для чтения набора записей из таблицы perf_columnar2
для определенного временного интервала:
explain (analyze, verbose, buffers) select ts from perf_columnar2 where ts < '2020-01-01 10:00:00'::timestamp with time zone and ts > '2020-01-01 10:00:05'::timestamp with time zone; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- Index Scan using test on public.perf_columnar2 (cost=0.43..153.05 rows=52 width=8) (actual time=14.620..14.821 rows=49 loops=1) Output: ts Index Cond: ((perf_columnar2.ts < '2020-01-01 10:00:00+03'::timestamp with time zone) AND (perf_columnar2.ts > '2020-01-01 10:00:05+03'::timestamp with time zone)) Buffers: shared hit=372 read=145 <----------- Query Identifier: -801549076851693482 Planning: Buffers: shared hit=97 Planning Time: 0.813 ms Execution Time: 14.978 ms (13 rows)
Для второй таблицы было просканировано гораздо меньше буферов, и запрос выполняется наиболее оптимальным образом.
F.11.9.4. Заключения
Можно сделать следующие выводы:
Самым эффективным методом сжатия данных является
zstd
;Размер полосы и блока влияет на количество буферов, сканируемых с использованием метода
Chunk Group Filtering
;При чтении небольшого объема данных использование индекса может оказаться более эффективным;
Чтобы проверить гипотезу, необходимо принудительно включить использование индексов с помощью команды
SET columnar.enable_custom_scan TO false
;Последовательная запись данных TS может значительно сократить размер индексов и объем распакованных блоков (
Chunk
). Поэтому рекомендуется сортировать данные перед их вставкой в базу данных или использовать кластеризацию (cluster
).