ref() и автоматический DAG

Главная магия dbt: вы не задаёте порядок запуска — dbt выводит его сам из ссылок ref().

ref() — Jinja-функция, которая ссылается на другую модель по имени; из всех ref() dbt строит DAG (направленный ациклический граф) и вычисляет правильный порядок запуска.

Проблема: кто за кем

Витрины строятся слоями: модель orders читает customers, а revenue_report читает orders. Если запустить их в неверном порядке, поздняя модель прочитает несуществующую или вчерашнюю таблицу. В голом SQL порядок держат руками. dbt снимает эту заботу.

Как выглядит ссылка

Вместо хардкода имени таблицы вы пишете {{ ref('имя_модели') }}:

-- models/orders_enriched.sql
select
    o.order_id,
    o.amount,
    c.email
from {{ ref('orders') }} as o
join {{ ref('customers') }} as c
  on o.customer_id = c.customer_id

Здесь модель orders_enriched зависит от orders и customers. dbt видит эти ref() и понимает: сначала надо построить orders и customers, и только потом orders_enriched.

DAG: граф порядка

customers ---+
             +--> orders_enriched --> revenue_report
orders ------+

Порядок, который выведет dbt:
  1. customers, orders   (можно параллельно)
  2. orders_enriched
  3. revenue_report

Граф направленный (стрелки задают зависимость) и ациклический (циклов нет — модель не может зависеть сама от себя через цепочку). По нему dbt делает топологическую сортировку и запускает модели волнами, параллеля независимые ветви.

Почему именно ref(), а не имя таблицы

Помимо порядка, ref() решает ещё две задачи. Во-первых, он подставляет правильную схему: в dev — вашу личную, в prod — общую. Один и тот же код работает в обоих окружениях. Во-вторых, он делает зависимости явными и проверяемыми — dbt сразу скажет, если вы сослались на несуществующую модель.

Как работает под капотом

При компиляции dbt разбирает Jinja в каждой модели и собирает все вызовы ref() в граф. {{ ref('orders') }} превращается в полностью квалифицированное имя analytics.dbt_dev.orders в зависимости от цели. Затем dbt строит DAG, проверяет отсутствие циклов и сортирует узлы топологически. dbt run идёт по этому порядку, а threads определяет, сколько независимых узлов считается одновременно.

Частые ошибки

  • Хардкодить имя таблицы вместо ref(). Тогда dbt не увидит зависимость, порядок сломается, и код не переедет между окружениями.
  • Создавать цикл. Если A через цепочку ссылается на себя, dbt откажется строить граф — DAG не допускает циклов.
  • Ссылаться на источник через ref(). Сырые внешние таблицы — это source() (следующий урок), а не ref().

Итоги

  • ref() связывает модели и делает зависимости явными.
  • Из всех ref() dbt строит DAG и сам выводит порядок запуска — это ключевая идея инструмента.
  • ref() ещё и подставляет правильную схему, так что код одинаков в dev и prod.
Проверьте себя
1. Что dbt делает из вызовов ref() в моделях?
AУскоряет запросы
BСтроит DAG и автоматически выводит порядок запуска моделей
CСоздаёт индексы
DКэширует результаты
2. Почему ref('orders') лучше, чем хардкод имени таблицы?
AКороче пишется
BДелает зависимость явной для DAG и подставляет правильную схему в dev/prod
CРаботает быстрее
DНе требует SQL
3. Что означает, что граф моделей ацикличен?
AВ нём нет соединений
BМодель не может через цепочку зависеть сама от себя
CВ нём только одна модель
DОн не имеет направления