Learning Platform
Глоссарий Troubleshooting
Урок 05.01 · 20 мин
Средний
Materialized ViewMVTrigger-on-insertTO clauseNull engineFan-out

Инкрементальные MV: триггер на вставку

Materialized View (MV) в ClickHouse — это триггер, а не кэш запроса. MV срабатывает при каждом INSERT в source-таблицу, применяет SELECT к вставленному блоку данных и записывает результат в target-таблицу. Это фундаментально отличается от MV в PostgreSQL, где materialized view — снимок результата запроса, обновляемый вручную через REFRESH.


Триггер-на-вставку: ключевая семантика

Самое важное правило MV в ClickHouse:

MV SELECT применяется к блоку вставки, а не ко всей таблице.

Когда вы выполняете INSERT INTO source_table, ClickHouse:

  1. Принимает блок данных (INSERT block)
  2. Записывает блок в source-таблицу (если source не Null engine)
  3. Для каждой MV, привязанной к source: применяет MV SELECT к этому блоку
  4. Результат MV SELECT вставляется в target-таблицу

MV не видит данные, которые уже были в source-таблице. MV не перечитывает всю таблицу. MV обрабатывает только текущий INSERT block.

Триггер-на-вставку: как MV обрабатывает данные
INSERT blockINSERT INTO source_table -- данные поступают блоком. MV срабатывает на БЛОК вставки, а не на финальное состояние таблицы. Два отдельных INSERT создают два отдельных вызова MV.
триггер
MV SELECT queryMV SELECT -- применяется к блоку вставки. GROUP BY, WHERE, функции -- все выполняются на уровне блока. Результат может содержать меньше строк, чем исходный блок.
результат
Target TableINSERT INTO target_table -- результат MV SELECT вставляется в целевую таблицу. Если target = AggregatingMergeTree, вставляются -State значения.

Source/Target паттерн с TO clause

MV определяется с ключевым словом TO, которое указывает целевую таблицу:

-- 1. Source-таблица: данные поступают сюда
CREATE TABLE raw_events (
    event_date Date,
    user_id UInt64,
    event_type String,
    duration_ms UInt32
) ENGINE = MergeTree()
ORDER BY (event_date, user_id);

-- 2. Target-таблица: MV записывает результат сюда
CREATE TABLE daily_summary (
    event_date Date,
    total_events UInt64,
    total_duration UInt64
) ENGINE = SummingMergeTree()
ORDER BY event_date;

-- 3. MV: связывает source и target через SELECT
CREATE MATERIALIZED VIEW daily_summary_mv TO daily_summary AS
SELECT
    event_date,
    count() AS total_events,
    sum(duration_ms) AS total_duration
FROM raw_events
GROUP BY event_date;

Клауза TO daily_summary означает: “результат этого SELECT вставляй в таблицу daily_summary”. Target-таблица создаётся отдельно и существует независимо от MV.


Два INSERT — два вызова MV

Каждый INSERT — отдельный триггер. Это критически важно для понимания:

-- Первый INSERT: MV обрабатывает 3 строки
INSERT INTO raw_events VALUES
    ('2024-01-15', 1, 'click', 100),
    ('2024-01-15', 2, 'view', 200),
    ('2024-01-15', 3, 'click', 150);

-- MV SELECT на первом блоке:
-- event_date=2024-01-15, total_events=3, total_duration=450
-- Этот результат вставляется в daily_summary

-- Второй INSERT: MV обрабатывает 2 строки (ОТДЕЛЬНО!)
INSERT INTO raw_events VALUES
    ('2024-01-15', 4, 'view', 300),
    ('2024-01-15', 5, 'click', 50);

-- MV SELECT на втором блоке:
-- event_date=2024-01-15, total_events=2, total_duration=350
-- Этот результат ТОЖЕ вставляется в daily_summary

После двух INSERT в daily_summary будут две строки с event_date=2024-01-15: (3, 450) и (2, 350). SummingMergeTree объединит их при фоновом merge: (5, 800). Если бы MV видел всю таблицу, была бы сразу одна строка (5, 800) — но MV работает на уровне блока.


Null engine: source как транзитный слой

Иногда исходные данные не нужны после обработки MV — нужен только агрегированный результат. Null engine отбрасывает все вставленные данные, сохраняя только результат MV:

-- Source: Null engine -- не хранит данные
CREATE TABLE raw_metrics (
    ts DateTime,
    host String,
    cpu Float64,
    mem Float64
) ENGINE = Null;

-- Target: хранит только агрегаты
CREATE TABLE host_stats (
    hour DateTime,
    host String,
    avg_cpu Float64,
    max_mem Float64
) ENGINE = MergeTree()
ORDER BY (hour, host);

-- MV: агрегирует и записывает в target
CREATE MATERIALIZED VIEW host_stats_mv TO host_stats AS
SELECT
    toStartOfHour(ts) AS hour,
    host,
    avg(cpu) AS avg_cpu,
    max(mem) AS max_mem
FROM raw_metrics
GROUP BY hour, host;

Данные, вставленные в raw_metrics, обрабатываются MV и немедленно отбрасываются Null engine. raw_metrics всегда пуст. Это экономит дисковое пространство, когда детальные данные не нужны.


Fan-out: несколько MV на одном source

К одной source-таблице можно привязать несколько MV. Каждый INSERT триггерит все привязанные MV:

-- Один source, три разных агрегации
CREATE MATERIALIZED VIEW mv_hourly TO hourly_stats AS
SELECT toStartOfHour(ts) AS hour, count() AS events
FROM raw_metrics GROUP BY hour;

CREATE MATERIALIZED VIEW mv_by_host TO host_stats AS
SELECT host, avg(cpu) AS avg_cpu, max(mem) AS max_mem
FROM raw_metrics GROUP BY host;

CREATE MATERIALIZED VIEW mv_alerts TO alert_log AS
SELECT ts, host, cpu
FROM raw_metrics
WHERE cpu > 90.0;

При каждом INSERT в raw_metrics все три MV срабатывают параллельно. Каждая MV обрабатывает один и тот же INSERT block, но применяет свой SELECT.


Каскадные MV

MV может использовать target другой MV как source. Это создаёт цепочку трансформаций:

-- Уровень 1: сырые данные -> почасовые агрегаты
CREATE MATERIALIZED VIEW mv_hourly TO hourly_stats AS
SELECT toStartOfHour(ts) AS hour, sum(amount) AS total
FROM raw_events GROUP BY hour;

-- Уровень 2: почасовые -> дневные (MV на target первой MV)
CREATE MATERIALIZED VIEW mv_daily TO daily_stats AS
SELECT toDate(hour) AS day, sum(total) AS daily_total
FROM hourly_stats GROUP BY day;

INSERT в raw_events триггерит mv_hourly, которая вставляет в hourly_stats. Вставка в hourly_stats триггерит mv_daily, которая вставляет в daily_stats.

WARNING

Каскадные MV с CollapsingMergeTree или ReplacingMergeTree в промежуточных target-таблицах могут давать неожиданные результаты — эти движки изменяют данные при merge, но MV работает с блоком до merge. Используйте AggregatingMergeTree для каскадных паттернов.


Когда MV не работает

MV не срабатывает при:

  • ALTER TABLE UPDATE/DELETE (мутации) — это не INSERT
  • OPTIMIZE TABLE (merge) — это не INSERT
  • Прямой вставке в target-таблицу (обходит MV)
  • Replikации данных между нодами (INSERT trigger локальный)

MV срабатывает только при INSERT (включая INSERT … SELECT) в source-таблицу.

VIEW и MATERIALIZED VIEW в PostgreSQL Streaming: event time, watermarks, windows

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

Результат: 0 из 0
Прикладной
Вопрос 1 из 4. Что произойдёт, если выполнить два отдельных INSERT в source-таблицу с привязанной MV?

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

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

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

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