Перейти к содержанию
Learning Platform
Глоссарий Troubleshooting
Урок 04.04 · 25 мин
Продвинутый
AggregatingMergeTree-State-MergeAggregateFunctionSimpleAggregateFunctionMaterialized View

AggregatingMergeTree: паттерн -State/-Merge

AggregatingMergeTree — самый мощный и самый сложный движок семейства. Он хранит частичные состояния агрегатных функций (бинарные blob), а при merge объединяет эти состояния. Каноническая связка: raw-таблица (MergeTree) -> Materialized View -> AggregatingMergeTree.


Проблема: pre-aggregation для сложных функций

SummingMergeTree суммирует числа при merge. Но что, если нужно pre-aggregate uniq(user_id) (HyperLogLog) или quantile(0.99)(latency) (t-digest)? Сложить два HyperLogLog — это не то же самое, что сложить два числа. Нужен движок, который понимает внутреннее представление агрегатных функций.

AggregatingMergeTree решает эту задачу через два механизма:

  • AggregateFunction — тип столбца, хранящий бинарное состояние агрегатной функции
  • -State / -Merge — комбинаторы (суффиксы) для записи и чтения агрегатных состояний

AggregateFunction: тип столбца

AggregateFunction(func, arg_type) — специальный тип столбца, который хранит бинарный blob промежуточного состояния агрегатной функции. Этот blob нельзя прочитать напрямую — он не число и не строка.

CREATE TABLE daily_stats (
    date Date,
    visits AggregateFunction(sum, UInt32),
    users AggregateFunction(uniq, UInt64),
    avg_dur AggregateFunction(avg, Float64)
) ENGINE = AggregatingMergeTree()
ORDER BY date;

Что хранит каждый столбец:

  • visits — бинарное состояние sum() (фактически UInt64-аккумулятор, но обёрнутый в blob)
  • users — бинарное состояние uniq() (HyperLogLog sketch, ~16 КБ)
  • avg_dur — бинарное состояние avg() (пара: сумма + count)

Попытка сделать SELECT visits FROM daily_stats вернёт бессмысленный бинарный результат. Для финализации нужен комбинатор -Merge.


-State: запись агрегатного состояния

Комбинатор -State добавляется к имени агрегатной функции и означает: “вычисли промежуточное состояние, но не финализируй”. INSERT использует -State для записи blob:

-- Вставка через -State (обычно делает Materialized View)
INSERT INTO daily_stats
SELECT
    toDate(timestamp) AS date,
    sumState(1) AS visits,
    uniqState(user_id) AS users,
    avgState(duration_ms) AS avg_dur
FROM raw_events
GROUP BY date;

sumState(1) не возвращает число — возвращает бинарный blob, содержащий частичную сумму. uniqState(user_id) возвращает HyperLogLog sketch. Эти blob записываются в столбцы типа AggregateFunction.


-Merge: чтение и финализация

Комбинатор -Merge добавляется к имени агрегатной функции и означает: “объедини промежуточные состояния и верни финальный результат”:

SELECT
    date,
    sumMerge(visits) AS total_visits,
    uniqMerge(users) AS unique_users,
    avgMerge(avg_dur) AS average_duration
FROM daily_stats
GROUP BY date
ORDER BY date;

sumMerge(visits) берёт все blob из столбца visits (по группе), объединяет их и возвращает число. uniqMerge(users) объединяет HyperLogLog sketches и возвращает приблизительный count distinct.


Merge behaviour: объединение состояний при слиянии parts

При фоновом merge AggregatingMergeTree делает то же, что -Merge, но на уровне хранилища:

  1. Находит строки с одинаковым ORDER BY ключом в разных parts
  2. Для каждого AggregateFunction столбца: объединяет два бинарных состояния в одно
  3. Записывает результат как одну строку с объединённым состоянием

Два HyperLogLog sketch объединяются в один (корректно, не суммированием count). Два sum-состояния складываются. Два avg-состояния (sum + count) суммируют свои компоненты.


Канонический паттерн: raw -> MV -> AggregatingMergeTree

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

-- 1. Raw-таблица: принимает все события
CREATE TABLE raw_events (
    timestamp DateTime,
    user_id UInt64,
    page String,
    duration_ms UInt32
) ENGINE = MergeTree()
ORDER BY (timestamp, user_id);

-- 2. Target-таблица: хранит агрегированные состояния
CREATE TABLE daily_stats (
    date Date,
    visits AggregateFunction(sum, UInt32),
    users AggregateFunction(uniq, UInt64)
) ENGINE = AggregatingMergeTree()
ORDER BY date;

-- 3. Materialized View: автоматически заполняет target при INSERT в raw
CREATE MATERIALIZED VIEW daily_stats_mv TO daily_stats AS
SELECT
    toDate(timestamp) AS date,
    sumState(1) AS visits,
    uniqState(user_id) AS users
FROM raw_events
GROUP BY date;

-- 4. Данные пишутся в raw -- MV автоматически обновляет daily_stats
INSERT INTO raw_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);

-- 5. Читаем через -Merge
SELECT
    date,
    sumMerge(visits) AS total_visits,
    uniqMerge(users) AS unique_users
FROM daily_stats
GROUP BY date;

Этот паттерн решает задачу pre-aggregation: raw-таблица хранит все детальные данные (для ad-hoc запросов), а AggregatingMergeTree хранит сжатые агрегаты для dashboard-запросов.


SimpleAggregateFunction: лёгкая альтернатива

SimpleAggregateFunction(func, type) хранит результат агрегации напрямую (число, строку, дату) — не бинарный blob. Поддерживает только простые функции: sum, min, max, any, anyLast.

CREATE TABLE counters (
    key String,
    total SimpleAggregateFunction(sum, UInt64),
    first_seen SimpleAggregateFunction(min, DateTime),
    last_seen SimpleAggregateFunction(max, DateTime)
) ENGINE = AggregatingMergeTree()
ORDER BY key;

-- INSERT обычными значениями (не -State!)
INSERT INTO counters VALUES ('page_a', 100, '2024-01-15 08:00:00', '2024-01-15 12:00:00');
INSERT INTO counters VALUES ('page_a', 200, '2024-01-15 14:00:00', '2024-01-15 18:00:00');

-- SELECT обычными функциями (не -Merge!)
SELECT key, sum(total), min(first_seen), max(last_seen)
FROM counters
GROUP BY key;

После merge: total=300 (sum), first_seen=08:00 (min), last_seen=18:00 (max).


AggregateFunction vs SimpleAggregateFunction

Сравнение типов агрегатных столбцов
АспектАспект сравнения: что именно отличает AggregateFunction от SimpleAggregateFunction
AggregateFunctionAggregateFunction: полный тип для произвольных агрегатных функций. Хранит бинарное промежуточное состояние (blob). Требует -State при INSERT и -Merge при SELECT.
SimpleAggregateFunctionSimpleAggregateFunction: облегчённый тип для простых агрегатных функций (sum, min, max, any). Хранит результат напрямую. INSERT/SELECT обычными значениями.
ХранениеЧто физически записано на диске в .bin файле столбца
Бинарный blob состоянияБинарный blob: HyperLogLog sketch (~16 КБ для uniq), t-digest (~10 КБ для quantile), аккумулятор для sum. Размер зависит от функции.
Значение напрямуюОбычное значение результата: UInt64 для sum, DateTime для min/max, String для any. Фиксированный размер.
INSERTКакой синтаксис используется при INSERT
sumState(), uniqState()Комбинатор -State: sumState(x), uniqState(x), avgState(x). Формирует промежуточное состояние из входных данных.
Обычные значенияОбычные значения: просто числа, даты, строки. Merge применяет функцию автоматически.
SELECTКакой синтаксис используется при SELECT
sumMerge(), uniqMerge()Комбинатор -Merge: sumMerge(x), uniqMerge(x), avgMerge(x). Объединяет blob и финализирует результат.
sum(), min(), max()Обычные агрегатные функции: sum(x), min(x), max(x). ClickHouse знает, что столбец уже частично агрегирован.
ФункцииКакие агрегатные функции поддерживаются
Любая (uniq, quantile, avg, ...)Любая агрегатная функция ClickHouse: sum, uniq, quantile, avg, groupArray, topK, argMax и т.д. Полная универсальность.
Только sum, min, max, anyТолько простые: sum, min, max, any, anyLast. Функции, результат которых можно выразить одним значением того же типа.

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

  • AggregateFunction — для сложных агрегатов: uniq (HyperLogLog), quantile (t-digest), avg (sum+count). Если функция хранит сложное внутреннее состояние.
  • SimpleAggregateFunction — для простых счётчиков: sum, min, max. Если достаточно хранить одно число. Проще в использовании, меньше overhead.
NOTE

AggregatingMergeTree лучше всего работает с Materialized View. Без MV вам придётся вручную формировать -State значения при каждом INSERT.


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

  1. AggregateFunction хранит бинарный blob промежуточного состояния агрегатной функции. Нельзя читать напрямую — нужен комбинатор -Merge.
  2. -State комбинатор (sumState, uniqState, avgState) формирует промежуточное состояние при INSERT. -Merge комбинатор (sumMerge, uniqMerge, avgMerge) финализирует при SELECT.
  3. Merge behaviour: при слиянии parts движок корректно объединяет бинарные состояния (HyperLogLog merge, t-digest merge, sum accumulation).
  4. Канонический паттерн: raw_events (MergeTree) -> Materialized View -> daily_stats (AggregatingMergeTree). MV автоматически формирует -State значения.
  5. SimpleAggregateFunction — облегчённая альтернатива для простых агрегатов (sum, min, max, any). Хранит значение напрямую, не blob. INSERT и SELECT обычными функциями.

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

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

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

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