dbt Mesh: архитектура и цели
В крупной компании 1000+ моделей в одном monolithic dbt-проекте становятся неуправляемыми: команды толкаются за merge, parse занимает минуты, изменение одной модели ломает другую без согласований. dbt Mesh — это паттерн разбиения monolithic-проекта на несколько связанных, где каждая команда владеет своим, но они interoperate через formal contracts.
dbt Mesh появился в dbt-core 1.6 (2023) и стал production-ready к 2025. Это главное architectural feature dbt для крупных организаций.
dbt Mesh: model groups, access, cross-project references (dbt II)Проблемы monolithic dbt-проекта
Чтобы понять, зачем Mesh, посмотрим, что ломается в monolith:
На 1000+ моделей monolith становится bottleneck. Команды не могут работать независимо, релизы coupled.
Все эти проблемы реальные на проектах enterprise scale. Mesh решает их через разбиение монолита на projects.
Что такое dbt Mesh
dbt Mesh — это паттерн (не product), где несколько dbt-проектов связаны через cross-project references, contracts, и access control.
Простой пример: вместо одного analytics-проекта на 1000 моделей, у вас 3 проекта:
finance_dbt/ (Finance team)
models/
stg_invoices.sql
fct_revenue.sql ← exposed как public model
marketing_dbt/ (Marketing team)
models/
stg_campaigns.sql
fct_marketing_roi.sql ← uses ref('finance', 'fct_revenue')
product_analytics_dbt/ (Product team)
models/
stg_events.sql
fct_user_metrics.sql
Каждый проект:
- Имеет свой repo (или папку в monorepo).
- Имеет свою команду owners.
- Имеет свой dbt-job на CI.
- Релизится independently.
- Exposed модели — это formal API, на который другие проекты depend.
Cross-project references
Самая важная feature Mesh — ref() с двумя аргументами:
-- В marketing_dbt/models/fct_marketing_roi.sql
select
campaign_id,
spend_usd,
revenue_attributed
from {{ source('marketing_app', 'campaigns') }}
left join {{ ref('finance', 'fct_revenue') }} using (date)
ref('finance', 'fct_revenue') — это cross-project reference. dbt при компиляции:
- Резолвит
financeкак имя другого dbt-проекта. - Через
project_dependencies.ymlнаходит manifest этого проекта. - Из manifest берёт schema/database
fct_revenue(например,analytics.finance.fct_revenue). - Подставляет в compiled SQL.
Это даёт decoupling: marketing_dbt не имеет SQL-кода fct_revenue, только зависит от него по контракту.
Decoupled releases
Главный win Mesh — каждая команда релизит независимо:
| Action | Monolith | Mesh |
|---|---|---|
| Finance меняет fct_revenue | PR в общий repo, ждут review всех | PR в finance_dbt repo, owners review |
| Tests упали в finance | Все runs blocked | Только finance pipeline blocked |
| Marketing деплоит новую модель | PR ревью у data lead | PR в marketing_dbt, marketing-lead approve |
| Hotfix в Finance | Через main branch monolith | Через finance_dbt main, без affecting Marketing |
Real-world team velocity на Mesh обычно 3-5x выше, чем на monolith — меньше waiting, больше autonomy.
Governance: access control
В Mesh не каждая модель доступна для cross-project ref. Это управляется через access:
# finance_dbt/models/schema.yml
models:
- name: fct_revenue
access: public # доступна другим проектам
description: "Daily revenue aggregated by date and product"
columns:
- name: date
data_type: date
- name: product_id
data_type: string
- name: revenue_usd
data_type: decimal(18,2)
- name: stg_invoices
access: private # только внутри finance_dbt
description: "Raw invoices staging"
- name: int_revenue_adjusted
access: protected # protected = только subprojects этого group
description: "Revenue с корректировками"
Три уровня доступа:
private(default) — только в этом проекте.protected— в этом проекте и в проектах того жеgroup.public— везде, любой другой проект.
Это даёт formal API. Когда model marked public, она becomes часть public surface — должна быть stable, документирована, имеет contract.
Contract enforcement cross-project
Public модели обычно идут с contracts:
# finance_dbt/models/schema.yml
models:
- name: fct_revenue
access: public
config:
contract:
enforced: true
columns:
- name: date
data_type: date
constraints:
- type: not_null
- name: product_id
data_type: string
constraints:
- type: not_null
- name: revenue_usd
data_type: decimal(18,2)
Это значит:
- dbt при build модели проверяет, что колонки соответствуют schema.
- Если SQL модели возвращает другие columns / types — dbt падает.
- При изменении контракта (rename column, change type) — это breaking change для consumers.
Для consumers Marketing/Product это API contract — они rely на конкретные columns/types.
Monorepo vs polyrepo
В Mesh есть два архитектурных подхода:
Polyrepo: один проект — один repo
github.com/myorg/finance_dbt/
github.com/myorg/marketing_dbt/
github.com/myorg/product_analytics_dbt/
Pros:
- Полная autonomy команд (свой PR процесс, свой owner).
- Independent CI/CD.
- Clear ownership на repo-level.
Cons:
- Сложнее cross-project dependencies (через packages.yml + git refs).
- Невозможен atomic change через несколько проектов.
- Discovery моделей: где какая? Sourcegraph-like tooling нужно.
Monorepo: все проекты — один repo
github.com/myorg/data_platform/
finance/
dbt_project.yml
models/
marketing/
dbt_project.yml
models/
product_analytics/
dbt_project.yml
models/
Pros:
- Atomic changes через несколько проектов.
- Единый CI с smart triggers (only run changed projects).
- Discovery models —
grep. - Single source of truth.
Cons:
- Все команды должны принимать PR process этого repo.
- Permissions на subfolders сложнее (CODEOWNERS).
- CI complexity (smart triggers per project).
Real-world practice (2025-2026): большинство больших компаний идут на monorepo для dbt Mesh. Atomic changes и unified tooling перевешивают autonomy issues.
Когда переходить на Mesh
Не каждому проекту нужен Mesh. Signs that you should:
- Больше 500-1000 моделей в одном проекте.
- 3+ команды работают на проекте, конфликтуют в PR.
- Parse занимает > 30s даже с partial parsing.
- Cross-team breakage — Marketing PR-ом ломают Finance модели (по accident).
- Domain boundaries clear — Finance vs Marketing vs Product, чёткое разделение.
Когда НЕ нужен Mesh:
- Меньше 200 моделей — overhead не оправдан.
- Маленькая команда (до 5 человек) — autonomy не нужна.
- Domains overlap heavily — модели cross-domain без чёткого ownership.
- Нет staff DE/Platform engineer — Mesh requires infra setup (manifest sharing, CI for multi-project).
Antipattern: разбить monolith на Mesh без bridge. Команды разъехались, никто не знает, какие модели public. Через 6 месяцев “Mesh” = chaos из несвязанных проектов. Mesh требует дисциплины на API contracts.
Пререкизиты для Mesh
Чтобы Mesh работал в production, нужно:
- Manifest sharing — каждый проект должен иметь доступ к manifests других. Обычно через S3/GCS bucket с CI upload.
- CI/CD coordination — когда public model changes — trigger downstream CI of consuming projects.
- Schema permissions — Marketing user может SELECT из Finance schema (cross-project ref требует данные).
- Naming convention — projects, models, schemas именуются consistent.
- Documentation discovery — где
dbt docsдля всех проектов? Обычно — centraldbt docs serveс merged manifests, или dbt Cloud.
Governance pattern: ownership labels
# finance_dbt/dbt_project.yml
config-version: 2
name: 'finance_dbt'
version: '1.0.0'
profile: 'finance'
vars:
team: finance
team_email: '[email protected]'
models:
+meta:
owner_team: '{{ var("team") }}'
owner_email: '{{ var("team_email") }}'
Каждая модель в Finance автоматически тэгирована owner_team=‘finance’. Это даёт:
- Discovery через
dbt-osmosis/ dbt docs. - Routing alerts (test failure -> finance Slack channel).
- Cost attribution (BI tool queries по owner).
Mesh на DuckDB: ограничения
Используем DuckDB для обучения, но в production Mesh — Snowflake/BigQuery. Почему:
- Cross-project ref требует shared database. На DuckDB single-process — каждый проект имеет свой
.duckdbфайл. Черезattachможно линковать, но cross-process write — нет. - Mesh — это team-scale architecture. DuckDB single-writer не сосуществует с командами.
Для обучения мы создадим два-проекта DuckDB Mesh setup (через attach) — но в реальности Mesh живёт на Snowflake/BQ/Databricks.
Real-world Mesh: scale example
Реальная компания (anonymized, e-commerce):
- Раньше: один монолит, 2300 моделей, 8 teams, parse 90 секунд, CI 50 минут.
- После Mesh:
- 12 проектов (по teams + 2 shared central).
- Total модели те же 2300, но разнесены.
- Каждый проект 100-300 моделей, parse 5-10 секунд.
- CI per project — 5-10 минут.
- Cross-team breakage снижены на 75% (contracts catch breaking changes).
- Team velocity (модели/неделю) выросла 3x.
Это типичный outcome успешного Mesh migration.
dbt Cloud vs dbt-core Mesh
dbt Cloud имеет dedicated Mesh features:
- Discovery API — central registry of public models.
- Auto-deferring — defer between projects automatically.
- Cross-project lineage в UI.
dbt-core имеет те же primitives (cross-project ref, contracts, access), но без UI. Tooling нужен самостоятельно — manifest sharing, lineage tools (dbt-loom, Spline).
В этом курсе фокусируемся на dbt-core (open source).
Резюме
- dbt Mesh — паттерн разбиения monolithic dbt-проекта на несколько связанных через cross-project ref + contracts + access control.
- Цели: decoupled team releases, ownership clarity, parse performance, formal API между teams.
ref('project', 'model')— cross-project reference.access: public/private/protected— управление видимостью моделей.- Contracts на public моделях — formal API, breaking change detection.
- Monorepo vs polyrepo — обычно monorepo для atomic changes и unified tooling.
- Когда нужен: 1000+ моделей, 3+ teams, parse pain, cross-team breakage.
- Пререкизиты: manifest sharing, multi-project CI, schema permissions, docs aggregation.
- DuckDB — для обучения, в production Mesh обычно Snowflake/BQ/Databricks.