Материализованные представления: предагрегация на лету

Считаем агрегаты заранее, прямо в момент вставки, — и отчёты становятся мгновенными.

Материализованное представление (materialized view) в ClickHouse — это триггер: при каждой вставке в исходную таблицу оно вычисляет агрегаты по новым строкам и дописывает их в отдельную таблицу-приёмник.

Чем оно отличается от обычного VIEW

Обычное представление — просто сохранённый запрос: каждый раз оно пересчитывает всё заново. Материализованное хранит уже посчитанный результат. В ClickHouse это работает не как «обновляемый кэш», а как триггер на INSERT: новые данные сразу превращаются в агрегированные строки приёмника.

сырые события  --INSERT-->  [исходная таблица]
                                  |  (триггер MV)
                                  v
                          [таблица-приёмник: посуточные агрегаты]

Зачем это нужно

Дашборд «уникальные пользователи и средняя длительность по дням» по миллиардам сырых событий считается долго. Но если заранее свернуть события в посуточные строки, отчёт будет читать тысячи строк вместо миллиардов — мгновенно. Материализованное представление делает эту свёртку автоматически, по мере поступления данных.

Как это собирается

Связка обычно такая: таблица-приёмник на AggregatingMergeTree хранит состояния агрегатов (помните -State из прошлого урока), а материализованное представление наполняет её при вставке:

-- Приёмник: хранит складываемые состояния по дням
CREATE TABLE events_by_day
(
    day          Date,
    users_state  AggregateFunction(uniq, UInt32),
    dur_state    AggregateFunction(avg, Float64)
)
ENGINE = AggregatingMergeTree
ORDER BY day;

-- MV: при вставке в events дописывает состояния в приёмник
CREATE MATERIALIZED VIEW events_by_day_mv TO events_by_day AS
SELECT
    toDate(event_time)  AS day,
    uniqState(user_id)  AS users_state,
    avgState(duration)  AS dur_state
FROM events
GROUP BY day;

Запрос к дашборду читает приёмник и финализирует состояния через -Merge:

SELECT
    day,
    uniqMerge(users_state) AS users,
    avgMerge(dur_state)    AS avg_dur
FROM events_by_day
GROUP BY day
ORDER BY day;

Как работает под капотом

Материализованное представление срабатывает на каждый вставленный блок исходной таблицы, прогоняет его через свой SELECT и пишет результат в приёмник. Оно видит только новые строки вставки, а не всю таблицу — поэтому работает быстро. Старые данные в приёмник нужно заливать отдельно (например, разовым INSERT … SELECT).

Частые ошибки

  • Думать, что MV пересчитывает старое. Оно реагирует только на новые вставки; исторические данные нужно досыпать вручную.
  • Хранить финальные числа вместо состояний. Для корректных уникальных и средних по разным периодам в приёмнике нужны -State, а не готовые числа.
  • Тяжёлый SELECT в MV. Он выполняется на каждой вставке — слишком сложное представление замедлит загрузку.

Итоги

  • Материализованное представление — триггер на вставку, а не обновляемый кэш.
  • Оно сворачивает новые данные в агрегаты приёмника на лету.
  • В паре с AggregatingMergeTree и -State/-Merge даёт мгновенные дашборды.
  • Видит только новые вставки; историю досыпают отдельно.
Проверьте себя
1. Как в ClickHouse работает материализованное представление?
AПересчитывает весь запрос при каждом обращении
BКак триггер: при вставке новых строк агрегирует их и пишет в таблицу-приёмник
CКак резервная копия таблицы
DУдаляет старые данные
2. Что увидит материализованное представление сразу после создания на уже наполненной таблице?
AВсе исторические данные автоматически
BТолько новые вставки после его создания; историю надо досыпать вручную
CНичего и никогда
DСлучайную выборку строк