Learning Platform
Глоссарий Troubleshooting
Урок 05.03 · 18 мин
Начальный
DAGupstreamdownstreamlineagetopological-sort

DAG как mental model dbt

Если из всего курса нужно вынести одно понятие — это DAG. Все команды dbt, все рассуждения про порядок выполнения, все debug-сценарии — они вокруг DAG.

Этот урок строит ментальную модель: что такое DAG в контексте dbt, как читать lineage, и почему dbt run --select X+ — это центральная команда дев-цикла.


DAG = Directed Acyclic Graph

DAG — это:

  • Graph — набор узлов (nodes) и рёбер (edges) между ними
  • Directed — рёбра имеют направление: A -> B значит «A до B», не наоборот
  • Acyclic — нет циклов: нельзя из A прийти в A по рёбрам, проходя через B, C, D

В dbt:

  • Узлы = модели, sources, seeds, snapshots, tests, exposures
  • Рёбра = зависимости через ref() или source()
  • Направление = от parent (upstream) к child (downstream)

Простой пример:

DAG из четырёх моделей
raw.ordersSource (raw таблица в warehouse). Узел в DAG, но не строится dbt — только декларирован.
source()
stg_ordersStaging: rename + cast от raw. View. Зависит от source.
ref()
int_orders_with_customersIntermediate: JOIN. View или ephemeral. Зависит от stg_orders и stg_customers.
ref()
fct_ordersMart: финальная fact-таблица для BI. Table.

В этом DAG:

  • fct_orderslowest in graph (нет downstream)
  • raw.ordershighest in graph (нет upstream)
  • Направление — сверху вниз (parents -> children)

Upstream и downstream

Базовая терминология:

  • Upstream = выше по графу = те, от кого зависит модель
  • Downstream = ниже по графу = те, кто зависит от модели

Для int_orders_with_customers:

  • Upstream: stg_orders, stg_customers, raw.orders, raw.customers (всё, что нужно построить раньше)
  • Downstream: fct_orders, dim_customers, и так далее (всё, что использует int_orders_with_customers через ref())

Аналогия с течением реки: данные «текут» вниз по графу. Source — родник. Marts — устье.

TIP

В docs.getdbt.com upstream иногда называется «parents», downstream — «children». Это синонимы, удобно знать оба.


Node selection: + и @

Selectors + и @ — это синтаксис для выбора частей DAG. Это центральный инструмент дев-цикла.

+ слева — upstream

dbt run --select +fct_orders

«Запусти fct_orders и всё, что нужно построить до него (upstream)».

Это: raw.orders -> stg_orders -> int_orders_with_customers -> fct_orders. Все upstream-модели + сама модель.

+ справа — downstream

dbt run --select stg_orders+

«Запусти stg_orders и всё, что зависит от неё (downstream)».

Это: stg_orders -> int_orders_with_customers -> fct_orders -> fct_revenue -> ....

+ с обеих сторон — both

dbt run --select +int_orders+

«Запусти int_orders_with_customers и всё upstream и downstream».

Это: целая «полоса» DAG, проходящая через эту модель.

N в + ограничивает глубину

dbt run --select +2fct_orders

«fct_orders и 2 уровня upstream» — не дальше.

dbt run --select stg_orders+1

«stg_orders и 1 уровень downstream».

Без числа после + — глубина неограниченная (всё, что есть).

@ — все upstream НО строит только саму модель

dbt run --select @fct_orders

Это reverse-selection: «возьми всё upstream от fct_orders, но строим именно fct_orders». Используется редко в специфических CI-сценариях.


Типичные сценарии node selection

Сценарий 1: Изменил staging-модель, хочу пересобрать всё, что от неё зависит

dbt run --select stg_orders+

Все downstream от stg_orders. Это типичный flow после изменения staging.

Сценарий 2: Нужен fct_orders с нуля, пересоберём всю цепочку

dbt run --select +fct_orders

Все upstream от fct_orders. Полная цепочка построения.

Сценарий 3: CI — запустить только то, что изменено с main

dbt run --select state:modified+ --state target/main

«Модифицированные с main версии + downstream». Это state comparison — модуль 17 курса.

Сценарий 4: Только staging-слой

dbt run --select staging

«Все модели в подпапке staging». Папка интерпретируется как selector.

Сценарий 5: Несколько моделей через запятую (union)

dbt run --select stg_orders stg_customers

«Запусти обе модели и их зависимости».

Сценарий 6: Пересечение через intersection (пробел)

dbt run --select tag:hourly+ tag:revenue

«Модели с тегом hourly + downstream, ПЕРЕСЕЧЕНИЕ с моделями имеющими тег revenue». Подробно в модуле 17.


Что показывает dbt run

При запуске dbt показывает порядок:

1 of 5 START sql view  model main.stg_orders ............ [RUN]
2 of 5 START sql view  model main.stg_customers ......... [RUN]
1 of 5 OK created sql view  model main.stg_orders ....... [OK in 0.04s]
2 of 5 OK created sql view  model main.stg_customers .... [OK in 0.04s]
3 of 5 START sql view  model main.int_orders ............ [RUN]
3 of 5 OK created sql view  model main.int_orders ....... [OK in 0.05s]
4 of 5 START sql table model main.fct_orders ............ [RUN]
5 of 5 START sql table model main.dim_customers ......... [RUN]
4 of 5 OK created sql table model main.fct_orders ....... [OK in 0.06s]
5 of 5 OK created sql table model main.dim_customers .... [OK in 0.05s]

Видим:

  • Параллелизм где возможен: stg_orders и stg_customers стартанули одновременно (1 of 5 и 2 of 5)
  • Последовательность где нужно: int_orders ждал обе staging
  • Снова параллелизм: fct_orders и dim_customers параллельно (4 of 5 и 5 of 5)

Это и есть топологическая сортировка DAG с учётом threads. dbt вычисляет «какие модели могут запуститься сейчас» и запускает их пачкой до threads.


Топологическая сортировка: что это технически

«Топологическая сортировка» звучит сложно. На самом деле это простой алгоритм:

while есть неиспользованные узлы:
    найти все узлы, у которых все upstream уже выполнены
    запустить их параллельно (до threads штук)
    подождать пока завершатся
    отметить как выполненные

Это всё. Для нашего DAG:

  1. Итерация 1: stg_orders, stg_customers (нет upstream -> запускаем оба)
  2. Итерация 2: int_orders (upstream завершены -> запускаем)
  3. Итерация 3: fct_orders, dim_customers (upstream завершены -> запускаем оба)

dbt делает это автоматически из manifest.json.

NOTE

Алгоритм гарантирует: модель не запустится, пока её upstream не завершились (даже если есть свободные threads). Это критично для корректности — модель не может прочитать таблицу, которой ещё нет.


Когда DAG ломается: типичные ошибки

1. Forgot to add ref()

Хардкод имени:

-- Неправильно
FROM dev.main.stg_orders

-- Правильно
FROM {{ ref('stg_orders') }}

Симптом: модель работает в dev, но в prod падает (имя dev.main.stg_orders не существует в prod). Или работает везде, но dbt docs serve не показывает зависимость в lineage UI.

2. Forgot to add source()

-- Неправильно
FROM raw.events

-- Правильно
FROM {{ source('raw', 'events') }}

Симптом: dbt не знает про этот source. Freshness не работает. Lineage пустой.

3. Циклическая зависимость

-- A.sql
SELECT * FROM {{ ref('B') }}

-- B.sql
SELECT * FROM {{ ref('A') }}

Симптом на dbt parse:

Compilation Error: Found a cycle: model.X.A --> model.X.B --> model.X.A

Решение: переосмыслить логику, разорвать круг через staging-слой.

4. ref() на несуществующую модель

SELECT * FROM {{ ref('typo_in_name') }}

Симптом:

Compilation Error: Model depends on a node named 'typo_in_name' which was not found

Проверить написание, наличие файла.

5. Запуск без учёта DAG

Команда меняет staging-модель и запускает dbt run --select stg_orders (без +). staging build, но downstream не пересчитан — данные в fct_orders устаревшие. Решение: всегда + справа, когда меняешь что-то в середине DAG.


lineage UI: dbt docs

После dbt docs generate && dbt docs serve открывается интерактивный lineage UI. Что там есть:

  • Левая панель — дерево моделей по проектам/папкам
  • Центр — описание модели (description, колонки, тесты, конфиги)
  • Иконка графа справа внизу — открывает lineage

В lineage:

  • Зелёные ноды — модели
  • Жёлтые — sources
  • Синие — seeds
  • Розовые — snapshots
  • Иконка теста — рядом с моделью с тестами

Кнопки фильтрации:

  • +1 — показать 1 уровень upstream и downstream
  • +2 — 2 уровня
  • - — спрятать узлы фильтрацией

Это полезно на больших проектах (1000+ моделей), где смотреть весь DAG целиком невозможно.


Глобальный DAG vs локальный взгляд

В большом проекте полный DAG не помещается на экран. Поэтому есть два режима работы:

1. Global view (dbt docs serve, или Cosmos/Dagster integrations): видеть весь граф проекта, фильтровать, наводить курсор на ноды для деталей.

2. Local view (dbt CLI + selectors): работать с поднабором — «сейчас я изменил вот эту модель, мне интересны только её downstream».

В дев-цикле большая часть работы — local view. Запустил dbt run --select +my_model+, отладил, ушёл. Global view используется реже — для планирования, дискуссий, ревью больших изменений.


Manifest как «материализованный DAG»

После каждого dbt parse / dbt compile в target/manifest.json оказывается полное JSON-представление DAG:

{
  "nodes": {
    "model.learning_models.stg_orders": {
      "name": "stg_orders",
      "depends_on": {
        "macros": [],
        "nodes": []
      },
      "config": {"materialized": "view"},
      "compiled_code": "SELECT ..."
    },
    "model.learning_models.fct_orders": {
      "name": "fct_orders",
      "depends_on": {
        "nodes": ["model.learning_models.int_orders"]
      },
      "config": {"materialized": "table"}
    }
  },
  "sources": {...},
  "tests": {...}
}

Это и есть «сериализованный DAG». На его базе работают:

  • dbt docs serve — рисует lineage из nodes/depends_on
  • Defer (CI без full rebuild) — сравнивает manifest с prod-версией
  • State comparison (--select state:modified+) — diff manifest.json
  • Интеграции (Cosmos для Airflow, dbt artifacts package, Elementary) — парсят manifest и кормят свои фичи

Если хотите увидеть граф «как код» — cat target/manifest.json | jq '.nodes | keys' покажет все ноды.


DAG как unit of debugging

Когда что-то не работает — мыслите DAG-ом. Типичный flow:

  1. fct_orders показывает неправильные данные
  2. Проверяю compiled SQL fct_orders — он ок
  3. Смотрю upstream через dbt list --select +fct_orders — там int_orders_with_customers
  4. Запускаю dbt show --select int_orders_with_customers --limit 5 — данные ок
  5. Запускаю dbt show --select stg_orders --limit 5 — оп, тут проблема
  6. Понимаю, что баг в staging-слое

Это descent по DAG от mart-уровня к staging. Без понимания DAG этот процесс хаотичный.


Попробуй сам

С моделями из предыдущего урока:

  1. Запустите dbt list --select +fct_orders — должно показать все upstream модели (если у вас цепочка staging -> int -> mart)

  2. Запустите dbt list --select stg_customers+ — должно показать downstream от stg_customers

  3. Запустите dbt run --select stg_orders+1 — построит только stg_orders и 1 уровень downstream (а не всю цепочку)

  4. Откройте target/manifest.json | jq '.nodes | to_entries[] | {name: .key, depends_on: .value.depends_on.nodes}' | head -50 — увидите DAG в виде JSON

  5. Откройте dbt docs serve, найдите любую модель, кликните на lineage — увидите визуальный граф

  6. Создайте искусственную циклическую зависимость: в stg_orders добавьте {{ ref('fct_orders') }}. Запустите dbt parse — увидите Compilation Error с сообщением про cycle.


DAG в Airflow: та же концепция
Проверка знанийKnowledge check
У вас DAG: stg_a -> stg_b -> int_ab -> mart_x, и stg_a -> int_ab напрямую (то есть int_ab имеет two upstream: stg_a и stg_b). Запускаете 'dbt run --select +mart_x'. В каком порядке dbt выполнит модели и почему?
ОтветAnswer
dbt построит топологическую сортировку DAG, учитывая что int_ab имеет два upstream (stg_a и stg_b), и mart_x зависит от int_ab. Порядок: (1) stg_a запустится первой — у неё нет upstream; (2) stg_b может запуститься параллельно со stg_a (тоже нет upstream) — на multi-threaded запуске они стартуют одновременно; (3) int_ab запустится только после того, как ОБЕ stg_a и stg_b завершатся — это критично, потому что int_ab имеет ребро от каждой; (4) mart_x запустится после int_ab. Если threads >= 2, stg_a и stg_b будут идти параллельно. Если threads = 1, dbt выберет любую из них (детерминированно, обычно алфавитный порядок) и пойдёт последовательно. Принцип: модель не запускается, пока все её upstream не завершились — даже если есть свободные threads. Это и есть гарантия корректности топологической сортировки.

Проверьте понимание

Результат: 0 из 0
Прикладной
Вопрос 1 из 6. Что означает префикс + в селекторе `dbt run --select stg_orders+`?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 4