Learning Platform
Глоссарий Troubleshooting
Урок 11.03 · 26 мин
Продвинутый
selectorsslim-cistate-comparisonci-cd

Selector-based runs: state:modified, result:error, source_status:fresher

В крупном проекте полный dbt run может занимать час, а на CI это ещё умножается на количество PR. Запускать всё ради изменения одного staging-models — расточительство compute. Selectors дают возможность запустить только то, что нужно — изменённое, упавшее, или то, у чего обновились данные.

Этот урок — про production-применение state:modified+, result:error+, source_status:fresher+ и композиции selectors для CI optimization.

Селекторы: что значит +model, model+ и @model (dbt I) Slim CI: state:modified+ deep dive (dbt II)

Базовый синтаксис selectors

В dbt-core selectors задаются через флаг --select (или -s):

dbt run --select model_name           # одна модель
dbt run --select stg_orders+          # модель + все downstream
dbt run --select +fct_orders          # модель + все upstream
dbt run --select +fct_orders+         # модель + upstream + downstream
dbt run --select tag:hourly           # все модели с тегом hourly
dbt run --select path:models/staging  # все модели в директории

Это базовая семантика. Operators + означают «и зависимости в этом направлении», tag:, path: — это methods (поиск по атрибуту).

Senior-уровень — state-based selectors, которые сравнивают текущий код с эталонным manifest’ом.

state:modified+ — slim CI

Slim CI — это паттерн: на каждом PR запускать только изменённые модели (и их downstream), не весь проект. Это даёт 10-100x экономию compute.

Механика:

  1. У вас есть manifest.json от prod (последний successful run).
  2. На CI вы делаете dbt parse для текущей ветки -> новый manifest.
  3. state:modified+ сравнивает два manifest-а и возвращает изменённые модели + downstream.
# CI script
dbt run --select state:modified+ --state target/prod_manifest/ --defer

Что значит --state target/prod_manifest/:

  • Папка содержит manifest.json от последнего prod-run.
  • dbt парсит свой код, сравнивает с этим manifest.

Что значит --defer:

  • Для refs на unchanged модели — используется их prod-версия (через database.schema.table из state).
  • Не нужно билдить весь upstream — только модифицированные модели + downstream видят upstream через prod.
state:modified+ на DAG-е

Изменена одна модель stg_orders. dbt с state:modified+ построит её + всё, что зависит. Остальное — defer to prod.

stg_usersНе изменена в PR — пропускаем. Используется из prod через --defer (analytics.PROD.stg_users)
stg_ordersИзменена в PR — пересчитываем. Состояние модели изменилось vs prod-manifest. Это и есть modified
stg_productsНе изменена — defer to prod. dbt build не делает её локально
int_ordersЗависит от stg_orders (изменена). Через + она попадает в selector. Будет пересобрана с использованием новой stg_orders
fct_user_metricsЗависит от int_orders. Downstream chain работает через +. Тоже rebuild

Это даёт огромный выигрыш — на PR, который меняет одну staging-модель, dbt билдит 3 модели вместо 1000.

Как работает «modified» в state-comparison

dbt смотрит на конкретные поля манифеста для определения modified. Главные:

  1. raw_code — SQL-код модели. Изменился — modified.
  2. config — настройки модели (materialized, unique_key, partition_by). Изменились — modified.
  3. description — документация модели. Это тоже триггерит modified! (Спорное поведение, но дефолтное.)
  4. columns — список колонок в schema.yml.
  5. refs/sources — список зависимостей.
  6. macros — компиляция использует macro X, X изменился — модель considered modified.

Полный список: state:modified.body (только raw_code), state:modified.configs, state:modified.macros, state:modified.contract и т.д. По умолчанию state:modified — union всех.

WARNING

Самый коварный сценарий: добавили строку документации в schema.yml для модели X. dbt считает X modified, билдит её и весь downstream. На большой DAG это значит, что простой PR с doc updates стартует часовой CI run. Решение — использовать более узкий selector state:modified.body (только реальные code changes), или исключить doc-only PRs из CI через path-checking.

Тонкость: чтобы исключить doc updates, иногда используют:

dbt run --select 'state:modified.body,state:modified.configs,state:modified.macros'

Это только code/config/macro changes. Документация исключена.

—state path: где брать prod manifest

Это критический вопрос для CI setup. Есть три паттерна:

  1. Скачивать prod manifest на каждом CI: prod artifact storage (S3, Azure Blob), на CI делаете aws s3 cp s3://bucket/prod-manifest/manifest.json target/prod_manifest/. Это рекомендуемый подход.
  2. dbt Cloud feature: dbt Cloud сохраняет manifests от scheduled runs автоматически. На CI можно через API получить.
  3. Git-versioned manifest: коммитить manifest.json в репозиторий. Антипаттерн — большой бинарный файл, постоянные конфликты.

Реальный CI script с GitHub Actions:

# .github/workflows/dbt-ci.yml
- name: Download prod manifest
  run: |
    aws s3 cp s3://my-dbt-state/prod/manifest.json target/prod_manifest/manifest.json

- name: dbt parse
  run: dbt parse

- name: dbt build modified
  run: dbt build --select state:modified+ --state target/prod_manifest/ --defer

result:error+1 — retry на failed моделях

После dbt run у вас есть target/run_results.json (см. модуль 05). Он содержит status каждой модели: success, error, skipped. Selector result:error+1 означает “все упавшие модели + ничего глубже”:

# Первый run — что-то упало
dbt run

# Retry только failed
dbt run --select result:error+1 --state target/

# +1 — означает один уровень downstream от failed (на случай если downstream ждал error model)

Реальный use case — flaky tests или transient errors:

# CI script с retry
dbt run --select state:modified+ --state target/prod_manifest/ --defer
if [ $? -ne 0 ]; then
    echo "Some models failed, retrying..."
    dbt run --select result:error+1 --state target/
fi

Это снижает false failures на CI из-за transient warehouse errors (например, connection timeouts).

TIP

Тонкость: result:error+1 vs result:error+. +1 — один уровень downstream. + — все downstream. На retry обычно нужен только +1, потому что глубже модели не успели стартовать, для них ничего нет в run_results.

Аналогично есть:

  • result:skipped — модели, скипнутые из-за upstream error.
  • result:success — успешные (редко нужен).
  • result:fail — failed tests (для dbt test).

source_status:fresher+ — freshness-driven runs

Это менее известный, но мощный selector. Идея: запускать модели только когда источники обновились.

dbt имеет dbt source freshness команду, которая проверяет, насколько свежие данные в источниках:

# sources.yml
sources:
  - name: app
    tables:
      - name: orders
        loaded_at_field: updated_at
        freshness:
          warn_after: { count: 1, period: hour }
          error_after: { count: 6, period: hour }

Запускаете:

dbt source freshness

Это создаёт target/sources.json с freshness статусами. Дальше:

# Запустить модели, у которых источники обновились
dbt run --select source_status:fresher+ --state target/

fresher+ означает «источник свежее, чем в state» + все downstream.

Production-сценарий — adaptive scheduling:

# Каждые 15 минут: проверка freshness
dbt source freshness

# Если хоть один источник обновился — run downstream
dbt run --select source_status:fresher+ --state target/

Это позволяет реагировать на свежие данные мгновенно, не тратя compute на пустой run, когда ничего не пришло.

Композиция selectors

Selectors можно комбинировать. Это даёт мощные паттерны:

AND через запятую (intersection)

dbt run --select 'tag:hourly,state:modified+'

— модели с тегом hourly И одновременно modified.

OR через пробелы (union)

dbt run --select 'state:modified+' 'source_status:fresher+'

— modified models OR fresher sources downstream.

Exclude

dbt run --select state:modified+ --exclude tag:experimental

— modified models, кроме тех с тегом experimental.

Полный пример CI

dbt run \
  --select 'state:modified+' 'source_status:fresher+' \
  --exclude tag:expensive_skip_on_pr \
  --state target/prod_manifest/ \
  --defer

Это: rebuild modified models + downstream fresher sources, исключая дорогие experimental модели.

State methods глубже

Кроме state:modified есть менее известные:

SelectorСемантика
state:newМодели, которых не было в state (новые)
state:oldМодели, которые были в state, но удалены в текущем коде
state:modifiedИзменённые (любым из критериев)
state:modified.bodyТолько raw_code
state:modified.configsТолько config changes
state:modified.contractТолько contract changes (см. dbt-ii контракты)
state:modified.macrosТолько зависящие macros изменились
state:unmodifiedНе изменены (редко нужно)

state:new особенно полезен — после создания новой модели на CI она должна быть build даже без upstream dependencies. state:modified+ это покрывает (новая модель = modified), но иногда хочется только новые: dbt run --select state:new.

defer без state:modified

--defer без selectors тоже работает. Это значит: «билдь то, что я попросил, для остального ref’ы возьми из state»:

dbt run --select fct_user_metrics --defer --state target/prod_manifest/

Здесь dbt билдит только fct_user_metrics, а её upstream-зависимости берёт из prod через defer. Это полезно для ad-hoc rebuild одной модели в prod-like environment.

Snowflake/BigQuery: cost-aware CI

На облачных warehouses каждый CI run стоит реальные деньги. С Slim CI можно сэкономить 90%:

SetupCI run timeCompute cost (Snowflake Medium $4/hr)
Full rebuild60 min$4.00 per PR
Slim CI (state:modified+)5 min$0.33 per PR
Slim CI + defer3 min$0.20 per PR

На команде из 20 разработчиков с 5 PR-ов в день каждый — это разница в $7,200/мес.

DuckDB: на CI с DuckDB cost ноль (локальный compute), но time экономия всё равно важна — PR feedback должен быть быстрый.

Failure modes

Production gotchas:

  1. prod manifest stale — забыли обновить артифакт в S3, на CI старый manifest. state:modified показывает кучу false positives. Решение — обновлять manifest на каждом prod-run (через on-run-end hook).

  2. Schema drift в —defer — defer ссылается на prod.schema.table, а вы в текущем PR поменяли columns. dbt предполагает, что схема такая же — build модели может сломаться runtime, потому что upstream имеет другие колонки.

  3. macros не считаются upstream через defer — если вы поменяли macro X, который используется в модели Y (не модифицированной), Y попадёт в state:modified через state:modified.macros. Это иногда отлавливает edge cases.

  4. Result selector без stateresult:error+1 без --state target/ не работает. dbt не знает, где run_results.

  5. packages не поддерживают state: если внутри package есть модели, и package обновлён, dbt пересчитывает их все. Это иногда триггерит большие rebuilds при dbt deps update.

WARNING

Самый болезненный production-инцидент: forgot --defer, CI build падает с «Relation analytics.PROD.stg_orders does not exist». Это потому что state:modified+ пытается ref’ить unchanged staging, которое в dev-schema не существует. Решение — всегда --defer для CI runs.

Mock CI sequence

Типичный полноценный CI run выглядит так:

# 1. Setup
git checkout origin/main -- target/prod_manifest/  # или скачать из S3

# 2. Parse — generate manifest для текущего PR
dbt parse

# 3. Source freshness (optional)
dbt source freshness

# 4. Build modified + fresher
dbt build \
  --select 'state:modified+' 'source_status:fresher+' \
  --state target/prod_manifest/ \
  --defer

# 5. Retry on flaky errors
if [ $? -ne 0 ]; then
    sleep 30
    dbt build --select result:error+1 --state target/
fi

# 6. Tests on changed (uncached)
dbt test --select 'state:modified,test_type:singular' --state target/prod_manifest/

# 7. Upload new manifest для следующего CI
aws s3 cp target/manifest.json s3://my-dbt-state/ci/$PR_ID/manifest.json
Проверка знанийKnowledge check
У вас Snowflake-проект 800 моделей, full run 45 минут на Medium warehouse ($3/час). 30 разработчиков, 8 PR/день каждый. Вы хотите минимизировать CI cost. Какой setup рекомендуете?
ОтветAnswer
Текущее без оптимизации: 30 dev × 8 PR × $3/час × 0.75h = $540/день, $11,880/месяц. Это огромные расходы на CI. Полная оптимизация Slim CI: (1) Setup prod manifest pipeline. На каждом successful prod-run (раз в день / раз в час) — сохранять manifest.json в S3 через on-run-end hook. Это нулевой ongoing cost, делается один раз. (2) CI script: - Download prod manifest from S3. - dbt parse — генерируем PR manifest. - dbt build --select state:modified.body,state:modified.configs,state:modified.macros+ --state target/prod_manifest/ --defer. - Использование .body,.configs,.macros (без .description) — exclude doc-only PRs, которые не должны триггерить run. (3) Тесты только на changed: - dbt test --select state:modified+ --state target/prod_manifest/ (4) Реалистичный сценарий: средний PR меняет 2-5 моделей. С --defer downstream может быть от 0 до 100 моделей в зависимости от позиции в DAG. Avg CI runtime — 3-7 минут. Расчёт после optimization: - 30 dev × 8 PR × $3/час × 0.08h (5 мин) = $58/день, $1,275/месяц. - Экономия $10,605/месяц (~88%). (5) Дополнительные оптимизации: - Multi-cluster Snowflake warehouse для CI (MAX_CLUSTER_COUNT=3) — несколько PR могут идти параллельно без queuing. - threads=24 на CI warehouse Medium. - Cancel duplicate CI runs (newer commit cancels older) — экономит на abandoned runs. - На PR без model changes (только yaml/markdown в /docs) — full skip CI dbt build, только dbt parse. (6) Failure modes к учёту: - Prod manifest stale (не обновился) — false positives state:modified. Mitigate через on-run-end auto-upload. - --defer на schema drift — если в PR меняли column в upstream, build downstream может сломаться. Mitigate через model contracts (см. dbt-ii модуль). - Cancel duplicate runs может пропустить important commit. Mitigate через cancel-in-progress: true только для feature branches. Реалистичный конечный setup — экономия ~$10K/мес, CI time с 45 min до 3-7 min, лучший dev experience.

Резюме

  • state:modified+ — основа Slim CI. Сравнивает с prod-manifest, билдит только изменённое + downstream.
  • --defer дополняет — для unchanged upstream использует prod-таблицы.
  • state:modified.body — узкий вариант (исключает doc updates).
  • result:error+1 — retry упавших моделей.
  • source_status:fresher+ — freshness-driven runs.
  • Композиция через запятые (AND), пробелы (OR), --exclude.
  • Production cost: Slim CI экономит 80-95% compute на больших проектах.
  • Gotchas: prod manifest stale, schema drift на defer, packages не в state.

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

Результат: 0 из 0
Прикладной
Вопрос 1 из 6. Что делает команда dbt build --select state:modified+ --state target/prod_manifest/ --defer?

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

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

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

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