Learning Platform
Глоссарий Troubleshooting
Урок 12.02 · 22 мин
Продвинутый
cross-project-refproject-dependenciesmanifestmesh

Cross-project ref: project_dependencies.yml и резолвинг

В прошлом уроке разобрали Mesh как architectural pattern. Сейчас спускаемся на уровень механики: как именно dbt резолвит ref('other_project', 'model') через project_dependencies.yml.

Это знание нужно, чтобы:

  1. Setup-ить Mesh правильно.
  2. Debug-ить ошибки cross-project ref (“Project ‘finance’ not found”).
  3. Понимать failure modes (stale manifest, missing public access).
ref(): соединяем модели в граф (dbt I)

project_dependencies.yml: декларация зависимостей

project_dependencies.yml — отдельный файл рядом с dbt_project.yml. Он декларирует, на какие другие dbt-проекты этот зависит:

# marketing_dbt/project_dependencies.yml
projects:
  - name: finance
  - name: product_analytics

Минимальная декларация — только имя. dbt при parse:

  1. Читает project_dependencies.yml.
  2. Для каждого declared project пытается найти manifest этого проекта.
  3. Загружает manifest в memory.
  4. Регистрирует public models из manifest как доступные через ref('finance', 'model_name').

Где dbt ищет manifest? Это самая важная часть setup.

Где dbt ищет cross-project manifests

Способов несколько, выбирается через --state или --defer-state:

Способ 1: dbt Cloud (managed)

В dbt Cloud все проекты в одном account имеют central manifest registry. dbt автоматически находит manifest по project name. Это zero-config — работает из коробки.

Способ 2: —state каталог для каждого проекта

В dbt-core (open source) нужно вручную указать каталоги:

dbt run --state path/to/finance-state/ path/to/product_analytics-state/

Внутри path/to/finance-state/ — файл manifest.json от последнего run проекта finance. dbt находит его по имени проекта из manifest (project_name field).

Способ 3: dbt-loom (community tool)

dbt-loom — open source extension, который:

  • Скачивает manifests из S3/GCS/HTTP per project.
  • Регистрирует их в dbt as cross-project sources.
  • Автоматически refresh при каждом run.

Config:

# dbt-loom.config.yml
manifests:
  - name: finance
    type: s3
    bucket: my-dbt-manifests
    key: finance/manifest.json
  - name: product_analytics
    type: s3
    bucket: my-dbt-manifests
    key: product_analytics/manifest.json

Это самый популярный paттерн в open source. На каждом dbt run loom скачивает свежие manifests, ref()‘ы резолвятся.

Как ref(‘finance’, ‘fct_revenue’) работает внутри dbt

Trace через source code (упрощённо):

# В dbt/contracts/graph/nodes.py — упрощено
def resolve_cross_project_ref(project_name, model_name, manifest):
    # 1. Найти manifest другого проекта
    if project_name not in self.project_manifests:
        raise CompilationError(f"Project '{project_name}' not found")
    
    other_manifest = self.project_manifests[project_name]
    
    # 2. Найти модель в этом manifest
    target_node = None
    for node in other_manifest.nodes.values():
        if node.name == model_name and node.resource_type == 'model':
            target_node = node
            break
    
    if target_node is None:
        raise CompilationError(f"Model '{model_name}' not found in '{project_name}'")
    
    # 3. Проверить access
    if target_node.access != 'public':
        raise CompilationError(
            f"Model '{model_name}' in '{project_name}' has access='{target_node.access}'. "
            f"Cross-project ref requires access='public'."
        )
    
    # 4. Вернуть relation (database.schema.table)
    return target_node.relation_name  # например, 'analytics.finance.fct_revenue'

dbt при компилировании SQL подставляет relation_name:

-- Source:
select * from {{ ref('finance', 'fct_revenue') }}

-- Compiled:
select * from analytics.finance.fct_revenue

После этого compiled SQL отправляется на warehouse. Warehouse физически читает данные из analytics.finance.fct_revenue — таблица, которую раньше создал finance dbt-проект.

Ограничение: physical access to data

dbt при cross-project ref подставляет имя таблицы — но warehouse должен иметь physical permissions к ней.

Пример: marketing dbt-run использует role MARKETING_TRANSFORMER. Эта role должна иметь SELECT на analytics.finance.fct_revenue:

-- Setup в Snowflake (run from admin)
GRANT USAGE ON SCHEMA analytics.finance TO ROLE MARKETING_TRANSFORMER;
GRANT SELECT ON ALL TABLES IN SCHEMA analytics.finance TO ROLE MARKETING_TRANSFORMER;
GRANT SELECT ON FUTURE TABLES IN SCHEMA analytics.finance TO ROLE MARKETING_TRANSFORMER;

Без этих grants — compile_sql ОК, но runtime upadет с “Permission denied” или “Table does not exist”.

WARNING

Самая частая ошибка в Mesh setup — забыли cross-team GRANT. dbt compile проходит (он только смотрит на manifest), но dbt run падает на warehouse permission error. Симптом: модель работала локально (один user, все permissions), не работает в CI (другая role).

Что включается в cross-project ref

Не любую модель можно cross-project ref. Требования:

  1. access: public (см. урок 03).
  2. Materializedtable, incremental, view. Ephemeral модели нельзя ref cross-project (они inlined SQL, не имеют физической таблицы).
  3. Имя model не overlap — если в обоих проектах есть stg_users, ref должен быть с project name (ref('marketing', 'stg_users')).

Можно ли ref source? Нет. Sources — это локальные для проекта концепты. Чтобы expose source — нужно сделать view/table в проекте и mark public.

Versioned cross-project ref

Public model может иметь versions (concept из dbt-ii). Cross-project ref может pin to version:

-- Marketing uses pinned version
select * from {{ ref('finance', 'fct_revenue', v=2) }}

dbt резолвит к fct_revenue_v2. Это даёт Marketing safety от breaking changes — Finance может deprecate v1, добавить v2, Marketing pin to v2 explicitly.

Без version — dbt берёт latest_version:

# finance_dbt/models/schema.yml
models:
  - name: fct_revenue
    latest_version: 2
    versions:
      - v: 1
        deprecation_date: 2026-12-31
      - v: 2

ref('finance', 'fct_revenue') без version -> берёт v=2 (latest_version).

Детальнее versions разберём в уроке 04.

Loading cross-project manifest: performance

На больших Mesh-проектах с 5-10 dependencies — loading manifests добавляет overhead на parse. Реалистично:

SetupParse time
Один проект, 500 моделей5-10s
Mesh, 5 проектов × 500 моделей15-25s (manifest load × 5)
Mesh, 10 проектов × 500 моделей30-50s

dbt-loom добавляет network latency (download from S3). На 10 manifests — 2-5 секунд download. Можно кэшировать через dbt-loom --cache-manifests.

Failure modes

Production gotchas:

1. Project not found

CompilationError: Project 'finance' not found in project_dependencies.yml

Причина: либо забыли добавить в project_dependencies.yml, либо manifest для finance не загружен (dbt-loom misconfigured, или --state path неверный).

Fix:

  • Проверить project_dependencies.yml.
  • Проверить, что manifest finance существует и accessible.
  • Запустить dbt parse --debug — увидите, какие manifests dbt пытался загрузить.

2. Model not public

CompilationError: Model 'int_revenue_adjusted' in 'finance' has access='private'. 
                   Cross-project ref requires access='public'.

Причина: marketing пытается ref Internal модель Finance. Finance не expose её.

Fix:

  • Если модель должна быть public — Finance team добавляет access: public в schema.yml.
  • Если не должна — Marketing должна не использовать её. Можно сделать derived public model в Finance.

3. Stale manifest

CompilationError: Model 'fct_revenue' not found in 'finance'

Если эта модель была недавно создана. Причина — finance manifest на disk старый, не включает новую модель.

Fix:

  • Запустить finance dbt-run, выгрузить новый manifest.
  • На marketing — обновить cached manifest (через dbt-loom refresh).

4. Permission denied at runtime

Database error: Insufficient privileges to operate on schema 'finance'

Compile OK, run падает. Это physical access issue.

Fix:

  • Admin делает GRANT через Snowflake/BQ.
  • Marketing user должен иметь SELECT на finance schema.

5. Column drift с contracts

Database error: column 'amount_usd' not found in table 'fct_revenue'

Marketing ссылается на колонку, которую Finance переименовали. Если был contract — dbt не позволил бы Finance переименовать без bump version. Без contract — silent breakage.

Fix:

  • Long-term: enforce contracts на public models.
  • Short-term: Marketing обновляет SQL под новое column name.

Best practices для cross-project ref

  1. Только через explicit ref(‘project’, ‘model’). Не делайте select * from {{ source('finance_db', 'fct_revenue') }} — это обходит Mesh, нет lineage.

  2. access: public явно — для каждой модели, которую другие проекты используют. Default private — safe-by-default.

  3. Contracts на public — обязательно. Без них public model не stable.

  4. Versions для breaking changes — добавляйте v2, оставляйте v1 deprecated, дайте consumers migrate.

  5. Documentation — public models должны иметь хорошие descriptions, examples в meta.

  6. Slim CI поддержка — manifest sharing setup так, чтобы CI auto-update.

  7. Monitoring deprecations — alert когда consumer ещё использует deprecated version.

DuckDB Mesh example (для обучения)

В labs мы покажем mini-Mesh на DuckDB через attach:

# marketing_dbt/profiles.yml
marketing_dbt:
  target: dev
  outputs:
    dev:
      type: duckdb
      path: './marketing.duckdb'
      attach:
        - path: '../finance_dbt/finance.duckdb'
          alias: finance_db
# marketing_dbt/project_dependencies.yml
projects:
  - name: finance
# Sequence:
cd finance_dbt && dbt run    # генерирует finance.duckdb + manifest.json
cd ../marketing_dbt && dbt run --state ../finance_dbt/target/

Marketing привязывает к finance.duckdb через attach (read-only access). ref('finance', 'fct_revenue') резолвится к finance_db.main.fct_revenue в attached DB.

Это упрощённый Mesh для обучения. В production — Snowflake/BQ multi-schema.

Sample setup script

Production-аккуратный setup CI для Mesh с polyrepo:

# .github/workflows/marketing-dbt-ci.yml
name: Marketing dbt CI

on:
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      # Скачать manifest зависимостей
      - name: Download finance manifest
        run: |
          aws s3 cp s3://dbt-manifests/finance/manifest.json target/finance-state/manifest.json
      
      - name: Download product_analytics manifest
        run: |
          aws s3 cp s3://dbt-manifests/product_analytics/manifest.json target/product-state/manifest.json

      - name: dbt parse
        run: dbt parse --state target/finance-state/ target/product-state/

      - name: dbt build
        run: |
          dbt build \
            --select state:modified+ \
            --state target/prod-state/ \
            --defer

      # Upload свой manifest для downstream проектов
      - name: Upload marketing manifest
        if: github.ref == 'refs/heads/main'
        run: |
          aws s3 cp target/manifest.json s3://dbt-manifests/marketing/manifest.json

Каждый проект на merge to main uploads свой manifest. Downstream projects на следующем CI run pick up latest.

DuckDB и cross-project: ограничения

В DuckDB через attach есть ограничения для cross-project:

  1. Read-only attach — Marketing не может писать в finance.duckdb. Только read.
  2. Concurrent attach — два процесса одновременно attached к одному .duckdb могут конфликтовать.
  3. Schema isolation — каждый проект пишет в свою DB, no shared catalog.

Это работает для educational scenarios. Production Mesh — другой warehouse.

Проверка знанийKnowledge check
Marketing команда деплоит изменение, которое ссылается на новую модель Finance fct_revenue_v3. Compile fails: "Model 'fct_revenue_v3' not found in 'finance'". Finance lead подтверждает: "fct_revenue_v3 deployed in prod вчера". Что не так и как fix?
ОтветAnswer
Это **классическая stale manifest проблема** в Mesh. Compile ошибка означает, что marketing dbt smотрит на старый manifest finance, в котором ещё нет fct_revenue_v3. Diagnosis: (1) **Узнать, как marketing получает finance manifest**. Варианты: - dbt-loom downloads с S3. - CI скачивает с 'aws s3 cp s3://dbt-manifests/finance/manifest.json'. - Manual upload. (2) **Проверить timestamps**. Если manifest finance в S3 от вчера (до deploy fct_revenue_v3) — root cause найден. Manifest не auto-updated. Fix sequence: (a) **Immediate fix**: вручную обновить manifest: - На finance side: запустить dbt parse + upload manifest на S3. - На marketing side: re-run CI после обновления. (b) **Root cause fix**: настроить **auto-upload manifest** на каждом finance run: '''yaml # finance_dbt/.github/workflows/deploy.yml - name: Upload manifest to S3 if: github.ref == 'refs/heads/main' run: aws s3 cp target/manifest.json s3://dbt-manifests/finance/manifest.json ''' Это гарантирует, что после каждого finance merge to main — manifest updated в S3. Downstream projects pick up automatically. (c) **Defense in depth**: marketing CI tries to pull latest manifest with version stamp: '''bash # Проверять что manifest свежий (менее 24 часов) last_modified=$(aws s3api head-object --bucket dbt-manifests --key finance/manifest.json --query 'LastModified' --output text) if [[ $(($(date +%s) - $(date -d "$last_modified" +%s))) -gt 86400 ]]; then echo "Warning: finance manifest is > 24h old. Cross-project ref may break." fi aws s3 cp s3://dbt-manifests/finance/manifest.json target/finance-state/ ''' (d) **Monitoring**: alert когда manifest старше Х hours: - Cloudwatch alert: S3 object LastModified > 24h ago. - Slack channel notification. (e) **Long-term: Mesh management tool**: - dbt Cloud-like central registry. - dbt-loom с automatic refresh. - Или custom service: scheduled job синхронизирует manifests cross projects. (f) **Communication aspect**: Finance lead должен **сообщить downstream** about new public models. Best practice: - PR template Finance: "Adding new public model? Notify [list of consumers]." - Slack channel #data-platform-changes. - Quarterly arch review: consumers and producers sync up on public APIs. (g) **Coordination для breaking changes**: - Finance добавил fct_revenue_v3 в дополнение к v2 (additive, не breaking). - Если бы Finance deleted v2 без deprecation — Marketing рушится hard. - Поэтому: versioning + deprecation_date + comms. После исправления: Marketing CI passes, fct_revenue_v3 successfully ref'fed. Long-term — auto-upload + monitoring чтобы избежать repeat. Это и есть Mesh operational reality — много coordination, требует disciplines и tooling.

Резюме

  • project_dependencies.yml — декларация cross-project dependencies.
  • ref('project', 'model') — cross-project reference, резолвится через manifest другого проекта.
  • Manifest sharing — central piece infra. Через dbt Cloud / dbt-loom / --state path.
  • Compile vs runtime — compile проверяет manifest, runtime проверяет warehouse permissions.
  • access: public обязательно — private/protected модели нельзя cross-project ref.
  • Versioning через ref(..., v=2) для safety от breaking changes.
  • Failure modes: project not found, model not public, stale manifest, permission denied, column drift.
  • DuckDB Mesh через attach — для обучения, в prod Snowflake/BQ multi-schema.

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

Результат: 0 из 0
Концептуальный
Вопрос 1 из 6. Что декларируется в project_dependencies.yml?

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

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

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

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