В этой статье мы разберем один из способов оптимизации хранения данных и запросов, который поможет ускорить процесс выполнения задачи с помощью использования кодеков сжатия в колонках. И протестируем какие результаты можно получить при использовании кодеков.
В clickhouse есть несколько алгоритмов сжатия: LZ4 (по умолчанию), ZSTD, LZ4HC и экспериментальный DEFLATE_QPL. Подробнее про них можно прочитать в документации.
В clickhouse есть несколько кодеков для обработки данных:
Delta(delta_bytes) — Метод сжатия, при котором необработанные значения заменяются разностью двух соседних значений, за исключением первого значения, которое остается неизменным.
DoubleDelta - Вычисляет дельту дельт и записывает ее в компактной двоичной форме.
Gorilla - Вычисляет XOR между текущим и предыдущим значением с плавающей запятой и записывает его в компактной двоичной форме.
FPC — Новый кодек сжатия для чисел с плавающей точкой, появился в версии 22.6.
T64 — Обрезает лишние биты для целых значений, включая типы даты/времени.
А как оптимизировать хранение для строк?
Также есть способы оптимизации для строк, но с ними меньше вариантов, если в колонке со строками вариативность значений меньше 10 тысяч, то отличный эффект по скорости и сжатию даст тип колонки LowCardinality(String)
https://clickhouse.com/docs/ru/sql-reference/data-types/lowcardinality/
Какой кодек когда использовать?
И второй вопрос, кодеки сжимают данные, но как влияют на скорость запросов?
С одной стороны применение кодека требует выполнять операцию по декодированию данных, с другой, сжатые данные могут занимать меньше места на диске, а следовательно операция чтения при запросе будет происходить быстрее.
Кодеки также дают разные результат на 32-битных и 64-битных числах, поэтому будем сравнить и разряд чисел.
Рассмотрим работу кодеков для нескольких типов распределения данных в таблице:
rand_seq - cлучайная последовательность чисел;
const_seq - монотонно-возрастающая последовательность;
gauss_seq - гауссово распределение;
Для следующих типов float32, float64, int32, int64 и DateTime. Алгоритм сжатия LZ4.
Заполним таблицу 100 млн. строк и сравним результаты с кодеками и без. Скорость выполнения запросов будем проверять таким запросом
SELECT max(test_column) from test_table
Код создания таблицы и кодеков для колонок
create table test_table
(
rand_seq_Int64_raw Int64,
rand_seq_Int64_T64 Int64 CODEC(T64, LZ4),
rand_seq_Int64_Delta Int64 CODEC(Delta(8), LZ4),
rand_seq_Int64_DoubleDelta Int64 CODEC(DoubleDelta, LZ4),
rand_seq_Int64_Gorilla Int64 CODEC(Gorilla, LZ4),
rand_seq_Int32_raw Int32,
rand_seq_Int32_T64 Int32 CODEC(T64, LZ4),
rand_seq_Int32_Delta Int32 CODEC(Delta(8), LZ4),
rand_seq_Int32_DoubleDelta Int32 CODEC(DoubleDelta, LZ4),
rand_seq_Int32_Gorilla Int32 CODEC(Gorilla, LZ4),
rand_seq_DateTime_raw DateTime,
rand_seq_DateTime_T64 DateTime CODEC(T64, LZ4),
rand_seq_DateTime_Delta DateTime CODEC(Delta(8), LZ4),
rand_seq_DateTime_DoubleDelta DateTime CODEC(DoubleDelta, LZ4),
rand_seq_DateTime_Gorilla DateTime CODEC(Gorilla, LZ4),
const_seq_Int64_raw Int64,
const_seq_Int64_T64 Int64 CODEC(T64, LZ4),
const_seq_Int64_Delta Int64 CODEC(Delta(8), LZ4),
const_seq_Int64_DoubleDelta Int64 CODEC(DoubleDelta, LZ4),
const_seq_Int64_Gorilla Int64 CODEC(Gorilla, LZ4),
const_seq_Int32_raw Int32,
const_seq_Int32_T64 Int32 CODEC(T64, LZ4),
const_seq_Int32_Delta Int32 CODEC(Delta(8), LZ4),
const_seq_Int32_DoubleDelta Int32 CODEC(DoubleDelta, LZ4),
const_seq_Int32_Gorilla Int32 CODEC(Gorilla, LZ4),
const_seq_DateTime_raw DateTime,
const_seq_DateTime_T64 DateTime CODEC(T64, LZ4),
const_seq_DateTime_Delta DateTime CODEC(Delta(8), LZ4),
const_seq_DateTime_DoubleDelta DateTime CODEC(DoubleDelta, LZ4),
const_seq_DateTime_Gorilla DateTime CODEC(Gorilla, LZ4),
gauss_float_seq_Float64_raw Float64,
gauss_float_seq_Float64_Delta Float64 CODEC(Delta(8), LZ4),
gauss_float_seq_Float64_DoubleDelta Float64 CODEC(DoubleDelta, LZ4),
gauss_float_seq_Float64_Gorilla Float64 CODEC(Gorilla, LZ4),
gauss_float_seq_Float64_FPC Float64 CODEC(FPC, LZ4),
gauss_float_seq_Float32_raw Float32,
gauss_float_seq_Float32_Delta Float32 CODEC(Delta(8), LZ4),
gauss_float_seq_Float32_DoubleDelta Float32 CODEC(DoubleDelta, LZ4),
gauss_float_seq_Float32_Gorilla Float32 CODEC(Gorilla, LZ4),
gauss_float_seq_Float32_FPC Float32 CODEC(FPC, LZ4),
rand_float_seq_Float64_raw Float64,
rand_float_seq_Float64_Delta Float64 CODEC(Delta(8), LZ4),
rand_float_seq_Float64_DoubleDelta Float64 CODEC(DoubleDelta, LZ4),
rand_float_seq_Float64_Gorilla Float64 CODEC(Gorilla, LZ4),
rand_float_seq_Float64_FPC Float64 CODEC(FPC, LZ4),
rand_float_seq_Float32_raw Float32,
rand_float_seq_Float32_Delta Float32 CODEC(Delta(8), LZ4),
rand_float_seq_Float32_DoubleDelta Float32 CODEC(DoubleDelta, LZ4),
rand_float_seq_Float32_Gorilla Float32 CODEC(Gorilla, LZ4),
rand_float_seq_Float32_FPC Float32 CODEC(FPC, LZ4),
const_float_seq_Float64_raw Float64,
const_float_seq_Float64_Delta Float64 CODEC(Delta(8), LZ4),
const_float_seq_Float64_DoubleDelta Float64 CODEC(DoubleDelta, LZ4),
const_float_seq_Float64_Gorilla Float64 CODEC(Gorilla, LZ4),
const_float_seq_Float64_FPC Float64 CODEC(FPC, LZ4),
const_float_seq_Float32_raw Float32,
const_float_seq_Float32_Delta Float32 CODEC(Delta(8), LZ4),
const_float_seq_Float32_DoubleDelta Float32 CODEC(DoubleDelta, LZ4),
const_float_seq_Float32_Gorilla Float32 CODEC(Gorilla, LZ4),
const_float_seq_Float32_FPC Float32 CODEC(FPC, LZ4),
gauss_int_seq_Int64_raw Int64,
gauss_int_seq_Int64_T64 Int64 CODEC(T64, LZ4),
gauss_int_seq_Int64_Delta Int64 CODEC(Delta(8), LZ4),
gauss_int_seq_Int64_DoubleDelta Int64 CODEC(DoubleDelta, LZ4),
gauss_int_seq_Int64_Gorilla Int64 CODEC(Gorilla, LZ4),
gauss_int_seq_Int32_raw Int32,
gauss_int_seq_Int32_T64 Int32 CODEC(T64, LZ4),
gauss_int_seq_Int32_Delta Int32 CODEC(Delta(8), LZ4),
gauss_int_seq_Int32_DoubleDelta Int32 CODEC(DoubleDelta, LZ4),
gauss_int_seq_Int32_Gorilla Int32 CODEC(Gorilla, LZ4),
dt DateTime default now()
)
engine = MergeTree ORDER BY dt;
Результаты замеров
Выводы
Для случайных данных - заметный прирост по скорости и сжатию дает применение кодека Т64, но только если большая часть значений в колонке значительно меньше квинтиллиона. В большинстве случаев int64 используется для хранения куда меньших чисел.
Если вам повезло и данные монотонно возрастающие то вариантов оптимизации много, в идеальном случае для int лучшее сжатие DoubleDelta, Delta оптимальнее по сжатию и скорости чтения. Для float лучший результат сжатия дает новый кодек FPC, но чтение будет медленнее.
Для распределения гаусса (например данные метрик) - для int64 и int32 лучшее сжатие и время выполнение обеспечивает кодек T64. Для float сжатие дает только новый кодек FPC
Как добавить кодек к существующей таблице?
ALTER TABLE test_table MODIFY COLUMN column_a CODEC(ZSTD(2));
но работает только с новыми данными в таблицу, чтобы применить кодек к старым данным:
ALTER TABLE test_table UPDATE column_a = column_a WHERE 1