сеньорчикОткрыть в Telegram
← вся теориятеория к собесу · ETL и оркестрация

dbt: трансформации в хранилище

dbt: трансформации как код

dbt это буква T в ELT: трансформации внутри хранилища, оформленные как версионируемый код с тестами и lineage. Собес проверяет, понимаешь ли ты роль ref(), материализации и зачем инкременту окно поздних данных.

Стержень: модели это SELECT-файлы, а ref() строит из них граф зависимостей и разруливает окружения.

// Формулировки: «что такое dbt и зачем ref()?», «какие материализации бывают?», «как устроен incremental?».

Модели и ref()

Модель dbt это SELECT-файл, из которого dbt материализует таблицу или вьюху. Из набора моделей dbt собирает объекты в правильном порядке по графу зависимостей.

Граф строится через ref(): вместо хардкода имени таблицы модель ссылается на другую через ref('name'). Это даёт dbt DAG (directed acyclic graph) зависимостей, автоматический разбор окружений (dev/prod схемы) и lineage. Прямое имя таблицы ломает и порядок сборки, и разделение сред - прод-имя протечёт в dev.

// dbt при этом не качает данные: extract и load (Fivetran, Airbyte, свои загрузчики) - отдельно, dbt только трансформирует уже загруженное. Оркеструется Airflow'ом или dbt Cloud.

model
SELECT-файл, материализуемый в таблицу/вьюху
ref()
ссылка на модель - строит DAG и окружения

Материализации

Материализация - как модель ложится в базу. View - легко и всегда свежо, но читать медленно. Table - пересборка целиком. Incremental - дозапись только новых периодов. Ephemeral - встраивается как CTE (common table expression), без своего объекта. Выбор - по объёму и частоте чтения.

Incremental устроен так: WHERE по is_incremental() ограничивает дозагрузку, окно поздних данных ловит опоздавшие строки, unique_key нужен для merge. Периодический full-refresh страхует от накопленного дрейфа.

// Крайности вредны: всё таблицами при ежечасной пересборке гигантов - час работы ради трёх читателей; а incremental без окна поздних данных теряет вчерашние опоздавшие строки навсегда.

incremental
материализация с дозаписью только новых данных
is_incremental()
флаг: полная сборка или дозагрузка нового

Тесты, слои, документация

Тесты живут рядом с моделью (unique, not_null, relationships плюс кастомные SQL) и гоняются в CI и по расписанию: модель без теста на ключ не должна ехать в prod. Тесты только в dev означают, что качество осталось в ноутбуке.

Слои проекта: staging (1:1 с источником - переименование и типизация) → intermediate → marts. Staging-модель на каждый источник - единая точка правки схемы.

// Документация и lineage идут из коробки: описания в yml, dbt docs строит граф от источника до витрины. Плюс есть snapshot - встроенный механизм SCD2 (slowly changing dimension type 2) для истории измерений. «Откуда взялось это поле» перестаёт быть археологией.

source
декларация сырой таблицы с freshness-проверками
snapshot
встроенный SCD2-механизм dbt для истории

Как отвечать: «Что такое dbt и зачем нужен ref()?»

dbt это инструмент трансформаций в подходе ELT: данные уже загружены в хранилище сырыми, а dbt превращает их в витрины, причём каждая трансформация это просто SELECT-файл под версионным контролем, с тестами и документацией. dbt сам определяет порядок сборки моделей, и делает это через ref(). Вместо того чтобы захардкодить имя таблицы, я пишу ref('stg_orders'), и это даёт три вещи. Во-первых, dbt из этих ссылок строит граф зависимостей и собирает модели в правильном порядке. Во-вторых, разруливает окружения - в dev подставит dev-схему, в prod prod-схему, так что один код работает везде. В-третьих, ведёт lineage, и видно, откуда какое поле пришло. Если захардкодить schema.table напрямую, ломается и порядок сборки, и разделение сред, и прод-имя может протечь в dev. Поэтому ref() - не синтаксический сахар, а то, на чём держится вся модель dbt.

Почему это сильный ответ: определён dbt в контексте ELT (extract, load, transform) (только T), и ref() объяснён через три конкретные функции (DAG, окружения, lineage) с ценой хардкода - понятно, почему это ядро, а не удобство.

На чём валят

  • Хардкод schema.table вместо ref() - сломанный lineage и прод-имена в dev.
  • Incremental без окна поздних данных - вчерашние опоздавшие строки потеряны навсегда.
  • Всё таблицами при ежечасной пересборке гигантов - час работы ради трёх читателей.
  • Одна модель на 800 строк SQL с пятью смыслами - дели по слоям, это и есть смысл dbt.
  • Тесты только в dev: прод-запуск без dbt test - качество осталось в ноутбуке.

Проверьте себя

Пять вопросов из банка по этой подтеме. Всего их 9, остальные разбираются в тренажёре.

  1. #dbt_transformations1 / 5
    Что делает функция ref() в dbt-модели?
    A)Ссылка на модель → граф зависимостей + порядок сборки + имя окружения
    B)Ref() выполняет немедленный запрос к другой модели и вставляет её данные в текущую строкой кода
    C)Ref() задаёт материализацию модели (view или table), определяя, как она физически создаётся в DWH
    D)Ref() — это ссылка на внешний сырой источник данных, объявленный вне проекта dbt
    показать ответ и разбор
    +A)Ссылка на модель → граф зависимостей + порядок сборки + имя окружения

    // разбор: ref('other_model') — способ сослаться в SQL на другую модель dbt вместо явного имени таблицы. Из всех ref() dbt автоматически строит DAG зависимостей: понимает, что модель B читает A, и собирает A раньше B, распараллеливая независимые ветки. Заодно ref() подставляет правильное имя таблицы для текущего окружения (dev/prod-схема), так что один и тот же код работает везде. Прямое хардкодное имя таблицы этого не даёт — dbt не увидит зависимость и может собрать в неверном порядке.

  2. #dbt_transformations2 / 5
    Зачем в dbt объявляют source() и проверяют его freshness?
    A)Source() создаёт новую таблицу-источник в хранилище и сам наполняет её данными из внешних систем
    B)source описывает сырые входные таблицы как зависимости, а freshness проверяет, не устарели ли они (свежесть загрузки)
    C)Freshness проверяет синтаксическую корректность SQL модели перед её запуском в хранилище данных
    D)Source() нужен для документации и на построение графа зависимостей не влияет
    показать ответ и разбор
    +B)source описывает сырые входные таблицы как зависимости, а freshness проверяет, не устарели ли они (свежесть загрузки)

    // разбор: source() в dbt декларирует сырые таблицы, загруженные извне (из EL-слоя), как явные входы проекта: модели ссылаются на них через source(), а не хардкодом, попадая в граф зависимостей и документацию. Freshness-проверка смотрит на max(loaded_at) источника и сигналит, если данные не обновлялись дольше порога — то есть апстрим-загрузка отстала или сломалась, и строить витрины поверх устаревшего сырья нельзя. Это data-contract на входе: пайплайн падает/предупреждает до того, как посчитает отчёт по вчерашним данным.

  3. #dbt_transformations3 / 5
    Какие бывают материализации модели в dbt и в чём их разница?
    A)Все материализации создают строго одинаковый объект в хранилище и отличаются лишь названием в конфиге
    B)Материализация определяет, на каком языке (SQL или Python) написана модель dbt внутри файла
    C)View/table/incremental/ephemeral — разный способ материализации в DWH
    D)Тип материализации задаёт права доступа к модели: view для чтения, table для записи данных
    показать ответ и разбор
    +C)View/table/incremental/ephemeral — разный способ материализации в DWH

    // разбор: Материализация задаёт, как dbt превращает SELECT модели в объект хранилища. view — создаёт представление, данные считаются при каждом обращении (дёшево строить, дорого читать тяжёлое). table — при run пересобирает таблицу целиком (быстро читать, дорого собирать на больших объёмах). incremental — при run дописывает/обновляет только новые строки, не пересчитывая всю историю (для больших append-таблиц). ephemeral — не материализуется отдельно, а подставляется как CTE в модели, которые на неё ссылаются. Выбор — размен между стоимостью сборки и чтения.

  4. #dbt_transformations4 / 5
    Что такое incremental-модель в dbt и когда её берут?
    A)Incremental-модель при каждом запуске пересобирает таблицу с нуля по всей истории данных
    B)Incremental-модель применима к небольшим справочникам, но не к большим факт-таблицам
    C)Incremental требует, чтобы данные источника не менялись и не приходили с опозданием
    D)Обрабатывает лишь дельту, не пересобирая всё; для больших append-таблиц
    показать ответ и разбор
    +D)Обрабатывает лишь дельту, не пересобирая всё; для больших append-таблиц

    // разбор: Incremental-модель при первом запуске собирается целиком, а при последующих обрабатывает только дельту: внутри пишут блок is_incremental(), фильтрующий вход по времени/ключу больше уже загруженного, и задают unique_key + стратегию (append/merge/delete+insert), чтобы решить, дописать или обновить. Берут для больших, преимущественно дописываемых таблиц (события, логи, факты), где полный пересчёт при каждом run слишком дорог. Плата — сложнее логика, риск пропустить поздние/изменённые строки; периодически делают full-refresh для сверки.

  5. #dbt_transformations5 / 5
    Что такое тесты в dbt и чем generic-тесты отличаются от singular?
    A)Generic — типовые декларативные, singular — кастомный SQL-тест
    B)Generic-тесты проверяют синтаксис SQL моделей, а singular — их производительность на больших данных
    C)Dbt-тесты проверяют Python-код проекта и к содержимому таблиц не обращаются
    D)Разницы между ними нет: generic и singular — два имени одного и того же типа теста в dbt
    показать ответ и разбор
    +A)Generic — типовые декларативные, singular — кастомный SQL-тест

    // разбор: dbt-тесты проверяют не код, а сами данные модели. Generic-тесты — параметризованные и переиспользуемые, объявляются в YAML на колонку: unique, not_null, relationships (ссылочная целостность на другую модель), accepted_values (значение из списка). Singular-тест — это отдельный SQL-запрос, возвращающий «плохие» строки под конкретную бизнес-проверку (например, выручка не отрицательна и сходится с источником). Тест считается пройденным, если запрос вернул ноль строк. Так качество данных версионируется и гоняется вместе со сборкой (dbt test).

дальше

Теорию прочитали. Навык ставится повторением

В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.