Materialized Views
Материализация materialized_view должна быть SELECT‑запросом к существующей (исходной) таблице. В отличие от PostgreSQL, materialized view в ClickHouse не является «статичной» (и не имеет соответствующей операции REFRESH). Вместо этого она работает как триггер вставки, добавляя новые строки в целевую таблицу, применяя заданное SELECT‑преобразование к строкам, вставляемым в исходную таблицу. См. документацию по materialized view в ClickHouse для получения дополнительных сведений о том, как materialized view работает в ClickHouse.
Общие концепции материализаций и их общие настройки (engine, order_by, partition_by и т. д.) см. на странице Materializations.
Как управляется целевая таблица
Когда вы используете материализацию materialized_view, dbt-clickhouse создает как materialized view, так и целевую таблицу, в которую вставляются преобразованные строки. Существуют два способа управления целевой таблицей:
| Подход | Описание | Статус |
|---|---|---|
| Неявная целевая таблица | dbt-clickhouse автоматически создает и управляет целевой таблицей в рамках той же модели. Схема целевой таблицы выводится из SQL MV. | Stable |
| Явная целевая таблица | Вы определяете целевую таблицу как отдельную материализацию table и ссылаетесь на нее из своей MV-модели с помощью макроса materialization_target_table(). MV создается с оператором TO, указывающим на эту таблицу. Эта функциональность доступна, начиная с dbt-clickhouse версии 1.10. Внимание: эта функция находится в бета-версии, и ее API может измениться на основе отзывов сообщества. | Beta |
Выбранный вами подход влияет на то, как обрабатываются изменения схемы, полные обновления (full refresh) и конфигурации с несколькими MV. В следующих разделах каждый подход описан более подробно.
Материализация с неявной целевой таблицей
Это поведение по умолчанию. Когда вы определяете модель materialized_view, адаптер будет:
- Создавать целевую таблицу с именем модели
- Создавать в ClickHouse materialized view с именем
<model_name>_mv
Схема целевой таблицы выводится из столбцов в операторе SELECT соответствующей materialized view (MV). Все ресурсы (целевая таблица и все materialized view) используют одну и ту же конфигурацию модели.
Дополнительные примеры см. в тестовом файле.
Несколько materialized view
ClickHouse позволяет нескольким materialized view записывать данные в одну и ту же целевую таблицу. Чтобы поддержать это в dbt-clickhouse при использовании подхода с неявной целевой таблицей, вы можете построить UNION в файле модели, оборачивая SQL для каждого materialized view комментариями вида --my_mv_name:begin и --my_mv_name:end.
Например, следующий пример создаст два materialized view, которые оба записывают данные в одну и ту же целевую таблицу модели. Имена этих materialized view будут иметь вид <model_name>_mv1 и <model_name>_mv2:
ВАЖНО!
При обновлении модели с несколькими materialized view (MV), особенно при переименовании одной из MV, dbt-clickhouse не удаляет старую MV автоматически. Вместо этого вы получите следующее предупреждение:
Warning - Table <previous table name> was detected with the same pattern as model name <your model name> but was not found in this run. In case it is a renamed mv that was previously part of this model, drop it manually (!!!)
Как управлять обходом схемы целевой таблицы
Начиная с dbt-clickhouse версии 1.9.8, вы можете управлять тем, как выполняется обход схемы целевой таблицы, когда dbt run обнаруживает разные столбцы в SQL материализованного представления (MV).
По умолчанию dbt не будет применять какие-либо изменения к целевой таблице (значение настройки ignore), но вы можете изменить эту настройку, чтобы поведение соответствовало конфигурации on_schema_change в инкрементальных моделях.
Кроме того, вы можете использовать эту настройку как дополнительный механизм защиты. Если вы установите её в значение fail, сборка завершится с ошибкой, если столбцы в SQL материализованного представления (MV) отличаются от целевой таблицы, которая была создана при первом выполнении dbt run.
Дозагрузка данных
По умолчанию при создании или пересоздании materialized view (MV) целевая таблица сначала заполняется историческими данными до создания самой MV (catchup=True). Это поведение можно отключить, установив параметр catchup в значение False.
| Operation | catchup: True (по умолчанию) | catchup: False |
|---|---|---|
Initial deployment (dbt run) | Целевая таблица заполняется историческими данными | Целевая таблица создаётся пустой |
Full refresh (dbt run --full-refresh) | Целевая таблица перестраивается и заполняется заново | Целевая таблица пересоздаётся пустой, существующие данные теряются |
| Normal operation | materialized view фиксирует новые вставки | materialized view фиксирует новые вставки |
Использование catchup: False с dbt run --full-refresh приведёт к тому, что все существующие данные в целевой таблице будут удалены. Таблица будет пересоздана пустой и далее будет содержать только новые данные. Убедитесь, что у вас есть резервные копии, если исторические данные могут понадобиться позже.
Материализация с явной целевой таблицей (Beta)
Эта функция находится на стадии бета-тестирования и доступна, начиная с dbt-clickhouse версии 1.10. API может измениться в зависимости от отзывов сообщества.
По умолчанию dbt-clickhouse создаёт и управляет как целевой таблицей, так и materialized view в рамках одной модели (подход implicit target, описанный выше). У этого подхода есть несколько ограничений:
- Все ресурсы (целевая таблица + MV) используют одну и ту же конфигурацию. Если несколько MV указывают на одну и ту же целевую таблицу, они должны быть определены вместе с использованием синтаксиса
UNION ALL. - Все эти ресурсы нельзя обрабатывать по отдельности — ими нужно управлять через один и тот же файл модели.
- Вы не можете гибко управлять именем каждой MV.
- Все настройки общие для целевой таблицы и MV, что затрудняет индивидуальную конфигурацию каждого ресурса и понимание того, какая конфигурация относится к какому ресурсу.
Функция explicit target позволяет определить целевую таблицу отдельно как обычную материализацию table, а затем ссылаться на неё из ваших моделей materialized view.
Преимущества
- Полностью разделённые ресурсы: теперь каждый ресурс может определяться отдельно, что улучшает читаемость.
- Соответствие ресурсов dbt и CH 1:1: теперь вы можете использовать инструменты dbt, чтобы независимо управлять этими ресурсами и итеративно их развивать.
- Доступны разные конфигурации: теперь к каждому ресурсу можно применять собственную конфигурацию.
- Больше нет необходимости соблюдать соглашения об именовании: теперь все ресурсы создаются с именем, которое задаёт пользователь, а не с добавленным суффиксом
_mvдля MVs.
Ограничения
- Определение целевой таблицы не является естественным для dbt: это не SQL, который читает из исходной таблицы, поэтому здесь мы теряем проверки dbt. SQL материализованного представления по‑прежнему будет проверяться с использованием утилит dbt, а его совместимость со столбцами целевой таблицы будет проверяться на уровне ClickHouse.
- Мы выявили некоторые проблемы, связанные с ограничениями функции
ref(): нам нужно использовать её для ссылок между моделями, но её можно использовать только для ссылок на вышестоящие модели, а не на нижестоящие. Это создаёт определённые проблемы для данной реализации. Мы создали issue в репозитории dbt-core и сейчас обсуждаем с ними поиск возможных решений (dbt-labs/dbt-core#12319):- Когда
ref()вызывается изнутри блока config, она возвращает текущую модель, а не общую. Это не позволяет нам определять её в секции config(), вынуждая использовать комментарий для добавления этой зависимости. Мы следуем тому же подходу, который описан в документации dbt, с подходом "--depends_on:". ref()работает для нас, поскольку она заставляет сначала создать целевую таблицу, но на графе зависимостей в сгенерированной документации целевая таблица будет показана как ещё одна вышестоящая зависимость, а не нижестоящая, что несколько усложняет понимание.unit-testтакже вынуждает нас определять некоторые данные для целевой таблицы, даже если задумка состоит в том, чтобы не читать из неё. Обходной путь — просто оставить данные для этой таблицы пустыми.
- Когда
Использование
Шаг 1. Определите целевую таблицу как обычную модель таблицы
Модель events_daily.sql:
Это обходной вариант, который мы упоминаем в разделе с ограничениями. Здесь вы можете лишиться части проверок dbt, но схема по‑прежнему будет проверяться на уровне ClickHouse.
Шаг 2: Определите materialized views, указывающие на целевую таблицу
Например, вы можете определить разные MV в разных моделях следующим образом, даже если они указывают на одну и ту же целевую таблицу. Обратите внимание на новый вызов макроса {{ materialization_target_table(ref('events_daily')) }}, который настраивает целевую таблицу для MV.
Модель page_events_aggregator.sql:
Модель mobile_events_aggregator.sql:
Параметры конфигурации
При использовании явных целевых таблиц доступны следующие параметры конфигурации:
Для целевой таблицы (materialized='table'):
| Option | Description | Default |
|---|---|---|
mv_on_schema_change | Как обрабатывать изменения схемы, когда таблица используется materialized view под управлением dbt. Поведение соответствует параметру конфигурации on_schema_change в инкрементальных моделях. | Внимание: Модель materialized='table' будет вести себя как обычно, если к ней не привязаны materialized view, поэтому, даже если этот параметр задан, он будет проигнорирован. Если таблица является целевой для materialized view, этот параметр по умолчанию будет иметь значение mv_on_schema_change='fail', чтобы защитить данные в этих таблицах. |
repopulate_from_mvs_on_full_refresh | При --full-refresh вместо выполнения SQL таблицы перестраивать таблицу, выполняя INSERT-SELECT на основе SQL всех materialized view, которые на неё ссылаются. | False |
Для materialized view (materialized='materialized_view'):
| Option | Description | Default |
|---|---|---|
catchup | Нужно ли заполнять исторические данные при создании materialized view. | True |
Обычно имеет смысл устанавливать catchup в True только в materialized view или repopulate_from_mvs_on_full_refresh в True только в их целевых таблицах. Если установить оба параметра в True, это может привести к дублированию данных.
Распространённые операции
Полное обновление с явными целевыми таблицами
При использовании --full-refresh явные целевые таблицы будут пересозданы (поэтому есть риск потери данных, если приём данных происходит во время этого процесса). Поведение будет отличаться в зависимости от ваших настроек:
Вариант 1: поведение --full-refresh по умолчанию. Всё пересоздаётся, но во время пересоздания материализованных представлений (MV) целевая таблица будет пустой или частично загруженной.
Всё удаляется и пересоздаётся. Если вы хотите повторно вставить данные с помощью SQL материализованных представлений (MV), оставьте настройку catchup=True:
Вариант 2: я хочу пересоздать целевую таблицу и не хочу, чтобы при пересоздании MV читались пустые данные.
Если вам сначала нужно обновить SQL определений MV, задайте в них catchup=False, а затем выполните dbt run или dbt run --full-refresh для MV. Убедитесь, что MV созданы до запуска --full-refresh для целевой таблицы, так как при этом используются определения MV из ClickHouse.
Установите repopulate_from_mvs_on_full_refresh=True в модели целевой таблицы. При выполнении dbt run --full-refresh это приведёт к следующему:
- Будет создана новая временная таблица
- Будет выполнен INSERT-SELECT с использованием SQL каждого MV
- Таблицы будут атомарно поменяны местами
Таким образом, пользователи вашей таблицы не столкнутся с пустыми данными, пока MV пересоздаются.
Изменение целевой таблицы
Нельзя изменить целевую таблицу материализованного представления (MV) без выполнения --full-refresh. Если вы попытаетесь запустить обычный dbt run после изменения ссылки в materialization_target_table(), сборка завершится с ошибкой с сообщением о том, что целевая таблица изменилась.
Чтобы изменить целевую таблицу:
- Обновите вызов
materialization_target_table() - Выполните
dbt run --full-refresh -s your_mv_model
Сравнение поведения между подходами с неявной и явной целевой таблицей
| Operation | Implicit target | Explicit target |
|---|---|---|
| First dbt run | Все ресурсы созданы | Все ресурсы созданы |
| Next dbt run | Отдельными ресурсами нельзя управлять независимо, все операции выполняются вместе: target table: изменения обрабатываются с помощью настройки on_schema_change. По умолчанию установлено значение ignore, поэтому новые столбцы не обрабатываются.MVs: все обновляются с помощью операций alter table modify query | Изменения могут применяться по отдельности: target table: автоматическое определение того, являются ли они целевыми таблицами для MVs, определённых в dbt. Если да, то изменения структуры столбцов по умолчанию управляются с помощью настройки mv_on_schema_change со значением fail, поэтому выполнение завершится ошибкой при изменении столбцов. Мы добавили это значение по умолчанию как дополнительный уровень защитыMVs: их SQL обновляется операциями alter table modify query. |
| dbt run --full-refresh | Отдельными ресурсами нельзя управлять независимо, все операции выполняются вместе: target table: target table пересоздаётся пустой. Параметр catchup позволяет настроить backfill с использованием объединённого SQL всех MVs. По умолчанию catchup имеет значение TrueMVs: все пересоздаются. | Изменения будут применяться по отдельности: target table: будет пересоздана как обычно. MVs: удаляются и пересоздаются. Параметр catchup доступен для начального backfill. По умолчанию catchup имеет значение True. Примечание: В процессе выполнения target table будет пустой или частично загруженной до тех пор, пока MVs не будут пересозданы. Чтобы этого избежать, см. следующий раздел о том, как поэтапно обновлять целевую таблицу. |
Миграция от неявной к явной целевой таблице
Если у вас есть существующие модели materialized view, использующие подход с неявной целевой таблицей, и вы хотите перейти на подход с явной целевой таблицей, выполните следующие шаги:
1. Создайте модель целевой таблицы
Создайте новый файл модели с materialized='table', который задаёт ту же схему, что и текущая целевая таблица MV. Используйте предложение WHERE 0, чтобы создать пустую таблицу. Используйте то же имя, что и у текущей модели неявного materialized view. Теперь вы сможете использовать эту модель для итеративного изменения целевой таблицы.
2. Обновите MV‑модели
Создайте новые модели, которые будут включать SQL‑код MV и вызов макроса materialization_target_table(), указывающий на новую целевую таблицу. Если вы ранее использовали UNION ALL, удалите эту часть и комментарии.
Для имён моделей необходимо придерживаться следующего соглашения об именовании:
- если было определено только одно MV, оно будет иметь имя:
<old_model_name>_mv - если было определено несколько MV, каждое будет иметь имя:
<old_model_name>_mv_<name_in_comments>
Ранее в my_model.sql (неявная целевая таблица, одна модель с UNION ALL):
После (явно заданная цель, отдельные файлы моделей):
3. При необходимости повторяйте их, руководствуясь инструкциями из раздела «Явная цель».
Поведение во время активной ингестии
Поскольку materialized view в ClickHouse действуют как триггеры на вставку (insert triggers), они обрабатывают данные только пока существуют. Если materialized view удаляется и создаётся заново (например, во время --full-refresh), любые строки, вставленные в исходную таблицу в этот промежуток времени, не будут обработаны этой MV. Такое состояние MV называют «слепым» (blind).
Кроме того, процесс catch-up (как через catchup у самой MV, так и через repopulate_from_mvs_on_full_refresh целевой таблицы) выполняет INSERT INTO ... SELECT, используя SQL самой materialized view. Если вставки в исходную таблицу происходят одновременно, запрос catch-up может включить строки, которые MV уже обработала (или обработает сразу после создания), что потенциально приводит к появлению дубликатов данных в целевой таблице. Использование дедуплицирующего движка, такого как ReplacingMergeTree, для целевой таблицы снижает этот риск.
В следующей таблице показана степень безопасности каждой операции, когда вставки активно выполняются в исходную таблицу.
Неявные операции с целевой таблицей
| Operation | Internal process | Safety while inserts are happening |
|---|---|---|
Первый dbt run | 1. Создать целевую таблицу 2. Вставить данные (если catchup=True)3. Создать MV(ы) | ⚠️ MV «ничего не видит» между шагами 1 и 3. Любые строки, вставленные в исходную таблицу в этот промежуток, не будут зафиксированы. |
Последующие dbt run | ALTER TABLE ... MODIFY QUERY | ✅ Безопасно. MV обновляется атомарно. |
dbt run --full-refresh | 1. Создать резервную таблицу 2. Вставить данные (если catchup=True)3. Удалить MV(ы) 4. Поменять таблицы местами 5. Воссоздать MV(ы) | ⚠️ MV «ничего не видит» во время пересоздания. Данные, вставленные в исходную таблицу между шагами 3 и 5, не попадут в новую целевую таблицу. |
Явные операции с целевыми объектами
Модели materialized view:
| Operation | Internal process | Safety while inserts are happening |
|---|---|---|
First dbt run | 1. Создать MV (с TO-клаузой)2. Выполнить догоняющее заполнение (если catchup=True) | ✅ MV создаётся первой, поэтому новые вставки сразу же попадают в неё. ⚠️ Догоняющее заполнение может дублировать данные — запрос backfill может пересекаться со строками, которые уже обрабатываются MV. Безопасно при использовании дедуплицирующего движка (например, ReplacingMergeTree). |
Subsequent dbt run | ALTER TABLE ... MODIFY QUERY | ✅ Безопасно. MV обновляется атомарно. |
dbt run --full-refresh на MV | 1. Удалить и пересоздать MV 2. Выполнить догоняющее заполнение (если catchup=True) | ⚠️ MV "слепа" во время пересоздания (между удалением и созданием). ⚠️ Догоняющее заполнение может дублировать данные, если вставки выполняются одновременно. |
Модель целевой таблицы:
| Operation | Internal process | Safety while inserts are happening |
|---|---|---|
dbt run | Изменения схемы применяются согласно настройке mv_on_schema_change | ✅ Безопасно. Перемещения данных не происходит. |
dbt run --full-refresh (по умолчанию) | Пересоздать таблицу (оставив её пустой) | ⚠️ Целевая таблица пуста, пока MV не заполнят её в ходе backfill-а. MV продолжают вставлять данные в новую таблицу, как только она появляется. |
dbt run --full-refresh с repopulate_from_mvs_on_full_refresh=True | 1. Создать резервную таблицу 2. Вставить данные, используя SQL каждой MV 3. Атомарно поменять таблицы местами | ⚠️ MV "слепа" во время пересоздания. Данные, вставленные между шагами 1 и 3, не появятся в новой таблице. Это может измениться в следующих версиях |
- По возможности приостанавливайте приём данных во время операций dbt: это сделает все операции безопасными и исключит потерю данных.
- По возможности используйте дедуплицирующий движок (например,
ReplacingMergeTree) на целевой таблице для обработки потенциальных дубликатов из-за пересечения догоняющего заполнения. - Отдавайте предпочтение
ALTER TABLE ... MODIFY QUERY(обычныйdbt runбез--full-refresh), когда это возможно — это всегда безопасно. - Учитывайте проблемные интервалы времени во время операций dbt.
Обновляемые materialized view
Refreshable Materialized Views — это особый тип materialized view в ClickHouse, которые периодически повторно выполняют запрос и сохраняют результат, аналогично тому, как materialized view работают в других базах данных. Это полезно в сценариях, когда нужны периодические срезы или агрегации, а не триггеры вставки в режиме реального времени.
Refreshable materialized view могут использоваться как с неявной целевой таблицей, так и с явной целевой таблицей. Конфигурация refreshable не зависит от того, как управляется целевая таблица.
Чтобы использовать refreshable materialized view, добавьте объект конфигурации refreshable в вашу MV-модель со следующими параметрами:
| Option | Description | Required | Default Value |
|---|---|---|---|
| refresh_interval | Обязательное поле с интервалом | Yes | |
| randomize | Параметр рандомизации, будет добавлен после RANDOMIZE FOR | ||
| append | Если установлено в True, при каждом обновлении в таблицу вставляются новые строки без удаления существующих. Вставка не является атомарной, так же как обычный INSERT SELECT. | False | |
| depends_on | Список зависимостей для refreshable materialized view. Укажите зависимости в следующем формате: {schema}.{view_name} | ||
| depends_on_validation | Нужно ли проверять существование зависимостей, указанных в depends_on. Если зависимость не содержит схемы, проверка выполняется в схеме default. | False |
Пример с неявно заданной целевой таблицей
Пример с явным указанием целевой таблицы
Ограничения
- При создании refreshable materialized view (MV) в ClickHouse, у которой есть зависимость, ClickHouse не выдаёт ошибку, если указанная зависимость не существует на момент создания. Вместо этого refreshable MV остаётся в неактивном состоянии, ожидая удовлетворения зависимости, прежде чем начать обрабатывать обновления или выполнять refresh. Такое поведение является ожидаемым, но может привести к задержкам в доступности данных, если требуемая зависимость не будет своевременно обеспечена. Рекомендуется перед созданием refreshable materialized view убедиться, что все зависимости корректно определены и существуют.
- В настоящее время не существует фактической «dbt-связи» между MV и её зависимостями, поэтому порядок создания не гарантируется.
- Функциональность refreshable не тестировалась с несколькими MV, направляющими данные в одну и ту же целевую модель.