❓ Сегодня продолжим про ClickHouse — на этот раз про гранулы, ORDER BY и индексы
В прошлом посте мы посмотрели на ClickHouse в целом: колоночное хранение, Block’и, Part’ы и MergeTree.
Теперь разберёмся, как именно ClickHouse читает данные так быстро — за счёт организации данных внутри Part и индексов.
Что такое гранула в ClickHouse?
🔘Внутри каждой Part данные делятся на гранулы (granules) — минимальные куски чтения.По умолчанию index_granularity = 8192 строк.
То есть Part логически разбивается на блоки по ~8192 строк.
🔘ClickHouse всегда читает данные гранулами, а не по одной строке.
Если запрос зацепил хотя бы одну строку в грануле — на диск идёт чтение всего блока (но только нужных колонок).
⚙️ORDER BY и Primary Key
ORDER BY (...) в MergeTree — это кластеризационный ключ. Он Определяет физический порядок строк внутри Part.
PRIMARY KEY обычно тот же самый ORDER BY и нужен для ускорения диапазонных запросов, а не для уникальности.
📎Primary index
Primary index в ClickHouse — это разреженный индекс по гранулам: Для каждой гранулы хранится значение ключа ORDER BY первой строки. По WHERE ClickHouse выбирает только те гранулы, которые могут подойти, и читает с диска только их. Индекс не ищет строку, он помогает пропустить лишние куски данных.
📝Skip-индексы.Помимо primary index, существуют skip-индексы (data skipping indexes) — дополнительные структуры, которые помогают ещё агрессивнее пропускать гранулы.
⏺minmax - Сохраняет минимум и максимум по колонке внутри гранулы.
Если min(price) > 1000, а запрос ищет price < 500, эту гранулу можно не читать вообще.
⏺ set - Хранит множество значений (до лимита).
Если в set-индексе по грануле нет искомого значения — гранула отбрасывается.
⏺bloom_filter / ngrambf_v1 / tokenbf_v1 - Индексы для IN (...), текстового поиска, LIKE и т.п.
Они позволяют не лезть в гранулу, если точно понятно, что внутри нет нужного токена / хеша.
Важно: skip-индексы работают поверх гранул, а не по строкам.
И их имеет смысл добавлять только там, где колонка часто участвует в фильтрах WHERE, и распределение значений позволяет реально отбрасывать куски данных.
Где это использовать?
➡️PARTITION BY - Используется для грубого разбиения данных (по датам, бизнес-ключам).
Партиции позволяют сразу выбросить огромные куски таблицы (например, старые месяцы).
➡️ ORDER BY - Определяет, как строки лежат внутри партиции.
Первый столбец в ORDER BY обычно выбирают под самый частый и селективный фильтр (например, user_id, category_id, date — зависит от витрины).
➡️ Primary index - Работает по тому же выражению, что ORDER BY, и решает, какие гранулы читать.
Чем более «упорядочены» данные относительно реальных запросов, тем меньше гранул будет затронуто.
➡️ Skip-индексы. Используются точечно — для тяжёлых фильтров (по тексту, IN (...), длинным спискам и т.д.), когда простого порядка ORDER BY недостаточно.
В прошлом посте мы посмотрели на ClickHouse в целом: колоночное хранение, Block’и, Part’ы и MergeTree.
Теперь разберёмся, как именно ClickHouse читает данные так быстро — за счёт организации данных внутри Part и индексов.
Что такое гранула в ClickHouse?
🔘Внутри каждой Part данные делятся на гранулы (granules) — минимальные куски чтения.По умолчанию index_granularity = 8192 строк.
То есть Part логически разбивается на блоки по ~8192 строк.
🔘ClickHouse всегда читает данные гранулами, а не по одной строке.
Если запрос зацепил хотя бы одну строку в грануле — на диск идёт чтение всего блока (но только нужных колонок).
⚙️ORDER BY и Primary Key
ORDER BY (...) в MergeTree — это кластеризационный ключ. Он Определяет физический порядок строк внутри Part.
PRIMARY KEY обычно тот же самый ORDER BY и нужен для ускорения диапазонных запросов, а не для уникальности.
📎Primary index
Primary index в ClickHouse — это разреженный индекс по гранулам: Для каждой гранулы хранится значение ключа ORDER BY первой строки. По WHERE ClickHouse выбирает только те гранулы, которые могут подойти, и читает с диска только их. Индекс не ищет строку, он помогает пропустить лишние куски данных.
📝Skip-индексы.Помимо primary index, существуют skip-индексы (data skipping indexes) — дополнительные структуры, которые помогают ещё агрессивнее пропускать гранулы.
⏺minmax - Сохраняет минимум и максимум по колонке внутри гранулы.
Если min(price) > 1000, а запрос ищет price < 500, эту гранулу можно не читать вообще.
⏺ set - Хранит множество значений (до лимита).
Если в set-индексе по грануле нет искомого значения — гранула отбрасывается.
⏺bloom_filter / ngrambf_v1 / tokenbf_v1 - Индексы для IN (...), текстового поиска, LIKE и т.п.
Они позволяют не лезть в гранулу, если точно понятно, что внутри нет нужного токена / хеша.
Важно: skip-индексы работают поверх гранул, а не по строкам.
И их имеет смысл добавлять только там, где колонка часто участвует в фильтрах WHERE, и распределение значений позволяет реально отбрасывать куски данных.
Где это использовать?
➡️PARTITION BY - Используется для грубого разбиения данных (по датам, бизнес-ключам).
Партиции позволяют сразу выбросить огромные куски таблицы (например, старые месяцы).
➡️ ORDER BY - Определяет, как строки лежат внутри партиции.
Первый столбец в ORDER BY обычно выбирают под самый частый и селективный фильтр (например, user_id, category_id, date — зависит от витрины).
➡️ Primary index - Работает по тому же выражению, что ORDER BY, и решает, какие гранулы читать.
Чем более «упорядочены» данные относительно реальных запросов, тем меньше гранул будет затронуто.
➡️ Skip-индексы. Используются точечно — для тяжёлых фильтров (по тексту, IN (...), длинным спискам и т.д.), когда простого порядка ORDER BY недостаточно.