Learning Platform
Глоссарий Troubleshooting
Урок 04.01 · 15 мин
Средний
MergeTreeAppend-onlyINSERTPart

MergeTree: фундамент хранилища

Все движки семейства MergeTree — ReplacingMergeTree, SummingMergeTree, AggregatingMergeTree, CollapsingMergeTree — наследуют от базового MergeTree. Понять MergeTree — значит понять фундамент, на котором стоит всё хранилище ClickHouse.


Append-only семантика

MergeTree — append-only движок. Каждый INSERT создаёт новый immutable part на диске. Данные никогда не изменяются in-place:

  • Нет row-level UPDATE как в PostgreSQL (ALTER TABLE UPDATE создаёт мутацию — тяжёлую фоновую перезапись parts)
  • Нет row-level DELETE (lightweight DELETE добавляет маску _row_exists=0, физическое удаление — при следующем merge)
  • Нет WAL, нет MVCC, нет row-level locking — immutable parts и есть модель данных
CREATE TABLE events (
    event_date Date,
    user_id UInt32,
    event_type String,
    payload String
) ENGINE = MergeTree()
ORDER BY (event_date, user_id);

-- Каждый INSERT создаёт отдельный part на диске
INSERT INTO events VALUES ('2024-01-15', 1, 'click', '{"page":"/home"}');
INSERT INTO events VALUES ('2024-01-15', 2, 'view', '{"page":"/docs"}');

-- Два INSERT = два part на диске (до фонового слияния)
SELECT name, rows, bytes_on_disk
FROM system.parts
WHERE table = 'events' AND active = 1;

Это фундаментальное отличие от PostgreSQL: в PostgreSQL INSERT изменяет heap page in-place, а WAL гарантирует durability. В ClickHouse INSERT — это запись нового файла. Нет конкуренции за страницы, нет блокировок на запись.


Свойства MergeTree из модуля 01

В Module 01 мы разобрали анатомию part и механику слияний. Вот как эти свойства выглядят с точки зрения движка:

Свойства MergeTree
Append-onlyImmutable partsAppend-only: каждый INSERT создаёт новый immutable part на диске. Данные никогда не перезаписываются in-place. Это исключает конкуренцию за страницы и row-level блокировки -- основной источник contention в OLTP-системах.
Background mergeПланировщик слиянийBackground merge: фоновый поток BackgroundProcessingPool непрерывно объединяет мелкие parts в крупные. Merge уменьшает число файлов на диске, улучшает locality данных и снижает overhead при SELECT. Порог: parts_to_throw_insert=300 в одной партиции.
Sparse indexprimary.idx в RAMSparse index: primary.idx хранит значение ORDER BY ключа для первой строки каждой гранулы (по умолчанию 8192 строки). Весь индекс помещается в RAM: 1 млрд строк = ~122K записей = ~1 МБ для UInt64 ключа. Бинарный поиск за микросекунды.
Columnar storageФайл на столбецColumnar storage: каждый столбец хранится в отдельном .bin файле. Запрос, читающий 3 из 50 столбцов, не касается файлов остальных 47. Compression ratio выше за счёт однородности данных в одном столбце.
  • Parts — самодостаточные директории с .bin, .mrk2, primary.idx, checksums.txt
  • Merges — фоновый процесс объединения parts. Merge-sort по ORDER BY ключу
  • Sparse index — primary.idx в RAM, бинарный поиск по гранулам (не по строкам)
  • Granules — логические блоки по 8192 строк (адаптивный index_granularity)

Когда использовать MergeTree

MergeTree — правильный выбор для чистой аналитики, когда данные пишутся один раз и не требуют обновлений:

  • Логи — access logs, application logs, error tracking
  • Clickstream — page views, clicks, user actions
  • Time-series — метрики серверов, IoT-сенсоры, финансовые тики
  • Events — audit trail, notification history, CDC events

Общий паттерн: данные неизменяемы по бизнес-логике. Лог-запись не обновляется. Метрика сенсора не редактируется. Событие — факт, который произошёл.


Контраст с PostgreSQL

СвойствоPostgreSQLClickHouse MergeTree
Модель записиHeap pages + WALImmutable parts (append-only)
UPDATEIn-place (HOT, heap rewrite)ALTER TABLE UPDATE (мутация — фоновая перезапись parts)
DELETEПометка + VACUUMLightweight DELETE (маска) + merge
КонкуренцияRow-level locking, MVCCНет блокировок — parts immutable
ИндексированиеB-tree (запись на строку)Sparse index (запись на 8192 строк)
ХранениеRow-oriented (heap)Column-oriented (.bin per column)

MergeTree не конкурирует с PostgreSQL — это архитектурно другая модель. PostgreSQL оптимизирован для OLTP (маленькие транзакции, row lookups). MergeTree оптимизирован для OLAP (массовые аналитические запросы, columnar scans).


SQL: минимальный рабочий пример

-- Создание таблицы
CREATE TABLE page_events (
    timestamp DateTime,
    user_id UInt32,
    page String,
    duration_ms UInt32
) ENGINE = MergeTree()
ORDER BY (timestamp, user_id);

-- Вставка данных (пакетом -- не по одной строке!)
INSERT INTO page_events VALUES
    ('2024-01-15 10:00:00', 1, '/home', 250),
    ('2024-01-15 10:00:01', 2, '/docs', 1200),
    ('2024-01-15 10:00:02', 1, '/pricing', 800);

-- Аналитический запрос
SELECT
    page,
    count() AS views,
    avg(duration_ms) AS avg_duration
FROM page_events
WHERE timestamp >= '2024-01-15'
GROUP BY page
ORDER BY views DESC;
TIP

MergeTree — правильный выбор по умолчанию. Используйте специализированные движки (ReplacingMergeTree, SummingMergeTree и др.) только когда базовый MergeTree не подходит: нужна дедупликация, автоматическая агрегация или collapse-семантика.


Ключевые выводы

  1. MergeTree — фундамент всех движков семейства. ReplacingMergeTree, SummingMergeTree, AggregatingMergeTree наследуют его поведение.
  2. Append-only: каждый INSERT создаёт новый immutable part. Нет in-place updates, нет row-level deletes.
  3. Фоновые слияния объединяют мелкие parts в крупные, уменьшая число файлов и улучшая производительность запросов.
  4. Sparse index (primary.idx) помещается в RAM и обеспечивает бинарный поиск по гранулам за микросекунды.
  5. Используйте MergeTree для чистой аналитики: логи, clickstream, time-series, events — данные, которые пишутся один раз и не требуют обновлений.
LSM-Tree Architecture в Apache Paimon OLAP-системы: агрегации и аналитика на больших данных

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 4. Что происходит, когда вы выполняете INSERT 1000 строк в таблицу MergeTree?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 7