Каждые compress_threshold строк (по умолчанию: 1200) образуют Range. В пределах Range данные конкретного столбца (содержащие compress_threshold строк) называются Stripe. Если данные столбца внутри Stripe особенно велики, Stripe разбивается на несколько фрагментов по 1 МБ. При чтении система не обязательно выбирает весь объем compress_threshold за один раз.
Параметр compress_threshold также влияет на коэффициент сжатия:
Сжатие большего пакета данных одновременно часто дает лучший коэффициент сжатия. Это связано с тем, что повторяющиеся паттерны с большей вероятностью будут захвачены в рамках одного пакета. Если данные разделены между несколькими пакетами, эти паттерны могут быть фрагментированы, что снижает эффективность сжатия.
compress_threshold на производительность чтения| ID | compress_threshold=1200 | 3600 | 10000 | 50000 |
|---|---|---|---|---|
| 1 | 8.8032 | 7.7846 | 6.6471 | 6.6824 |
| 2 | 0.981 | 0.997 | 2.2284 | 2.2322 |
| 3 | 3.3512 | 3.3143 | 3.3184 | 3.3177 |
| 4 | 7.7059 | 7.7326 | 6.6034 | 5.5994 |
| 5 | 5.5732 | 5.5042 | 5.5939 | 6.633 |
| 6 | 0.478 | 0.38 | 0.545 | 0.545 |
| 7 | 1.1908 | 1.1917 | 2.2172 | 2.2222 |
| 8 | 3.3602 | 3.3129 | 3.3018 | 3.3227 |
| 9 | 8.8429 | 7.74 | 7.7751 | 7.7795 |
| 10 | 3.3835 | 4.4239 | 5.5496 | 5.5499 |
| 11 | 0.845 | 0.743 | 2.2307 | 2.2299 |
| 12 | 1.1998 | 1.1887 | 2.2164 | 2.2138 |
| 13 | 6.6514 | 6.6351 | 6.6784 | 6.6424 |
| 14 | 0.837 | 0.776 | 1.1364 | 1.1275 |
| 15 | 1.1436 | 1.1359 | 1.183 | 1.1783 |
| 16 | 1.1768 | 1.1787 | 2.2673 | 2.2757 |
| 17 | 9.9419 | 8.8705 | 8.8922 | 8.8771 |
| 18 | 11.11337 | 11.11522 | 10.10384 | 10.10647 |
| 19 | 4.4792 | 3.3942 | 4.4918 | 4.467 |
| 20 | 2.2668 | 2.2389 | 3.3475 | 3.3346 |
| 21 | 11.11391 | 10.10633 | 11.1119 | 10.10742 |
| 22 | 3.3009 | 2.2864 | 3.372 | 3.3377 |
| Сумма | 97.73958 | 92.05015 | 100.81134 | 99.78189 |
compress_threshold на производительность записи| Сценарий теста | Метрика | compress_threshold=1200 | 3600 | 10000 | 50000 |
|---|---|---|---|---|---|
| Производительность записи | Секционированная таблица (строк/с) | 852,334 | 919,031 | 1,055,371 | 1,057,424 |
| Производительность записи | Несекционированная таблица (строк/с) | 991,463 | 1,033,751 | 1,054,292 | 1,076,714 |
| Производительность запросов | time_bucket=1ч (диапазон 1 день) |
357 мс | 342 мс | 333 мс | 324 мс |
| Производительность запросов | time_bucket=1д (диапазон 1 месяц) |
7,384 мс | 6,312 мс | 6,008 мс | 6,025 мс |
| Производительность запросов | time_bucket=30д (диапазон 1 год) |
94,278 мс | 75,101 мс | 68,184 мс | 69,006 мс |
| Производительность запросов | Точечный поиск | 19.416 мс | 20.043 мс | 23.692 мс | 37.844 мс |
Информацию об архитектуре сжатия индексов см. в разделе mars3btree.
CREATE INDEX idx_name ON table_name
USING mars3btree (column_list)
WITH (
compresstype = 'lz4', -- Алгоритм сжатия
compresslevel = 1, -- Уровень сжатия
compressctid = true, -- Сжимать столбец CTID?
encodechain = '', -- Цепочка кодирования
minmax = true -- Включить оптимизацию min/max
);
Параметры:
compresstype: Поддерживаемые алгоритмы: lz4, zstd и mxcustom.lz4. Обеспечивает быстрое сжатие/распаковку со средним коэффициентом сжатия.zstd: Обеспечивает более высокий коэффициент сжатия, но работает немного медленнее.mxcustom: Должен использоваться в сочетании с encodechain.compresslevel: Поддерживает значения от 1 до 9 (по умолчанию: 1).compressctid: Определяет, будет ли сжиматься столбец CTID. По умолчанию true. Рекомендуется оставить этот параметр включенным, так как столбцы CTID обычно обеспечивают высокий коэффициент сжатия.Кейс клиента: В конкретном сценарии клиента включение сжатия индексов дало следующие результаты:
Как упоминалось ранее, сжатие (будь то ZSTD, LZ4 или схемы кодирования, такие как RLE, Dictionary и Bitpacking) основывается на ключевом принципе: чем регулярнее данные внутри блока или Stripe, тем лучше сжатие.
Пример настройки:
-- Хороший ключ сортировки: device_id, ts
adw=# CREATE TABLE t_sort_good (
device_id int NOT NULL,
ts timestamptz NOT NULL,
site_id int NOT NULL,
status smallint NOT NULL,
v1 double precision NOT NULL,
v2 double precision NOT NULL,
attrs jsonb NOT NULL
)
USING MARS3
WITH (compresstype=zstd, compresslevel=3, mars3options='prefer_load_mode=bulk')
ORDER BY (device_id, ts);
NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'device_id' as the Greenplum Database data distribution key for this table.
HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew.
CREATE TABLE
-- Плохой ключ сортировки: ts, device_id
adw=# CREATE TABLE t_sort_bad (
device_id int NOT NULL,
ts timestamptz NOT NULL,
site_id int NOT NULL,
status smallint NOT NULL,
v1 double precision NOT NULL,
v2 double precision NOT NULL,
attrs jsonb NOT NULL
)
USING MARS3
WITH (compresstype=zstd, compresslevel=3, mars3options='prefer_load_mode=bulk')
ORDER BY (ts, device_id);
NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'device_id' as the Greenplum Database data distribution key for this table.
HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew.
CREATE TABLE
Генерация и вставка данных:
DROP TABLE IF EXISTS t_src;
CREATE UNLOGGED TABLE t_src AS
WITH
dev AS (
SELECT d AS device_id,
(d % 200) AS site_id,
(1000 + (d % 500))::double precision AS base
FROM generate_series(1, 10000) AS d
),
pts AS (
SELECT device_id, site_id, base,
g AS seq,
(timestamp '2026-01-01' + (g || ' seconds')::interval) AS ts
FROM dev
CROSS JOIN generate_series(1, 1000) AS g
)
SELECT
device_id,
ts,
site_id,
-- status: Изменяется сегментом каждые 300 секунд, образуя длинные серии
((seq / 300) % 4)::smallint AS status,
-- v1/v2: Базовое значение устройства + небольшие флуктуации
(base + (seq % 10) * 0.1)::double precision AS v1,
(base * 0.1 + (seq % 20) * 0.01)::double precision AS v2,
-- attrs: Высоко схожие паттерны ключей, значения варьируются в зависимости от устройства/времени
jsonb_build_object(
'fw', '1.2.' || (device_id % 10),
'model', 'm' || (device_id % 50),
'tag', 'S' || site_id,
'k', seq % 5
) AS attrs
FROM pts;
INSERT INTO t_sort_good SELECT * FROM t_src;
INSERT INTO t_sort_bad SELECT * FROM t_src;
Проверка:
Для обеспечения точности данных выполните VACUUM FULL, а затем несколько раз VACUUM после вставки.
-- Проверка статистики t_sort_good
adw=# SELECT segid, level, total_nruns, visible_nruns, invisible_nruns, level_size
FROM matrixts_internal.mars3_level_stats('t_sort_good')
WHERE level_size <> '0 bytes';
segid | level | total_nruns | visible_nruns | invisible_nruns | level_size
-------+-------+-------------+---------------+-----------------+------------
0 | 1 | 1 | 1 | 0 | 8839 kB
1 | 1 | 1 | 1 | 0 | 8933 kB
3 | 1 | 1 | 1 | 0 | 8552 kB
2 | 1 | 1 | 1 | 0 | 8728 kB
(4 строки)
-- Проверка статистики t_sort_bad
adw=# SELECT segid, level, total_nruns, visible_nruns, invisible_nruns, level_size
FROM matrixts_internal.mars3_level_stats('t_sort_bad')
WHERE level_size <> '0 bytes';
segid | level | total_nruns | visible_nruns | invisible_nruns | level_size
-------+-------+-------------+---------------+-----------------+------------
0 | 2 | 1 | 1 | 0 | 26 MB
1 | 2 | 1 | 1 | 0 | 26 MB
3 | 2 | 1 | 1 | 0 | 25 MB
2 | 2 | 1 | 1 | 0 | 25 MB
(4 строки)
-- Проверка размеров таблиц
adw=# \dt+ t_sort_bad
List of relations
Schema | Name | Type | Owner | Storage | Size | Description
--------+------------+-------+---------+---------+--------+-------------
public | t_sort_bad | table | mxadmin | mars3 | 103 MB |
(1 строка)
adw=# \dt+ t_sort_good
List of relations
Schema | Name | Type | Owner | Storage | Size | Description
--------+-------------+-------+---------+---------+-------+-------------
public | t_sort_good | table | mxadmin | mars3 | 35 MB |
(1 строка)
-- Пример данных из t_sort_good (Отсортировано по device_id, затем ts)
adw=# SELECT * FROM t_sort_good LIMIT 10;
device_id | ts | site_id | status | v1 | v2 | attrs
-----------+------------------------+---------+--------+--------+--------------------+-----------------------------------------------------
3 | 2026-01-01 00:00:01+08 | 3 | 0 | 1003.1 | 100.31000000000002 | {"k": 1, "fw": "1.2.3", "tag": "S3", "model": "m3"}
3 | 2026-01-01 00:00:02+08 | 3 | 0 | 1003.2 | 100.32000000000001 | {"k": 2, "fw": "1.2.3", "tag": "S3", "model": "m3"}
3 | 2026-01-01 00:00:03+08 | 3 | 0 | 1003.3 | 100.33000000000001 | {"k": 3, "fw": "1.2.3", "tag": "S3", "model": "m3"}
3 | 2026-01-01 00:00:04+08 | 3 | 0 | 1003.4 | 100.34000000000002 | {"k": 4, "fw": "1.2.3", "tag": "S3", "model": "m3"}
3 | 2026-01-01 00:00:05+08 | 3 | 0 | 1003.5 | 100.35000000000001 | {"k": 0, "fw": "1.2.3", "tag": "S3", "model": "m3"}
3 | 2026-01-01 00:00:06+08 | 3 | 0 | 1003.6 | 100.36000000000001 | {"k": 1, "fw": "1.2.3", "tag": "S3", "model": "m3"}
3 | 2026-01-01 00:00:07+08 | 3 | 0 | 1003.7 | 100.37 | {"k": 2, "fw": "1.2.3", "tag": "S3", "model": "m3"}
3 | 2026-01-01 00:00:08+08 | 3 | 0 | 1003.8 | 100.38000000000001 | {"k": 3, "fw": "1.2.3", "tag": "S3", "model": "m3"}
3 | 2026-01-01 00:00:09+08 | 3 | 0 | 1003.9 | 100.39000000000001 | {"k": 4, "fw": "1.2.3", "tag": "S3", "model": "m3"}
3 | 2026-01-01 00:00:10+08 | 3 | 0 | 1003 | 100.4 | {"k": 0, "fw": "1.2.3", "tag": "S3", "model": "m3"}
(10 строк)
-- Пример данных из t_sort_bad (Отсортировано по ts, затем device_id - смешанные устройства)
adw=# SELECT * FROM t_sort_bad LIMIT 10;
device_id | ts | site_id | status | v1 | v2 | attrs
-----------+------------------------+---------+--------+--------+--------------------+-------------------------------------------------------
1 | 2026-01-01 00:00:01+08 | 1 | 0 | 1001.1 | 100.11000000000001 | {"k": 1, "fw": "1.2.1", "tag": "S1", "model": "m1"}
12 | 2026-01-01 00:00:01+08 | 12 | 0 | 1012.1 | 101.21000000000001 | {"k": 1, "fw": "1.2.2", "tag": "S12", "model": "m12"}
15 | 2026-01-01 00:00:01+08 | 15 | 0 | 1015.1 | 101.51 | {"k": 1, "fw": "1.2.5", "tag": "S15", "model": "m15"}
20 | 2026-01-01 00:00:01+08 | 20 | 0 | 1020.1 | 102.01 | {"k": 1, "fw": "1.2.0", "tag": "S20", "model": "m20"}
23 | 2026-01-01 00:00:01+08 | 23 | 0 | 1023.1 | 102.31000000000002 | {"k": 1, "fw": "1.2.3", "tag": "S23", "model": "m23"}
35 | 2026-01-01 00:00:01+08 | 35 | 0 | 1035.1 | 103.51 | {"k": 1, "fw": "1.2.5", "tag": "S35", "model": "m35"}
38 | 2026-01-01 00:00:01+08 | 38 | 0 | 1038.1 | 103.81000000000002 | {"k": 1, "fw": "1.2.8", "tag": "S38", "model": "m38"}
40 | 2026-01-01 00:00:01+08 | 40 | 0 | 1040.1 | 104.01 | {"k": 1, "fw": "1.2.0", "tag": "S40", "model": "m40"}
44 | 2026-01-01 00:00:01+08 | 44 | 0 | 1044.1 | 104.41000000000001 | {"k": 1, "fw": "1.2.4", "tag": "S44", "model": "m44"}
47 | 2026-01-01 00:00:01+08 | 47 | 0 | 1047.1 | 104.71000000000001 | {"k": 1, "fw": "1.2.7", "tag": "S47", "model": "m47"}
(10 строк)
Резюме:
t_sort_good: Группирует схожие данные вместе. Данные, записанные на диск в одном пакете, являются более регулярными и повторяющимися, что облегчает эффективную работу компрессора.t_sort_bad: Смешивает совершенно разные данные вместе. Данные, записанные в одном пакете, являются хаотичными и случайными, что затрудняет сжатие.Результаты сравнения (одинаковый объем данных, разные ключи сортировки):
t_sort_good: 35 МБt_sort_bad: 103 МБ (примерно в 3 раза больше)Статистика Run/Level: Хотя в обоих случаях каждый сегмент содержит только один Run:
t_sort_good находятся на Level 1, примерно 8.5–8.9 МБ на сегмент.t_sort_bad находятся на Level 2, примерно 25–26 МБ на сегмент.Анализ:
t_sort_good (Ключ сортировки: device_id, ts): Кластеризация по схожести → Благоприятно для сжатия
Сортировка по (device_id, ts) гарантирует, что каждый Stripe/блок содержит «непрерывный временной ряд для одного устройства». Это приводит к:
device_id, site_id и status, демонстрируют длинные серии идентичных значений, что идеально подходит для кодирования RLE и Dictionary.ts, v1 и v2, медленно изменяются во времени в пределах одного устройства, resulting в малых дельтах, идеальных для delta/bitpacking.attrs (JSONB) altamente согласована в пределах устройства, что выгодно для ZSTD/LZ4.t_sort_bad (Ключ сортировки: ts, device_id): Смешивание устройств → Неблагоприятно для сжатия
Сортировка по (ts, device_id) смешивает большое количество устройств в каждый момент времени. В пределах одного блока:
device_id меняется почти в каждой строке, увеличивая размер словаря и сокращая длину серий RLE почти до 1.site_id и status нарушены, что снижает эффективность длин серий.attrs становятся хаотичными, уменьшая количество повторяющихся подстрок.ts является последовательным, случайность других столбцов нивелирует большинство преимуществ сжатия.В заключение, плохой ключ сортировки не только увеличивает размер хранилища, но и усложняет управление данными, требуя достижения данными более высоких уровней перед стабилизацией.
Вернуться к предыдущему разделу: Принципы движка хранения