Vertex Data

dbt Mesh: When to Split One Project into Many

When should you split one dbt repo into many? Concrete signals, what breaks in production, and a safe migration path—plus CI, contracts, and ownership.

Eric Provencio, Principal Analytics Engineer at Vertex Data Consulting
Eric Provencio
7 min read

Your team is feeling the drag: slow parses, flaky CI, long builds, and unclear ownership in one repo. dbt mesh is the pattern of splitting a monolith into multiple projects with explicit interfaces and versioned contracts. The short answer: stay monolithic until you have at least two real domains with separate roadmaps and 5+ engineers stepping on each other. Split when ownership, not tooling, is the bottleneck. Expect costs—more repos, stricter interfaces, and new CI—but the payoff is safer change and faster delivery inside each domain. If your pain is primarily performance, tune first. If the pain is coordination and broken promises at boundaries, a mesh moves the constraint from interpersonal negotiation to code and policy.

When to split: concrete signals and real costs

Teams ask for more repos when pain peaks. First separate org issues from tuning. If you haven’t profiled parse time, tightened selectors, or fixed incremental logic, do that before fragmentation. If your data platform has multiple domain roadmaps and your queue blocks unrelated work, you’re ready to carve edges. This is about operating data at scale more sanely, not chasing trends.

SignalSplit now?Notes
Two domains (Finance, Marketing) with distinct SLOs/on-callYesConflicting priorities stall the queue.
Frequent downstream breakages from ad hoc model useYesPublish a small interface; hide internals.
PR builds block for hoursUsuallySmaller DAGs cut blast radius per change.
dbt parse > 60–120s and growingMaybeFix selectors/state before splitting.
Centralized data team of ≤4 engineersNoMonolith remains simpler and faster.
  • Inter-project dependencies and version pins replace implicit sharing.
  • Breaking changes require deprecation windows and release notes.
  • Orchestrating joins across repos complicates the workflow.
  • More governance: who owns a public contract, who approves schema changes.

If your primary pain is repo performance, start with an audit and refactor before you fragment ownership. See dbt Repo Performance and dbt run slow? Why it happens and 7 fixes. Those fixes apply across a modern data stack as much as they do within a single project.

Designing boundaries: domains, interfaces, and architecture

Split along domains that own their inputs and semantics. A good boundary yields a small, stable interface and a big, fast-moving interior. In practice:

  • Domain projects own ingestion-to-mart for their area. Example: Finance owns revenue and cost marts; Marketing owns campaign attribution from its data source.
  • Each interface exposes a handful of public tables with contracts and clear owners. Treat each as a data product, not a grab bag.
  • Shared dimensions (dates, geography) live in a “foundation” project or are duplicated intentionally with precedence rules.

Keep the architecture boring:

  • One warehouse schema per project environment for isolation—dev, staging, and prod function as virtual data environments.
  • Consistent naming: <domain>__<subject>__<grain> on public models; keep the data model at the interface stable.
  • Thin interfaces: don’t leak Finance business logic into Marketing.

Team-size heuristics from production: a centralized data team of 3–4 stays in one repo. At 6–12 engineers across two domains with independent backlogs, start marking interfaces with contracts and access, then split. Over 12 with decentralized data ownership, expect multiple projects per domain over time. This tracks the core principles and principles of data mesh—domain ownership, data as a product, self-serve platform, and federated governance—without ceremony-heavy data architectures.

Access modifiers, contracts, and versioning at the edges

Boundaries only work if edges are enforced. Three tools matter most, and they’re effective even inside one repo before you split to multiple dbt projects.

  1. Access modifiers to control visibility of dbt models:
# models/finance/public/orders.sql
{{ config(access='public') }}

select ...
# models/finance/staging/_stg_orders.sql
{{ config(access='private') }}

select ...
  1. Model contracts to freeze schemas at interfaces:
# models/finance/public/orders.yml
version: 2
models:
  - name: orders
    config:
      contract:
        enforced: true
    columns:
      - name: order_id
        data_type: int
      - name: order_date
        data_type: date
      - name: total_amount
        data_type: numeric

With contracts enforced, accidental column drops or type changes fail fast in CI. If you’re formalizing agreements with upstream platform teams, pair this with a written data contract; field notes: Data Contracts in Practice.

  1. Versioned models to manage breaking changes between projects:
# models/finance/public/customer_orders.sql
-- v1 (deprecated but still built)
{{ config(version=1, access='public') }}
select ...

-- v2 (new schema)
{{ config(version=2, latest=True, access='public') }}
select ...

Run both versions during a grace period. Pin consumers to a specific version, communicate a cutoff date, then retire the old one. That keeps data lineage clean and avoids the Friday surprise where a consumer silently picks up a breaking change.

Dependencies across projects and cross-platform mesh

There are two reliable patterns for dependencies between projects:

  • Package installs (Core and Cloud): install the producer repo and use a two-arg ref.
# packages.yml in the marketing project
packages:
  - git: "https://github.com/your-org/finance-dbt.git"
    revision: main

# models/marketing/marts/campaign_roi.sql
select
  m.campaign_id,
  sum(spend) as spend,
  sum(revenue) as revenue
from {{ ref('finance', 'orders') }} m
...
  • Artifact deferral (Cloud-friendly): orchestrate so the consumer runs after the producer; use --defer to resolve references to the producer’s last successful state.
# Consumer build
dbt build --select marketing+ --defer --state s3://artifacts/finance/prod

Is it available in Core? Yes—the building blocks (access control, contracts, versions, packages) are in Core. dbt Labs improves operations in dbt Cloud and dbt Explorer with inter-project lineage and environment management. That visibility helps when you’re operating cross-platform mesh patterns: you still need explicit pipes to share data across warehouses (shares, external tables, or copies) and you schedule the handoff. Treat dev, staging, and prod as separate, controlled environments; your lineage and artifacts become the handshake.

Ownership and governance that actually scales

Mesh fails when “everybody owns everything” or “nobody owns anything.” Make these explicit on day one:

  • Domain ownership: who merges, pages, and approves interface changes—name people, not teams.
  • Public catalog: a short, curated list per domain with service levels and contacts.
  • Change policy: minimum deprecation window, which versions build by default, and how consumers are notified.
  • Review gates: breaking-change PRs require sign-off from all downstream owners.

Governance without bloat: keep a single lightweight RFC template for schema changes and a weekly sync among domain leads. Automate the rest. Tags like pii or restricted on columns, plus tests, let you enforce data governance checks in CI. This is how you scale data quality without centralizing every decision. In an enterprise, keep a small central platform group to run shared services while domain teams own analytics logic. That split respects data practitioners who know the semantics and avoids a bottlenecked central authority.

CI/CD, DAG joins, and state: what breaks and how to fix it

Splitting increases CI complexity. The mistakes we see most in data engineering:

  • Rebuilding the world in every PR: use selectors and state to limit scope; keep the workflow fast for feedback.
  • Ignoring inter-repo DAG joins: connect runs via artifacts and schedules.
  • Skipping contract checks: dbt test and dbt build with contracts enforced catch schema drift.
# .github/workflows/dbt.yml
- name: Build changed nodes with state
  run: |
    dbt deps
    dbt parse
    dbt build \
      --select state:modified+ \
      --defer --state s3://artifacts/prod \
      --fail-fast

For orchestration, keep DAGs readable. In Airflow, have a producer DAG publish artifacts and a consumer DAG wait on them:

from airflow import DAG
from airflow.operators.bash import BashOperator

with DAG("finance_build", schedule_interval="@hourly", start_date=...):
    build = BashOperator(
        task_id="build",
        bash_command="dbt build --project-dir finance --profiles-dir . && aws s3 cp target s3://artifacts/finance/prod --recursive"
    )

with DAG("marketing_build", schedule_interval="@hourly", start_date=...):
    wait = BashOperator(
        task_id="wait_for_finance",
        bash_command="aws s3 ls s3://artifacts/finance/prod/manifest.json || exit 1"
    )
    build = BashOperator(
        task_id="build",
        bash_command="dbt build --project-dir marketing --defer --state s3://artifacts/finance/prod"
    )
    wait >> build

Want a turnkey starting point that catches issues before prod? Borrow from our write-up: dbt CI/CD That Catches Problems Before They Reach Prod. Those are pragmatic best practices you can adapt to your data stack and governance rules.

Migration path: from one repo to multiple projects without outages

Don’t jump to many repos overnight. De-risk with an incremental path that works whether you self-host or run dbt Cloud.

  1. Stabilize the monolith: prune dead nodes, wire freshness/tests to critical paths, speed up builds. If you’re moving to managed tooling, plan it with dbt Cloud Migration.
  2. Introduce boundaries in-place: add access, declare contracts, and tag public interfaces. Start versioning where you expect schema churn.
  3. Carve out a pilot domain: create a new repo and install it as a package back into the monolith. Point a few consumers at it.
# In the monolith's packages.yml
packages:
  - git: "https://github.com/your-org/finance-dbt.git"
    revision: main
  1. Flip refs gradually: change ref('orders') to ref('finance', 'orders') area by area, with CI and canaries.
  2. Wire inter-project CI: publish producer artifacts; run consumers with --defer; validate data lineage in Explorer.
  3. Retire duplicates: after a defined window, remove the old models and update docs. Use dbt Explorer to confirm fan-out.
Repo strategyProsTrade-offs
Mono-repo (multiple projects)Single PR across producer/consumer; shared toolingStill need per-project selectors; broader reviews
Multiple repos (one per project)Isolation and clear ownershipRequires version pinning and inter-repo orchestration

This migration pattern fits most implemented dbt estates. Whether you’ve already implemented dbt or you’re planning a new dbt implementation, keep dev/staging/prod clean, treat schemas as environments, and avoid surprises. It also plays well with tools like dbt in a broader ecosystem and the modern data stack you already run.

Practical FAQ: dbt Mesh in the real world

What is dbt data mesh?

A practical application of data mesh architecture using dbt: domain-aligned projects expose a few public tables with contracts and versions; other domains consume them via refs and orchestrated handoffs.

Is dbt mesh available in dbt Core?

Yes for essentials (access, contracts, versions, packages). dbt Cloud and dbt Explorer improve inter-project lineage and scheduling.

What are the 4 pillars of data mesh?

Domain ownership, data as a product, self-serve platform, and federated governance. The first two map directly to how you use dbt; the latter two live in your platform and processes.

What is dbt vs Snowflake?

dbt manages code, testing, and data transformation. Snowflake runs the SQL and stores data. They complement each other.

SQLMesh or dbt?

Prefer SQLMesh when you need a planning/diff-first approach with heavy Python. We often pick dbt for ecosystem, dbt community, and Cloud features; it fits most analytics workflows. Mixed stacks are fine.

How does it work across platforms?

Move or share the data, then build. The coordination is orchestration and artifacts, not cross-warehouse SQL. That’s the reality of cross-platform mesh.

How to adopt quickly?

Pick one boundary, mark public nodes, turn on contracts, and add deferred CI. If you’re moving off self-hosted orchestrations, we can help stabilize first.


Your next step: pick one boundary, mark public models, enable contracts, and set up a deferred CI build. If you want a checkpointed plan with production guardrails, start a project with Vertex.

About the author

Eric Provencio Analytics engineer who has built and run production data platforms for Disney, Hulu, Nike, Peloton, Gopuff, and Kaplan. Founded Vertex Data Consulting to do the deep work most data teams never find time for: dbt Cloud migrations, repo performance, Airflow reliability, and AI agents that actually touch the stack.

Keep reading

Start a conversation

Tell us what's slowing your data team down.

We maintain a small client roster on purpose. If we're the wrong fit, we'll say so — and usually we know somebody who isn't.

  • Replies within 2 business days
  • NDA before specifics
  • Fixed-scope first engagement, retainer if it works
What do you want help with? *

We reply within 2 business days if there's a fit. No newsletters, ever.