What you will be able to do
- Explain how the microbatch strategy splits a model into independent, idempotent time-bounded batches
- Configure a microbatch model with event_time, begin, batch_size and any adapter-specific settings
- Predict which upstream refs dbt filters per batch, and opt a ref out of auto-filtering
- Decide when batches run in parallel or sequentially, and override that with concurrent_batches
- Backfill a date range and reprocess only the failed batches
Key concept
Batch as an atomic unit — A microbatch model is built as a series of separate queries, and each one covers a single bounded time period of the event_time column. Each batch can be built, replaced, retried or run alongside others on its own, and rebuilding one with the same input always gives the same result.
1.Microbatch: an incremental strategy built around time
Microbatch is an incremental strategy for large time-series tables. It is not a materialization of its own. You still set materialized='incremental' and then choose incremental_strategy='microbatch'. Like any incremental model, it avoids rebuilding the whole table on every run (the other strategies and how to choose between them are in [selecting an incremental strategy](lesson:1.11)). What changes is how the work is divided up. Microbatch uses a single time column, event_time, to define time ranges, and runs one query for each range.
Microbatch requires dbt v1.9 or higher. A project that defines its own custom microbatch macro must also set a distinct behavior flag in dbt_project.yml to enable batched execution. Without a custom macro there is nothing to set, because dbt handles microbatching automatically for any model that uses the strategy.
Other incremental strategies think in terms of "old" data and "new" data. Microbatch treats every batch as an atomic unit instead. A batch is one bounded time period, a single day by default, and dbt builds or replaces it on its own. This happens on the first build, on routine incremental runs and on backfills. A daily model run on October 2 builds the October 2 batch and swaps it into the target table. It makes no difference whether that day already had rows. Given the same input, reprocessing a batch any number of times produces the same table.
The model SQL must return exactly one batch of data, and it carries no is_incremental() filter. That is the sharpest difference from other strategies, where you write the "what is new" filter yourself. Here dbt generates the time filters from event_time, batch_size and lookback, and picks the replacement mechanism for the adapter. As a bonus, you can preview the model and see exactly the rows one batch will write.
{{ config(
materialized='incremental',
incremental_strategy='microbatch',
event_time='session_start',
begin='2020-01-01',
batch_size='day'
) }}Because batches are independent, dbt can do things a single incremental query cannot. It can rerun only the batches that failed, run batches at the same time, and backfill a date range without the hand-written conditional logic that backfills usually need. Microbatch adds to the existing strategies rather than replacing them. Without a trustworthy event timestamp, or when you want full control over the incremental logic, another strategy is the better choice.
Checkpoint 1 of 9· Check yourself
A team's orders table has no reliable event timestamp, and the team wants to hand-write the incremental filter logic. What fits best?
Microbatch defines every batch from event_time and takes the incremental logic over from you. Without a reliable event_time, or when you need more control, the other strategies are the recommended choice.
“Consider other strategies for use cases such as not having a reliable event_time column or if you want more control over the incremental logic.”Source: docs.getdbt.com
Sources1
2.Required configs and adapter-specific settings
Three settings are required on every microbatch model. event_time names the column that says when each row occurred. begin is the "beginning of time": the first batch that an initial build or full refresh processes. batch_size sets how large each batch is, and accepts hour, day, month or year. Take a daily model with begin set a year back. A full build of it runs one query per day, plus one for today.
| Config | What it controls | Default | Required |
|---|---|---|---|
| event_time | The column indicating at what time the row occurred | N/A | Yes |
| begin | Starting point for initial or full-refresh builds | N/A | Yes |
| batch_size | Batch granularity: hour, day, month or year | N/A | Yes |
| lookback | Batches before the latest bookmark to reprocess for late-arriving records | 1 | No |
| concurrent_batches | Overrides auto-detection of parallel batch execution | None | No |
lookback is how microbatch deals with late-arriving data. The bookmark is the most recent batch already processed, and lookback is the number of batches before it that each run reprocesses along with the current one. With the default of 1, each run also rebuilds the batch just before the bookmark, so rows that arrived a day late are picked up without a full refresh. A larger value widens that window, at the cost of reprocessing more batches on every run.
A worked case: a daily model with lookback=3, run on October 1, builds four batches, September 28, 29, 30 and October 1. The same arithmetic sizes the window for a delay. Data that can arrive up to 48 hours late on a daily model needs a lookback of 2, so that the two prior days are rebuilt on each run.
On each adapter, dbt replaces a whole batch using the most efficient atomic mechanism that adapter offers. That choice explains the extra settings some adapters need. dbt-postgres replaces batches through merge, and a merge must match incoming rows to existing ones, so Postgres also needs unique_key. dbt-bigquery and dbt-spark replace batches through insert_overwrite, so they need partition_by. The documented extra-config list covers only these three adapters.
| Adapter | Batch replacement mechanism | Extra required config |
|---|---|---|
| dbt-postgres | merge | unique_key |
| dbt-redshift | delete+insert | None documented |
| dbt-snowflake | delete+insert | None documented |
| dbt-bigquery | insert_overwrite | partition_by |
| dbt-spark | insert_overwrite | partition_by |
| dbt-databricks | replace_where | None documented |
Checkpoint 2 of 9· Fill the gap
This microbatch model runs on dbt-postgres. Which config completes it?
{{ config(
materialized='incremental',
incremental_strategy='microbatch',
? ='sales_id', ## required for dbt-postgres
event_time='transaction_date',
begin='2023-01-01',
batch_size='day'
) }}On Postgres, microbatch replaces batches with merge. A merge needs unique_key to match incoming rows against the rows already in the table.
Source: docs.getdbt.comSet full_refresh: false on microbatch models so they ignore invocations that pass --full-refresh. Running dbt run --full-refresh on a microbatch model by itself does not reset or reload data unless the model has a begin config. To reprocess history deliberately, use a targeted backfill: a full refresh with both --event-time-start and --event-time-end supplied.
Checkpoint 3 of 9· Check yourself
A microbatch model needs its January 2024 data rebuilt through a full refresh. Which invocation is documented to work?
A full-refresh backfill of a microbatch model needs both event-time flags. Plain --full-refresh does not reset data without a begin config, and dbt does not know the earliest event_time in your data.
“You must configure both for the full refresh to successfully run.”Source: docs.getdbt.com
Checkpoint 4 of 9· Exam question
A dbt project has a `fct_iot_readings` model that ingests billions of sensor readings per day into a Snowflake warehouse. New readings sometimes arrive up to 48 hours late, occasional runs fail partway through, and the team wants to reprocess only the time slices that failed without touching prior successful loads. Which materialization approach best fits these requirements?
Correct answer: A — Configure the model as `incremental` with the `microbatch` strategy, setting `event_time`, `batch_size`, and a `lookback` window so late readings are recaptured and failed batches retry independently.
- A. The microbatch strategy divides processing into independent, time-bounded batches keyed on `event_time`, and a `lookback` window reprocesses recent batches so late-arriving sensor readings get captured. Because each batch is atomic, a failed run can be resolved with `dbt retry` without rerunning batches that already succeeded.
- B. The merge strategy updates and inserts rows by `unique_key` in a single statement against the whole filtered set, so a wide `incremental_predicates` window only widens one scan rather than isolating and retrying individual failed time slices. It does not give the batch-level atomicity or selective retry the scenario needs.
- C. Rebuilding the full table every run reprocesses billions of rows regardless of which slices actually failed, which defeats the goal of cheaply reprocessing only failed time windows. It also does nothing special to capture late-arriving data beyond brute-force reprocessing.
- D. An ephemeral model is inlined as a CTE into whatever references it and cannot itself be built, queried, or retried independently, so it cannot provide batch-level retry behavior. Pushing the incremental logic downstream does not change how the late-arriving readings are batched or recovered.
3.Which upstream refs get filtered per batch
In a microbatch model, dbt filters upstream inputs for you. When a parent has event_time configured, dbt wraps that ref() in a filter on the current batch's time range. Sources and seeds can carry event_time too, and are filtered the same way. You never write a where clause on the batch window yourself. The filter applies only to parents that declare event_time, and the parent's column can have a different name from the child's.
Here is a concrete case. A sessions model joins page_views to customers. page_views is a large time-series table that configures page_view_start as its event_time. customers is a small dimension table whose rows change at any time, and it configures no event_time.
models:
- name: page_views
config:
event_time: page_view_startwith page_views as (
select * from (
-- filtered on configured event_time
select * from "analytics"."page_views"
where page_view_start >= '2024-10-01 00:00:00' -- Oct 1
and page_view_start < '2024-10-02 00:00:00'
)
),
customers as (
select * from "analytics"."customers"
),A full scan of a small dimension table is harmless. A full scan of a billion-row fact table that someone forgot to tag costs a lot, because it is repeated for every batch. Configure event_time on every large parent that should be filtered. The reverse case also exists: a parent has event_time, but this model needs all of it. Write ref('upstream_model').render() to opt that one reference out. Use this sparingly, since the parents that carry event_time are usually the large ones.
Choose the event_time column carefully. It should record when the event happened, such as account_created_at or session_began_at, not when the row was loaded. Ingestion timestamps can work, but they have known failure modes.
With last_updated_at or loaded_at, the column changes whenever a row is updated. An updated row therefore lands in a later batch while its old copy still sits in the earlier batch, and the table ends up with both. lookback reduces this, but an update that falls outside the lookback window is never reprocessed, so it cannot prevent duplicates entirely.
ingested_at is stamped by the ingestion tool, not by the source. If the connector is resynced, every row gets a new ingested_at, so rows already loaded under their first date are loaded again under a second date. Microbatches stay correct as long as no resync happens, or you run a full refresh when one does. The same event_time setting also drives [sample-mode runs](lesson:1.13).
Checkpoint 5 of 9· Check yourself
A microbatch model refs a 2-billion-row events model with no event_time configured. Each daily batch is very slow. What is the most likely cause?
dbt can only bound a parent to the batch window when that parent declares event_time. Without it, every batch reads the full events table.
“dbt cannot automatically filter them during batch processing and will perform full table scans on every batch run.”Source: docs.getdbt.com
4.Parallel or sequential batch execution
Independent batches can run at the same time, but only under certain conditions. Parallel batch execution is available on Snowflake and BigQuery. Even there, the first and last batches of a run never run in parallel with others. A model with 12 batches can run its middle batches in parallel, up to the number of available threads.
In most cases you don't configure this, because dbt detects it itself. Once the prerequisites are met and concurrent_batches is unset, dbt checks whether the model uses {{ this }}. Batches that read from the model's own table would conflict with each other, so a model that references {{ this }} runs its batches sequentially. Otherwise they run in parallel.
Which mode is right depends on the logic. Parallel runs are faster, but the result must not depend on the order in which batches run or finish. Per-transaction calculations meet that rule. Cumulative metrics do not, because each period builds on the one before it. They need sequential runs, which are slower but keep the batches in order. When auto-detection gets it wrong, concurrent_batches overrides it. You can set it in the model's config block or in dbt_project.yml. It is an override, not a gate: setting it to true does not lift the adapter and first/last-batch conditions.
models:
+concurrent_batches: true # value set to true to run batches in parallelCheckpoint 6 of 9· Match them up
Match each situation on Snowflake, with concurrent_batches unset, to how dbt runs the batch
Tap a term, then the definition that fits it.
Only middle batches can run in parallel. Among those, a model that uses {{ this }} is detected and run sequentially.
“A batch can only be run in parallel if: The batch is not the first batch. The batch is not the last batch.”Source: docs.getdbt.com
Checkpoint 7 of 9· Exam question
A team configures a microbatch model `sessions` as shown below. Every batch run performs a full table scan of `stg_customers` instead of scanning only the current batch's time window. Which change fixes the excessive scanning? ```yaml models: - name: page_views config: event_time: page_view_start - name: sessions config: materialized: incremental incremental_strategy: microbatch event_time: session_start begin: '2020-01-01' batch_size: day ``` The `sessions.sql` file joins `{{ ref('page_views') }}` and `{{ ref('stg_customers') }}`.
Correct answer: C — Add an `event_time` config to the `stg_customers` model so dbt can filter that reference to the batch's time range instead of scanning the whole table.
- A. Changing `batch_size` only changes how many batches run and how wide each batch's time window is; it does not change whether an unfiltered upstream reference gets scanned in full on every batch. `stg_customers` would still be fully scanned each time, just fewer times.
- B. `lookback` controls how many prior batches get reprocessed to catch late-arriving records in the model being built, it does not add filtering to an upstream reference that lacks `event_time`. The full-table scan of `stg_customers` would continue unchanged.
- C. dbt only auto-filters a `ref()` to the current batch's time range when that upstream model has `event_time` configured, exactly as `page_views` does here. Adding `event_time` to `stg_customers` lets dbt apply the same batch-window filter to that reference instead of scanning the whole table.
- D. `concurrent_batches` controls whether independent batches execute in parallel or sequentially; it has no effect on whether a given upstream reference is filtered by the batch window. Parallelizing full-table scans would make the problem more expensive, not less.
Sources4
5.Backfilling a date range and retrying failed batches
Backfills reuse the batch machinery. Sometimes historical data has to be rebuilt, because a source was corrected or business logic changed. In that case, select the model and give dbt a start and an end for event_time. dbt processes every batch in that window as an independent query, exactly as on a normal run, with no conditional backfill code in the model.
dbt run --event-time-start "2024-09-01" --event-time-end "2024-09-04"The two flags only work as a pair. If you pass --event-time-start alone, or --event-time-end alone, the command is invalid. All time values are read as UTC: the event_time column, begin, and both flags. A team in UTC+10 that expects "2024-09-01" to mean its local midnight will find the window ten hours off.
When some batches fail, you don't have to rebuild the window. Each batch is idempotent, so [dbt retry](lesson:7.2) reprocesses only the batches that failed, and the successful ones stay as they are. Suppose 2 of 30 daily batches time out: the retry reruns two queries, not thirty.
Checkpoint 8 of 9· Check yourself
An engineer runs dbt run --select sessions --event-time-start "2024-09-01" to reprocess September. What happens?
The backfill flags are mutually necessary: specifying one means you must specify the other, so a start without an end is not a valid backfill.
“Note that --event-time-start and --event-time-end are mutually necessary, meaning that if you specify one, you must specify the other.”Source: docs.getdbt.com
Checkpoint 9 of 9· Exam question
A microbatch model on Databricks currently runs its batches sequentially, and the team wants dbt to run independent batches concurrently during `dbt run` without changing any warehouse settings outside the model config. Which single action accomplishes this?
Correct answer: B — Set `concurrent_batches: true` in the model's config block so dbt overrides its auto-detection and executes independent batches in parallel during the run.
- A. `full_refresh: false` only changes whether the `--full-refresh` flag is honored for the model; it has no bearing on whether batches execute sequentially or in parallel during a normal run.
- B. `concurrent_batches` is the config that overrides dbt's automatic detection of parallel-execution safety, letting independent, non-overlapping batches run concurrently instead of one at a time. This directly changes the execution behavior the team wants.
- C. `lookback` controls how many prior batches are reprocessed to catch late-arriving data on each run; setting it to zero changes which time ranges get reprocessed, not whether batches run in parallel.
- D. `on_schema_change` governs how dbt reacts to column additions or removals in an incremental model's source data; it has no relationship to batch scheduling or concurrency during execution.
Sources1
Exam traps
Each one states something that sounds right. Open it to see what is actually true.
1.Microbatch is its own materialization, configured with materialized='microbatch'.Why is that wrong?
Microbatch is an incremental strategy. The model uses materialized='incremental' together with incremental_strategy='microbatch'.
Covered in Microbatch: an incremental strategy built around time
2.Running dbt run --full-refresh on a microbatch model reloads all of its history.Why is that wrong?
On its own it does not reset or reload data unless the model has a begin config. A deliberate reload needs both --event-time-start and --event-time-end, and full_refresh: false is the recommended guard.
3.Setting event_time on the microbatch model is enough for dbt to filter all of its upstream refs.Why is that wrong?
dbt filters only parents that configure their own event_time. Any other parent is read in full for every batch.
Covered in Which upstream refs get filtered per batch
4.Setting concurrent_batches: true makes every batch, including the first and last, run in parallel on any adapter.Why is that wrong?
concurrent_batches only overrides dbt's auto-detection. Parallel runs still need a supported adapter and apply only to batches that are neither first nor last.
Covered in Parallel or sequential batch execution
5.A cumulative running-total metric is safe to compute with parallel batches.Why is that wrong?
Cumulative calculations depend on batch order, so they need sequential runs.
Covered in Parallel or sequential batch execution
Sources
Every claim above is drawn from one of these pages, quoted as it was written on the date shown.
- 1.
“It complements, rather than replaces, existing incremental strategies by focusing on efficiency and simplicity in batch processing.”
↩︎ Microbatch: an incremental strategy built around time“Given the same input data, the resulting table is the same no matter how many times a batch is reprocessed.”
↩︎ Microbatch: an incremental strategy built around time“eliminate the need to implement complex conditional logic for backfilling.”
↩︎ Microbatch: an incremental strategy built around time“Available for dbt v1 Latest and dbt v1.9 or higher.”
↩︎ Microbatch: an incremental strategy built around time“set a distinct behavior flag in your dbt_project.yml to enable batched execution.”
↩︎ Microbatch: an incremental strategy built around time“You must write your model query to process (read and return) exactly one "batch" of data.”
↩︎ Microbatch: an incremental strategy built around time“Process X batches prior to the latest bookmark to capture late-arriving records.”
↩︎ Required configs and adapter-specific settings“This is the starting point for any initial or full-refresh builds.”
↩︎ Required configs and adapter-specific settings“unique_key is required because dbt-postgres microbatch uses the merge strategy, which needs a unique_key to identify which rows dbt should merge”
↩︎ Required configs and adapter-specific settings“we recommend configuring full_refresh: false on microbatch models so that they ignore invocations with the --full-refresh flag.”
↩︎ Required configs and adapter-specific settings“dbt would template separate queries for each day between September 28 and October 1, inclusive — four batches in total.”
↩︎ Required configs and adapter-specific settings“you can specify ref('upstream_model').render() to opt out of auto-filtering.”
↩︎ Which upstream refs get filtered per batch“dbt will automatically filter upstream inputs (source or ref) that define event_time”
↩︎ Which upstream refs get filtered per batch“If one or more of your batches fail, you can use dbt retry to reprocess only the failed batches.”
↩︎ Backfilling a date range and retrying failed batches“For now, dbt assumes that all values supplied are in UTC:”
↩︎ Backfilling a date range and retrying failed batches“Each batch is independent and idempotent.”
↩︎ Key concept“Microbatch is an incremental strategy designed for large time-series datasets”
↩︎ Exam trap 1“You must configure both for the full refresh to successfully run.”
↩︎ Exam trap 2“Consider other strategies for use cases such as not having a reliable event_time column or if you want more control over the incremental logic.”
↩︎ Checkpoint“You must configure both for the full refresh to successfully run.”
↩︎ Checkpoint“The customers table isn't filtered, resulting in a full scan for every batch.”
↩︎ Prediction“Note that --event-time-start and --event-time-end are mutually necessary, meaning that if you specify one, you must specify the other.”
↩︎ Checkpoint - 2.
“Accepted values are hour, day, month, or year.”
↩︎ Required configs and adapter-specific settings - 3.
“May result in duplicate entries in the resulting table in the data warehouse over multiple runs.”
↩︎ Which upstream refs get filtered per batch“it will change if/when you need to resync your connector for some reason.”
↩︎ Which upstream refs get filtered per batch“dbt cannot automatically filter them during batch processing and will perform full table scans on every batch run.”
↩︎ Exam trap 3“dbt cannot automatically filter them during batch processing and will perform full table scans on every batch run.”
↩︎ Checkpoint - 4.
“Use a supported adapter: Snowflake or BigQuery.”
↩︎ Parallel or sequential batch execution“If it references {{ this }}, the batches will run sequentially”
↩︎ Parallel or sequential batch execution“Parallel batch execution is faster but requires logic independent of batch execution order.”
↩︎ Parallel or sequential batch execution“the concurrent_batches config is available as an override (not a gate)”
↩︎ Exam trap 4“Sequential processing is slower but essential for calculations like cumulative metrics in microbatch models.”
↩︎ Exam trap 5“A batch can only be run in parallel if: The batch is not the first batch. The batch is not the last batch.”
↩︎ Checkpoint