Мы рассмотрели три материализации — view, table, ephemeral — и упомянули, что есть ещё incremental (модуль 7) и materialized_view (есть на других warehouse, нет в DuckDB). Теперь синтезируем всё в практичное правило выбора, которым пользуется индустрия.
Это Golden Rule материализаций — последовательность вопросов, которые ты задаёшь себе для каждой новой модели.
Само правило
View -> Table when slow -> Incremental when table is slow -> Materialized View when incremental is wrong fit.
Читай слева направо: начинай с view. Если стало медленно (downstream тормозит) — переходи в table. Если table стал медленным при dbt run (таблица большая, пересчёт долгий) — переходи в incremental. Materialized view — спецстратегия для редких случаев, не для DuckDB.
Начинай с view, эскалируй только при необходимости. Каждый шаг трейд-офф: больше storage, сложнее код, но быстрее downstream. Не оптимизируй преждевременно.
Шаг 1: всегда начинай с view
Когда создаёшь новую модель — не материализуй её сразу как table. Начни с view (default или явный). Причины:
- view быстро создаётся через dbt run — не блокирует разработку;
- если ошибки в логике, исправление мгновенное;
- ты не знаешь заранее, насколько эта модель будет использоваться downstream;
- 80% staging-моделей навсегда остаются view, потому что они достаточно дешёвые.
Это прицнип ленивой оптимизации: оптимизируй когда (если) проблема появится, не до того.
Шаг 2: переходи в table, если
Триггеры для перехода view -> table:
- SELECT по view выполняется > 3-5 секунд (заметно для пользователей дашбордов);
- Модель используется downstream’ом > 3-5 раз — повторные вычисления накапливаются;
- Запрос содержит сложные JOIN, оконные функции, GROUP BY на больших данных;
- Модель — mart, который читается дашбордами/BI.
В YAML или config:
{{ config(materialized='table') }}
SELECT ...
Или в dbt_project.yml (рекомендуется для marts):
models:
jaffle_shop:
marts:
+materialized: table
Шаг 3: переходи в incremental, если
Триггеры для перехода table -> incremental:
- dbt run на table занимает > 5-10 минут;
- Таблица растёт линейно по объёму (например, ежедневные events, орderы — append-only);
- Большая часть данных не меняется при каждом run (только последние N часов/дней);
- Стоимость warehouse compute начинает беспокоить (актуально для Snowflake/BigQuery).
Incremental — отдельная тема, целый модуль 7. Превью:
{{ config(
materialized='incremental',
unique_key='order_id'
) }}
SELECT *
FROM {{ source('jaffle_shop', 'orders') }}
{% if is_incremental() %}
WHERE updated_at > (SELECT max(updated_at) FROM {{ this }})
{% endif %}
Логика: первый run — полный SELECT. Последующие — только новые/изменённые строки.
Шаг 4: materialized view — для редких случаев
Materialized view — это сохранённая таблица, которая автоматически обновляется warehouse при изменении источников (на тех warehouse, где это поддерживается). На Snowflake — частично, на BigQuery — частично, на Postgres — REFRESH MATERIALIZED VIEW.
В DuckDB не поддерживается. Если попытаешься:
{{ config(materialized='materialized_view') }}
SELECT ...
Получишь ошибку компиляции от dbt-duckdb. Это сразу даёт понять, что нужно выбрать другую материализацию.
В курсе junior’а можно просто запомнить: “есть такая опция на других warehouses, на DuckDB нет, в обычных проектах не нужно”. Когда дойдёшь до middle и реальных cloud warehouse — будем разбирать.
materialized_view даже там, где поддерживается (Snowflake/BigQuery), имеет много ограничений: не любой запрос можно сделать materialized, обновление работает только на triggers (изменение source), есть лимиты по freshness. На junior-проекте всегда обходись table или incremental — они проще и предсказуемее.
Анатомия config() блока
{'{{'} config(...) {'}}'} — это Jinja-функция, которая задаёт параметры модели. Принимает любые именованные аргументы. Основные:
{{ config(
materialized='table', -- view/table/ephemeral/incremental/materialized_view
schema='marts', -- custom schema, добавляется к target.schema
database='analytics', -- custom database (редко)
alias='customers_dim', -- переименование объекта в warehouse
tags=['daily', 'critical'], -- для node selection
on_schema_change='fail', -- для incremental: что делать при изменении колонок
pre_hook=['ANALYZE'], -- SQL перед моделью
post_hook=['GRANT SELECT ...'] -- SQL после модели
) }}
Важные особенности:
- config должен быть до SELECT. Если поставишь config после SELECT — dbt не увидит его, материализация будет default (view).
- Один config на модель. Если вызовешь дважды, второй переопределит первый. Используется редко (когда условно через if).
- Можно задавать default в dbt_project.yml. Если для папки указан
+materialized: table, ты можешь не писать config в каждой модели — будет table по умолчанию.
Конфликт config: в SQL и в YAML
Иерархия: config в SQL -> config в YAML _models.yml -> dbt_project.yml -> dbt default. То что ближе к модели (в самом SQL) — сильнее.
Пример:
# dbt_project.yml
models:
jaffle_shop:
marts:
+materialized: table
-- models/marts/dim_customers.sql
{{ config(materialized='view') }} -- ЭТО ПОБЕДИТ
SELECT ...
dim_customers будет view, потому что config в SQL имеет приоритет. Это удобно: дефолт на папку, override в отдельных моделях.
Полная таблица материализаций (для junior-уровня)
Каждая материализация — это набор trade-off'ов между storage, speed, freshness и сложностью. Не существует 'best' — есть подходящий для конкретной модели.
Антипаттерны выбора
1. Все модели — table. “Чтобы было быстро”. Это перегружает storage и делает dbt run медленным. Используй view для staging.
2. Глубокие чейны ephemeral. 4+ уровня ephemeral -> ephemeral -> … убивают query planner. Материализуй промежуточные слои.
3. View на mart. Дашборды читают view, view раскрывается в полный JOIN на каждый запрос — пользователи матерятся на тормозящий BI. Marts — табл.
4. Incremental без понимания. Преждевременная оптимизация: модель на 100k строк, dbt run за 2 секунды, но “хочу инкрементально”. Получишь сложный код, забудешь про --full-refresh, словишь баги. Сначала табл.
5. Materialized view “потому что красивое название”. Если ты на DuckDB — оно вообще не работает. Если на других warehouse — много ограничений, лучше incremental.
Best practice по слоям
Стандартная dbt-структура staging -> intermediate -> marts диктует материализации:
# dbt_project.yml
models:
jaffle_shop:
staging:
+materialized: view # дешевые трансформации
intermediate:
+materialized: ephemeral # или view, по необходимости
marts:
+materialized: table # читается дашбордами
Это работает в 80% случаев. Отдельные модели переопределяют через config в SQL. Большие факт-таблицы в marts можно поставить incremental вручную:
-- models/marts/fact_orders.sql
{{ config(materialized='incremental', unique_key='order_id') }}
SELECT ...
Команды для отладки
Посмотреть, как именно материализуется каждая модель:
dbt list --select jaffle_shop --output json | jq '.[] | {name, config}'
Или просто запусти dbt run — output показывает тип материализации в логах:
14:23:01 1 of 5 START sql view model main.stg_customers .......... [RUN]
14:23:01 2 of 5 START sql table model main.dim_customers ........ [RUN]
14:23:01 3 of 5 START sql incremental model main.fact_orders .... [RUN]
sql view model, sql table model, sql incremental model — типы материализаций. Если хочешь убедиться, что для папки применился default — проверь здесь.
Попробуй сам
В своём проекте задай в dbt_project.yml:
models:
jaffle_shop:
staging:
+materialized: view
marts:
+materialized: table
Создай две модели:
-- models/staging/stg_test.sql
SELECT 1 AS x
-- models/marts/mart_test.sql
SELECT * FROM {{ ref('stg_test') }}
Запусти dbt run --select stg_test+. Проверь:
SELECT table_name, table_type FROM information_schema.tables WHERE table_name IN ('stg_test', 'mart_test');
stg_test должен быть VIEW, mart_test — BASE TABLE. Это и есть Golden Rule в действии: дешёвый staging как view, потребляемый mart как table.
Что мы поняли
Golden Rule — это эмпирический алгоритм: начни с view, эскалируй к table при тормозах downstream, к incremental при тормозах dbt run, materialized_view — для редких случаев и не работает в DuckDB. config() блок в SQL имеет приоритет над YAML и dbt_project.yml. Стандартный паттерн: staging -> view, marts -> table, intermediate -> ephemeral/view по обстоятельствам.
На этом мы закончили модуль про материализации. В следующем модуле углубимся в incremental — самая важная материализация для production-проектов с большими данными.
VIEW и MATERIALIZED VIEW в PostgreSQL — SQL-уровень того, что строит dbt Medallion-паттерн в production: staging / intermediate / marts в большом проекте