Обновляемое материализованное представление
Обновляемые материализованные представления концептуально похожи на материализованные представления в традиционных OLTP-базах данных: они хранят результат заданного запроса для быстрого доступа и уменьшают необходимость многократно выполнять ресурсоемкие запросы. В отличие от инкрементных материализованных представлений ClickHouse, здесь требуется периодическое выполнение запроса по всему набору данных, результаты которого сохраняются в целевой таблице, из которой затем выполняются запросы. Теоретически этот результирующий набор данных должен быть меньше исходного набора, что позволяет выполнять последующие запросы быстрее.
На диаграмме показано, как работают обновляемые материализованные представления:

Также вы можете просмотреть следующее видео:
Когда следует использовать обновляемые материализованные представления?
Инкрементные материализованные представления в ClickHouse чрезвычайно мощны и, как правило, масштабируются значительно лучше, чем подход, используемый обновляемыми материализованными представлениями, особенно в случаях, когда необходимо выполнить агрегирование по одной таблице. Поскольку агрегирование вычисляется только для каждого блока данных в момент вставки, а инкрементные состояния сливаются в итоговую таблицу, запрос всегда выполняется только над подмножеством данных. Этот метод масштабируется вплоть до петабайт данных и обычно является предпочтительным.
Однако существуют сценарии, когда этот инкрементный процесс не требуется или неприменим. Некоторые задачи либо несовместимы с инкрементным подходом, либо не требуют обновлений в режиме реального времени, и более уместной является периодическая перестройка. Например, может потребоваться регулярно выполнять полный пересчёт представления над всем набором данных, поскольку оно использует сложное соединение, которое несовместимо с инкрементным подходом.
Обновляемые материализованные представления могут запускать пакетные процессы, выполняющие такие задачи, как денормализация. Между обновляемыми материализованными представлениями можно создавать зависимости таким образом, что одно представление зависит от результатов другого и выполняется только после его завершения. Это может заменить планировщик рабочих процессов или простые DAG, такие как задание dbt. Чтобы узнать больше о том, как задавать зависимости между обновляемыми материализованными представлениями, перейдите к разделу
Dependenciesна странице CREATE VIEW.
Как происходит обновление обновляемого материализованного представления?
Обновляемые материализованные представления автоматически обновляются с интервалом, который задаётся при их создании. Например, следующее материализованное представление обновляется каждую минуту:
Если вы хотите принудительно обновить материализованное представление, можно использовать оператор SYSTEM REFRESH VIEW:
Вы также можете отменять, останавливать и запускать обновление представления. Дополнительные сведения см. в документации по управлению обновляемыми материализованными представлениями.
Когда обновляемое материализованное представление обновлялось в последний раз?
Чтобы узнать время последнего обновления обновляемого материализованного представления, вы можете выполнить запрос к системной таблице system.view_refreshes, как показано ниже:
Как изменить частоту обновления?
Чтобы изменить частоту обновления обновляемого материализованного представления, используйте синтаксис ALTER TABLE...MODIFY REFRESH.
После этого вы можете выполнить запрос Когда в последний раз обновлялось обновляемое материализованное представление?, чтобы убедиться, что частота обновления обновилась:
Использование APPEND для добавления новых строк
Функция APPEND позволяет добавлять новые строки в конец таблицы вместо замены всего представления.
Одно из применений этой возможности — создание снимков значений в определённый момент времени. Например, представим, что у нас есть таблица events, которую заполняет поток сообщений из Kafka, Redpanda или другой платформы потоковой передачи данных.
В этом наборе данных в столбце uuid содержится 4096 значений. Мы можем написать следующий запрос, чтобы найти те из них, у которых наибольшее суммарное количество:
Предположим, мы хотим каждые 10 секунд получать количество записей для каждого uuid и сохранять его в новой таблице events_snapshot. Схема таблицы events_snapshot будет выглядеть следующим образом:
Затем мы можем создать обновляемое материализованное представление, которое будет заполнять эту таблицу:
Затем мы можем выполнить запрос к events_snapshot, чтобы получить временной ряд количества для конкретного uuid:
┌──────────────────ts─┬─uuid─┬───count─┐ │ 2024-10-01 16:12:56 │ fff │ 5424711 │ │ 2024-10-01 16:13:00 │ fff │ 5424711 │ │ 2024-10-01 16:13:10 │ fff │ 5424711 │ │ 2024-10-01 16:13:20 │ fff │ 5424711 │ │ 2024-10-01 16:13:30 │ fff │ 5674669 │ │ 2024-10-01 16:13:40 │ fff │ 5947912 │ │ 2024-10-01 16:13:50 │ fff │ 6203361 │ │ 2024-10-01 16:14:00 │ fff │ 6501695 │ └─────────────────────┴──────┴─────────┘
Примеры
Теперь давайте рассмотрим, как использовать обновляемые материализованные представления на примере нескольких наборов данных.
Stack Overflow
В руководстве по денормализации данных показаны различные методы денормализации данных с использованием набора данных Stack Overflow. Мы заполняем данными следующие таблицы: votes, users, badges, posts и postlinks.
В этом руководстве мы показали, как денормализовать набор данных postlinks в таблицу posts с помощью следующего запроса:
Затем мы показали, как выполнить разовую вставку этих данных в таблицу posts_with_links, но в производственной среде нам нужно запускать эту операцию периодически.
Обе таблицы — posts и postlinks — потенциально могут изменяться. Поэтому, вместо того чтобы пытаться реализовать это соединение с использованием инкрементных материализованных представлений, часто бывает достаточно просто запланировать выполнение этого запроса с фиксированным интервалом, например раз в час, сохраняя результаты в таблице post_with_links.
В таком случае помогает обновляемое материализованное представление, и мы можем создать его с помощью следующего запроса:
Представление будет выполнено немедленно, а затем каждый час, согласно настройкам, чтобы гарантировать, что обновления в исходной таблице отражаются в нём. Важно, что при повторном выполнении запроса результирующий набор данных обновляется атомарно и прозрачно.
Синтаксис здесь идентичен инкрементальному материализованному представлению, за исключением того, что мы добавляем оператор REFRESH:
IMDb
В руководстве по интеграции dbt и ClickHouse мы заполнили набор данных IMDb следующими таблицами: actors, directors, genres, movie_directors, movies и roles.
Затем мы можем написать следующий запрос для вычисления сводной информации по каждому актёру, упорядоченной по числу появлений в фильмах.
Получение результата занимает не так уж много времени, но предположим, что мы хотим сделать это ещё быстрее и менее ресурсоёмко. Допустим, этот набор данных также постоянно обновляется — фильмы постоянно выходят, а новые актёры и режиссёры продолжают появляться.
Пришло время для обновляемого материализованного представления, поэтому сначала создадим целевую таблицу для результатов:
Теперь можно определить представление:
Представление будет выполнено сразу, а затем — каждую минуту, как задано в конфигурации, чтобы изменения в исходной таблице сразу отражались в нём. Наш прежний запрос, получавший сводную информацию об актёрах, стал синтаксически проще и значительно быстрее!
Предположим, мы добавим в наши исходные данные нового актёра по имени «Clicky McClickHouse», который, как выясняется, снялся во множестве фильмов!
Менее чем через 60 секунд наша целевая таблица обновляется и отражает активную актёрскую деятельность Клики: