Backfill: перезаливка истории

Урок объясняет backfill — запуск конвейера задним числом за прошлые периоды.

Backfill — выполнение DAG за прошедшие даты: например, чтобы пересчитать отчёты за весь прошлый месяц после исправления логики.

Зачем гонять прошлое

Представьте: вы нашли баг в преобразовании, который две недели портил витрину. Исправили код — но старые данные уже неверны. Backfill позволяет прогнать обновлённый конвейер за каждый из прошедших дней и пересчитать всё корректно. Это одна из главных причин, почему конвейеры строят на оркестраторе, а не на голом cron.

Backfill нужен не только при багах. Типичные ситуации: запустили новый отчёт и хотите видеть в нём историю за полгода назад; добавили новую колонку в витрину и нужно заполнить её для старых дней; источник прислал исправленные данные за прошлую неделю. Во всех случаях задача одна — прогнать конвейер за прошлые периоды так, будто он работал тогда. И ровно поэтому в задачах используется логическая дата, а не now(): только так backfill за июнь возьмёт июньские данные.

было неверно:  [01][02][03][04] ... [14]   (баг в transform)
backfill:      ▶ прогнать DAG заново за 01..14 с исправленной логикой
стало верно:   [01][02][03][04] ... [14]   ✔

Backfill против catchup

BackfillCatchup
запускают вручную за диапазон датавто-догон пропусков от start_date
осознанное действие инженераповедение при включении DAG

Команда backfill в CLI задаёт диапазон дат. Это bash, не исполняется в браузере.

airflow dags backfill daily_report \
    --start-date 2026-06-01 \
    --end-date 2026-06-14

Как работает под капотом

Главное условие успешного backfill — идемпотентность: повторный прогон за дату должен заменять данные, а не добавлять дубли. Смоделируем «перезапись партиции за день» — backfill за 02 июня просто переписывает данные этого дня.

warehouse = {"2026-06-01": 100, "2026-06-02": 50}  # уже загружено

def load_day(day, value):
    warehouse[day] = value  # перезапись, не +=  → идемпотентно

# backfill за 02 июня с исправленным значением
load_day("2026-06-02", 999)
for day in sorted(warehouse):
    print(day, warehouse[day])

Вывод:

2026-06-01 100
2026-06-02 999

Обратите внимание: backfill за 02 июня не затронул 01 июня — каждый день обрабатывается независимо. Это и есть сила «партиция-на-день»: можно перезалить любой проблемный период, не трогая соседние и не боясь задеть корректные данные. На практике инженеры дробят большой backfill на части (например, по неделям) и запускают с паузами, чтобы не положить источник и хранилище лавиной исторических запросов.

Частые ошибки

  • Backfill неидемпотентного конвейера. Если загрузка добавляет строки (INSERT без перезаписи), повторный прогон удвоит данные за день.
  • Brать в коде now() вместо логической даты. Тогда backfill за июнь обработает июньские данные текущими, а не историческими.
  • Запускать гигантский backfill без пауз. Десятки тысяч исторических запусков разом перегрузят источники; диапазон дробят.

Итог

  • Backfill прогоняет конвейер за прошедшие даты, например после исправления логики.
  • Он отличается от catchup: backfill запускают вручную за диапазон дат.
  • Безопасный backfill требует идемпотентности — перезаписи, а не добавления данных.
Проверьте себя
1. Для чего используют backfill?
AЧтобы удалить старые данные
BЧтобы прогнать конвейер за прошедшие даты, например после исправления бага
CЧтобы ускорить текущий запуск
DЧтобы отключить расписание
2. Почему backfill безопасен только для идемпотентного конвейера?
AИначе он работает медленнее
BИначе повторный прогон за дату добавит дубли вместо замены данных
CИдемпотентность нужна только для streaming
DBackfill не связан с идемпотентностью