Microbatch: концепт и зачем появился
В dbt 1.9 (декабрь 2024) появилась новая incremental-стратегия — microbatch. На первый взгляд она кажется ещё одной опцией в списке append/merge/delete+insert, но архитектурно это принципиально иной подход к incremental моделям, оптимизированный для большого объёма event-time partitioned данных.
В этом модуле разберём microbatch с нуля: что это, как настроить, как запускать, какие подводные камни. Microbatch — относительно новая фича, и большинство production-команд только начинают её осваивать. Это даёт нам возможность научиться правильно с самого начала.
Зачем понадобилась microbatch
Чтобы понять microbatch, нужно понять, чего не хватало в обычной incremental. Возьмём типичную модель:
{{ config(
materialized='incremental',
incremental_strategy='merge',
unique_key='event_id'
) }}
select * from {{ source('events', 'raw_events') }}
{% if is_incremental() %}
where event_timestamp > (select max(event_timestamp) from {{ this }}) - interval '7 days'
{% endif %}
Это работает на 1B-таблицу, но имеет несколько limitations:
- Single run = single SQL operation. Один большой MERGE по всей дельте за окно. Если что-то сломалось — весь run падает, надо —full-refresh.
- Backfill — это снова —full-refresh. Если нужно пересчитать данные за prilast month (исправили баг в логике), —full-refresh пересчитает ВСЁ — последние 7 дней и год назад одинаково.
- Late-arrivals лимитированы lookback. Окно фиксировано в SQL, для конкретного backfill нужно вручную менять через vars.
- Parallelism ограничен. Один MERGE = одна транзакция, warehouse выполняет последовательно.
Microbatch решает все четыре. Подход — разбить incremental run на маленькие batches по time partition (час/день/месяц), обрабатывать их параллельно или последовательно, retry failures независимо.
Что такое microbatch
Microbatch — это incremental-стратегия, где dbt разбивает run на множество маленьких операций по time partitions, и каждый batch обрабатывается отдельно.
Обычный incremental: один большой MERGE с фильтром по дате. Microbatch: множество маленьких DELETE+INSERT по дням/часам.
Главные понятия microbatch:
- event_time — колонка, по которой батчи определяются (например,
event_timestamp). - batch_size — размер батча:
hour,day,month,year. - begin — начальная дата для backfills.
- lookback — сколько прошлых batches переcчитывать на каждый run (default 1).
Базовая microbatch модель
{{ config(
materialized='incremental',
incremental_strategy='microbatch',
event_time='event_timestamp',
batch_size='day',
begin='2024-01-01',
lookback=2
) }}
select
event_id,
user_id,
event_type,
event_timestamp,
properties
from {{ ref('stg_events') }}
where event_timestamp >= '{{ var("min_date", "2024-01-01") }}'
Что здесь происходит:
incremental_strategy='microbatch'— указывает стратегию.event_time='event_timestamp'— колонка для partitioning.batch_size='day'— каждый batch — один день.begin='2024-01-01'— first batch на этот день, дальше по batch_size.lookback=2— переcчитывать 2 предыдущих batch на каждый run (для late-arrivals).where event_timestamp >= ...— обычный WHERE для optimization (можно опустить).
Заметьте — нет unique_key, нет {% if is_incremental() %}. Microbatch не использует unique_key (по дизайну), и dbt автоматически генерирует фильтры по batches.
Что под капотом
Когда вы запускаете dbt run --select my_microbatch_model, dbt:
- Находит максимальный
event_timeв существующей target. - Вычисляет необработанные batches: от
max(event_time) - lookbackдоnow. - Для каждого batch генерирует SQL:
-- Batch 2026-05-18: DELETE FROM analytics.events WHERE event_timestamp не меньше '2026-05-18' AND event_timestamp < '2026-05-19'; INSERT INTO analytics.events SELECT * FROM staging.events WHERE event_timestamp не меньше '2026-05-18' AND event_timestamp < '2026-05-19'; -- Batch 2026-05-19: DELETE FROM analytics.events WHERE event_timestamp не меньше '2026-05-19' AND event_timestamp < '2026-05-20'; INSERT INTO analytics.events SELECT * FROM staging.events WHERE event_timestamp не меньше '2026-05-19' AND event_timestamp < '2026-05-20'; - Выполняет batches последовательно или параллельно (
--concurrent-batches).
Каждый batch — это DELETE+INSERT по time range. Нет MERGE, нет unique_key, нет lookup matches. Это самая простая incremental на уровне SQL, и потому самая быстрая.
Почему microbatch не использует unique_key
Это специфика дизайна. Microbatch по конструкции делает DELETE целиком за batch перед INSERT — она перезаписывает весь batch. Если в source за этот day появились новые/изменённые строки, они в INSERT попадут. Если что-то удалилось из source — оно не попадёт в INSERT, в target тоже не будет (за этот day).
То есть microbatch это “snapshot per batch” — каждый batch отражает текущее состояние source за этот time range. unique_key не нужен, потому что dbt просто перезаписывает batch целиком.
Это даёт несколько свойств:
- Idempotent — повторный запуск этого же batch даёт тот же результат.
- No duplicates — DELETE перед INSERT исключает дубли.
- Reflects current source state — если source данные изменились задним числом, batch их подхватит.
Но это и trade-off:
- Нельзя делать UPDATE существующих с сохранением created_at — батч переписывается целиком, исторические timestamps теряются.
- Нет fine-grained dedup — на уровне batch dbt не различает «эта строка обновилась» vs «эта строка появилась» — обе INSERT’ятся.
Когда microbatch блистает
Microbatch идеален для:
- High-volume event tables с natural time partition (events, logs, telemetry).
- Backfill scenarios — частые переcчёты диапазонов дат после изменения логики.
- Parallel processing — DuckDB на 8+ ядер, Snowflake с большим warehouse — могут обработать 10 batches параллельно.
- Retry tolerance — partial failures не теряют прогресс других batches.
Когда microbatch не подходит
- Mutable dimensions — dim_customers, где important сохранять
created_atпри обновлении. Microbatch DELETE+INSERT теряет историю. - Маленькие таблицы — на 100K-1M строк microbatch overkill, обычный merge проще.
- No natural event_time — модель агрегирует across все historic data (например, customer_lifetime_revenue). Microbatch требует partitioning by time.
- Data without late-arrivals — если source гарантированно immutable, обычный append проще.
Версии и поддержка
- dbt-core 1.9+ — microbatch GA.
- dbt-duckdb 1.10+ — поддерживает microbatch.
- Snowflake, BigQuery, Databricks, Postgres — все поддерживают.
- DuckDB-специфика: unique_key с microbatch — config error (некоторые adapters позволяют, dbt-duckdb нет).
Production patterns
dbt-i: первое знакомство с microbatch dbt-iii: microbatch parallel — idempotency и --concurrent-batches Airflow Datasets — оркестрация microbatch-моделей по событиюPattern 1: hourly events
{{ config(
materialized='incremental',
incremental_strategy='microbatch',
event_time='event_timestamp',
batch_size='hour',
begin='2025-01-01',
lookback=2
) }}
batch_size='hour' — для high-volume streams. Каждый час — отдельный batch. На 24 batches/день можно retry’ить и parallel’ить.
Pattern 2: daily events с monthly backfill
{{ config(
materialized='incremental',
incremental_strategy='microbatch',
event_time='event_timestamp',
batch_size='day',
begin='2024-01-01',
lookback=3
) }}
batch_size='day', lookback=3 — стандарт для daily events. Backfill через --event-time-start='2026-04-01' --event-time-end='2026-05-01'.
Pattern 3: monthly aggregates
{{ config(
materialized='incremental',
incremental_strategy='microbatch',
event_time='month_start',
batch_size='month',
begin='2020-01-01',
lookback=1
) }}
batch_size='month' — для monthly aggregates типа MRR-snapshots. Batch — отдельный месяц.
DuckDB-специфика
- DuckDB поддерживает microbatch с dbt-duckdb 1.10+.
- DuckDB single-writer per file — concurrent batches могут конфликтовать. Лучше sequential на local DuckDB.
- На MotherDuck concurrent batches работают (multi-writer на cloud).
- DuckDB performance microbatch сопоставим с обычным merge на маленьких объёмах, выигрывает на больших (>100M).
Производительность
Реальные числа на 5B-таблице events (production-команда 2025):
| Стратегия | Run time | Backfill 1 month | Retry after failure |
|---|---|---|---|
| merge с lookback 7d | 25 min | 35 min —full-refresh | 25 min full retry |
| microbatch daily, lookback 2 | 8 min | 12 min —event-time-start/end | 30 sec (retry one batch) |
| microbatch hourly, lookback 6 | 6 min | 15 min | 5 sec |
Microbatch выигрывает в three dimensions: regular run, backfill, retry. Это особенно важно для оперативного восстановления после incidents.
Production gotchas (preview)
Microbatch имеет специфические gotchas, которые мы разберём в следующих уроках:
- UTC requirement — event_time должен быть в UTC. Локальные timestamps ломают batch boundaries.
--full-refreshвсегда работает — если хотите запретить, ставьтеfull_refresh: falseв config.--event-time-startбез--event-time-end— открытое окно, может пересчитать больше, чем надо.lookback=1(default) пропускает late-arrivals за > 1 batch.- Parallel batches с stateful logic — race conditions, если bizrules зависят от ordering.
- DuckDB single-writer — concurrent batches на local DuckDB могут lock’аться.
Попробуй сам
В своём проекте (или Jaffle Shop из dbt-i):
- Возьмите event-like модель (orders, events). Переведите её на microbatch с
batch_size='day'. - Запустите
dbt run. Посмотрите в логи — увидите multiple SQL operations (одна за day). - Попробуйте backfill:
dbt run --select my_model --event-time-start='2026-04-01' --event-time-end='2026-04-08'. dbt пересчитает только эту неделю. - Симулируйте failure: после успешного run вручную сломайте source (DROP COLUMN), запустите dbt run. Заметите, что упадёт на конкретном batch, остальные останутся committed.