Сжатие уменьшает количество байтов, которые нужно хранить и читать. База тратит CPU на распаковку, но экономит чтение диска и передачу данных. Выигрыш зависит от того, что ограничивает конкретную нагрузку: медленный диск, CPU, память или сеть.
Колоночное хранение хорошо сочетается со сжатием. В одном потоке идут значения одного типа: время рядом со временем, статус рядом со статусом. Повторения и закономерности использовать проще, чем в смеси всех полей строки.
Кодирование и сжатие решают разные части задачи
Кодирование меняет представление значений без изменения их смысла. Например, словарь LowCardinality заменяет повторяющиеся строки кодами. Сжатие уменьшает размер полученного представления с помощью алгоритма. При чтении исходные значения восстанавливаются.
Для времени 09:00:00, 09:00:01, 09:00:02 разности соседних значений малы и повторяются. Кодек Delta сохраняет разности, а не просто последовательность полных чисел. После такого преобразования универсальный алгоритм сжатия может получить более удобный вход.
CREATE TABLE codec_demo
(
ts DateTime('UTC') CODEC(Delta, ZSTD(1)),
source LowCardinality(String),
duration_ms UInt32 CODEC(ZSTD(1))
)
ENGINE = MergeTree
ORDER BY (source, ts);CODEC(Delta, ZSTD(1)) задаёт цепочку: сначала Delta, затем ZSTD с указанным уровнем. Для случайной длительности разности тоже могут быть случайными, и Delta принесёт мало пользы.
LZ4 и ZSTD
LZ4 обычно выбирают за высокую скорость сжатия и распаковки. ZSTD часто даёт меньший размер, особенно при подходящих данных, но цена записи может вырасти. Повышение уровня ZSTD меняет компромисс и не означает, что аналитический запрос ускорится пропорционально.
DoubleDelta используют для закономерно меняющихся разностей, Gorilla — для некоторых последовательностей чисел с плавающей точкой. Кодек выбирают для конкретного столбца по структуре данных и измерениям.
Почему порядок данных важен
Для ORDER BY (source, ts) строки одного источника идут рядом, а время внутри источника упорядочено. У source образуются длинные серии повторений. У времени — близкие соседние значения. Случайный порядок может разрушить эти закономерности.
Тип тоже влияет на результат. Число, записанное в String, нужно сначала разобрать при вычислении; типизированное целое уже имеет понятное представление. UInt8 перед сжатием меньше UInt64, но выбирать его можно только при допустимом диапазоне. Правильная схема часто приносит больше пользы, чем смена кодека при неудачных типах и порядке.
Длинный сортировочный ключ увеличивает работу при записи. Сначала учитывают частые фильтры, затем измеряют влияние порядка на сжатие и чтение.
Как измерить размер
Статистику столбцов таблицы показывает следующий запрос:
SELECT
name,
data_compressed_bytes,
data_uncompressed_bytes,
round(data_uncompressed_bytes /
nullIf(data_compressed_bytes, 0), 2) AS ratio
FROM system.columns
WHERE database = currentDatabase() AND table = 'codec_demo'
ORDER BY name;system.columns показывает сведения о колонках. data_compressed_bytes — сжатые данные, data_uncompressed_bytes — их распакованное представление. При ratio = 4 сжатый объём примерно в четыре раза меньше. Индексы, метаданные и служебные файлы учитывают отдельно.
Для очень маленькой таблицы показатель может быть малоинформативен: заголовки и служебная структура заметны сильнее полезных данных. Сравнивать кодеки нужно на одинаковом наборе, с одинаковым порядком, состоянием частей и условиями запросов.
В объяснениях используем MB и GB в десятичном смысле: 1 GB = 1 000 000 000 байт. Если вызвать formatReadableSize, ClickHouse может вывести MiB или GiB: 1 GiB = 1 073 741 824 байта. Это разные единицы, поэтому значение инструмента не следует переименовывать без пересчёта.