dbt на собеседовании Data Engineer
Карьерник — Duolingo для аналитиков: 10 минут в день тренируй SQL, Python, A/B, статистику, метрики и ещё 3 темы собеса. 1500+ вопросов в Telegram-боте. Бесплатно.
Содержание:
Зачем спрашивают dbt
dbt стал стандартом моделирования в DWH в РФ за последние два года: Тинькофф, Авито, X5, Озон публично выкладывают шаблоны и доклады. Если в вакансии есть Snowflake/BigQuery/ClickHouse и слово «моделирование» — почти наверняка спросят про dbt.
Главная боль без dbt — SQL-скрипты, разбросанные по Airflow-DAG, без зависимостей, без тестов, без документации. dbt даёт три вещи: lineage (DAG моделей), материализацию (как сохранить результат) и тесты на данные. Кандидат, который путает «dbt — это инструмент трансформации» с «dbt — это оркестратор», на середине собеса плывёт.
Модели и ref/source
Модель — .sql файл с одним SELECT-запросом. dbt оборачивает его в CREATE TABLE/VIEW согласно materialization.
-- models/marts/orders_daily.sql
SELECT
DATE_TRUNC('day', created_at) AS order_date,
COUNT(*) AS orders_cnt,
SUM(amount) AS revenue
FROM {{ ref('stg_orders') }}
GROUP BY 1ref() — ссылка на другую модель проекта. dbt по ref строит DAG зависимостей и решает порядок запуска.
source() — ссылка на сырую таблицу из внешнего источника. Декларируется в sources.yml:
sources:
- name: raw
tables:
- name: ordersИспользование: FROM {{ source('raw', 'orders') }}. Зачем source отдельно — ref нужен для зависимостей внутри проекта, source маркирует «вход в DWH из мира». На source-таблицы вешают freshness-проверки.
На собесе спросят: «В чём разница между ref и source?» Правильный ответ:
ref— модели, которые dbt сам создаётsource— таблицы, которые загрузил кто-то другой (Airbyte, Fivetran, Airflow)
Materializations
Materialization — как dbt сохранит результат модели:
| Тип | Что создаёт | Когда использовать |
|---|---|---|
view |
VIEW | Лёгкая трансформация, читают редко |
table |
CREATE TABLE AS SELECT | Тяжёлая модель, читают часто |
incremental |
INSERT новых строк | Большие таблицы фактов |
ephemeral |
CTE в зависимых моделях | Промежуточная логика, без материализации |
Указывается в dbt_project.yml или прямо в модели:
{{ config(materialized='incremental', unique_key='order_id') }}ephemeral — частая ловушка. Модель с ephemeral не материализуется в БД, а инлайнится как CTE в каждую модель, которая её ссылается через ref. Если на неё ссылаются 5 моделей и она тяжёлая — она пересчитается 5 раз. Использовать только для лёгких staging-преобразований.
Incremental-стратегии
Инкрементальная модель пересчитывает только новые/изменившиеся строки, не всю таблицу. Стратегии — как dbt будет мерджить новые данные:
append— просто INSERT новых строк. Дешёво. Не дедуплицирует — если придёт дубль, в таблице будет дубль.merge— UPSERT поunique_key. Дороже, но обновляет существующие строки. Дефолт для Snowflake/BigQuery/Postgres 15+.delete+insert— удалить строки по ключу, вставить заново. Используется для ClickHouse/Redshift, где нет нативного MERGE.insert_overwrite— перезаписать партиции целиком. Лучший вариант для BigQuery/Spark с партиционированием.
Шаблон incremental-модели:
{{ config(
materialized='incremental',
unique_key='event_id',
incremental_strategy='merge'
) }}
SELECT
event_id,
user_id,
event_type,
created_at
FROM {{ source('raw', 'events') }}
{% if is_incremental() %}
WHERE created_at > (SELECT MAX(created_at) FROM {{ this }})
{% endif %}Тонкость: фильтр в is_incremental() блоке должен брать данные с запасом. Если события приходят с задержкой 30 минут (late-arriving), то MAX(created_at) пропустит их. Безопаснее: created_at > MAX(created_at) - INTERVAL '1 hour' + unique_key для дедупликации через merge.
На собесе спросят: «Как сделать backfill incremental-модели?» Ответ: dbt run --full-refresh -s model_name — пересоздаст таблицу заново, игнорируя is_incremental().
Тесты в dbt
Два уровня тестов:
Generic tests — встроенные, описываются в YAML:
models:
- name: stg_orders
columns:
- name: order_id
tests:
- not_null
- unique
- name: status
tests:
- accepted_values:
values: ['new', 'paid', 'cancelled']
- name: user_id
tests:
- relationships:
to: ref('stg_users')
field: user_idЧетыре дефолтных теста — unique, not_null, accepted_values, relationships — закрывают 80% базовых проверок данных.
Singular tests — .sql файлы в tests/. Запрос возвращает строки = тест упал:
-- tests/no_negative_revenue.sql
SELECT *
FROM {{ ref('orders_daily') }}
WHERE revenue < 0Запуск: dbt test. В CI — dbt build запустит run + test + seed в правильном порядке с учётом DAG.
dbt-utils и dbt-expectations — пакеты с расширенными тестами (expression_is_true, equal_rowcount, статистические проверки).
Snapshots и SCD
Snapshot — реализация SCD Type 2 в dbt. Отслеживает изменения строк во времени, добавляя колонки dbt_valid_from и dbt_valid_to.
-- snapshots/orders_snapshot.sql
{% snapshot orders_snapshot %}
{{ config(
target_schema='snapshots',
unique_key='order_id',
strategy='check',
check_cols=['status', 'amount']
) }}
SELECT * FROM {{ source('raw', 'orders') }}
{% endsnapshot %}Две стратегии:
timestamp— сравнивает поupdated_atколонке. Если значение колонки новее, чем в snapshot — пишет новую версию. Дешёво.check— сравнивает значения колонок изcheck_cols. Используется, когда нет надёжногоupdated_at.
Запуск: dbt snapshot. На каждый запуск — новые версии строк, у предыдущих закрывается dbt_valid_to.
На собесе спросят: «Когда snapshot, когда обычная инкрементальная модель?» Ответ: snapshot — когда нужна история изменений (адрес клиента менялся, нужен исторический срез на любую дату). Incremental — когда нужен только текущий снимок или append-only лог.
Частые ошибки
Использовать ephemeral для тяжёлых моделей. Каждая зависимая модель пересчитает её заново. Для тяжёлой логики — view или table.
Incremental без unique_key. При стратегии merge без ключа dbt не знает, как дедуплицировать. Будут дубли. С append — те же дубли, но без warning.
Полный refresh каждую ночь. Если incremental-модель падает — лень разбираться, ставят --full-refresh в cron. Через год эта табличка из 1 ТБ рестартится 4 часа. Лучше — алерт на падение и точечный backfill.
Тесты только в YAML. YAML-тесты простые, но не покрывают сложные инварианты. Singular tests в tests/ дают полноту.
source без freshness. Sources.yml без freshness — упустишь момент, когда источник перестал обновляться. Минимум — warn_after / error_after.
Снапшоты в production-схеме. Snapshots генерируют много версий, могут раздуть прод. Хранить в отдельной схеме snapshots.
Связанные темы
- dbt: что это и зачем нужно
- Подготовка к собесу Data Engineer
- Airflow на собеседовании DE
- SQL для Data Engineer: собеседование
- ETL vs ELT: когда что
FAQ
dbt — это оркестратор, как Airflow?
Нет. dbt не запускает себя по расписанию и не дёргает внешние API. Это движок трансформации в DWH. В проде Airflow или dbt Cloud Scheduler запускают dbt run по расписанию.
dbt-core vs dbt Cloud — что выбирать?
dbt-core — open-source CLI, ставится pip, работает где угодно. dbt Cloud — SaaS поверх core: UI, scheduler, IDE, alerts. На собесе обычно ждут понимания core; Cloud — приятный плюс.
Как тестировать модели локально?
dbt run -s model_name запустит конкретную модель. dbt test -s model_name прогонит её тесты. dbt build -s model_name+ — модель и все downstream. dbt compile покажет финальный SQL без выполнения.
Что делать с медленными dbt-моделями?
Профилировать dbt_project_evaluator, смотреть граф через dbt docs serve, переводить тяжёлые VIEW в TABLE/incremental, добавлять кластеризацию/партиционирование на уровне DWH. В ClickHouse — материализованные представления вместо incremental.
dbt в ClickHouse работает нормально?
Через адаптер dbt-clickhouse. Есть особенности: merge стратегия эмулируется через delete+insert или ReplacingMergeTree, snapshots поддерживаются с ограничениями. Для тяжёлых джоинов лучше использовать материализованные представления Click House напрямую.
Это официальная информация?
Нет. Статья основана на публичной документации dbt и опыте команд. Конкретные требования к dbt-стеку зависят от компании.
Тренируйте Data Engineering — откройте тренажёр с 1500+ вопросами для собесов.