Архитектура ClickHouse: парты, гранулы, sparse index, сжатие
↑ 3.1 ClickHouse · ← Предыдущая · → Следующая
Зачем это на собесе
Понимание парта, гранулы и разреженного индекса объясняет скорость ClickHouse и правила проектирования.
Хранение: парты
Данные таблицы MergeTree делятся на парты (data parts) — неизменяемые каталоги на диске. Каждый INSERT создаёт новый парт. Фоновый процесс сливает (merge) мелкие парты в крупные, отсюда название MergeTree.
Внутри парта:
- по файлу (или файлам) на столбец (
.bin— сжатые данные,.mrk— метки/засечки); primary.idx— разреженный первичный индекс;columns.txt, контрольные суммы, метаданные партиции.
Имя парта: 202609_1_5_2 = партиция, минимальный и максимальный номера блоков, уровень слияния.
Гранулы
Данные внутри парта разбиты на гранулы (по умолчанию index_granularity = 8192 строк). Гранула — минимальная единица чтения.
Разреженный (sparse) первичный индекс
Индекс хранит по одной записи на гранулу (значение ключа сортировки первой строки гранулы), а не на каждую строку. Поэтому он компактен и целиком помещается в память даже для миллиардов строк.
ORDER BY (user_id, ts)
granule 0: (1, 10:00) ← индекс хранит только эти опорные значения
granule 1: (1, 10:40)
granule 2: (2, 09:15)Поиск: бинарный поиск по индексу → нужные гранулы → чтение только их. Это не уникальный индекс: дубликаты допустимы; он не находит одну строку, а сужает диапазон.
Следствия:
- запрос быстро работает, если условие использует префикс ключа сортировки (
user_id, затемts); - условие только по 2-му столбцу ключа менее эффективно;
- ключ сортировки выбирают под основные запросы: сначала столбцы с низкой кардинальностью и часто используемые в фильтрах.
Сжатие
Данные столбца сжимаются блоками. Кодеки:
| Кодек | Когда |
|---|---|
LZ4 (по умолчанию) | быстро, умеренное сжатие |
ZSTD(n) | лучше сжатие, дороже |
Delta, DoubleDelta | временные ряды, монотонные значения |
Gorilla | float-метрики |
T64 | целые с малым диапазоном |
LowCardinality | словарное кодирование строк |
CREATE TABLE metrics (
ts DateTime CODEC(DoubleDelta, ZSTD(1)),
host LowCardinality(String),
value Float64 CODEC(Gorilla, ZSTD(1))
) ENGINE = MergeTree ORDER BY (host, ts);Выполнение запроса
- Отбор партиций и партов (по
PARTITION BY, min/max индексу). - Отбор гранул по первичному индексу и дополнительным (skip) индексам.
- Чтение только нужных столбцов и гранул.
- Векторизованная обработка блоками, параллельно по потокам.
Полезные инструменты: EXPLAIN indexes = 1, system.parts, system.columns, system.query_log.
Skip-индексы (data skipping)
Дополнительные индексы над блоками гранул: minmax, set(n), bloom_filter, tokenbf_v1, ngrambf_v1. Помогают пропускать гранулы для условий не по ключу сортировки; не заменяют правильный ORDER BY.
Потоки и параллелизм
Запрос выполняется многопоточно на одном узле, между шардами — распределённо (Distributed).
Вопросы с ответами
Что такое гранула и зачем она?
Блок строк (по умолчанию 8192), минимальная единица чтения и индексации. Индекс хранит по записи на гранулу, поэтому он мал и находится в памяти.
Уникален ли первичный ключ в ClickHouse?
Нет. Он определяет разреженный индекс для быстрого отбора диапазонов. Дубликаты допустимы, уникальность нужно обеспечивать иначе (ReplacingMergeTree, приложение).
Почему много мелких вставок вредно?
Каждая вставка создаёт парт; слишком много партов — фоновые слияния не успевают, растёт нагрузка и ошибки «Too many parts».