Learning Platform
Глоссарий Troubleshooting
Урок 08.02 · 20 мин
Средний
Time-SeriesCODECDeltaGorillaDoubleDeltaZSTDLZ4DateTime64Metrics

Моделирование временных рядов

Метрики инфраструктуры, IoT-телеметрия, финансовые котировки — все эти данные представляют собой временные ряды: последовательности значений, привязанных к метке времени. ClickHouse создавался именно для этого класса нагрузки: высокая скорость записи, эффективное сжатие монотонных последовательностей, быстрые range-сканы по временному диапазону.

Правильный выбор схемы определяет разницу между 10x и 100x сжатием, между секундами и миллисекундами на запрос.


Базовая схема time-series

CREATE TABLE metrics (
    ts DateTime64(3) CODEC(Delta, ZSTD(1)),
    metric_name LowCardinality(String),
    host LowCardinality(String),
    value Float64 CODEC(Gorilla, LZ4),
    tags Map(String, String)
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(ts)
ORDER BY (metric_name, host, ts);

Каждое решение в этой схеме имеет конкретную причину. Разберём по порядку.


Codec chains: сжатие для каждого типа столбца

ClickHouse позволяет назначить цепочку кодеков (codec chain) каждому столбцу индивидуально. Первый кодек — препроцессор (Delta, DoubleDelta, Gorilla), второй — компрессор (ZSTD, LZ4). Препроцессор трансформирует данные так, чтобы компрессор работал эффективнее.

Рекомендации по codec chain для типов столбцов
Timestamp: Delta + ZSTDTimestamp (DateTime/DateTime64): Delta кодирует разницу между соседними значениями. Для монотонно растущих timestamps разница = константа (например, 1 секунда). ZSTD эффективно сжимает последовательность одинаковых delta. Результат: 10-20x сжатие.
Float: Gorilla + LZ4Float значения (метрики, температура, CPU%): Gorilla -- XOR-кодирование из Facebook Gorilla paper. Для медленно меняющихся float-значений XOR между соседними = почти нули. LZ4 для скорости декомпрессии (метрики читаются часто). Результат: 5-15x сжатие.
Counter: DoubleDelta + ZSTDМонотонные счётчики (bytes_sent, requests_total): DoubleDelta кодирует разницу разниц. Для линейно растущих счётчиков вторая производная = 0. ZSTD сжимает последовательность нулей почти бесплатно. Результат: 20-50x сжатие.
String: LowCardinality + ZSTDLowCardinality/String (имена метрик, хосты): словарное кодирование LowCardinality уже обеспечивает компрессию. Дополнительный кодек ZSTD(1) для дальнейшего сжатия словаря. Delta не применим к строковым данным.

Как работает Delta

Delta кодирует разницу между соседними значениями вместо абсолютных значений:

Исходные timestamps: [1000, 1001, 1002, 1003, 1004]
После Delta:         [1000,    1,    1,    1,    1]

Для регулярных временных рядов (метрика каждую секунду) delta — константа. ZSTD сжимает последовательность одинаковых значений в несколько байт.

Как работает Gorilla

Gorilla (из Facebook Gorilla paper, 2015) XOR-кодирует float-значения:

Исходные значения: [72.5, 72.6, 72.5, 72.7, 72.6]
XOR с предыдущим:  [72.5, 0x...small, 0x...small, ...]

Для медленно меняющихся метрик (CPU utilization, temperature) XOR между соседними значениями — малое число с большим количеством нулевых бит. LZ4 сжимает это эффективно, при этом обеспечивая быструю декомпрессию.

Как работает DoubleDelta

DoubleDelta — разница разниц (вторая производная):

Исходные значения:  [100, 200, 300, 400, 500]
Delta:              [100, 100, 100, 100, 100]
DoubleDelta:        [100,   0,   0,   0,   0]

Идеален для монотонно растущих счётчиков с постоянным приростом.


ORDER BY: metric first, timestamp second

ORDER BY (metric_name, host, ts)

Почему metric_name первым, а не timestamp?

Кардинальность и фильтрация: типичный запрос к time-series — “покажи CPU utilization хоста X за последний час”. Первый столбец ORDER BY (metric_name) отсекает все остальные метрики на уровне гранул. Второй столбец (host) сужает до конкретного хоста. Третий (ts) обеспечивает range-scan внутри отфильтрованной группы.

Если поставить timestamp первым:

-- Плохой ORDER BY для time-series
ORDER BY (ts, metric_name, host)

Гранулы будут содержать все метрики всех хостов за один временной интервал. Фильтрация по конкретной метрике потребует чтения всех гранул — sparse index бесполезен.

TIP

Правило для time-series ORDER BY: dimension-столбцы (metric, host, region) перед timestamp. Timestamp всегда последним для range-сканов внутри группы.


PARTITION BY: lifecycle, не оптимизация запросов

PARTITION BY toYYYYMM(ts)

Партиционирование в ClickHouse — инструмент управления жизненным циклом данных, а не оптимизации запросов (для этого есть ORDER BY и sparse index).

Месячные партиции для метрик:

  • TTL: ALTER TABLE metrics MODIFY TTL ts + INTERVAL 90 DAY — автоматическое удаление старых партиций
  • DROP PARTITION: ALTER TABLE metrics DROP PARTITION '202401' — моментальное удаление целого месяца
  • Detach/Attach: перенос холодных данных на S3 через tiered storage

Дневные партиции подходят только при очень высоком объёме записи (более 100 миллионов строк в день), когда месячная партиция становится слишком крупной для одного merge.

WARNING

Избыточное партиционирование (по часу, по минуте) создаёт тысячи мелких parts, которые не успевают сливаться. Это замедляет SELECT и увеличивает потребление ZooKeeper/Keeper ресурсов в распределённых конфигурациях.


DateTime vs DateTime64: выбор гранулярности

ТипГранулярностьХранениеКогда использовать
DateTime1 секунда4 байтаМетрики инфраструктуры (Prometheus-style, scrape interval более 1 секунды)
DateTime64(3)1 миллисекунда8 байтТрейсинг, финансовые данные, IoT с sub-second записью
DateTime64(6)1 микросекунда8 байтHigh-frequency trading, научные измерения
DateTime64(9)1 наносекунда8 байтОчень специализированные сценарии (network packet timing)

DateTime64 занимает 8 байт вместо 4 для DateTime. При миллиардах строк это удваивает размер timestamp-столбца. Выбирайте минимальную достаточную гранулярность.

-- Prometheus-style метрики: секундной гранулярности достаточно
CREATE TABLE infra_metrics (
    ts DateTime CODEC(Delta, ZSTD(1)),
    ...
);

-- Distributed tracing: нужны миллисекунды для span ordering
CREATE TABLE traces (
    ts DateTime64(3) CODEC(Delta, ZSTD(1)),
    ...
);

Пример: полная схема метрик с TTL

CREATE TABLE system_metrics (
    ts DateTime64(3) CODEC(Delta, ZSTD(1)),
    metric_name LowCardinality(String),
    host LowCardinality(String),
    dc LowCardinality(String),
    value Float64 CODEC(Gorilla, LZ4),
    min_1m Float64 CODEC(Gorilla, LZ4),
    max_1m Float64 CODEC(Gorilla, LZ4),
    count_1m UInt64 CODEC(DoubleDelta, ZSTD(1)),
    tags Map(String, String)
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(ts)
ORDER BY (metric_name, dc, host, ts)
TTL ts + INTERVAL 90 DAY;

-- Запрос: CPU за последний час для конкретного хоста
SELECT
    toStartOfMinute(ts) AS minute,
    avg(value) AS avg_cpu,
    max(max_1m) AS peak_cpu
FROM system_metrics
WHERE metric_name = 'cpu.usage'
  AND host = 'web-01'
  AND ts >= now() - INTERVAL 1 HOUR
GROUP BY minute
ORDER BY minute;

ORDER BY (metric_name, dc, host, ts) обеспечивает: sparse index отсекает все метрики кроме cpu.usage, затем dc и host сужают до конкретного сервера, ts обеспечивает range-scan по последнему часу.


Ключевые выводы

  1. Codec chains — назначайте индивидуально: Delta+ZSTD для timestamps, Gorilla+LZ4 для float, DoubleDelta+ZSTD для счётчиков.
  2. ORDER BY — dimension-столбцы (metric, host) перед timestamp. Timestamp всегда последним.
  3. PARTITION BY toYYYYMM — для lifecycle management (TTL, DROP PARTITION), не для оптимизации запросов.
  4. DateTime vs DateTime64 — выбирайте минимальную достаточную гранулярность. DateTime (4 байта) вдвое компактнее DateTime64 (8 байт).
  5. Типичное сжатие time-series в ClickHouse: 10-50x по сравнению с несжатыми данными, благодаря комбинации codec chains и столбцового хранения.
Внутренности алгоритмов компрессии: LZ77, Huffman, ANS Streaming: event time, watermarks, windows

Проверьте понимание

Результат: 0 из 0
Прикладной
Вопрос 1 из 4. Таблица metrics содержит столбец ts DateTime64(3) с монотонно растущими timestamps (метрики приходят каждую секунду). Какая codec chain обеспечит максимальное сжатие для этого столбца?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 7