Big Data
dbt и Data Transformation
В типичной компании в 2026 году 200+ SQL-витрин, 50 аналитиков и 5 источников данных. Каждое изменение схемы - это карточный домик: дёрнул колонку, сломал 7 дашбордов. Как сделать data warehouse не страшнее backend-микросервиса?
- **GitLab** документирует весь handbook аналитики через dbt: 1500+ моделей, тесты на каждом фактовом поле, lineage публичный
- **HubSpot** перевёз 4 года legacy SQL-скриптов в dbt за 8 месяцев; CI сократился с 45 минут до 7 за счёт state:modified
- **dbt Cloud** в проде Airbnb обрабатывает 30 000 моделей с column-level lineage в Snowflake
dbt models: SQL как код
В типичном хранилище данных 200+ витрин: `daily_active_users`, `revenue_by_country`, `funnel_signup_to_paid`. До dbt их писали как набор cron-задач, гоняющих SQL-скрипты в Airflow. Любая правка превращалась в археологию: где определена эта таблица? кто её обновляет? **dbt** превращает SQL-преобразования в проект с git-историей, зависимостями и автоматическим оркестрированием.
Каждый файл `models/daily_active_users.sql` - это один SELECT, который dbt оборачивает в CREATE TABLE/VIEW при `dbt run`. Внутри SELECT используется функция `{{ ref('table_name') }}` - dbt автоматически строит граф зависимостей по этим ссылкам и запускает модели в правильном порядке.
**Materializations** в dbt: `view` (нулевая стоимость, медленное чтение), `table` (полная перезапись), `incremental` (добавление новых строк - для больших фактов), `ephemeral` (CTE, не материализуется). Выбор зависит от размера и частоты обновлений.
Модель `revenue_daily` зависит от `stg_payments`, которая зависит от `raw_payments`. При запуске `dbt run --select revenue_daily` что произойдёт?
Тесты данных
В коде на Java/Python тесты считаются нормой, а в SQL-витринах их обычно нет. Результат: дашборд показывает revenue = -42 из-за NULL в payments, и об этом узнают через неделю от CFO. **dbt tests** - это утверждения о данных: not_null, unique, accepted_values, отношения между таблицами. Они запускаются командой `dbt test` и валят пайплайн при провале.
Тесты бывают двух видов. **Generic tests** объявляются в YAML рядом с моделью - применяются к колонке. **Singular tests** - произвольный SQL-файл в `tests/`, который должен вернуть 0 строк (если вернул больше - тест провален).
**Severity: warn vs error** - тест с `severity: warn` не валит пайплайн, а только пишет в лог. Полезно для метрик, где допустима небольшая аномалия (revenue упал на 5% - подозрительно, но не катастрофа).
Тест `unique` на колонке user_id таблицы dim_users падает: найдено 3 дубликата. Что произойдёт с моделями, которые зависят от dim_users?
Документация
В компании 50 аналитиков и 200 моделей. Новый аналитик спрашивает: 'А чем `revenue` отличается от `gross_revenue` и `net_revenue`?'. Обычный ответ: 'Спроси Васю, он знает'. Через пол года Вася уволился. **dbt docs** - встроенная система документации: описания моделей и колонок живут в YAML рядом с SQL и публикуются в виде сайта.
Описания берутся из `description:` полей и **doc-блоков** (markdown-сниппетов). Команда `dbt docs generate` собирает их вместе с автоматической схемой и lineage-графом, `dbt docs serve` поднимает локальный сайт. В проде сайт деплоится в S3 или GitHub Pages.
**Колоночные тесты + описания** - это data contract в коде. Когда аналитик добавляет колонку без описания, можно настроить hook, валящий CI: `dbt run-operation check_descriptions`.
Чем dbt docs принципиально лучше Confluence-страницы с описанием витрин?
Lineage и impact analysis
PM спрашивает: 'Если мы переименуем колонку user_id -> uid в raw_events, что сломается?'. Без lineage ответ: 'надо проверить руками 200 моделей'. С dbt lineage запрос `dbt list --select +my_dashboard_table` мгновенно показывает все апстрим-зависимости, а `dbt list --select +stg_events+` - всё, что зависит от stg_events во весь даунстрим.
Lineage-граф строится автоматически из `{{ ref() }}` и `{{ source() }}` вызовов. В UI dbt docs это интерактивный граф со стрелками, где видно: какие модели тянут данные из raw, какие витрины используют конкретное поле, кто owner. На уровне колонок (column-level lineage) работают платные расширения (dbt Cloud, OpenLineage, DataHub).
**state:modified+** - паттерн CI/CD: dbt сравнивает текущий код с продакшен-manifest.json и запускает только изменённые модели + их даунстрим. Это сокращает CI с 2 часов до 5 минут на больших проектах.
dbt - это просто шаблонизатор SQL с Jinja, можно обойтись Airflow + raw SQL.
dbt - это фреймворк с DAG, тестами, документацией и state-based deployments; Airflow только запускает задачи, без lineage и тестов на данных.
Airflow знает 'какой скрипт за каким', dbt - 'какие колонки откуда берутся'. Это разные уровни абстракции, и lineage недостижим без явных ref().
Аналитик хочет добавить новое поле в stg_orders. Какой dbt-команды достаточно, чтобы оценить impact на даунстрим?
Ключевые идеи
- **Models = SQL + ref()** - dbt автоматически строит DAG зависимостей по {{ ref() }} и запускает в правильном порядке
- **Тесты данных** - not_null, unique, relationships - валят пайплайн при провале и блокируют распространение плохих данных
- **Документация в YAML** живёт рядом с кодом, ревьюится в PR и не расходится со схемой - в отличие от Confluence
- **Lineage и state:modified+** - готовые инструменты для impact analysis и инкрементального CI
- Materializations (view/table/incremental) выбираются по размеру модели и частоте обновлений - один из главных рычагов оптимизации стоимости
Связанные темы
Возвращаясь к 200 витринам: dbt превращает их в проект с тестами, документацией и lineage. Эта дисциплина связана с:
- Apache Airflow и оркестрация — Airflow запускает `dbt run` по расписанию или по событию; dbt отвечает за SQL-преобразования, Airflow - за зависимости вне dbt
- Распределённое хранилище (Snowflake, BigQuery) — dbt компилирует SQL под конкретный warehouse; разные movies (incremental) используют warehouse-specific фичи (MERGE, INSERT OVERWRITE)
Вопросы для размышления
- Если в проекте 5 аналитиков пишут SQL без code review - помогает ли dbt сам по себе, или это инструмент только при наличии инженерной культуры?
- Where проходит граница между 'это место для трансформации в dbt' и 'это место для ETL-задачи в Airflow с Python'? Какие задачи невозможно решить только в SQL?
- Column-level lineage есть в платных версиях (dbt Cloud, DataHub). Стоит ли его внедрять в команде из 10 аналитиков, или достаточно model-level?
Связанные уроки
- bd-13 — Airflow оркестрирует dbt трансформации
- bd-15 — Чистые dbt-таблицы подаются в Spark MLlib
- bd-16 — Feature store строится на dbt-трансформациях
- alg-18-topological — dbt lineage graph - DAG зависимостей моделей
- ml-04-data-preprocessing — dbt - декларативный data preprocessing
- bt-06-rest — dbt интерфейс - контракт как REST API для данных
- db-05-sql-basics