Бэкфилл данных (backfill) — это перезаливка или пересчёт исторических данных за прошлые периоды, когда логики раньше не существовало, она была сломана или вы добавили новый источник. Главное правило: бэкфилл должен быть идемпотентным — повторный запуск за тот же период не создаёт дублей и не меняет итог. Достигается это удалением партиции перед вставкой (delete-insert), либо MERGE/UPSERT по ключу. Если этого нет — вы получите задвоенную выручку и расхождение с продакшеном.
На собесе аналитика данных вопрос про бэкфилл — индикатор зрелости. Junior отвечает «ну, перезальём данные», Middle уточняет про идемпотентность и дедупликацию. Разберём так, чтобы вы попали во вторую категорию.
Что такое бэкфилл данных простыми словами?
Представьте: вы три месяца считали витрину заказов, а потом обнаружили баг — не учитывались возвраты. Исправить логику на будущее мало: исторические цифры за три месяца неверны. Нужно пересчитать прошлое по новой логике — это и есть бэкфилл.
Типичные триггеры бэкфилла:
- Добавили новую колонку или метрику в витрину и хотите заполнить её за прошлые даты.
- Нашли и исправили баг в трансформации (
JOINтерял строки, неверная фильтрация дат). - Подключили новый источник данных, у которого есть история.
- DAG в Airflow падал несколько дней — образовалась «дыра» в данных, её надо закрыть.
- Сменили гранулярность (считали по дням, перешли на часы).
Бэкфилл отличается от обычного инкрементального запуска тем, что он идёт «назад во времени» и обрабатывает уже существующие периоды, а не только свежий день.
Чем бэкфилл отличается от реплея и инкремента?
Термины часто путают. Держите различия в голове:
| Понятие | Что делает | Когда |
|---|---|---|
| Инкремент | Грузит только новые данные (последний день) | Ежедневно по расписанию |
| Бэкфилл | Пересчитывает прошлые периоды по текущей логике | После фикса бага / новой колонки |
| Реплей (replay) | Прогоняет события заново из лога/очереди | Восстановление после сбоя стрима |
| Full refresh | Удаляет всё и строит витрину с нуля | Радикальная смена модели |
Бэкфилл — это почти всегда «частичный full refresh» по конкретному диапазону дат. Поэтому ключевой навык — уметь ограничить пересчёт нужными партициями и не задеть остальное.
Что такое идемпотентность и зачем она нужна?
Идемпотентность — свойство операции давать один и тот же результат при любом числе повторов. Если запустить бэкфилл за 2026-01-15 один раз или пять раз подряд, в таблице должна быть одна версия данных за эту дату, а не пять.
Почему это критично: бэкфиллы падают на середине, их перезапускают, Airflow ретраит таски автоматически. Если ваш INSERT просто добавляет строки, каждый ретрай удваивает данные. Идемпотентность — это страховка от человеческого фактора и от инфраструктуры.
Три рабочих паттерна идемпотентного бэкфилла:
- Delete-insert (перезапись партиции) — самый частый. Перед вставкой удаляете данные за период, потом вставляете заново.
- MERGE / UPSERT — обновляете строку по ключу, если она есть, иначе вставляете.
- Insert overwrite — в ClickHouse/Spark/dbt атомарно заменяете партицию целиком.
Как сделать бэкфилл в SQL без дублей?
Базовый delete-insert внутри одной транзакции выглядит так: сначала чистим целевой день, затем вставляем пересчитанный результат.
DELETE FROM mart.orders_daily WHERE event_date = '2026-01-15';
INSERT INTO mart.orders_daily SELECT event_date, SUM(amount) FROM raw.orders WHERE event_date = '2026-01-15' GROUP BY event_date;
Оберните оба запроса в транзакцию (BEGIN ... COMMIT), чтобы при падении INSERT не остаться с пустой партицией.
Если в источнике сами по себе встречаются дубли событий, добавьте дедупликацию через оконную функцию — оставляете одну строку на бизнес-ключ:
WITH ranked AS (SELECT *, ROW_NUMBER() OVER (PARTITION BY order_id ORDER BY updated_at DESC) AS rn FROM raw.orders WHERE event_date = '2026-01-15') SELECT * FROM ranked WHERE rn = 1
Альтернатива delete-insert — MERGE (или INSERT ... ON CONFLICT DO UPDATE в PostgreSQL). MERGE предпочтителен, когда нужно частично обновить существующие строки, а не выкидывать всю партицию. Эти конструкции — частая практика на тренажёре: отработать ROW_NUMBER, ON CONFLICT и оконные функции можно в SQL-тренажёре с автопроверкой.
Для ClickHouse идиома другая — INSERT INTO ... SELECT с предварительным ALTER TABLE ... DROP PARTITION, потому что обычный DELETE там тяжёлый (мутация). Замена партиции целиком атомарна и идемпотентна.
Как организовать бэкфилл в Airflow?
В Airflow бэкфилл — это запуск DAG за прошлые execution_date. Ключевые принципы:
- Параметризуйте таски датой. Используйте
{{ ds }}(дата запуска) в SQL вместоCURRENT_DATE. Тогда таск за 15 января обработает именно 15 января, а не сегодня. - Делайте таски идемпотентными. Каждый таск сам чистит свою партицию перед записью — тогда повторный запуск безопасен.
- Команда бэкфилла:
airflow dags backfill -s 2026-01-01 -e 2026-01-31 orders_dagпрогонит DAG по всем дням диапазона. - Ограничьте параллелизм. Флаг
max_active_runsи пулы не дадут бэкфиллу за месяц забить кластер 31 одновременным запуском и положить базу. - catchup. Если
catchup=True, Airflow сам догонит пропущенные интервалы при включении DAG. Часто это ставят вFalse, а историю заливают отдельной командой контролируемо.
Главная ошибка новичков — захардкодить CURRENT_DATE в SQL. Тогда бэкфилл за январь перезапишет январские партиции… сегодняшними данными. Всегда подставляйте дату из контекста выполнения.
Какие типичные ошибки убивают бэкфилл?
- Неидемпотентный INSERT. Нет delete/merge перед вставкой — ретрай задваивает данные. Самая частая причина расхождения витрины с источником.
- Хардкод текущей даты вместо
execution_date/{{ ds }}. - Бэкфилл всё разом без батчей по дням — один гигантский запрос съедает память и падает по таймауту. Дробите по партициям.
- Игнор таймзон. Граница суток UTC и MSK не совпадает — строки уезжают в соседний день, появляются «дыры» и дубли на стыках.
- Изменилась схема источника. Историю за полгода назад грузите по старой структуре колонок — проверяйте совместимость.
- Нет лимита параллелизма — 31 одновременный run кладёт прод-базу.
- Не пересчитали зависимые витрины. Бэкфилл сырого слоя без пересчёта агрегатов сверху = несогласованные цифры между таблицами.
Чек-лист перед запуском бэкфилла: операция идемпотентна → дата параллелизуется → задан диапазон → ограничен параллелизм → есть бэкап или способ откатиться → продуманы зависимые downstream-витрины.
Как проверить, что бэкфилл прошёл корректно?
После прогона обязательно сверьтесь:
- Count по дням.
SELECT event_date, COUNT(*) FROM mart.orders_daily GROUP BY event_date— нет ли пустых дат и нет ли аномальных пиков (признак дублей). - Уникальность ключа. Количество строк должно совпадать с количеством уникальных бизнес-ключей:
COUNT(*) = COUNT(DISTINCT order_id). - Сверка сумм с источником. Итоговая выручка в витрине = выручка в raw-слое за тот же период.
- Сравнение «до/после». Сохраните метрики до бэкфилла, чтобы видеть, что изменилось ровно то, что планировалось.
Где потренироваться?
Бэкфилл проверяют на собесах через SQL-задачи (дедупликация, оконные функции, идемпотентный пересчёт) и через системные вопросы по ETL. Прокачать оба слоя:
- SQL-тренажёр — отработайте
ROW_NUMBER,MERGE,ON CONFLICT, GROUP BY и оконные функции на реальных датасетах с автопроверкой в браузере (PostgreSQL и SQLite). - Тестовые задания — задачи формата «спроектируй идемпотентный пайплайн / напиши дедуп-запрос», как на собеседовании в крупных IT-компаниях.
- Курс «SQL с нуля до Window Functions» — последовательно от SELECT до оконных функций и CTE, на которых строится любой бэкфилл; с практикой внутри каждого урока.
Связка простая: понимаете идемпотентность концептуально, умеете руками написать delete-insert и дедуп через ROW_NUMBER, знаете подводные камни Airflow — и вопрос про бэкфилл на собесе перестаёт быть страшным. Начните с тренажёра, там это закрепляется быстрее всего.