Запуск microbatch: обычный run, backfill, parallel, retry
В прошлых уроках мы видели концепт и конфигурацию microbatch. Сегодня — практика: как запускать microbatch в production. Разберём четыре сценария: обычный daily run, surgical backfill, parallel execution, retry after failure.
Обычный run
Самый простой случай — ежедневный/ежечасный run в Airflow или dbt Cloud:
dbt run --select fct_events
Что происходит:
- dbt находит max(event_time) в target.
- Вычисляет batches от
max - lookbackдоnow. - Запускает каждый batch sequentially.
- Каждый batch — это DELETE+INSERT для своего time range.
- Batches коммитятся независимо. Если 3-й batch упал, 1-й и 2-й остаются в target.
Логи в dbt run:
Running with dbt=1.10.21
Found 1 model
12:00:01 Concurrency: 4 threads (target='dev')
12:00:01 1 of 1 START sql incremental model events.fct_events ............ [RUN]
12:00:02 Batch 1/4: 2026-05-16 00:00 - 2026-05-17 00:00 ...... [OK in 5.2s]
12:00:07 Batch 2/4: 2026-05-17 00:00 - 2026-05-18 00:00 ...... [OK in 4.8s]
12:00:11 Batch 3/4: 2026-05-18 00:00 - 2026-05-19 00:00 ...... [OK in 5.1s]
12:00:16 Batch 4/4: 2026-05-19 00:00 - 2026-05-20 00:00 ...... [OK in 3.9s]
12:00:20 1 of 1 OK created sql incremental model events.fct_events ...... [OK in 19s]
Это log одного microbatch run с 4 batches (3 lookback + 1 current). Каждый batch видно отдельно.
Selective backfill через —event-time-start/—event-time-end
Главная сила microbatch — surgical backfill через CLI флаги. Use case: исправили баг в логике, нужно пересчитать конкретный период.
# Пересчитать только апрель 2026:
dbt run \
--select fct_events \
--event-time-start='2026-04-01' \
--event-time-end='2026-05-01'
dbt запустит batches только для дат внутри окна (30 days × batch_size=‘day’ = 30 batches).
Важные правила:
--event-time-endexclusive.'2026-05-01'означает «до 1 мая, не включая 1 мая».- Оба флага в формате ISO date (
YYYY-MM-DD) или ISO timestamp (YYYY-MM-DDTHH:MM:SS). - Без
--event-time-endокно открытое — пересчитывается до now. Часто это не то, что хотите. - Backfill не использует lookback — он пересчитывает ровно указанный диапазон.
Backfill scenarios
Scenario 1: Bug fix
# Нашли баг в case-statement, исправили, пересчитываем апрель:
dbt run --select fct_events --event-time-start='2026-04-01' --event-time-end='2026-05-01'
Scenario 2: Late-arriving data вне lookback
# Source за last week восстановили retroactively, lookback=3 не покрыл:
dbt run --select fct_events --event-time-start='2026-05-12' --event-time-end='2026-05-19'
Scenario 3: New downstream model нуждается в historical data
# Создаём новый dim_users, нужно пересчитать fct_events с самого начала:
dbt run --select fct_events --event-time-start='2024-01-01' --event-time-end='2026-05-19'
Scenario 4: Full refresh + selective
# Если хотите полный пересчёт, но через microbatch механизм (не --full-refresh):
dbt run --select fct_events --event-time-start='2024-01-01' --event-time-end='2026-05-19'
Технически это эквивалентно --full-refresh, но даёт visibility — видно progress per batch.
Parallel execution через —concurrent-batches
По умолчанию microbatch выполняет batches последовательно. Это безопасно, но медленно. На больших backfills (год+ данных) — часы compute.
--concurrent-batches параметр запускает batches параллельно:
# Запустить с 8 parallel batches:
dbt run \
--select fct_events \
--event-time-start='2024-01-01' \
--event-time-end='2026-05-19' \
--concurrent-batches=8
Что происходит:
- dbt вычисляет все batches (~730 для 2 лет daily).
- Запускает 8 одновременно через thread pool.
- Когда один завершается, начинается следующий из очереди.
- Все 730 обработаются за ~ (730 / 8) × avg_batch_time.
На warehouse с big compute (Snowflake LARGE, BigQuery slots) это 5-10x ускорение backfill.
Когда parallel опасен
--concurrent-batches работает не для всех models:
-
Stateful logic — если модель зависит от ordering (например, running total per user), параллельные batches могут вычислять неправильно. Безопасно — explicit ORDER BY в SQL, чтобы каждый batch self-contained.
-
Concurrent writes на DuckDB — single-writer per file. Local DuckDB упадёт при concurrent batches. На MotherDuck — OK.
-
Race conditions на shared resources — если модель пишет в shared table или dependency, может быть corruption.
Правило: parallel батчи safe, если каждый batch self-contained — не зависит от предыдущих batches на уровне логики, не пишет в shared state.
Retry после failure
Это главное преимущество microbatch перед обычным incremental. Если один batch упал — не нужен полный —full-refresh.
Что происходит при failure
12:00:01 1 of 1 START sql incremental model events.fct_events ............ [RUN]
12:00:02 Batch 1/30: 2026-04-01 - 2026-04-02 ...... [OK in 5s]
12:00:07 Batch 2/30: 2026-04-02 - 2026-04-03 ...... [OK in 4s]
12:00:11 Batch 3/30: 2026-04-03 - 2026-04-04 ...... [OK in 5s]
12:00:16 Batch 4/30: 2026-04-04 - 2026-04-05 ...... [ERROR]
12:00:17 1 of 1 ERROR creating sql incremental model events.fct_events ....
Error: out of memory
После failure:
- Batches 1-3 уже committed в target (DELETE+INSERT prior batch успешно).
- Batch 4 НЕ committed — failure до COMMIT, target откатился к pre-batch state.
- Batches 5-30 не запускались.
dbt retry
dbt retry
dbt смотрит в run_results.json предыдущего run, находит, что failure был на batch 4 модели fct_events, запускает только batches 4-30 (не 1-3, они OK).
Это критичный фичей для production:
- Обычный incremental retry = весь run заново. На 4-часовом run после failure на 3-м часе — 4 часа заново.
- Microbatch retry = только failed + downstream batches. На 30 batches retry batch 4 — 5 секунд.
Manual restart с конкретного batch
Если хотите явно указать starting point:
dbt run \
--select fct_events \
--event-time-start='2026-04-04' \
--event-time-end='2026-05-01'
dbt запустит batches с 4 апреля. Useful когда dbt retry не доступен (changed config, etc.).
Run на dbt Cloud / Airflow
Airflow: backfill-safe DAGs — datetime.now и data_intervalВ Airflow через dbt-airflow или dbt-cosmos:
from airflow import DAG
from cosmos import DbtTaskGroup, ProjectConfig, ProfileConfig
with DAG('daily_events_run', schedule='0 1 * * *') as dag:
dbt_run = DbtTaskGroup(
project_config=ProjectConfig('my_dbt_project'),
profile_config=ProfileConfig(...),
operator_args={
'select': 'fct_events',
}
)
В dbt Cloud — обычный schedule job с командой dbt run --select fct_events.
Production gotchas
1. —event-time-end забыли
Без --event-time-end окно открытое. Backfill пересчитывает с start до сегодня. Часто это не то, что хотите. Всегда указывайте оба явно.
2. —concurrent-batches на shared resource
Запустили --concurrent-batches=16 на DuckDB locally — все batches пытаются писать в один .duckdb файл — кучи lock errors. Решение — sequential на local DuckDB, parallel только на MotherDuck/Snowflake/BQ.
3. dbt retry после рестарта Airflow
Если Airflow рестартовал между runs, run_results.json может потеряться (особенно с in-memory backends). dbt retry не найдёт state. Решение — persistent storage для run_results.json (S3, GCS).
4. Конкурентные runs от разных jobs
Два Airflow задач одновременно запускают dbt run --select fct_events — оба видят свои batches как pending, оба запускают, конфликт на target. Решение — concurrency lock в Airflow на уровне задачи.
5. CET vs UTC mismatch
--event-time-start='2026-04-01' — dbt интерпретирует как UTC midnight. Если ваш timezone CET (UTC+1), вы можете ожидать 00:00 CET, а получите 01:00 CET. На границе months — потеря 1 часа данных. Всегда работайте в UTC.
Real production schedule
Типичный production schedule для microbatch event-table:
# Airflow DAG
schedule: '0 */4 * * *' # каждые 4 часа
tasks:
- name: refresh_events
operator: BashOperator
bash_command: 'dbt run --select fct_events --target prod'
Каждые 4 часа dbt запускает microbatch. Lookback=3 пересчитывает последние 3 days. Если что-то падает — dbt retry через manual trigger в Airflow UI.
Параллельно еженедельный full-week-backfill для catch-up late-arrivals за пределами lookback:
schedule: '0 2 * * 0' # Sunday 2am
tasks:
- name: weekly_backfill
operator: BashOperator
bash_command: |
dbt run --select fct_events \
--event-time-start="{{ ds_add(ds, -14) }}" \
--event-time-end="{{ ds }}" \
--target prod
Это паттерн 3-tier: regular (every 4h), recovery (manual via retry), catch-all (weekly).
DuckDB-специфика
--concurrent-batchesна local DuckDB — не использовать, single-writer.--concurrent-batches=4на MotherDuck — OK, но мониторьте memory_limit.dbt retryработает идентично Snowflake/BQ.--event-time-start/--event-time-end— поддерживается с dbt-duckdb 1.10+.
Попробуй сам
В своём проекте на microbatch модели:
- Запустите обычный
dbt run. Посмотрите log — сколько batches? - Сделайте selective backfill:
--event-time-start='X' --event-time-end='Y'. Засеките время. - Сравните с
--full-refresh. Microbatch backfill должен быть быстрее на большом окне (благодаря per-batch retry potential). - Симулируйте failure: добавьте
assert(false)в SQL для одного batch (через case). Запустите run, увидите failure посередине. Запуститеdbt retry— увидите, что предыдущие batches не пересчитываются. - (Опционально) Запустите с
--concurrent-batches=4(только на NOT-local DuckDB) — увидите speedup.