Суть Читай реальные конфиги dbt-моделей и сигнатуру счёта Snowflake, предсказывай поведение пайплайна — идемпотентность, инкрементальную загрузку, backfill — и выбирай фикс наибольшего рычага.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 14 min
Баги пайплайна живут в конфиге модели и в SQL, а не на диаграмме. Прочитай каждую dbt-модель и трейс, предскажи, что она делает на втором и третьем запуске по расписанию, и выбери фикс, к которому сеньор тянется первым.
Цель
Отработай цикл, который ты гоняешь на каждом ревью пайплайна: прочитай конфиг материализации и инкрементальный фильтр, предскажи, дублирует ли повтор, пересканирует или чисто делает upsert, и тянись к фиксу наибольшего рычага.
Модель материализована как 'incremental', но без unique_key и без блока is_incremental(). Что происходит на каждом запуске по расписанию после первого и каков фикс?
Heads-up Upsert требует unique_key со стратегией merge. Без обоих у dbt нет ключа для сопоставления и нет фильтра для ограничения скана, поэтому она просто аппендит весь результат.
Heads-up Сама по себе инкрементальная материализация ничего не делает — фильтр, ограничивающий строки, должен быть внутри блока is_incremental(). Без него нет WHERE, поэтому dbt снова выбирает всё.
Heads-up dbt спокойно запускается — в этом и опасность. Он тихо аппендит, поэтому баг ты обнаруживаешь как удвоенную выручку и растущий счёт, а не как ошибку.
Сниппет 2 — идемпотентный merge
-- models/marts/fct_orders.sql{{ config( materialized='incremental', incremental_strategy='merge', unique_key='order_id') }}select order_id, customer_id, status, updated_at, totalfrom {{ ref('stg_orders') }}{% if is_incremental() %} where updated_at > (select max(updated_at) from {{ this }}){% endif %}
Викторина
Completed
Статус заказа меняется с 'pending' на 'shipped' (тот же order_id, более новый updated_at). Затем EL-инструмент падает посреди запуска, и оркестратор повторяет весь батч. Какое финальное состояние fct_orders?
Heads-up Так сделал бы append. Merge сопоставляет по unique_key='order_id', поэтому обновляет существующую строку, а не вставляет вторую. Повтор безопасен.
Heads-up Дубль-строка не создаётся, поэтому SUM ниже видит заказ один раз. Merge плюс unique_key — ровно то, что делает загрузку идемпотентной, а итоги корректными.
Heads-up Merge обновляет, когда ключ есть, и вставляет, когда нет, — это upsert. Строка с более новым updated_at совпадает по order_id и перезаписывает статус на 'shipped'.
{{ config(materialized='table') }} -- не incrementalselect * from {{ ref('stg_pageviews') }} -- 2 ТБ, непартиционированная, ежечасно
Викторина
Completed
Эта одна модель сканирует 2.1 ТБ двадцать четыре раза в день. Каков корневой драйвер стоимости и каков фикс наибольшего рычага — не самый соблазнительный?
Heads-up Бо́льший warehouse завершит тот же скан 2 ТБ быстрее, но тарифицирует больше кредитов в секунду — суммарная стоимость почти не меняется. Рычаг — сканировать меньше данных, а не те же данные быстрее.
Heads-up Это срежет частоту, но каждый запуск всё равно пересканирует 2 ТБ, и ты жертвуешь свежестью. Инкрементальность держит ежечасную свежесть, сканируя только дельту, — строго лучше, чем душить расписание.
Heads-up Партиционирование помогает, только если запрос фильтрует по ключу партиции. SELECT * без WHERE читает каждую партицию всё равно. Сначала нужен инкрементальный фильтр, чтобы партиционирование заработало.
Сниппет 4 — backfill
-- Нужен backfill на 90 дней после фикса бага трансформации.-- Модель использует microbatch:{{ config( materialized='incremental', incremental_strategy='microbatch', event_time='event_time', batch_size='day', begin='2024-01-01') }}
dbt run --select fct_events --event-time-start "2024-01-01" --event-time-end "2024-04-01"
Викторина
Completed
Почему microbatch-модель — правильный инструмент для этого 90-дневного backfill по сравнению с одним full-refresh по тому же диапазону?
Heads-up Они различаются фундаментально: full-refresh пересобирает всю таблицу одной транзакцией, поэтому падение на 80-м дне теряет всю предыдущую работу. Microbatch делает каждый день независимой заменяемой единицей, которую можно перезапустить изолированно.
Heads-up Microbatch всё равно гоняет SQL на тарифицируемом warehouse — он не обходит стоимость compute. Его ценность в перезапускаемости и ограниченном радиусе поражения на батч, а не в бесплатном compute.
Heads-up Одна гигантская транзакция на 90 дней — противоположность безопасности на масштабе: она долгая, может ловить таймауты, а любое падение отбрасывает весь прогресс. Атомарные батчи по дню локализуют падение и дают возобновиться с упавшего дня.
Итог
Каждое ревью пайплайна читает конфиг и фильтр: инкрементальная модель без unique_key и без is_incremental() тихо аппендит и пересканирует всё; merge по unique_key делает повтор идемпотентным (upsert, не дубль); полный SELECT * по таблице, сканируемый ежечасно, — это баг стоимости, чинимый инкрементальностью, а не бо́льшим warehouse; а microbatch превращает долгий backfill в независимые, перезапускаемые, идемпотентные дневные единицы. Сначала читай материализацию, потом фильтр, потом решай.