materialized_view должна представлять собой SELECT из существующей исходной таблицы. В отличие от PostgreSQL, materialized view в ClickHouse не является «статическим» объектом (и для неё не существует соответствующей операции REFRESH). Вместо этого она работает как триггер вставки: новые строки вставляются в целевую таблицу за счёт применения заданного преобразования SELECT к строкам, вставляемым в исходную таблицу. Подробнее о том, как работают materialized view в ClickHouse, см. в документации ClickHouse по materialized view.
Общие сведения о концепциях материализации и общих конфигурациях (engine, order_by, partition_by и т. д.) см. на странице Materializations.
Как управляется целевая таблица
materialized_view, dbt-clickhouse должен создать и materialized view, и целевую таблицу, в которую вставляются преобразованные строки. Управлять целевой таблицей можно двумя способами:
Выбранный подход влияет на то, как обрабатываются изменения схемы, полные обновления и конфигурации с несколькими materialized view. В следующих разделах каждый подход описан подробно.
Материализация с неявной целевой таблицей
materialized_view, адаптер:
- Создаёт целевую таблицу с именем модели
- Создаёт в ClickHouse materialized view с именем
<model_name>_mv
SELECT materialized view. Все ресурсы (целевая таблица + materialized view) используют одну и ту же конфигурацию модели.
Несколько materialized view
UNION в файле модели, заключив SQL для каждого materialized view между комментариями вида --my_mv_name:begin и --my_mv_name:end.
Например, следующий код создаст два materialized view, которые будут записывать данные в одну и ту же целевую таблицу модели. Имена materialized view будут иметь вид <model_name>_mv1 и <model_name>_mv2:
Как изменять схему целевой таблицы
dbt run обнаруживает различия в столбцах в SQL-коде materialized view.
ignore), но вы можете изменить эту настройку, чтобы она вела себя так же, как config on_schema_change в incremental-моделях.
Кроме того, эту настройку можно использовать как механизм защиты. Если задать для неё значение fail, сборка завершится ошибкой, если столбцы в SQL materialized view отличаются от столбцов в целевой таблице, созданной при первом dbt run.
Догрузка данных
catchup=True). Это поведение можно отключить, установив параметру catchup значение False.
Материализация с явной целевой таблицей (бета)
- Все ресурсы (целевая таблица + MV) используют одну и ту же конфигурацию. Если несколько MV указывают на одну и ту же целевую таблицу, их нужно определять вместе с помощью синтаксиса
UNION ALL. - Этими ресурсами нельзя управлять по отдельности — все они должны описываться в одном и том же файле модели.
- Нельзя легко задать имя для каждой MV.
- Все настройки общие для целевой таблицы и MV, поэтому сложно настраивать каждый ресурс отдельно и понимать, какая конфигурация к какому ресурсу относится.
table, а затем ссылаться на нее из моделей materialized view.
Преимущества
- Полностью разделённые ресурсы: Теперь каждый ресурс можно определять отдельно, что улучшает читаемость
- Соответствие ресурсов 1:1 между dbt и CH: Теперь вы можете использовать инструменты dbt, чтобы управлять ими и изменять их по отдельности.
- Теперь доступны разные конфигурации: Теперь к каждому из них можно применять отдельную конфигурацию.
- Больше не нужно придерживаться соглашений об именовании: Теперь все ресурсы создаются с использованием заданного вами имени, а не пользовательского имени с добавлением _mv для MV.
Ограничения
- Определение целевой таблицы не вполне естественно для dbt: это не SQL-запрос, который читает из исходной таблицы, поэтому здесь вы лишаетесь проверок dbt. SQL materialized view по-прежнему будет проверяться с помощью утилит dbt, а его совместимость со столбцами целевой таблицы — на уровне CH.
- Мы обнаружили несколько проблем, связанных с ограничениями функции
ref(): она нужна, чтобы ссылаться на модели друг из друга, но её можно использовать только для ссылок на вышестоящие модели, а не на нижестоящие. Это создаёт определённые сложности для данной реализации. Мы создали issue в репозитории dbt-core и сейчас обсуждаем с их командой возможные решения (dbt-labs/dbt-core#12319):- Когда
ref()вызывается внутри блока config, она возвращает текущую модель, а не ту, на которую должна ссылаться. Из-за этого мы не можем определить её в секции config() и вынуждены использовать комментарий, чтобы добавить эту зависимость. Мы придерживаемся того же подхода, что описан в документации dbt: вариант с “—depends_on:”. ref()нам подходит, поскольку заставляет сначала создать целевую таблицу, но на графе зависимостей в сгенерированной документации целевая таблица будет показана как ещё одна вышестоящая зависимость, а не как нижестоящая, из-за чего это немного сложнее понять.unit-testтакже вынуждает нас задавать данные для целевой таблицы, даже если по задумке чтения из неё быть не должно. Обходной путь здесь простой — оставить данные для этой таблицы пустыми.
- Когда
Использование
events_daily.sql:
{{ materialization_target_table(ref('events_daily')) }}, который задаёт целевую таблицу для MV.
Модель page_events_aggregator.sql:
mobile_events_aggregator.sql:
Параметры конфигурации
materialized='table'):
Для materialized view (
materialized='materialized_view'):
Обычно достаточно установить
catchup в True для MV либо repopulate_from_mvs_on_full_refresh в True для их целевых таблиц. Если установить True для обоих параметров, это может привести к дублированию данных.Основные операции
Полное обновление с явными целевыми таблицами
--full-refresh явные целевые таблицы будут пересозданы (поэтому вы можете потерять данные, если в ходе этого процесса продолжается ингестия). Поведение будет различаться в зависимости от конфигурации:
Вариант 1: поведение --full-refresh по умолчанию. Всё будет пересоздано, но во время пересоздания MV целевая таблица будет пустой или загруженной лишь частично.
Всё будет удалено и создано заново. Если вы хотите повторно выполнить вставку данных с помощью SQL для MV, оставьте параметр catchup=True:
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
- Таблицы будут атомарно обменены местами
Изменение целевой таблицы
--full-refresh. Если после изменения ссылки materialization_target_table() попробовать выполнить обычную команду dbt run, сборка завершится ошибкой с сообщением о том, что целевая таблица была изменена.
Чтобы изменить целевую таблицу:
- Обновите вызов
materialization_target_table() - Выполните
dbt run --full-refresh -s your_mv_model
Устранение типичных неполадок
Целевая таблица пуста во время или после выполнения run
- Для materialized view может быть задано
catchup=False, или для целевой таблицы может быть заданоrepopulate_from_mvs_on_full_refresh=False, поэтому при создании materialized views или пересоздании целевой таблицы дозагрузка не выполняется. Это ожидаемое поведение, поэтому, если вы хотите повторно вставить данные с помощью SQL materialized views, убедитесь, что для materialized view установленоcatchup=True(это значение по умолчанию) или что для целевой таблицы заданоrepopulate_from_mvs_on_full_refresh=True. Не включайте оба параметра одновременно, чтобы избежать дубликатов. Подробнее см. в разделе конфигурации. - Во время выполнения
dbt run --full-refresh, если materialized views используют значение по умолчаниюcatchup=True, целевая таблица будет пересоздана, а MV последовательно повторно вставят данные. Чтобы избежать этой ситуации, см. раздел Полное обновление с явными целевыми таблицами.
dbt run --full-refresh для целевой таблицы с repopulate_from_mvs_on_full_refresh=True использует логику из старых версий materialized view, а не из SQL, который сейчас находится в проекте
repopulate_from_mvs_on_full_refresh=True использует существующий SQL для MV, уже определённый в ClickHouse. Чтобы использовалось новое определение materialized view, выполните dbt run для каждой materialized view перед запуском dbt run --full-refresh для целевой таблицы.
После выполнения запуска появляются дубликаты данных
- Одновременно могут быть включены и
catchup=Trueдля materialized view, иrepopulate_from_mvs_on_full_refresh=Trueдля целевой таблицы: оставьте только один из этих параметров в зависимости от того, какие операции вы хотите выполнять. Подробнее см. в разделе конфигурации. - Целевая таблица определена без
WHERE 0: целевая таблица должна создаваться пустой, но внутренний запрос может вставить данные, если не указаноWHERE 0. Убедитесь, что это условие добавлено.
Потеря данных во время активной ингестии после выполнения dbt run --full-refresh
dbt run --full-refresh.
materialized view в ClickHouse работают как триггеры вставки — они захватывают данные только пока существуют. Во время полного обновления возникает короткое окно, когда MV удаляется и создаётся заново («слепое окно»). Любые строки, вставленные в исходную таблицу в течение этого окна, не будут захвачены. Подробнее см. в разделе Поведение во время активной ингестии.
Методы отладки
Проверьте текущую целевую таблицу MV в ClickHouse
system.tables, чтобы посмотреть, в какую таблицу пишет materialized view:
Проверьте, распознаёт ли dbt таблицу как целевую таблицу materialized view
Таблица <table_name> используется как целевая таблица для materialized view, управляемого dbt. Чтобы предотвратить потерю данных, для mv_on_schema_change по умолчанию устанавливается значение “fail”.
Если это сообщение появляется, значит dbt обнаружил, что таблица служит целевой как минимум для одного materialized view, управляемого dbt. Если вы ожидаете увидеть это сообщение, но его нет, проверьте следующее:
- Модель materialized view корректно задаёт
{{ materialization_target_table(ref('your_target')) }} - В config модели materialized view задано
materialized='materialized_view' - И materialized view, и целевая таблица были выполнены как минимум один раз
Переход от неявной к явной целевой таблице
materialized='table', который определяет ту же схему, что и текущая целевая таблица MV. Используйте условие WHERE 0, чтобы создать пустую таблицу. Используйте то же имя, что и у текущей неявной модели materialized view. После этого вы сможете использовать эту модель для дальнейшей доработки целевой таблицы.
materialization_target_table(), указывающий на новую целевую таблицу. Если ранее вы использовали UNION ALL, удалите эту часть и комментарии.
Для имён моделей нужно придерживаться следующего соглашения об именовании:
- если была определена только одна MV, она должна называться:
<old_model_name>_mv - если было определено несколько MV, каждая должна называться:
<old_model_name>_mv_<name_in_comments>
my_model.sql (неявная целевая таблица, одна модель с UNION ALL):
Сравнение поведения подходов с неявной и явной целевой таблицей
Общее поведение
Поведение во время активной ингестии
- Поскольку materialized view в ClickHouse работают как триггеры вставки, они захватывают данные только пока существуют. Если materialized view удаляется и создаётся заново (например, во время
--full-refresh), любые строки, вставленные в исходную таблицу в этот промежуток, не будут обработаны materialized view. Это состояние называют «слепотой» materialized view. - Все процессы
catchupоснованы на операцияхINSERT INTO ... SELECT, использующих SQL materialized view, и не зависят от того, как работают сами materialized view. После запускаINSERTновые данные в него уже не попадают, но будут захвачены присоединённой materialized view.
Операции с неявной целевой таблицей
Операции с явной целевой таблицей
Модель целевой таблицы:
Refreshable Materialized Views
refreshable в модель MV со следующими параметрами:
Пример с неявной целевой таблицей
Пример с явной целевой таблицей
Ограничения
- При создании в ClickHouse refreshable materialized view (MV) с зависимостью ClickHouse не генерирует ошибку, если указанная зависимость не существует на момент создания. Вместо этого refreshable MV остается в неактивном состоянии и ожидает, пока зависимость не будет удовлетворена, прежде чем начнет обрабатывать обновления или обновляться. Такое поведение является штатным, но оно может приводить к задержкам в доступности данных, если требуемая зависимость не будет своевременно создана. Перед созданием refreshable materialized view следует убедиться, что все зависимости корректно определены и существуют.
- На данный момент фактическая «связь dbt» между mv и его зависимостями отсутствует, поэтому порядок создания не гарантируется.
- Возможность refreshable не тестировалась для нескольких mv, направляющих данные в одну и ту же целевую модель.