Data as of Aug 25, 2026 · Based on 321 AI responses from ChatGPT Search and Google AI Mode · See how Parse measures this
Brands AI recommends here
Named in 79% of answers
Named in 72% of answers
Named in 59% of answers
Named in 38% of answers
Absolutely. The biggest dbt speedups usually come from **building less data**, **scanning less data**, and **running fewer models**—not from tweaking dbt itself. ## 1. Find the actual bottleneck first Run your project and identify:
Absolutely. The biggest dbt speedups usually come from building less data, scanning less data, and running fewer models—not from tweaking dbt itself.
Run your project and identify:
dbt's docs emphasize state-aware approaches that can skip nodes whose code/data hasn't meaningfully changed.
A useful first pass is:
dbt run --select +slow_model+
Then inspect the generated SQL and your warehouse's query profile for that model.
If you're doing this:
{{ config(materialized='table') }}
select *
from {{ ref('events') }}
every run potentially rebuilds the entire table.
For a large, append-heavy fact table, consider:
{{ config(
materialized='incremental',
unique_key='event_id'
) }}
select
event_id,
user_id,
event_timestamp,
event_type
from {{ ref('events') }}
{% if is_incremental() %}
where event_timestamp >= (
select max(event_timestamp)
from {{ this }}
)
{% endif %}
The important part isn't merely setting materialized='incremental': your SQL must actually restrict the incremental workload.
For mutable source data, use an appropriate lookback window, e.g. the last 1–3 days, rather than assuming records are perfectly append-only.
This pattern can be disastrous:
select ...
from huge_events e
join huge_users u
on e.user_id = u.user_id
where e.event_date >= '2026-08-01'
Prefer pushing the filter into a CTE/subquery:
with recent_events as (
select ...
from {{ ref('events') }}
where event_date >= '2026-08-01'
)
select ...
from recent_events e
join {{ ref('users') }} u
on e.user_id = u.user_id
Even better, make the date boundary dynamic for incremental models.
Also avoid select * in large transformations. Reading 50 columns when you need 8 can materially increase I/O and downstream processing.
If your CI currently does:
dbt build
on every PR, you're potentially doing far more work than necessary.
Use state-aware selection where your CI/deployment setup supports it, so you can build changed nodes and their relevant downstream dependencies rather than the entire project. dbt's current documentation describes dbt State specifically as a mechanism for reusing nodes when logic and data haven't changed.
A common CI strategy is conceptually:
dbt build --select state:modified+
with an appropriate previous-state manifest.
A project can become slow because it has chains like:
raw
↓
staging_1
↓
staging_2
↓
intermediate_1
↓
intermediate_2
↓
fact
where each layer creates a physical table.
Ask of each model:
Don't blindly turn everything into ephemeral models, though—repeated expensive transformations can simply move the cost downstream.
Look especially for:
date(timestamp) instead of appropriately structured keys.For example, if a dimension isn't actually unique on user_id, this:
join users u
on e.user_id = u.user_id
may multiply your fact rows dramatically.
This is where your warehouse matters enormously.
Depending on whether you're on Snowflake, BigQuery, Databricks, Redshift, Postgres, etc., the best tactics differ:
In many cases, a dbt model that takes 20 minutes is really a 20-minute warehouse query.
A surprisingly common problem is a giant test suite repeatedly scanning massive tables.
For example, generic uniqueness tests against a 5-billion-row table can be expensive.
Consider:
dbt can execute independent DAG branches concurrently.
If your project is effectively:
A → B → C → D → E
there isn't much parallelism.
But:
→ B →
A → → E
→ C →
→ D →
can use substantially more warehouse concurrency.
So look at your DAG for artificial dependencies. Sometimes a model references another model simply because it was convenient, creating serialization that isn't logically necessary.
I'd tackle it in this order:
If you tell me which warehouse you're using (Snowflake, BigQuery, Databricks, Redshift, etc.), roughly how many models you have, and an example of one of the slow models, I can give you a much more concrete optimization plan—including the dbt config and SQL I'd change.
For example, if a dimension isn't actually unique on user_id, this:
join users u
on e.user_id = u.user_id
may multiply your fact rows dramatically.
This is where your warehouse matters enormously.
Depending on whether you're on Snowflake, BigQuery, Databricks, Redshift, Postgres, etc., the best tactics differ:
In many cases, a dbt model that takes 20 minutes is really a 20-minute warehouse query.
A surprisingly common problem is a giant test suite repeatedly scanning massive tables.
For example, generic uniqueness tests against a 5-billion-row table can be expensive.
Consider:
dbt can execute independent DAG branches concurrently.
If your project is effectively:
A → B → C → D → E
there isn't much parallelism.
But:
→ B →
A → → E
→ C →
→ D →
can use substantially more warehouse concurrency.
So look at your DAG for artificial dependencies. Sometimes a model references another model simply because it was convenient, creating serialization that isn't logically necessary.
I'd tackle it in this order:
If you tell me which warehouse you're using (Snowflake, BigQuery, Databricks, Redshift, etc.), roughly how many models you have, and an example of one of the slow models, I can give you a much more concrete optimization plan—including the dbt config and SQL I'd change.
When dbt runs start dragging, the bottleneck is usually a mix of inefficient materializations, database warehouse configurations, and unoptimized SQL. You can tackle this systematically across three key pillars: **dbt configuration**, **materialization strategy** , and **warehouse/SQL…
When dbt runs start dragging, the bottleneck is usually a mix of inefficient materializations, database warehouse configurations, and unoptimized SQL. You can tackle this systematically across three key pillars: dbt configuration, materialization strategy , and warehouse/SQL tuning.
threads): Check your profiles.yml . By default, threads might be set low (e.g., 1 or 4). Increase the number of threads (often 4 to 8+ depending on your data warehouse capacity) so dbt can run independent models in parallel.--select state:modified+ combined with --defer . This ensures you only rebuild models that have actually changed or their direct downstream dependents, skipping redundant work.table materializations, switch them to incremental . This ensures dbt only processes new or updated rows rather than rewriting the entire historical table every run.is_incremental() boilerplate.view for intermediate, staging, or lightweight transformations that are queried infrequently or only pass data to a single downstream model.JOIN s: Strip out heavy SELECT * statements, unnecessary subselects, and redundant distinct/joins that force the data warehouse to shuffle massive amounts of intermediate data.date or id ) in your data warehouse (Snowflake, BigQuery, Redshift) so dbt scans only the data it needs.If you want to dive deeper, let me know:
profiles.yml?I can give you specific warehouse-level parameters and profile configurations to speed things up.
Slow dbt runs usually come from one (or more) of these areas: 1. **Too much data being reprocessed** 2. **A DAG that has too much serial dependency**
Slow dbt runs usually come from one (or more) of these areas:
Here’s a practical tuning approach.
Start by identifying your slowest models:
dbt run --select state:modified+ --defer --state path/to/previous/run
and inspect:
dbt’s run artifacts (run_results.json, logs) are useful for timing analysis.
A good first question:
“Which 5 models account for 80% of runtime?” Optimize those first.
If a model rebuilds billions of rows every run, incremental materialization is usually the biggest win.
Example:
{{ config(
materialized='incremental',
unique_key='order_id'
) }}
select *
from {{ ref('stg_orders') }}
{% if is_incremental() %}
where updated_at >= (
select max(updated_at)
from {{ this }}
)
{% endif %}
Instead of:
process all historical data
↓
rebuild table
you get:
process new/changed records
↓
merge into existing table
Incremental models are designed specifically to avoid full reprocessing.
For very large time-series datasets, newer dbt versions also support microbatch incremental strategies, which split processing into smaller time windows.
A common mistake:
{% if is_incremental() %}
where created_at > current_date - 7
{% endif %}
but then joining against a massive table:
select *
from events e
join customers c
on e.customer_id = c.customer_id
The warehouse may still scan huge amounts of data.
Better:
distinctExample:
with recent_events as (
select
event_id,
customer_id,
event_time
from {{ ref('events') }}
{% if is_incremental() %}
where event_time >= dateadd(day, -3, current_date)
{% endif %}
)
select *
from recent_events
join {{ ref('customers') }}
using (customer_id)
dbt can run independent models concurrently using threads.
Example:
# profiles.yml
target: prod
outputs:
prod:
type: snowflake
threads: 8
But more threads are not always faster.
Too many threads can:
Tune based on warehouse capacity.
A useful test:
threads: 4
runtime: 55 min
threads: 8
runtime: 32 min
threads: 16
runtime: 38 min
Your sweet spot is probably around 8.
A common slow pattern:
source
↓
staging
↓
intermediate_1
↓
intermediate_2
↓
intermediate_3
↓
fact_table
Every layer adds execution time.
Sometimes this is justified, but avoid creating models that only:
A flatter DAG often runs faster.
Instead of:
dbt run
use targeted execution:
dbt run --select tag:daily
or:
dbt run --select model_name+
Examples:
dbt build --select state:modified+
dbt build --select tag:daily
dbt build --select tag:hourly
Depending on your platform:
Look at:
Focus on:
Focus on:
Example:
{{ config(
materialized='table',
partition_by={
"field": "event_date",
"data_type": "date"
}
) }}
Tests can become a hidden bottleneck.
Common expensive tests:
- relationships
- unique
- not_null
on billion-row tables.
Options:
Large projects often accumulate:
Use:
dbt ls
to audit what actually runs.
I’d usually tackle dbt performance in this order:
If you share your warehouse (Snowflake/BigQuery/Redshift/Databricks), approximate model count, and current runtime (for example “500 models, 3 hours”), I can suggest more specific optimizations.
{{ config(
materialized='table',
partition_by={
"field": "event_date",
"data_type": "date"
}
) }}
Tests can become a hidden bottleneck.
Common expensive tests:
- relationships
- unique
- not_null
on billion-row tables.
Options:
Large projects often accumulate:
Use:
dbt ls
to audit what actually runs.
I’d usually tackle dbt performance in this order:
If you share your warehouse (Snowflake/BigQuery/Redshift/Databricks), approximate model count, and current runtime (for example “500 models, 3 hours”), I can suggest more specific optimizations.
Optimizing a slow dbt project usually comes down to `eliminating unnecessary data scanning, reducing compilation overhead, and leveraging data warehouse performance features properly`. Here is a structured, scannable playbook to speed up your dbt runs: 1. Reduce Data Scanned & Transformed
Optimizing a slow dbt project usually comes down to eliminating unnecessary data scanning, reducing compilation overhead, and leveraging data warehouse performance features properly.
Here is a structured, scannable playbook to speed up your dbt runs:
incremental strategies. This ensures you only process new or updated rows using is_incremental().where clauses and partition/cluster filters as early as possible in your staging (stg_ ) models. Do not wait until the final select statement to filter massive datasets.ephemeral Materialization Sparingly: While ephemeral models keep your warehouse clean of unneeded physical views, chaining too many ephemeral CTEs can bloat the final compiled SQL query into an un-optimizable monolith for the query planner. Materialize as views or tables when complexity grows.dbt run --select +my_modeldbt run --select state:modified+ (make sure to configure --defer and --state against your production manifest).threads setting in your profiles.yml . Setting this closer to your warehouse's concurrency limits speeds up parallel execution, but setting it too high can cause queue contention or warehouse resource exhaustion.incremental_predicates in your config block to push partition pruning down into the merge/insert operations.partition_by / cluster_by).pre-hook or post-hook operations or complex Jinja loops that run dynamic queries against information schemas during the parsing phase can heavily lag your startup time. Run dbt --debug to see where compilation hangs.If you'd like to narrow this down, let me know:
I can give you a more targeted optimization strategy!
No sources cited
When dbt runs start taking forever, the biggest wins usually come from finding **where the time is going** rather than blindly adding compute. A good tuning pass usually focuses on four areas: **model materializations, SQL workload, DAG structure, and execution parallelism**. [dbt…
When dbt runs start taking forever, the biggest wins usually come from finding where the time is going rather than blindly adding compute. A good tuning pass usually focuses on four areas: model materializations, SQL workload, DAG structure, and execution parallelism.
Start with your run artifacts:
target/run_results.jsonexecution_timeA 30-minute model at the top of the DAG can hurt more than 20 smaller slow models because everything waits on it.
A common cause of slow dbt runs is rebuilding large tables every time:
{{ config(materialized='table') }}
select *
from raw_events
For large append-heavy datasets, switch to incremental:
{{ config(
materialized='incremental',
unique_key='event_id'
) }}
select *
from raw_events
{% if is_incremental() %}
where updated_at >= (
select max(updated_at)
from {{ this }}
)
{% endif %}
This avoids reprocessing historical data on every run. dbt incremental models are specifically designed to process only new or changed rows instead of rebuilding everything.
Common candidates:
Be careful with:
unique_key choicesIncremental does not automatically mean fast.
Examples:
appendmergeFor very large time-series tables, microbatch-style processing can reduce the amount processed per query by splitting work into time windows.
A common pattern:
source
↓
staging view
↓
intermediate view
↓
another intermediate view
↓
fact table
Each view can force the warehouse to repeatedly execute upstream logic.
Consider:
Example:
{{ config(materialized='ephemeral') }}
select
customer_id,
lower(email) as email
from {{ ref('customers') }}
dbt can execute independent models concurrently using threads.
Check your profiles.yml:
target: prod
outputs:
prod:
type: snowflake
threads: 8
But more threads are not always faster.
Too many threads can:
Tune threads against your warehouse limits.
Look for:
A
|
B
|
C
|
D
|
E
This runs mostly sequentially.
You want more shapes like:
A
/ | \
B C D
\ | /
E
Ways to improve:
Your SQL may be the real bottleneck.
Check:
select * everywhere?Example improvement:
Before:
select *
from events
join users using(user_id)
After:
select
e.event_id,
e.user_id,
e.event_time,
u.country
from (
select *
from events
where event_date >= current_date - 7
) e
join users u using(user_id)
If your CI pipeline runs the entire project for every PR, use state comparison:
dbt build --select state:modified+
This limits builds to changed models and their downstream dependencies.
Audit your jobs:
Bad:
dbt run --full-refresh
every night.
Better:
Newer dbt versions include performance improvements in graph processing and incremental workflows. Keeping dbt Core and adapters current can help, especially for larger DAGs.
A practical tuning order I’d use:
If you share:
I can suggest more targeted changes.
Before:
select *
from events
join users using(user_id)
After:
select
e.event_id,
e.user_id,
e.event_time,
u.country
from (
select *
from events
where event_date >= current_date - 7
) e
join users u using(user_id)
If your CI pipeline runs the entire project for every PR, use state comparison:
dbt build --select state:modified+
This limits builds to changed models and their downstream dependencies.
Audit your jobs:
Bad:
dbt run --full-refresh
every night.
Better:
Newer dbt versions include performance improvements in graph processing and incremental workflows. Keeping dbt Core and adapters current can help, especially for larger DAGs.
A practical tuning order I’d use:
If you share:
Optimizing a sluggish dbt project requires attacking bottlenecks at both the warehouse layer and the dbt configuration layer. Here is a structured approach to speeding up your runs:[[1]](https://medium.com/@blakelassiter/snowflake-warehouse-sizing-for-dbt-a-practical-guide-2f674a720f29) - **Materializations Strategy**…
Optimizing a sluggish dbt project requires attacking bottlenecks at both the warehouse layer and the dbt configuration layer. Here is a structured approach to speeding up your runs:
view or table to incremental . This ensures you only process new or updated rows instead of full table scans on every run.SELECT * with explicit column declarations to reduce memory overhead and data transfer.--select and State Comparison , Use --select state:modified+ via the --state flag in production CI/CD pipelines. This ensures you only build models that have actually changed or depend on changed upstream data, rather than rebuilding the entire DAG.threads in your profiles.yml file (matching your warehouse's concurrency limits) so independent models run in parallel rather than serially.ephemeral materializations for small, single-use transformations that act as simple code clean-ups, but avoid them for complex or heavily-reused logic which can bloat the compiled SQL.If you want to dive deeper, let me know:
Optimizing a dbt project for speed requires a mix of configuration tweaks, architectural adjustments, and database-level tuning. Here is a structured approach to speeding up your dbt runs:[[1]](https://select.dev/posts/best-practices-for-dbt-workflows-1) - **Use incremental models** : Instead of rebuilding entire…
Optimizing a dbt project for speed requires a mix of configuration tweaks, architectural adjustments, and database-level tuning. Here is a structured approach to speeding up your dbt runs:
incremental using the is_incremental() macro so they only process new or updated rows.--select and state : Avoid running your entire DAG every time. Use state-based execution like dbt run --select state:modified+ to run only models that have changed and their downstream dependencies (compare against a previous manifest artifact).ephemeral if they are referenced multiple times (which re-evaluates the CTE everywhere), and don't materialize everything as a table if a view or incremental model suffices.SELECT * , reduce expensive cross-joins or heavy window functions on raw data, and pre-aggregate early in your DAG.dbt docs generate adds overhead. Keep it out of your primary, high-frequency transformation pipelines unless explicitly needed.--select state:modified ) rather than a full refresh to drastically cut down CI/CD build times.If you'd like to dive deeper, tell me:
I can give you specific configuration snippets or warehouse-level tuning tips for your stack.
When dbt runs take forever, the biggest gains usually come from figuring out **where the time is going** first: model SQL execution, unnecessary model builds, warehouse contention, or dbt overhead. A good optimization pass usually looks like this: ## 1. Find the slowest models first Start with run timing data:
When dbt runs take forever, the biggest gains usually come from figuring out where the time is going first: model SQL execution, unnecessary model builds, warehouse contention, or dbt overhead. A good optimization pass usually looks like this:
Start with run timing data:
dbt run
Look at the models with the longest execution times. In many projects, a handful of models account for most of the runtime. dbt recommends analyzing model timing and focusing optimization effort on the biggest bottlenecks rather than tuning everything.
Also inspect:
target/run_results.jsonThe biggest win is often avoiding rebuilding large tables.
Before:
{{ config(materialized='table') }}
select *
from {{ source('app', 'events') }}
After:
{{
config(
materialized='incremental',
unique_key='event_id'
)
}}
select *
from {{ source('app', 'events') }}
{% if is_incremental() %}
where updated_at >= (
select max(updated_at)
from {{ this }}
)
{% endif %}
Incremental models process only new or changed data instead of recreating the entire dataset.
Common improvements:
updated_at or event timestamp columnmerge, append, delete+insert, etc.)full-refresh runsA common mistake is:
dbt run
for every change.
Instead:
dbt build --select my_model+
or:
dbt build --select state:modified+
State-based selection can avoid rebuilding unchanged parts of your project.
For CI pipelines, consider:
dbt build \
--select state:modified+ \
--defer \
--state path/to/prod/artifacts
This lets unchanged models resolve against production artifacts rather than rebuilding everything.
Look for:
Bad:
select *
from huge_table
Better:
select
id,
created_at,
customer_id
from huge_table
where created_at >= '2026-01-01'
Bad:
select *
from (
select *
from events
join users using(user_id)
)
where event_date >= current_date - 7
Better:
select *
from events e
join users u using(user_id)
where e.event_date >= current_date - 7
Push filters as close to the source as possible.
Typical patterns:
| Model type | Usually best materialization |
|---|---|
| Raw cleanup/staging | view |
| Large fact tables | incremental |
| One-use transformations | ephemeral |
| Final marts | table/incremental |
Be careful with long chains of views. A warehouse may repeatedly expand and execute complex SQL when querying downstream models.
Depending on your warehouse:
select *If your warehouse has capacity, increase dbt threads:
profiles.yml
target: prod
outputs:
prod:
type: snowflake
threads: 8
More threads help only when:
More threads can make things slower if you overload the warehouse.
If startup time is the issue (not SQL execution), enable partial parsing:
flags:
partial_parse: true
Partial parsing avoids reparsing unchanged project files and can significantly reduce startup time in large projects.
Tests can quietly add hours.
Look for:
relationships tests on huge tablesConsider:
dbt run of the entire DAGIf you share:
I can suggest a more targeted tuning plan.
Speeding up sluggish **dbt** (data build tool) runs requires a mix of configuration tweaks, architectural changes, and incremental processing. Here is a prioritized, scannable guide to optimizing your dbt…
Speeding up sluggish dbt (data build tool) runs requires a mix of configuration tweaks, architectural changes, and incremental processing. Here is a prioritized, scannable guide to optimizing your dbt project:
config(materialized='incremental') . Filter for only new or updated records (e.g., using updated_at > (SELECT MAX(updated_at) FROM {this}})) on subsequent runs, and only do a full refresh when necessary via the --full-refresh flag.config(materialized='ephemeral') . This injects the code as a CTE directly into the downstream queries, saving storage and creation overhead.SELECT * , redundant joins, or expensive window functions. On data warehouses like PostgreSQL, Snowflake, or BigQuery, ensure you are defining appropriate clustering keys, partition fields, or sorting keys in your model configs to minimize data scanning.profiles.yml file or via the CLI flag -t 8 (or higher, depending on your data warehouse's concurrency limits and CPU capacity).dbt run --select +my_model (upstream dependencies) or dbt run --select state:modified (using artifact comparison against a production manifest) to run only what changed.manifest.json using --select state:modified+ , you build only the models that were modified and their downstream children, drastically cutting down PR build times.warn severities for non-critical checks, scoping tests with where clauses (e.g., testing only active records), or using custom generic tests that run more efficiently.If you want to dive deeper, let me know:
I can give you more targeted, warehouse-specific recommendations or help you set up a Slim CI workflow.
The biggest dbt speedups usually come from **building less data, running fewer models, and making the warehouse do less work**. I’d optimize in this order: ### 1. Find where the time actually goes Start by separating **dbt overhead** from **warehouse query time**.
The biggest dbt speedups usually come from building less data, running fewer models, and making the warehouse do less work. I’d optimize in this order:
Start by separating dbt overhead from warehouse query time.
Look at:
If a model takes 20 minutes because the warehouse query takes 20 minutes, changing dbt configuration won't fix the underlying SQL.
This is often the biggest win.
Instead of:
select *
from {{ ref('events') }}
use an incremental strategy that only processes new/changed records:
{{ config(
materialized='incremental',
unique_key='event_id'
) }}
select
event_id,
user_id,
event_timestamp,
event_type
from {{ ref('events') }}
{% if is_incremental() %}
where event_timestamp >= (
select max(event_timestamp)
from {{ this }}
)
{% endif %}
The important part isn't merely setting materialized='incremental': your SQL needs to avoid scanning/reprocessing the historical dataset.
Also consider your warehouse's best incremental strategy—e.g. merge, delete+insert, or insert-overwrite—and partition/cluster the underlying data appropriately.
Avoid carrying billions of rows through intermediate CTEs only to filter them at the end.
Prefer:
with events as (
select *
from {{ ref('events') }}
where event_date >= current_date - 30
),
users as (
select *
from {{ ref('users') }}
where is_active = true
)
...
over joining massive unfiltered relations and applying predicates afterward.
Also avoid select * in production models when you only need a subset of columns.
A common anti-pattern is having many downstream models independently perform the same expensive transformation.
For example:
raw_events
├── model_a → expensive deduplication
├── model_b → expensive deduplication
└── model_c → expensive deduplication
Instead:
raw_events
↓
deduped_events
├── model_a
├── model_b
└── model_c
Materializing an expensive shared transformation once can dramatically reduce total warehouse work.
The opposite can also happen: creating dozens of unnecessary intermediate tables can increase I/O. Benchmark rather than blindly materializing everything.
dbt can execute independent DAG branches concurrently. If your project has:
A → B → C → D
there isn't much dbt can parallelize.
But:
→ B →
A → → E
→ C →
→ D →
gives the runner more opportunity to execute work concurrently.
Look for unnecessary dependencies such as models referencing another model merely because it was convenient, rather than because the data dependency is real.
Also check your warehouse's concurrency limits—raising dbt's thread count isn't necessarily faster if it causes warehouse contention.
Don't rebuild the entire project for every pull request.
dbt's current documentation highlights dbt State as a way to skip unnecessary rebuilds by identifying nodes whose code/data haven't meaningfully changed.
A typical CI pattern is conceptually:
dbt build --select state:modified+
The + is important: it includes downstream dependencies of modified nodes.
For CI, combine this with a previous production manifest/state directory so a PR only builds the affected portion of the DAG.
Developers shouldn't routinely run the entire warehouse.
Useful patterns include:
dbt build --select my_model+
and limiting development data with a variable or environment-aware filter:
where event_date >= current_date - 7
You can also use --defer with a production manifest so unchanged upstream models can resolve to existing production relations instead of being rebuilt locally.
This is often more important than dbt itself.
Check for:
distinct over huge datasetsorder byFor example, this can be disastrous on a large table:
where cast(event_timestamp as date) = current_date - 1
whereas filtering directly on a partition-friendly timestamp/date column can allow the warehouse to prune most of the table.
A surprisingly slow dbt project can actually be a testing problem.
Tests such as uniqueness and relationships can scan enormous tables. Consider:
If your project has thousands of models/macros, local startup/parsing can become noticeable. dbt supports partial parsing, which avoids unnecessarily reparsing the entire project when only limited files have changed.
If parsing is a significant portion of your runtime, also inspect:
dbt's current tooling also emphasizes faster parsing/development through its Fusion engine and VS Code integration.
If you give me your warehouse (Snowflake, BigQuery, Databricks, Redshift, Postgres, etc.), roughly how many models you have, and a snippet of your slowest model, I can help you identify the highest-impact changes.
A good optimization exercise is:
| Area | What to measure | Typical opportunity |
|---|---|---|
| Model SQL | Query execution time | ⭐⭐⭐⭐⭐ |
| Incrementalization | Rows processed vs. rows needed | ⭐⭐⭐⭐⭐ |
| DAG | Parallelism / unnecessary dependencies | ⭐⭐⭐ |
| CI selection | Models rebuilt per PR | ⭐⭐⭐⭐ |
| Tests | Time spent testing | ⭐⭐⭐ |
| Warehouse config | Scans, partitions, concurrency | ⭐⭐⭐⭐⭐ |
| dbt parsing | Startup/compile time | ⭐⭐ |
The key principle: don't start by tweaking threads. First determine whether you're spending the time parsing dbt, waiting on the warehouse, rebuilding unnecessary data, or running an unnecessarily large DAG. Then optimize that bottleneck.