Что такое движок таблиц MergeTree и как он работает?
MergeTree — основной движок ClickHouse: данные вставляются как immutable parts, сортируются по ORDER BY, затем фоново мержатся. Это обеспечивает быструю вставку и эффективное чтение колоночных данных.
Движок MergeTree: архитектура и принцип работы
MergeTree — базовый движок хранения в ClickHouse, на основе которого построено всё семейство: ReplicatedMergeTree, SummingMergeTree, AggregatingMergeTree и другие. Название отражает ключевую идею: данные вставляются в виде неизменяемых parts, которые фоново мержатся (объединяются) в более крупные parts.
Структура хранения: Parts
Каждый INSERT создаёт одну или несколько директорий на диске — parts. Part содержит:
primary.idx— sparse primary index<column>.bin— сжатые данные каждой колонки отдельно<column>.mrk2— marks (offsets для быстрого доступа к гранулам)checksums.txt,columns.txt,count.txt— метаданные
CREATE TABLE orders
(
order_id UInt64,
user_id UInt64,
created_at DateTime,
amount Decimal(10, 2),
status LowCardinality(String)
)
ENGINE = MergeTree()
PARTITION BY toYYYYMM(created_at)
ORDER BY (user_id, created_at)
SETTINGS index_granularity = 8192;
Жизненный цикл данных
1. INSERT: строки сортируются по ORDER BY в памяти, записываются в новую part. Это очень быстро — нет блокировок, нет поиска существующих строк.
2. Background merge: фоновый процесс периодически объединяет соседние мелкие parts в более крупные. Мерж читает несколько parts, сортирует (merge sort, так как каждая part уже отсортирована), пишет новую part, удаляет старые.
3. SELECT: запрос читает все активные parts, использует primary index для пропуска ненужных гранул, результаты объединяются.
-- Посмотреть состояние parts таблицы
SELECT
partition,
name,
active,
rows,
formatReadableSize(bytes_on_disk) AS size,
modification_time
FROM system.parts
WHERE table = 'orders'
ORDER BY partition, name;
-- Текущие фоновые мержи
SELECT
table,
num_parts,
formatReadableSize(total_size_bytes_compressed) AS size,
elapsed,
progress
FROM system.merges;
Партиционирование
Parts принадлежат конкретной партиции. Мержи происходят только внутри одной партиции. Это важно: партиционирование позволяет быстро удалять данные (DROP PARTITION) и ограничивает размер мержей.
-- Быстрое удаление месяца данных (мгновенно)
ALTER TABLE orders DROP PARTITION '202401';
-- Принудительный мерж (для тестирования)
OPTIMIZE TABLE orders PARTITION '202401' FINAL;
Семейство MergeTree
- ReplicatedMergeTree: репликация через ZooKeeper/ClickHouse Keeper
- SummingMergeTree: при мерже суммирует числовые колонки для одинаковых ключей
- AggregatingMergeTree: хранит состояния агрегатных функций (для Materialized View)
- ReplacingMergeTree: при мерже оставляет только последнюю версию строки по ключу
- CollapsingMergeTree: отменяет строки через sign-колонку (+1/-1)
- VersionedCollapsingMergeTree: CollapsingMergeTree + версионирование
-- ReplacingMergeTree: дедупликация по ключу при мерже
CREATE TABLE user_states
(
user_id UInt64,
status String,
updated_at DateTime
)
ENGINE = ReplacingMergeTree(updated_at) -- version column
ORDER BY user_id;
Подводные камни
- Мерж асинхронный: сразу после INSERT дубликаты (в ReplacingMergeTree) ещё видны — дедупликация произойдёт только после мержа. Используйте
FINALдля немедленной дедупликации (дорого) илиGROUP BYв запросах. - Too many parts: если INSERT'ы приходят быстрее, чем фоновые мержи успевают обрабатывать, количество parts растёт и превышает лимиты. Решение: батчевые вставки от 1000+ строк.
- OPTIMIZE TABLE FINAL блокирует: принудительный мерж с FINAL для большой таблицы может занять часы и нагрузить I/O.
- Партиций не должно быть слишком много: рекомендуется не более 1000 партиций на таблицу. Партиционирование по дням при многолетних данных создаёт тысячи партиций.
- Удалённые строки живут до мержа:
ALTER TABLE DELETE— мутация, физически строки удаляются только после перезаписи part в фоне. - ReplacingMergeTree не заменяет транзакции: гарантий атомарности нет, при concurrent insert возможны race condition в логике приложения.
- Сортировка при INSERT в памяти: при очень больших батчах (сотни миллионов строк в одном INSERT) сортировка в памяти может привести к OOM. Лучше разбивать на части.
Common mistakes
- Объяснять mergetree как OLTP-механику row-store базы вместо аналитической колоночной модели ClickHouse.
- Путать primary key ClickHouse с уникальным constraint из PostgreSQL или MySQL.
- Игнорировать parts, merges, ORDER BY, sparse index и стоимость маленьких вставок.
- Предлагать синтаксис или транзакционное поведение, которого в ClickHouse нет.
What the interviewer is testing
- Кандидат объясняет mergetree через реальный механизм ClickHouse, а не общими словами.
- Приводит корректный SQL или диагностический запрос для этой СУБД.
- Называет ограничения, версионные отличия или эксплуатационные последствия.
- Связывает ответ с проектированием приложения, производительностью, надежностью или безопасностью.