Действуй как инженер, который проводил много бекфиллов. Спланируй бекфилл: {{scenario}}. Объём: {{data_volume}}.
Когда нужен бекфилл
| Триггер | Почему | | Исправлен баг в трансформации | Историчные mart'ы содержат битые цифры — пересчитать | | Добавили новую колонку из источника | Нужны исторические значения, не только новые | | Изменилась бизнес-логика | "Теперь refunds учитываем по-другому", аналитика просит ретро | | Восстановление после инцидента | Период с пропусками или дублями | | Новый mart/модель | Историчные данные нужны для тренда |
Перед бекфиллом — спроси: "А зачем? Может, новая логика только с момента релиза?" Часто это самый дешёвый ответ.
Idempotency — это нулевое требование
Бекфилл, который не идемпотентен, нельзя запустить. Точка. Проверь:
MERGE/INSERT OVERWRITE PARTITIONвместоINSERT- Уникальный ключ на каждой строке (composite ok)
- Partition by date — переналив одного дня атомарен и не трогает остальные
- Если используешь external API — храни response, чтобы при повторе не платить за тот же запрос
Тест: запусти один день бекфилла дважды. Результат должен быть бит в бит одинаков.
Chunking — обязательно
Никогда не "одним SQL за полгода". Чанкуй:
| Гранулярность | Когда | | По дню | Дефолт для большинства pipeline'ов | | По часу | Высокообъёмные события (>100M/день), хочется параллелизма | | По tenant/customer | Multi-tenant, изоляция сбоев | | По диапазону id | Реляционные таблицы без даты |
# Пример: ежедневная итерация
for date in $(generate_dates 2025-11-01 2026-05-01); do
dbt run --select fct_orders --vars "{run_date: $date}"
sleep 30 # throttling
done
Throttling — чтобы не положить прод
Бекфилл = N × обычная нагрузка. Без throttling:
- Source DB: replicas лагают, OLTP запросы тормозят, пользователи замечают
- Warehouse: compute упирается, дорогие интерактивные запросы аналитиков встают
- API: rate limit, бан
Правила:
- Между chunk'ами: sleep N секунд (начинай с 30, тюнингуй по метрикам)
- Параллелизм: максимум 2-3 chunk'а одновременно
- Окно: только off-peak (для прод DB — ночь по основной таймзоне)
- Circuit breaker: если replica lag > порога — останови и подожди
- Resource budget: отдельный warehouse/cluster для бекфилла, чтобы не конкурировать с прод-pipeline'ами
# Псевдо
for chunk in chunks:
if replica_lag_seconds() > 60:
sleep(300) # ждём, пока догонит
continue
process(chunk)
sleep(30)
Monitoring прогресса
Без него бекфилл превращается в "запустил и забыл" → утром обнаружишь, что упало 8 часов назад.
Дашборд должен показывать:
- Прогресс:
processed_chunks / total_chunks - ETA:
chunks_left × avg_chunk_duration - Throughput: rows/min, чтобы видеть деградацию
- Replica lag, warehouse load — пороги для throttling
- Алерт на 2 подряд failed chunk'а
Храни состояние в backfill_state(scenario, chunk_key, status, started_at, finished_at, rows_processed, error). Тогда перезапуск понимает, что уже сделано, а что нет.
Rollback при ошибке
План rollback'а ОБЯЗАТЕЛЕН, продумай ДО запуска:
| Стратегия | Когда |
| Partition swap | Бекфиллишь в shadow-партицию, при ошибке остаётся старая. Идеал для warehouse'а |
| Versioned tables | Пишешь в fct_orders_v2, перенаправляешь BI после валидации |
| Soft delete | Все строки с пометкой backfill_run_id; при ошибке — DELETE WHERE backfill_run_id = X |
| Point-in-time restore | Снапшот warehouse до бекфилла; самый дорогой, для крайних случаев |
После бекфилла — reconciliation: сравни ключевые метрики (sum(revenue), count(distinct user) по дням) до/после. Расхождение > 5% по необъяснимым причинам = откат.
Вывод
## Scope
Период: ... → ...
Таблицы: ...
Объём: ~X rows
## Idempotency
- unique_key: ...
- merge column: ...
- partition: ...
## Chunking
- Гранулярность: day / hour / tenant
- Total chunks: N
- Concurrency: K
## Throttling
- Sleep между chunk'ами: ...
- Pause condition: replica_lag > ...
## State tracking
backfill_state(...)
## Rollback plan
Стратегия: ...
Команда отката: ...
## Reconciliation
| Метрика | Pre | Post | Diff % | Acceptable? |
## Run book
1. Заморозить downstream consumers
2. Backup / snapshot
3. Запустить с --dry-run на 1 chunk
4. Полный прогон
5. Reconciliation
6. Разморозить consumers
Anti-patterns
- ❌ Одна команда
UPDATEза полгода — WAL bloats, replica лагает, может убить прод - ❌ Запуск без
backfill_state— после рестарта не знаешь, что уже сделано - ❌ Параллелизм без throttling по lag — replica отстаёт, app начинает читать stale data
- ❌ Никакой reconciliation — спустя месяц аналитик найдёт расхождение и придётся бекфиллить заново
- ❌ Бекфилл в тот же warehouse, что и прод-pipeline'ы, без resource isolation — встанет всё разом
Идемпотентность: ключи, storage, retry
Idempotency keys (UUID), Redis storage с TTL, retry strategy, edge-cases с concurrent same-key и retry после success.
Дизайн webhooks: payload, подпись, retry, идемпотентность
Полный дизайн исходящих webhooks: схема payload, HMAC-подпись, политика ретраев, идемпотентность, защита от replay, observability, dead letter, юзер-debug.
Проектирование ELT-пайплайна: extract → load raw → transform
Идемпотентность, staging vs marts, инкрементальная загрузка vs full refresh, dbt-стиль трансформаций и freshness SLA.