Глубокое погружение в движки MergeTree: Replacing, Summing, Aggregating и Collapsing. Урок 5

DeepDive в движки семейства MergeTree в бесплатном курсе https://bigdataschool.ru

В предыдущих статьях мы узнали, что семейство движков MergeTree — это основа для хранения аналитических данных в ClickHouse. Мы создавали таблицы с помощью базового MergeTree и даже упоминали о его специализированных версиях. Теперь пришло время для глубокого погружения.

Эти движки — не просто вариации, а мощные инструменты, которые выполняют часть работы по обработке данных автоматически, в фоновом режиме. Правильный выбор движка может радикально упростить ваши запросы, уменьшить объем хранимых данных и повысить производительность. Давайте разберем четыре самых важных из них.

ReplacingMergeTree: Храним только последнюю версию

Проблема: У вас есть данные, которые часто обновляются, и вам важно хранить только самую последнюю, актуальную версию для каждой сущности. Классический пример — справочник профилей пользователей, где может меняться email или last_login.

Решение: ReplacingMergeTree при слиянии данных (merge) удаляет все строки с одинаковым значением ключа сортировки (ORDER BY), оставляя только одну — самую последнюю.

Механика:

  • “Последняя” запись определяется по значению опционального столбца-версии (ver), указанного при создании таблицы. Если он не указан, оставляется просто последняя по времени вставки строка.
  • Важный нюанс: Дедупликация происходит не мгновенно, а только в процессе слияния “кусков” данных в фоне. Это означает, что какое-то время после вставки вы можете видеть дубликаты при SELECT запросе.

Синтаксис и пример:  Создадим таблицу для профилей пользователей, где user_id — ключ, а updated_at — версия.

CREATE TABLE user_profiles (
    user_id UInt64,
    email String,
    updated_at DateTime
)
ENGINE = ReplacingMergeTree(updated_at)
ORDER BY user_id;

ReplacingMergeTree engine - - бесплатный курс ClickHouse от https://bigdataschool.ru


Теперь вставим несколько версий для одного пользователя:

INSERT INTO user_profiles VALUES (101, 'user101@email.com', '2025-06-20 10:00:00');
INSERT INTO user_profiles VALUES (101, 'user101_new@email.com', '2025-06-20 11:00:00');

SELECT после Optimize оставит только последнюю версию - - бесплатный курс ClickHouse от https://bigdataschool.ru

Если вы сделаете SELECT, вы можете увидеть обе записи. Но после того, как ClickHouse выполнит слияние (можно принудительно запустить командой OPTIMIZE TABLE user_profiles FINAL), останется только одна.

SummingMergeTree: Агрегация на лету

Проблема: Вы собираете сырые метрики (клики, просмотры, показы) и для отчетности вам постоянно нужны их суммы по различным измерениям (дата, кампания, регион). Хранить миллиарды сырых событий может быть избыточно.

Решение: SummingMergeTree автоматически суммирует значения во всех числовых столбцах, которые не входят в ключ сортировки (ORDER BY).

Механика:

  • При слиянии данных все строки с одинаковым ключом сортировки “схлопываются” в одну.
  • Значения в их числовых столбцах суммируются. Это происходит автоматически.

Синтаксис и пример: Создадим таблицу для подсчета просмотров и кликов по кампаниям. Ключ — event_date и campaign_id.

CREATE TABLE campaign_stats (
    event_date Date,
    campaign_id UInt32,
    views UInt64,
    clicks UInt64
)
ENGINE = SummingMergeTree()
ORDER BY (event_date, campaign_id);

Вставим сырые данные несколькими пачками:

INSERT INTO campaign_stats VALUES ('2025-06-20', 1, 100, 10);
INSERT INTO campaign_stats VALUES ('2025-06-20', 2, 150, 15);
INSERT INTO campaign_stats VALUES ('2025-06-20', 1, 50, 5); -- Еще данные для первой кампании

Работа с Summing MergeTree таблицей - - бесплатный курс ClickHouse от https://bigdataschool.ru

После слияния таблица будет выглядеть так, как будто мы сделали GROUP BY ... SUM(), что значительно уменьшает размер таблицы и ускоряет запросы на получение итогов.

Summing mergetree - - бесплатный курс ClickHouse от https://bigdataschool.ru

AggregatingMergeTree: Сложная агрегация без потерь

Проблема: Вам нужно вычислять не просто суммы, а более сложные агрегаты, такие как количество уникальных пользователей (uniq), среднее значение (avg) или перцентили (quantiles). Выполнять такие расчеты на сырых данных каждый раз — очень дорого.

Решение: AggregatingMergeTree хранит не сырые данные, а “промежуточные состояния” агрегатных функций.

Механика:

  • При создании таблицы для столбцов, которые вы хотите агрегировать, используется специальный тип данных AggregateFunction.
  • При вставке вы используете INSERT SELECT с агрегатными функциями с суффиксом -State.
  • При выборке итоговых данных вы используете те же функции, но с суффиксом -Merge.

Синтаксис и пример: Предположим, мы хотим ежедневно считать количество уникальных посетителей для каждой страницы.

-- Таблица для хранения промежуточных состояний
CREATE TABLE daily_unique_users (
    day Date,
    url String,
    -- Храним состояние функции uniq
    visitors AggregateFunction(uniq, UInt64)
)
ENGINE = AggregatingMergeTree()
ORDER BY (day, url);

-- Вставляем данные, предварительно их агрегируя
INSERT INTO daily_unique_users
SELECT
    toDate(timestamp) AS day,
    url,
    -- Вычисляем состояние и передаем его
    uniqState(user_id) AS visitors
FROM access_logs
GROUP BY day, url;

-- Получаем итоговый результат
SELECT
    day,
    url,
    -- "Схлопываем" состояния в финальное значение
    uniqMerge(visitors) AS unique_visitors
FROM daily_unique_users
GROUP BY day, url;

 

AggregatingMergeTree: Сложная агрегация без потерь - изучи на бесплатный курс ClickHouse от https://bigdataschool.ru

Этот подход гораздо эффективнее, чем каждый раз запускать SELECT uniq(user_id) FROM access_logs ....

CollapsingMergeTree: Работа с парными событиями

Проблема: Вам нужно отслеживать состояния, у которых есть начало и конец, например, пользовательские сессии, или обрабатывать изменения объектов, где важен знак операции (приход/расход).

Решение: CollapsingMergeTree удаляет пары строк с одинаковым ключом сортировки, у которых в специальном столбце Sign стоят противоположные значения (1 и -1).

Механика:

  • Вы создаете таблицу со столбцом Sign Int8.
  • Для “стартового” или “положительного” события вы вставляете строку с Sign = 1.
  • Для “конечного” или “отрицательного” события — строку с Sign = -1 и тем же ключом ORDER BY.
  • При слиянии эти две строки “схлопываются” и удаляются.

Этот движок используется реже, так как требует строгой логики на стороне приложения, которое вставляет данные. Он полезен для проверки консистентности данных или для сложных сценариев отслеживания баланса.

Заключение

Выбор правильного движка из семейства MergeTree — это мощный способ оптимизации в ClickHouse.

  • Нужно хранить только последние версии записей? — ReplacingMergeTree.
  • Нужно постоянно считать суммы метрик? — SummingMergeTree.
  • Нужны сложные агрегаты вроде uniq по большим данным? — AggregatingMergeTree.
  • Нужно обрабатывать парные события? — CollapsingMergeTree.

Освоив эти инструменты, вы сможете создавать гораздо более эффективные и элегантные решения. В следующей статье мы поговорим об оптимизации самих запросов и использовании индексов.


Использованные референсы и материалы

  1. Официальная документация ClickHouse по движкам семейства MergeTree: https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/mergetree
  2. SummingMergeTree 
  3. MergeTree table engine family Altinity blog
  4. Selecting ClickHouse table engine Alibaba

SQL-блокнот к Уроку 5 бесплатного курса доступен в нашем репозитории на GitHub