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, но на уровне хранилища:
- Находит строки с одинаковым ORDER BY ключом в разных parts
- Для каждого AggregateFunction столбца: объединяет два бинарных состояния в одно
- Записывает результат как одну строку с объединённым состоянием
Два 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 — для сложных агрегатов:
uniq(HyperLogLog),quantile(t-digest),avg(sum+count). Если функция хранит сложное внутреннее состояние. - SimpleAggregateFunction — для простых счётчиков:
sum,min,max. Если достаточно хранить одно число. Проще в использовании, меньше overhead.
AggregatingMergeTree лучше всего работает с Materialized View. Без MV вам придётся вручную формировать -State значения при каждом INSERT.
Ключевые выводы
- AggregateFunction хранит бинарный blob промежуточного состояния агрегатной функции. Нельзя читать напрямую — нужен комбинатор
-Merge. - -State комбинатор (sumState, uniqState, avgState) формирует промежуточное состояние при INSERT.
-Merge комбинатор(sumMerge, uniqMerge, avgMerge) финализирует при SELECT. - Merge behaviour: при слиянии parts движок корректно объединяет бинарные состояния (HyperLogLog merge, t-digest merge, sum accumulation).
- Канонический паттерн: raw_events (MergeTree) -> Materialized View -> daily_stats (AggregatingMergeTree). MV автоматически формирует -State значения.
- SimpleAggregateFunction — облегчённая альтернатива для простых агрегатов (sum, min, max, any). Хранит значение напрямую, не blob. INSERT и SELECT обычными функциями.