Движки таблиц и семейство MergeTree

Движок таблицы решает, как данные лежат на диске и что с ними можно делать.

Движок таблицы (table engine) в ClickHouse — указанный при CREATE TABLE компонент, который определяет способ хранения, индексирования и обработки данных.

Зачем вообще движки

В обычной базе вы пишете CREATE TABLE и не думаете о движке. В ClickHouse движок — обязательный и осознанный выбор: именно он задаёт, как строки физически хранятся, есть ли первичный ключ, как идёт вставка, как происходит слияние данных. Семейство MergeTree — основной выбор для аналитики; почти все «настоящие» таблицы используют его или его варианты.

Простейшая таблица на MergeTree

CREATE TABLE events
(
    event_date Date,
    user_id    UInt32,
    event_type String,
    value      Float64
)
ENGINE = MergeTree
ORDER BY (event_date, user_id);

Обязательны две вещи: ENGINE = MergeTree и ORDER BY. Про ORDER BY — отдельный урок, это ключ к скорости.

Данные хранятся кусками (parts)

Каждая вставка в MergeTree создаёт на диске отдельный кусок (part) — самостоятельную мини-таблицу с отсортированными по ORDER BY данными и индексом. Схематично:

INSERT #1  -->  part_1  [отсортированные строки + индекс]
INSERT #2  -->  part_2  [отсортированные строки + индекс]
INSERT #3  -->  part_3  [отсортированные строки + индекс]

«Merge» в названии — про то, что фоновый процесс сливает мелкие куски в крупные:

part_1 + part_2 + part_3  --(фоновое слияние)-->  part_big

Почему слияние важно

Много мелких кусков = медленные запросы (нужно заглянуть в каждый) и нагрузка на файловую систему. Слияние укрупняет куски, снова сортирует данные по ORDER BY и применяет дедупликацию/агрегацию (в специальных версиях движка). Это и есть «дерево слияний» — LSM-подобная идея: пишем быстро мелкими кусками, потом фоном наводим порядок.

Как работает под капотом

Внутри куска данные каждой колонки лежат отдельным сжатым файлом, отсортированы по ORDER BY, и к ним есть разрежённый индекс (засечки через каждые ~8192 строки). При запросе ClickHouse по индексу отбрасывает целые гранулы, которые точно не нужны, и читает только подходящие — это называется prune (отсечение).

Частые ошибки

  • Вставлять по одной строке. Каждый INSERT — отдельный part; тысячи мелких вставок = тысячи кусков, слияние не успевает, ошибка «too many parts». Вставляйте батчами.
  • Брать неподходящий движок. Для аналитической таблицы это почти всегда MergeTree-семейство, а не Log или Memory.

Итоги

  • Движок задаёт способ хранения и обработки; MergeTree — основной для аналитики.
  • Каждая вставка создаёт отдельный кусок (part), отсортированный по ORDER BY.
  • Фоновое слияние укрупняет куски и наводит порядок.
  • Мелкие частые вставки порождают слишком много кусков — это плохо.
Проверьте себя
1. Что создаёт каждая операция INSERT в таблицу MergeTree?
AНовую таблицу
BНовый отсортированный кусок (part) на диске
CНовую базу данных
DЗапись в журнал транзакций без файлов
2. Зачем MergeTree фоном сливает куски?
AЧтобы удалить все данные
BЧтобы укрупнить мелкие куски, ускорить запросы и навести порядок
CЧтобы создать резервную копию
DЧтобы отключить индексы