Refreshable MV: периодическое обновление
Инкрементальные MV (уроки 1-3) срабатывают на каждый INSERT — это реактивная модель. Refreshable MV — это принципиально другой механизм: периодический полный пересчёт по расписанию. Refreshable MV не имеет триггера на INSERT — это периодический полный пересчёт, а не инкрементальное обновление.
Статус: GA с 24.10
Refreshable MV стали production-ready начиная с ClickHouse 24.10. Никаких экспериментальных флагов не требуется. В версии 26.3 LTS это стабильная, полностью поддерживаемая функциональность.
REFRESH EVERY: фиксированный интервал
REFRESH EVERY запускает полный пересчёт через фиксированные интервалы времени:
CREATE MATERIALIZED VIEW hourly_report
REFRESH EVERY 1 HOUR
TO hourly_report_target AS
SELECT
toStartOfHour(event_time) AS hour,
count() AS total_events,
uniq(user_id) AS unique_users
FROM raw_events
GROUP BY hour;
ClickHouse полностью заменяет содержимое target-таблицы при каждом обновлении. Старые данные удаляются, новые вставляются. Это поведение по умолчанию (REPLACE).
Интервал отсчитывается от полуночи UTC: REFRESH EVERY 1 HOUR означает обновление в 00:00, 01:00, 02:00 и так далее.
REFRESH AFTER: относительный интервал
REFRESH AFTER запускает следующий пересчёт через заданное время после завершения предыдущего:
CREATE MATERIALIZED VIEW analytics_report
REFRESH AFTER 30 MINUTE
TO analytics_report_target AS
SELECT
toDate(event_time) AS date,
sum(revenue) AS daily_revenue
FROM orders
GROUP BY date;
Разница с REFRESH EVERY: если пересчёт занимает 10 минут и установлен REFRESH AFTER 30 MINUTE, следующий запуск будет через 30 минут после завершения, а не через 30 минут от начала. Это предотвращает наложение длительных пересчётов.
DEPENDS ON: каскадное обновление
Когда один refreshable MV зависит от результата другого, используйте DEPENDS ON для гарантии порядка:
-- Первый уровень: ежечасная агрегация
CREATE MATERIALIZED VIEW daily_stats
REFRESH EVERY 1 HOUR
TO daily_stats_target AS
SELECT
toDate(event_time) AS date,
event_type,
count() AS cnt
FROM raw_events
GROUP BY date, event_type;
-- Второй уровень: месячный отчёт, зависит от daily_stats
CREATE MATERIALIZED VIEW monthly_report
REFRESH EVERY 1 HOUR
DEPENDS ON daily_stats
TO monthly_report_target AS
SELECT
toStartOfMonth(date) AS month,
sum(cnt) AS total_events
FROM daily_stats_target
GROUP BY month;
monthly_report обновится только после того, как daily_stats завершит свой пересчёт. Если daily_stats не завершился — monthly_report ждёт. DEPENDS ON может указывать на несколько представлений через запятую.
APPEND режим
По умолчанию каждый refresh полностью заменяет данные в target-таблице. Режим APPEND добавляет новые строки без удаления существующих:
CREATE MATERIALIZED VIEW event_snapshots
REFRESH EVERY 1 HOUR
APPEND
TO event_snapshots_target AS
SELECT
now() AS snapshot_time,
count() AS current_events,
uniq(user_id) AS current_users
FROM raw_events
WHERE event_time >= now() - INTERVAL 1 HOUR;
APPEND полезен для построения временных рядов снимков: каждый час добавляется новая строка с текущими метриками. Target-таблица растёт со временем.
Отличие от инкрементальных MV
Refreshable MV и инкрементальные MV — это два разных механизма с одинаковым названием “материализованное представление”. Не путайте их.
| Характеристика | Инкрементальная MV | Refreshable MV |
|---|---|---|
| Триггер | INSERT в source-таблицу | Расписание (REFRESH EVERY / AFTER) |
| Обработка | Только новые строки из INSERT-блока | Полный пересчёт всего SELECT |
| SQL ограничения | Нет JOINs, table functions, UNION | Полная свобода SQL |
| Backfilling | Ручной INSERT INTO … SELECT | Автоматический при каждом refresh |
| Latency | Реальное время (миллисекунды) | По расписанию (минуты-часы) |
Инкрементальная MV обрабатывает только новые строки из каждого INSERT-блока и не видит состояние таблицы. Refreshable MV выполняет полный SELECT по всем данным при каждом обновлении — это даёт полную свободу SQL (JOINs, UNION, table functions, url()), но за счёт latency.
Полная свобода SQL
В отличие от инкрементальных MV, refreshable MV не имеет ограничений на SELECT:
-- JOIN между таблицами
CREATE MATERIALIZED VIEW enriched_events
REFRESH EVERY 2 HOUR
TO enriched_events_target AS
SELECT
e.event_time,
e.user_id,
u.country,
e.event_type
FROM raw_events e
JOIN users u ON e.user_id = u.id;
-- Table function url() для внешних данных
CREATE MATERIALIZED VIEW exchange_rates
REFRESH EVERY 1 DAY
TO exchange_rates_target AS
SELECT *
FROM url('https://api.example.com/rates.csv', CSVWithNames);
Это возможно потому, что refreshable MV выполняет полный SELECT, а не обрабатывает отдельные INSERT-блоки.
Мониторинг: system.view_refreshes
Состояние всех refreshable MV доступно в системной таблице:
SELECT
database,
view,
status,
last_refresh_result,
last_refresh_time,
next_refresh_time,
retry_count
FROM system.view_refreshes
FORMAT Vertical;
Возможные значения status: Scheduled, Running, WaitingForDependencies. last_refresh_result покажет ошибку, если предыдущий пересчёт завершился неудачно.
Когда использовать refreshable MV
Используйте refreshable MV, когда допустима задержка (минуты-часы) и нужна полная свобода SQL: JOINs, UNION, агрегация по всей таблице, внешние источники данных. Для real-time агрегации используйте инкрементальные MV.
Типичные сценарии:
- Периодические отчёты: ежечасные/ежедневные дашборды
- ETL-конвейеры: каскадная трансформация данных через DEPENDS ON
- Обогащение данных: JOIN с dimension-таблицами
- Внешние источники: импорт через url(), s3(), и другие table functions
Ключевые выводы
- Refreshable MV — это периодический полный пересчёт, не реактивный триггер на INSERT.
- REFRESH EVERY — фиксированный интервал от полуночи UTC. REFRESH AFTER — относительный интервал от завершения предыдущего пересчёта.
- DEPENDS ON гарантирует порядок каскадного обновления: зависимая MV ждёт завершения source-MV.
- APPEND добавляет строки без удаления. По умолчанию — полная замена (REPLACE).
- system.view_refreshes — единая точка мониторинга состояния всех refreshable MV.