Clickhouse: сжимаем данные эффективно

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

В этой статье мы разберем один из способов оптимизации хранения данных и запросов, который поможет ускорить процесс выполнения задачи с помощью использования кодеков сжатия в колонках. И протестируем какие результаты можно получить при использовании кодеков.

В 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

Источник: https://habr.com/ru/post/718618/


Интересные статьи

Интересные статьи

Теперь место, где поставить новый банкомат, определяет система машинного обучения. Мы готовили её целый год: за 2-3 месяца сделали первое решение, а потом дорабатывали, оптимизировали и ждали новых да...
Краткий обзорСегодня даже консервативные организации начинают внедрять Kubernetes. Платформа предлагает очевидные преимущества, — удобное развертывание, высокая скор...
Решения для больших компаний обычно должны выдерживать высокие нагрузки. Когда в штате много десятков тысяч человек, и значительная доля из них ежедневно пользуются ...
Показываю наглядно самый простой способ восстановить данные с NAND памяти смартфона, независимо от причины, по которой Вам это необходимо. В некоторых случаях телефон неработоспособен...
В этой статье мы рассмотрим, как система управления 1С-Битрикс справляется с большими нагрузками. Данный вопрос особенно актуален сегодня, когда электронная торговля начинает конкурировать по обороту ...