Learning Platform
Глоссарий Troubleshooting
Урок 04.04 · 22 мин
Средний
microbatch gotchasUTCfull_refreshDuckDB limitations

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 — мощный инструмент, но не для каждой модели:

  1. Маленькие таблицы (менее 10M строк) — overhead настройки не окупается, обычный merge проще.
  2. Mutable dimensions (dim_customers с обновлениями) — DELETE+INSERT теряет created_at history.
  3. Data without natural event_time — модели, агрегирующие across все historic data, без partitioning column.
  4. Простые append-only events — обычный append проще и быстрее.
  5. 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_date column.
  • Databricks — microbatch с Delta tables, time travel для recovery.

Концепты этого модуля транслируются 1:1 на любой из них. DuckDB здесь как teaching tool.

Попробуй сам

В своём проекте проверьте microbatch checklist:

  1. Открыть каждую microbatch модель.
  2. Пройти по checklist (см. выше).
  3. Если event_time не UTC — добавить конвертацию в staging.
  4. Если lookback=1 — поднять до 3.
  5. Если нет full_refresh: false — добавить.
  6. Если в SELECT отсутствует event_time — добавить.

Если у вас несколько критичных microbatch моделей, отдельно протестируйте:

  1. Backfill: dbt run --event-time-start='2026-05-01' --event-time-end='2026-05-19'. Засеките время.
  2. Симулируйте failure (через assert(false) в SQL), запустите dbt retry.
Проверка знанийKnowledge check
Senior Data Engineer обсуждает миграцию production проекта с обычного merge на microbatch для 5B-таблицы events. Какие 3 главных риска нужно адресовать перед миграцией?
ОтветAnswer
Тру миграции 5B-таблицы — серьёзный проект. Три главных риска. Первый — UTC compliance. Если event_timestamp колонка не в UTC (например, локальный server time), при переходе на microbatch boundary'и batches будут intersect old daily-buckets data. Это создаст ситуацию: одни события попадут в неправильный batch. Перед миграцией ОБЯЗАТЕЛЬНО audit, что event_timestamp в UTC, и если нет — convert на staging-слое явно через convert_timezone(). Иначе после миграции downstream метрики будут displaced на N часов timezone-offset. Второй — loss of unique_key behavior. Обычный merge с unique_key='event_id' гарантировал idempotency на уровне event: повторная отправка того же event_id не давала дублей. Microbatch не использует unique_key, batches перезаписываются целиком. Если источник может переотправлять events после initial sync (Fivetran retry, Kafka reset), некоторые сценарии могут привести к: события не теряются (batch перепишется с свежей версией source), но конкретное поведение на granularity нескольких minutes/hours может отличаться. Тестировать с representative production data. Третий — full_refresh path catastrophe. Без 'full_refresh: false' один невнимательный --full-refresh пересчитает 5B данных. На Snowflake это $$$, на BQ — slots на час. Установить full_refresh: false как первое действие после миграции. Дополнительные риски: stateful logic если есть (running totals — вынести в downstream), concurrent_batches на shared resources, lookback default 1 (поднять до 3+). План миграции: (1) test на 1% sample data; (2) parallel-run обоих стратегий 2 недели, сравнить outputs через audit_helper; (3) явный switch с rollback plan; (4) monitor first month после миграции, особенно late-arrivals coverage.

Проверьте понимание

Результат: 0 из 0
Прикладной
Вопрос 1 из 6. Почему event_time должен быть в UTC для microbatch?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 4