Learning Platform
Глоссарий Troubleshooting
Урок 07.04 · 18 мин
Начальный
golden ruleconfig blockmaterialized_viewdecision tree

Мы рассмотрели три материализации — 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.

Golden Rule: дерево решений

Начинай с view, эскалируй только при необходимости. Каждый шаг трейд-офф: больше storage, сложнее код, но быстрее downstream. Не оптимизируй преждевременно.

STARTновая модель
Viewdefault, дёшево, всегда свежо
downstream тормозит?Если view выполняется > 5 секунд при SELECT и downstream делает много обращений — пора в table.
Tableфизическая копия, downstream мгновенно
dbt run долгий?Если dbt run на table занимает > 5-10 минут, и таблица растёт линейно — пора в incremental.
Incrementalсчитается только дельта
нужен auto-refresh?Materialized view — для специальных случаев (auto-refresh на triggers, не работает в DuckDB). Junior'у обычно не нужно.
Materialized Viewредко, не в DuckDB

Шаг 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 — будем разбирать.

WARNING

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' — есть подходящий для конкретной модели.

viewнет данных, инлайнинг SQL
storage: 0speed: slow downstream
freshness: real-timebest: staging
tableполные данные, full refresh
storage: Nspeed: fast downstream
freshness: dbt runbest: marts
ephemeralинлайн как CTE в downstream
storage: 0speed: depends
freshness: real-timebest: приватный шаг
incrementalдельта в существующую таблицу
storage: Nspeed: fast run + fast downstream
freshness: dbt runbest: append-only факты

Антипаттерны выбора

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 в большом проекте
Проверка знанийKnowledge check
Ты создаёшь mart_revenue_daily — агрегирует 10M orders по дням. Запрос выполняется 30 секунд. Дашборды читают её 50 раз в день. Какая материализация и почему?
ОтветAnswer
Table. Логика: запрос дорогой (30 сек) и downstream-traffic высокий (50 раз в день). Если оставить view — 50 * 30 = 25 минут CPU в день на повторные вычисления. Table выполнится один раз при dbt run, дальше дашборды читают мгновенно. Если в будущем 10M станет 1B и dbt run будет занимать > 5 минут — переключиться в incremental, считать только дельту по дате. Materialized view на DuckDB не работает; не вариант.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 6. Сформулируй Golden Rule выбора материализации.

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 4