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, остальные разбираются в тренажёре.
- Что делает функция ref() в dbt-модели?A)Ссылка на модель → граф зависимостей + порядок сборки + имя окруженияB)Ref() выполняет немедленный запрос к другой модели и вставляет её данные в текущую строкой кодаC)Ref() задаёт материализацию модели (view или table), определяя, как она физически создаётся в DWHD)Ref() — это ссылка на внешний сырой источник данных, объявленный вне проекта dbt
показать ответ и разбор
+A)Ссылка на модель → граф зависимостей + порядок сборки + имя окружения// разбор: ref('other_model') — способ сослаться в SQL на другую модель dbt вместо явного имени таблицы. Из всех ref() dbt автоматически строит DAG зависимостей: понимает, что модель B читает A, и собирает A раньше B, распараллеливая независимые ветки. Заодно ref() подставляет правильное имя таблицы для текущего окружения (dev/prod-схема), так что один и тот же код работает везде. Прямое хардкодное имя таблицы этого не даёт — dbt не увидит зависимость и может собрать в неверном порядке.
- Зачем в 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 на входе: пайплайн падает/предупреждает до того, как посчитает отчёт по вчерашним данным.
- Какие бывают материализации модели в dbt и в чём их разница?A)Все материализации создают строго одинаковый объект в хранилище и отличаются лишь названием в конфигеB)Материализация определяет, на каком языке (SQL или Python) написана модель dbt внутри файлаC)View/table/incremental/ephemeral — разный способ материализации в DWHD)Тип материализации задаёт права доступа к модели: view для чтения, table для записи данных
показать ответ и разбор
+C)View/table/incremental/ephemeral — разный способ материализации в DWH// разбор: Материализация задаёт, как dbt превращает SELECT модели в объект хранилища. view — создаёт представление, данные считаются при каждом обращении (дёшево строить, дорого читать тяжёлое). table — при run пересобирает таблицу целиком (быстро читать, дорого собирать на больших объёмах). incremental — при run дописывает/обновляет только новые строки, не пересчитывая всю историю (для больших append-таблиц). ephemeral — не материализуется отдельно, а подставляется как CTE в модели, которые на неё ссылаются. Выбор — размен между стоимостью сборки и чтения.
- Что такое incremental-модель в dbt и когда её берут?A)Incremental-модель при каждом запуске пересобирает таблицу с нуля по всей истории данныхB)Incremental-модель применима к небольшим справочникам, но не к большим факт-таблицамC)Incremental требует, чтобы данные источника не менялись и не приходили с опозданиемD)Обрабатывает лишь дельту, не пересобирая всё; для больших append-таблиц
показать ответ и разбор
+D)Обрабатывает лишь дельту, не пересобирая всё; для больших append-таблиц// разбор: Incremental-модель при первом запуске собирается целиком, а при последующих обрабатывает только дельту: внутри пишут блок is_incremental(), фильтрующий вход по времени/ключу больше уже загруженного, и задают unique_key + стратегию (append/merge/delete+insert), чтобы решить, дописать или обновить. Берут для больших, преимущественно дописываемых таблиц (события, логи, факты), где полный пересчёт при каждом run слишком дорог. Плата — сложнее логика, риск пропустить поздние/изменённые строки; периодически делают full-refresh для сверки.
- Что такое тесты в 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).
дальше
Теорию прочитали. Навык ставится повторением
В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.