Движок таблицы определяет, как таблица хранит данные и участвует в чтении и записи. MergeTree — основа наиболее распространённого семейства движков ClickHouse. Его удобно понять через жизненный цикл одной вставки.
INSERT создаёт самостоятельную часть
При вставке сервер формирует блок значений, разделяет его по партициям, сортирует по ключу, строит индекс и записывает новый part — самостоятельную часть таблицы.
Part содержит данные, информацию об их структуре, индексах и контрольных суммах. После успешной вставки новые строки можно читать, даже если никакого слияния ещё не было. Чтение таблицы объединяет данные всех подходящих активных parts.
Один INSERT может создать несколько parts: январские и февральские строки попадут в разные партиции, а большой вход может разбиться на несколько блоков. Поэтому частоту вставок и число новых частей измеряют отдельно.
Что лежит внутри part
В формате Wide значения колонок хранятся в раздельных файлах. В Compact данные колонок объединены в один файл, что удобно для небольших частей. Формат зависит от настроек и размера части; SQL-схема остаётся той же.
Внутри part строки логически разделены на гранулы — диапазоны, с которыми связан разреженный индекс. Marks помогают найти нужный диапазон в потоках колонок: содержат смещения к сжатым данным и позициям внутри распакованного блока.
Эти понятия имеют разные роли:
| Понятие | Роль |
|---|---|
| Partition | Логическая группа, например месяц; граница обслуживания и слияния |
| Part | Самостоятельная физическая часть таблицы внутри одной партиции |
| Granule | Диапазон строк для индексного отбора |
| Compressed block | Единица сжатого представления потока данных |
| Mark | Информация для перехода к нужным позициям в колонках |
Один сжатый блок может пересекать несколько гранул.
Зачем нужны фоновые слияния
Постоянно читать тысячи маленьких частей дорого: у каждой есть метаданные, индекс и диапазоны чтения. Поэтому сервер объединяет подходящие части в более крупную. Операция называется merge, или слияние.
При слиянии сервер читает потоки исходных частей, объединяет их с сохранением сортировки, формирует новый индекс и пишет новую часть. Когда результат готов, он заменяет исходные части в наборе активных данных. Старые файлы некоторое время нужны уже выполняющимся запросам, затем удаляются.
Для обычного MergeTree слияние сохраняет все строки, включая одинаковые. Другие движки семейства могут дополнительно выбирать последнюю версию или объединять агрегатные состояния.
Сливаются только части одной партиции. Выбор частей и срок слияния зависят от их размера, свободного места и ресурсов фоновых задач. В большой партиции может оставаться несколько parts.
Почему мелкие вставки мешают
Тысяча INSERT по одной строке создаёт много служебной работы. Если новые parts появляются быстрее, чем слияния успевают их обрабатывать, число активных частей растёт. Запросы читают больше частей, а запись и merge конкурируют за CPU и диск. Сервер может задерживать или отклонять вставки с ошибкой Too many parts.
Для регулярной загрузки используют батчи или серверное объединение async inserts: они уменьшают число создаваемых частей.
OPTIMIZE TABLE ... FINAL принудительно запускает дорогое слияние с чтением и перезаписью данных. Регулярную загрузку рассчитывают на фоновые merges.
Метаданные частей
Системная таблица system.parts описывает физические части. Например, для MergeTree-таблицы с именем events:
SELECT
partition,
name,
part_type,
rows,
marks,
level
FROM system.parts
WHERE database = currentDatabase() AND table = 'events' AND active
ORDER BY partition, name;active исключает старые части. rows показывает строки части, marks — индексные отметки, level — уровень слияния. Имена и количество активных parts меняются по мере фоновой работы.
Виртуальная колонка _part показывает происхождение строки: SELECT _part, count() FROM events GROUP BY _part. Она связывает логическую таблицу SQL с её текущими физическими частями.