Learning Platform
Глоссарий Troubleshooting
Урок 03.01 · 22 мин
Средний
incrementalis_incremental()unique_keyфилософия

Recap is_incremental() + философия incremental

Этот модуль — самый сложный в курсе. Incremental models на dbt I вы видели на базовом уровне: «если данные большие, ставим materialized=‘incremental’». На production-масштабе всё сложнее: четыре разных стратегии, тонкие гарантии каждой, специфические gotchas, performance-trade-offs на миллиардах строк. Этот урок — короткий recap основ и философия, на которой строятся следующие четыре урока модуля.

NOTE

is_incremental(), unique_key, on_schema_change, 4 условия incremental — это материал dbt-i/06. Если помнишь — переходи к разделу “Философия incremental: когда нужен и когда нет”. Если хочешь освежить — сюда.

Recap в одной таблице

КонцептЧто значитСтратегии
is_incremental()True если: модель materialized=incremental, таблица существует, run не —full-refresh, не unit testвсе
unique_keyколонка(ы) уникальной строкиmerge/delete+insert обязателен; append/microbatch — нет
--full-refreshDROP + CREATE, как первый runблокирует is_incremental()
on_schema_changeignore (default), append_new_columns, sync_all_columns, failкритичен на проде
4 стратегии 1.10append, delete+insert, merge, microbatch (+ insert_overwrite для BQ/Spark)по объёму и dedup

Это базовый минимум — детали и edge cases следующих уроков предполагают эти знания.

Философия incremental: когда нужен и когда нет

dbt-i: базовые incremental — is_incremental() и unique_key ACID: что гарантирует транзакция под incremental

Incremental не «всегда лучше table». У него есть стоимость:

Trade-offs incremental vs table

Incremental экономит compute на большом объёме, но добавляет сложность. На малых объёмах overhead не окупается.

ComputeIncremental читает только дельту. На таблице 100M+ строк это экономит 90%+ времени и compute-стоимости. На таблице 1M строк экономия незаметна
trade
СложностьНужно правильно выбрать стратегию, unique_key, фильтр, протестировать full-refresh путь, обработать late-arriving data. Это часы работы и риск багов
Время rundbt run incremental-модели секунды (только дельта). dbt run table-модели может быть минуты-часы. Это важно для частых runs (ежечасных)
trade
DebuggingПри баге incremental-модели восстановление требует full-refresh, который дороже обычного run. Если базовая логика поменялась — full-refresh всех downstream incremental
LatencyIncremental можно запускать раз в 5-15 минут на больших таблицах. Table-модель раз в 6 часов. Это критично для real-time-aware аналитики
trade
Late dataЕсли событие пришло задним числом (timestamp в прошлом), incremental с фильтром по max(created_at) его пропустит. Нужна стратегия lookback

Правила, когда нужен incremental:

  1. Таблица большая — 10M+ строк, табличный run занимает заметное время (минуты+).
  2. Дельта маленькая — на каждый run новых строк меньше 10% существующих.
  3. Есть watermark — колонка типа created_at, по которой можно отличать новое от старого.
  4. Хочется частых runs — каждые 15 минут или чаще.

Правила, когда НЕ нужен incremental:

  1. Таблица маленькая — менее 1M строк, table run меньше 10 секунд. Overhead incremental не окупится.
  2. Дельта большая — на каждый run меняется 30%+ строк. Incremental тут медленнее, чем table.
  3. Нет watermark — нельзя надёжно определить, что новое. Можно делать full сравнение, но это та же table.
  4. Backfill частый — если каждый месяц меняется логика, и нужно пересчитать всё, table проще.

Production-цифры

Конкретные числа из реальных проектов 2024-2026:

  • Маленькая таблица (1M строк): table run на DuckDB локально — 1-2 секунды. Incremental — те же 1-2 секунды (overhead на проверку, что таблица существует). Выигрыш нулевой.
  • Средняя (50M строк): table run — 30-60 секунд. Incremental (1M дельты) — 2-5 секунд. Выигрыш 10-30x.
  • Большая (1B+ строк): table run — 10-30 минут на Snowflake medium warehouse, 515заrun.Incremental(5Mдельты)3060секунд,5-15 за run. Incremental (5M дельты) — 30-60 секунд, 0.10-0.30 за run. Выигрыш 50-100x по compute, 30-50x по cost.
  • Огромная (10B+ строк): table run — часы, десятки долларов. Incremental — 2-5 минут, $0.50-2. Выигрыш 100x+.

На production-цифрах incremental — единственный способ работать с большими таблицами в разумные сроки и бюджеты.

Стратегии — что покрывает этот модуль

Этот модуль (02) разберёт append, delete+insert, merge. Microbatch — отдельный модуль (03). insert_overwrite не покрываем подробно (warehouse-specific и редкий случай).

DuckDB-specific

На DuckDB поддержаны:

  • append — работает.
  • delete+insert — работает.
  • mergeDuckDB 1.4+. На 1.3 и старее — config error. Если у вас DuckDB 1.3 (или dbt-duckdb старее 1.10.0), merge не доступен.
  • microbatch — dbt-core 1.9+. Работает без unique_key (по дизайну, не баг).

В production на Snowflake/BigQuery все стратегии работают без version-ограничений.

Жизненный цикл incremental-модели

Lifecycle: первый run, обычный run, full-refresh

Три состояния incremental-модели в production. Каждое имеет свою логику и свои риски.

dbt run первый разТаблицы нет, is_incremental() = False. dbt выполняет CREATE TABLE с полным SELECT. Это и есть полная стоимость, такая же как у table-материализации
dbt run обычноТаблица есть, is_incremental() = True. Блок {% if %} активен, читает только дельту. Применяется выбранная стратегия (insert/delete+insert/merge)
dbt run --full-refreshТаблица есть, но игнорируется. is_incremental() = False (несмотря на наличие таблицы). DROP + CREATE с полным SELECT. Дорого, как первый run
Backfill scenarioЛогика бизнес-метрики поменялась, нужно пересчитать все исторические данные. Обычно --full-refresh, реже — переменная --vars + кастомная логика

Гарантии и риски incremental

Главные риски incremental в production:

  1. Дубли — append без unique_key проинсертит ту же строку дважды, если её источник прислал повторно.
  2. Пропущенные строки — фильтр created_at > max(created_at) пропустит строку с late-arriving timestamp (событие случилось вчера, но пришло сегодня — фильтр отсечёт).
  3. Дрейф схемы — добавление колонки в source может тихо игнорироваться incremental-моделью (есть on_schema_change параметр для управления).
  4. Race conditions — два dbt run одновременно могут вставить одни и те же строки дважды.
  5. Forgotten full-refresh — изменили бизнес-логику в модели, забыли запустить full-refresh — историческая часть таблицы осталась со старой логикой.

Каждый из этих рисков мы разберём в этом и следующем модулях с конкретными решениями.

Когда incremental — это плохо

Иногда команды переусердствуют с incremental:

  • «Везде incremental, потому что быстрее» — на 100 маленьких моделях incremental добавляет сложность без выигрыша. Профайлинг покажет, что table вообще норм.
  • «Incremental на dimension-таблице» — dim_customers обычно маленькая, и обычно полностью пересчитывается (изменилось всё, что угодно). Incremental тут — overhead.
  • «Incremental без unique_key на не-append логике» — гарантированные дубли, и в первый production-инцидент это заметит downstream.

Правило: по умолчанию table или view, incremental только когда профайлинг показал, что table не выдерживает. Это контр-интуитивно для junior, но это правильная философия для middle.

Framework выбора стратегии

Idempotency в Airflow-DAGs — параллельная проблема

После того как профайлинг подтвердил «table не выдерживает», следующий вопрос — какую из четырёх стратегий выбрать. Decision tree:

Выбор incremental стратегии

Алгоритм принятия решения на основе характеристик данных и downstream-требований.

Updates допустимы?Может ли source-строка измениться после первой записи? Если нет (event log, audit trail) — append. Если да — нужна dedup-стратегия
нет
appendСамая дешёвая. Никакой проверки на дубли — просто INSERT дельты. Работает только когда дубли невозможны на уровне источника или допустимы downstream
Warehouse поддерживает MERGE?Snowflake, BigQuery, Databricks, DuckDB 1.4+ — да. PostgreSQL до 15 — нет. Redshift — limited. Если нет — delete+insert
нет
delete+insertTwo-statement: DELETE FROM target WHERE unique_key IN (deltakeys), затем INSERT delta. Транзакционно безопасно на большинстве движков, но 2x I/O на затрагиваемые партиции
Event-time partitioned + parallel ok?Microbatch обрабатывает события по event_time партициями. Подходит когда есть чёткий event_time и можно параллельно backfill окнами. dbt 1.9+
да
microbatchПараллельные batches по event_time. Идеально для backfill, для high-throughput streams. Подробно в модуле 03
Default для updatesЕсли ничего из выше не подошло — merge самый универсальный. Один SQL statement, atomic, оптимальный план у warehouse
mergeMERGE INTO target USING delta ON unique_key WHEN MATCHED UPDATE WHEN NOT MATCHED INSERT. Atomic, optimal на современных warehouses

В реальных проектах распределение примерно: 40% merge, 30% append (логи и события), 20% delete+insert (legacy Postgres), 10% microbatch (heavy backfills). Если у вас перекос в сторону одной стратегии — это сигнал либо технологического outlier (вся компания на Postgres), либо что команда не пробовала альтернативы.

Что дальше в этом модуле

  • 02-append.mdx — append стратегия, immutable logs use case, no-dedup workflow.
  • 03-delete-insert-merge.mdx — delete+insert vs merge, deep dive, когда какая.
  • 04-incremental-predicates.mdx — incremental_predicates для оптимизации MERGE, DBT_INTERNAL_DEST trick.
  • 05-lookback-and-late-arriving.mdx — паттерн lookback, late-arriving data.

После этого модуля вы будете уметь выбирать правильную стратегию для конкретной задачи и понимать trade-offs.

Попробуй сам

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

  1. Сколько у вас incremental-моделей? (Скрипт: find models -name "*.sql" -exec grep -l "materialized='incremental'" {} \;)
  2. Для каждой запишите: размер таблицы (rows), время table-run, время incremental-run. Если разница меньше 5x, возможно incremental не нужна.
  3. Найдите incremental-модели без unique_key. Если стратегия не append — это потенциальный баг с дублями.
  4. Запустите dbt run --full-refresh для одной из incremental-моделей. Засеките время. Если это > 30 минут — у вас будет проблема при backfill.
Проверка знанийKnowledge check
Команда жалуется: 'на маленькой таблице 100K строк incremental работает медленнее table — что мы делаем не так?'. Что объясняет это и как фиксить?
ОтветAnswer
Это нормальное и предсказуемое поведение. Incremental имеет overhead: dbt должна проверить существование таблицы, выполнить запрос '(select max(timestamp) from this)' для нахождения watermark, выбрать стратегию (append/delete+insert/merge), применить фильтр в SQL — всё это занимает время. На таблице 100K строк table-run может быть 2-3 секунды (read 100K + write 100K), а incremental — 4-5 секунд (overhead + read 5K дельты + delete matching + insert). Incremental окупается только когда экономия на чтении дельты больше overhead'а — обычно с 10M строк и выше. Решение — перевести модель обратно на materialized='table'. Это контр-интуитивно для junior, но правильно: incremental не 'всегда лучше', а 'лучше для большого объёма + маленькой дельты'. Для маленьких таблиц table проще, дешевле, надёжнее, легче рефакторить, меньше шансов на баги. Главное правило середины: профайлинг (table vs incremental на конкретной модели), не интуиция. Если table-run меньше 30 секунд — оставьте table, incremental не окупится.

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

Результат: 0 из 0
Концептуальный
Вопрос 1 из 6. Какие четыре условия должны быть TRUE одновременно, чтобы is_incremental() вернул True?

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

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

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

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