Сжатие данных

Сжатие в ClickHouse — не только про экономию места, но и про скорость.

Кодек (codec) — алгоритм сжатия, применяемый к колонке; ClickHouse сжимает все данные по умолчанию, а кодек можно выбрать под характер колонки.

Почему сжатие ускоряет запросы

Кажется парадоксом: распаковка тратит процессорное время — разве это не замедляет? На практике наоборот. Узкое место — диск, а не CPU. Сжатые данные занимают меньше места, значит с диска нужно прочитать меньше байт. Современный процессор распакует их быстрее, чем диск прочёл бы несжатый объём. Итог: меньше чтения → быстрее запрос.

несжатая колонка:  [====== много байт с диска ======]  медленно
сжатая колонка:    [== мало байт ==] + распаковка на CPU  быстрее

Кодеки общего назначения

КодекПрофиль
LZ4 (по умолчанию)очень быстрый, умеренное сжатие
ZSTDсильнее жмёт, чуть медленнее

LZ4 — баланс по умолчанию. Для «холодных» данных, которые редко читают, но хочется хранить компактно, берут ZSTD (с уровнем, например ZSTD(3)).

Специализированные кодеки

Под определённые данные есть кодеки, которые радикально уменьшают объём до общего сжатия:

  • Delta — хранит разницы между соседними значениями. Идеален для почти упорядоченных колонок (id, время).
  • DoubleDelta — разница разниц; отлично жмёт равномерно растущие временные ряды.
  • Gorilla — для медленно меняющихся чисел с плавающей точкой (метрики).

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

CREATE TABLE metrics
(
    ts    DateTime CODEC(DoubleDelta, LZ4),
    host  LowCardinality(String),
    value Float64  CODEC(Gorilla, LZ4)
)
ENGINE = MergeTree
ORDER BY (host, ts);

Идея Delta на практике

Вспомним delta-кодирование из раздела про колоночное хранение — оно и лежит в основе кодека Delta (чистый Python):

ids = [1000, 1001, 1002, 1003, 1004]
delta = [ids[0]] + [ids[i] - ids[i-1] for i in range(1, len(ids))]
print("Было:  ", ids)
print("Delta: ", delta)

Вывод:

Было:   [1000, 1001, 1002, 1003, 1004]
Delta:  [1000, 1, 1, 1, 1]

Колонка из единиц жмётся почти в ничто — поэтому Delta так эффективен для последовательных значений.

Как работает под капотом

ClickHouse сжимает данные блоками (по умолчанию ~64 КБ или гранулами). При чтении распаковываются только нужные блоки нужных колонок. Кодек указывается на уровне колонки в CODEC(...), и для разных колонок выгодны разные кодеки.

Частые ошибки

  • Считать, что сжатие замедляет. На деле меньше чтения с диска обычно ускоряет аналитику.
  • Везде ZSTD с высоким уровнем. Для «горячих» часто читаемых данных это лишний расход CPU; LZ4 быстрее.
  • Игнорировать спецкодеки для рядов. Для времени и счётчиков Delta/DoubleDelta дают огромную экономию.

Итоги

  • Сжатие уменьшает чтение с диска и обычно ускоряет аналитику.
  • LZ4 — быстрый дефолт, ZSTD — сильнее жмёт для холодных данных.
  • Спецкодеки Delta/DoubleDelta/Gorilla — для рядов и метрик.
  • Кодеки задаются на уровне колонки и комбинируются.
Проверьте себя
1. Почему сжатие данных обычно ускоряет аналитические запросы?
AРаспаковка вообще ничего не стоит
BС диска читается меньше байт, а CPU распаковывает быстрее, чем диск читал бы несжатое
CСжатие отключает индексы
DСжатые данные не нужно читать вовсе
2. Для почти упорядоченной колонки (например, времени) какой кодек особенно эффективен?
ANullable
BDelta / DoubleDelta
CLowCardinality
DFixedString