Microbatch gotchas: UTC, full_refresh, DuckDB-specific
Microbatch — мощная фича, но молодая (с dbt 1.9, декабрь 2024), и production-команды только начинают её осваивать. Несколько gotchas мы упоминали в предыдущих уроках. Этот урок — сборник всех подводных камней с конкретными решениями, чтобы при реальном использовании microbatch вы знали, на что обращать внимание.
1. UTC requirement — главное правило
event_time колонка должна быть в UTC. Microbatch использует boundary’и вроде 2026-05-19 00:00:00 для DELETE/INSERT — и эти boundary’и интерпретируются warehouse как UTC.
Что идёт не так с локальным timezone
Допустим, ваш event_timestamp в Europe/Moscow (UTC+3). Событие случилось в 2026-05-19 02:00 MSK = 2026-05-18 23:00 UTC.
С batch_size='day':
- В Moscow это событие 19 мая.
- В UTC это событие 18 мая.
- Microbatch создаёт batches по UTC: batch 2026-05-18 от 00:00 до 23:59 UTC.
- Событие попадает в batch 2026-05-18, не в batch 2026-05-19.
Если ваша downstream-логика думает в Moscow time («показать events за 19 мая по Москве»), события 00:00-03:00 MSK 19 мая будут в batch 18 мая. Boundaries рассинхронизированы.
Решение
Конвертируйте event_time в UTC на staging-слое:
-- stg_events.sql
select
event_id,
user_id,
-- Приводим в UTC явно:
convert_timezone('Europe/Moscow', 'UTC', event_timestamp_local) as event_timestamp,
...
from {{ source('events', 'raw_events') }}
В Snowflake — CONVERT_TIMEZONE. В BigQuery — TIMESTAMP_ADD или DATETIME cast. В DuckDB — event_timestamp_local AT TIME ZONE 'Europe/Moscow' AT TIME ZONE 'UTC'.
Лучше всего — производитель события (приложение/Kafka) должен писать UTC сразу. Конвертация в downstream — workaround.
2. —full-refresh пути
dbt run --full-refresh на microbatch модели по умолчанию работает: DROP table, CREATE с нуля, запустить batches от begin до now. Это эквивалентно пересчёту всего исторического скопа.
На большой модели (2 года daily = 730 batches) это часы compute и десятки долларов. Один невнимательный --full-refresh стоит дорого.
Решение
full_refresh: false в config:
{{ config(
materialized='incremental',
incremental_strategy='microbatch',
event_time='event_timestamp',
batch_size='day',
begin='2024-01-01',
full_refresh=false -- запрещает --full-refresh
) }}
С full_refresh: false:
dbt run --full-refresh --select my_model— флаг проигнорирован для этой модели. dbt просто запустит обычный microbatch run.dbt run --full-refresh --select +my_model— флаг проигнорирован дляmy_model, но downstream-модели его получат.
Это production-must для критичных microbatch моделей. Без этого защиты нет.
Когда всё-таки нужен —full-refresh
Сценарии, требующие полного пересчёта:
- Изменилась core логика модели (новая колонка, переписали CTE).
- Изменился
beginилиbatch_size. - Поломался target schema и нужен rebuild from scratch.
В таких случаях временно убирают full_refresh: false, делают --full-refresh, возвращают защиту.
Альтернатива — selective backfill через --event-time-start='2024-01-01' --event-time-end='2026-05-19'. Это эквивалентно —full-refresh, но microbatch обрабатывает batches один за другим с retry granularity.
3. DuckDB-specific limitations
Microbatch на DuckDB (через dbt-duckdb 1.10+) имеет несколько ограничений:
3a. unique_key — config error
{{ config(
incremental_strategy='microbatch',
unique_key='event_id', -- ERROR
...
) }}
На dbt-duckdb microbatch + unique_key даёт config error. Microbatch по дизайну не использует unique_key — batches перезаписываются целиком. Если у вас был unique_key для обычного merge, при переходе на microbatch уберите его.
На Snowflake / BigQuery поведение зависит от adapter — некоторые adapters принимают unique_key с warning’ом и игнорируют. dbt-duckdb более strict.
3b. Single-writer на local DuckDB
DuckDB файл .duckdb — single-writer per process. Concurrent batches (через --concurrent-batches) на local DuckDB конфликтуют:
Error: Failed to lock database "/path/to/db.duckdb"
Решение — sequential на local (не указывать --concurrent-batches). На MotherDuck (cloud DuckDB) multi-writer поддерживается.
3c. Memory limit на больших batches
DuckDB по умолчанию использует много памяти на больших aggregations внутри batch. Если batch — это day с 50M строк и сложным GROUP BY, DuckDB может OOM.
Решение — в profiles.yml:
analytics:
outputs:
dev:
type: duckdb
path: './analytics.duckdb'
settings:
memory_limit: '8GB'
temp_directory: '/tmp/duckdb_spill'
memory_limit ограничивает heap, temp_directory указывает, куда spill при превышении. Это позволяет обрабатывать batches больше RAM ценой disk I/O.
3d. Production-обычай — Snowflake/BigQuery
Microbatch написана в первую очередь для warehouse’ов с multi-writer concurrent batches и partition pruning (BigQuery, Snowflake). На DuckDB она работает, но без full preformance benefit.
В курсе мы используем DuckDB для обучения. В production на масштабе 1B+ строк microbatch почти всегда живёт на Snowflake / BigQuery / Databricks — там она по-настоящему блистает. DuckDB подходит для:
- Learning concept.
- Small-medium scale prototypes.
- MotherDuck production (cloud DuckDB до 100GB-1TB).
Для multi-billion-row tables — production cloud warehouse.
4. —event-time-start без —event-time-end
dbt run --select my_model --event-time-start='2026-04-01'
Окно открытое — microbatch пересчитывает с 2026-04-01 до now. Часто это намного больше, чем вы хотели:
- Думали пересчитать «апрель» (30 days). Без
--event-time-endпересчитывается полтора месяца (45+ days). - Думали пересчитать «последнюю неделю». Без
--event-time-end='2026-05-19'пересчитывается с 2026-05-12 до сейчас, включая будущие partial day.
Решение: всегда оба флага явно. В скриптах:
# Backfill last 7 days:
TODAY=$(date -u +%Y-%m-%d)
WEEK_AGO=$(date -u -d '7 days ago' +%Y-%m-%d)
dbt run --select my_model \
--event-time-start="$WEEK_AGO" \
--event-time-end="$TODAY"
5. lookback=1 default
Default lookback=1 означает «пересчитывать только текущий batch + 1 предыдущий». На production это недостаточно для большинства EL-инструментов:
- Fivetran иногда задерживает sync на 24-48 часов -> lookback=1 пропустит.
- Airbyte retry circles 2-4 часа -> lookback=1 покрывает.
- Mobile offline events -> могут опаздывать на дни -> lookback=1 не помогает.
Production-обычай: lookback=3 для daily batches, lookback=12-24 для hourly. Дефолт 1 слишком оптимистичный для real-world data.
6. Concurrent batches с stateful logic
Frame clause и running totals — почему stateful logic ломается в microbatch--concurrent-batches=8 запускает 8 batches параллельно. Если модель содержит stateful logic — running totals, window functions, cohort analysis — параллельные batches могут вычислять неправильно:
-- ПЛОХО для concurrent batches:
select
user_id,
event_timestamp,
sum(amount) over (
partition by user_id
order by event_timestamp
) as running_total
from {{ ref('stg_events') }}
Каждый batch видит только свой day, не предыдущие. Running total per batch будет от 0, не от исторического running total.
Решение — stateful logic переносить на downstream model:
-- microbatch модель — без stateful:
select user_id, event_timestamp, amount from ...
-- intermediate / mart — stateful:
select
user_id,
event_timestamp,
amount,
sum(amount) over (partition by user_id order by event_timestamp) as running_total
from {{ ref('fct_events_microbatch') }}
Microbatch обрабатывает raw events parallel, агрегация делается downstream без параллелизма.
7. event_time column отсутствует в SELECT
{{ config(event_time='event_timestamp', ...) }}
select event_id, user_id from ... -- нет event_timestamp в SELECT
dbt при компиляции ругается: Column event_timestamp not found in model output. Microbatch обязан видеть event_time колонку в SELECT для генерации фильтров.
Решение — всегда включайте event_time в SELECT:
select
event_id,
user_id,
event_timestamp -- обязательно
from ...
8. Backward-incompatible изменения
Изменение batch_size или begin после первого run — breaking change:
- Был
batch_size='day', поменяли на'hour'— старые daily batches не пересоздаются как 24 hourly, target становится consistent. - Был
begin='2024-01-01', поменяли на'2023-01-01'— старые year не добавляются.
Решение: при таких изменениях обязательно --full-refresh (или --event-time-start='2023-01-01' --event-time-end='2026-05-19' для surgical).
Summary: production checklist для microbatch
Перед deploy’ем microbatch модели в production:
-
event_timeколонка в UTC. -
event_timeколонка immutable (timestamp события, не loaded_at). -
event_timeколонка в SELECT. -
batch_sizeподходит для volume (day для обычных, hour для high-volume). -
beginставит правильную historical границу (не меняй после). -
lookback=3+для daily, 12+ для hourly (не дефолт 1). -
full_refresh: falseдля production-critical моделей. - Stateful logic вынесена в downstream model.
-
--concurrent-batchesиспользовать только на MotherDuck/Snowflake/BQ. - Тесты на late-arriving events (manual injection + verification).
- Alert на out-of-window events (> lookback дней).
Когда НЕ использовать microbatch
Microbatch — мощный инструмент, но не для каждой модели:
- Маленькие таблицы (менее 10M строк) — overhead настройки не окупается, обычный merge проще.
- Mutable dimensions (dim_customers с обновлениями) — DELETE+INSERT теряет created_at history.
- Data without natural event_time — модели, агрегирующие across все historic data, без partitioning column.
- Простые append-only events — обычный append проще и быстрее.
- DuckDB local production (single file) — concurrent batches не работают.
DuckDB vs production warehouse
Это последний урок модуля, и время явно сказать: microbatch на DuckDB — это learning environment. Концепты переносимы на production, синтаксис идентичен, но performance benefits microbatch (parallel batches, partition pruning) доступны только на Snowflake/BQ/Databricks.
После курса в production у вас, вероятно, будут:
- Snowflake — microbatch с
--concurrent-batches=16на LARGE warehouse. - BigQuery — microbatch с partition pruning по
event_datecolumn. - Databricks — microbatch с Delta tables, time travel для recovery.
Концепты этого модуля транслируются 1:1 на любой из них. DuckDB здесь как teaching tool.
Попробуй сам
В своём проекте проверьте microbatch checklist:
- Открыть каждую microbatch модель.
- Пройти по checklist (см. выше).
- Если
event_timeне UTC — добавить конвертацию в staging. - Если
lookback=1— поднять до 3. - Если нет
full_refresh: false— добавить. - Если в SELECT отсутствует event_time — добавить.
Если у вас несколько критичных microbatch моделей, отдельно протестируйте:
- Backfill:
dbt run --event-time-start='2026-05-01' --event-time-end='2026-05-19'. Засеките время. - Симулируйте failure (через assert(false) в SQL), запустите
dbt retry.