Learning Platform
Глоссарий Troubleshooting
Урок 05.02 · 25 мин
Продвинутый
AggregatingMergeTreeMaterialized View-State-MergeAggregateFunctionSimpleAggregateFunctionPre-aggregation

MV + AggregatingMergeTree: канонический паттерн

Канонический паттерн pre-aggregation в ClickHouse — связка MV + AggregatingMergeTree. MV триггерится на каждый INSERT, вычисляет частичные агрегаты с -State функциями и записывает их в AggregatingMergeTree. При чтении -Merge функции объединяют частичные состояния в финальный результат.

Этот паттерн решает задачу, где raw-таблица хранит миллиарды строк, а dashboard-запросы должны выполняться за миллисекунды.


Почему SummingMergeTree недостаточно

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

AggregatingMergeTree хранит бинарные состояния агрегатных функций и объединяет их при merge. Это единственный движок, поддерживающий корректное объединение HyperLogLog, t-digest и других нетривиальных агрегатов.


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

AggregateFunction(func, arg_type) — специальный тип столбца, хранящий бинарный 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 — blob состояния sum() (фактически UInt64-аккумулятор, обёрнутый в blob)
  • users — blob состояния uniq() (HyperLogLog sketch, ~16 КБ)
  • avg_dur — blob состояния avg() (пара: сумма + count)

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


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

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

-- MV использует -State для записи blob в target
CREATE MATERIALIZED VIEW daily_stats_mv TO daily_stats AS
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 для user_id из текущего INSERT block.


-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.


Полный канонический паттерн

Канонический паттерн: raw -> MV -> AggregatingMergeTree -> результат
raw_events (MergeTree)raw_events (MergeTree) -- source-таблица принимает все детальные события. Хранит полные данные для ad-hoc запросов.
INSERT триггерит MV
sumState / uniqState / avgStateMV SELECT с -State функциями: sumState(), uniqState(), avgState(). Вычисляет частичные агрегаты на INSERT block. GROUP BY определяет ключи агрегации.
-State blob
daily_stats (AggregatingMergeTree)daily_stats (AggregatingMergeTree) -- target-таблица. Хранит бинарные состояния агрегатных функций. При merge объединяет состояния из строк с одинаковым ORDER BY ключом.
SELECT -Merge
РезультатФинальные агрегатыsumMerge(), uniqMerge(), avgMerge() финализируют промежуточные состояния и возвращают числа. GROUP BY обязателен при чтении.

Полный SQL-пример:

-- 1. Source: все события
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. MV: автоматическая агрегация при INSERT
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. Вставка данных -- MV автоматически обновляет daily_stats
INSERT INTO raw_events VALUES
    ('2024-01-15 10:00:00', 1, '/home', 250),
    ('2024-01-15 10:05:00', 2, '/docs', 1200),
    ('2024-01-15 10:10:00', 1, '/pricing', 800);

-- 5. Чтение через -Merge
SELECT
    date,
    sumMerge(visits) AS total_visits,
    uniqMerge(users) AS unique_users
FROM daily_stats
GROUP BY date;
-- Результат: date=2024-01-15, total_visits=3, unique_users=2

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;
-- total=300, first_seen=08:00, last_seen=18:00

Преимущества SimpleAggregateFunction:

  • INSERT обычными значениями — не нужен -State
  • SELECT обычными функциями — не нужен -Merge
  • Значение читаемо напрямую (число, дата) — не blob

Ограничения: не поддерживает uniq, quantile, avg и другие функции с нетривиальным промежуточным состоянием.

AggregateFunction vs SimpleAggregateFunction
AggregateFunctionБинарный blobAggregateFunction(sum, UInt32) хранит blob промежуточного состояния. INSERT через sumState(), SELECT через sumMerge(). Поддерживает все агрегатные функции включая uniq, quantile, avg.
SimpleAggregateFunctionОбычное значениеSimpleAggregateFunction(sum, UInt64) хранит число напрямую. INSERT обычными значениями, SELECT обычными функциями. Только простые функции: sum, min, max, any, anyLast.

Типичные ошибки

Забыли GROUP BY при чтении:

-- Неверно: без GROUP BY, -Merge объединит ВСЕ строки
SELECT sumMerge(visits) FROM daily_stats;  -- одно число за все дни

-- Верно: GROUP BY для разбивки по дням
SELECT date, sumMerge(visits) FROM daily_stats GROUP BY date;

Прямой SELECT без -Merge:

-- Неверно: бессмысленные бинарные данные
SELECT visits FROM daily_stats;

-- Верно: финализация через -Merge
SELECT sumMerge(visits) FROM daily_stats GROUP BY date;

SimpleAggregateFunction для uniq:

-- Неверно: uniq не поддерживается SimpleAggregateFunction
SimpleAggregateFunction(uniq, UInt64)  -- ошибка

-- Верно: используйте AggregateFunction
AggregateFunction(uniq, UInt64)
Dictionary Encoding: кросс-форматный анализ Streaming fundamentals: event time, watermarks, windows

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

Результат: 0 из 0
Прикладной
Вопрос 1 из 4. MV записывает данные в AggregatingMergeTree с AggregateFunction(sum, UInt32). Какая пара функций завершает паттерн: INSERT через MV и SELECT для чтения?

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

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

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

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