Sebuah bug transformasi ditemukan pada hari kedelapan: selama tujuh hari, pipeline lupa mengecualikan pesanan refund dari perhitungan pendapatan. Memperbaiki kode untuk run hari ini tidak menutup masalah — tujuh partisi di warehouse masih menyimpan angka yang salah dan laporan mingguan sudah terbit. Backfill adalah tindakan menjalankan ulang workflow untuk periode lampau: setiap run historis dieksekusi dengan parameter tanggalnya masing-masing, seolah penjadwal berjalan dari awal. Operasi semacam ini hanya aman bila satu syarat terpenuhi: task-nya idempotent.
Apa Itu Backfill #
Backfill menjalankan run untuk data interval yang sudah lewat. Orchestrator membangkitkan daftar run untuk rentang tanggal yang ditentukan, menjalankannya berurutan atau dengan paralelisme terbatas, dan setiap run membawa logical date sendiri sehingga membaca sumber serta menulis partisi yang benar. Dari sisi kode, backfill tidak berbeda dengan run biasa — yang berbeda hanyalah parameternya menunjuk masa lalu.
Tiga Skenario yang Menuntut Backfill #
- Data terlambat tiba: sumber baru melengkapi data untuk periode lama, misalnya partner baru mengunggah berkas historis.
- Perbaikan bug: logika v2 harus menghitung ulang periode terdampak, seperti contoh refund di pembuka chapter ini.
- Pipeline baru dengan start_date di masa lalu: mengisi historis agar analisis tidak menunggu berbulan-bulan data baru terkumpul.
Syarat Aman: Task Harus Idempotent #
Idempotent berarti menjalankan ulang task untuk periode yang sama menghasilkan isi yang identik — dua kali, tiga kali, berapa kali pun. Syarat ini penting karena backfill bisa terputus di tengah dan dijalankan ulang dari awal. Pola penulisan menentukan aman atau tidaknya:
| Pola penulisan partisi | Run ulang ke-2 | Aman untuk backfill |
|---|---|---|
Overwrite (gudang[tanggal] = baris) | mengganti isi lama dengan isi baru | ya |
Append murni (extend tanpa pembersihan) | menambah baris kembar | tidak |
Di SQL, pola aman setara dengan INSERT OVERWRITE pada partisi, atau DELETE lalu INSERT dalam satu transaksi per tanggal. Backfill atas task yang tidak idempotent menghasilkan duplikat — dan duplikat jauh lebih sulit diperbaiki daripada kegagalan run itu sendiri.
Strategi Backfill Bertahap #
Backfill besar menyerupai uji beban: menjalankan ratusan run sekaligus bisa membebani database produksi dan menyaingi pipeline harian yang sedang berjalan. Praktik yang aman:
- mulai dari satu hari, verifikasi hasilnya dibandingkan dengan sumber;
- perluas bertahap — tiga hari, satu minggu, satu bulan — dengan pemeriksaan di tiap tahap;
- batasi paralelisme agar sumber tidak kehabisan koneksi;
- jalankan di luar jam sibuk dan catat versi kode yang dipakai setiap run.
Catchup vs Backfill #
Catchup, yang kita bahas di Chapter Scheduling, terjadi otomatis ketika DAG baru diaktifkan atau dilanjutkan setelah lama berhenti. Backfill dipicu manusia dengan rentang eksplisit, biasanya setelah bug diperbaiki. Banyak tim memilih catchup=False sejak awal dan mengandalkan backfill terkontrol supaya keputusan menjalankan run historis selalu disengaja, terverifikasi, dan terdokumentasi.
Simulasi: Backfill 7 Hari yang Aman #
Simulasi berikut memakai fungsi harian idempotent — pola overwrite partisi — lalu menjalankan backfill 7 hari penuh dua kali berturut-turut. Inilah uji nyata keamanannya: hasil kedua kali harus identik.
"""Backfill mini 7 hari: fungsi harian idempotent (pola overwrite partisi)."""
from datetime import date, timedelta
gudang = {} # simulasi tabel terpartisi: {tanggal_partisi: [baris]}
def muat_partisi_harian(tanggal):
# pola INSERT OVERWRITE: tulis ulang seluruh partisi tanggal tersebut
baris = [f"{tanggal:%Y-%m-%d}|pesanan-{i}" for i in range(3)]
gudang[tanggal] = baris # kunci sama -> isi lama diganti, bukan ditambah
return len(baris)
def backfill(mulai, jumlah_hari):
total = 0
for ofs in range(jumlah_hari):
tgl = mulai + timedelta(days=ofs)
total += muat_partisi_harian(tgl)
return total
def ringkasan():
jumlah_baris = sum(len(b) for b in gudang.values())
return f"{len(gudang)} partisi, {jumlah_baris} baris"
mulai = date(2026, 1, 5)
print("backfill ke-1:", backfill(mulai, 7), "baris ditulis ->", ringkasan())
print("backfill ke-2:", backfill(mulai, 7), "baris ditulis ->", ringkasan())Output yang Dihasilkan:
backfill ke-1: 21 baris ditulis -> 7 partisi, 21 baris
backfill ke-2: 21 baris ditulis -> 7 partisi, 21 barisMari kita telaah baris-baris kode di atas:
- Baris 4 (dictionary
gudang): mensimulasikan tabel terpartisi — kunci dictionary berperan sebagai tanggal partisi, isi list berperan sebagai baris data. - Baris 9-10 (pola overwrite): setiap run menulis seluruh isi partisi; penugasan ke kunci yang sama mengganti isi lama, persis perilaku
INSERT OVERWRITEpada partisi tanggal. - Baris 14-19 (
backfill): membangkitkan run 5 sampai 11 Januari berurutan, dan setiap run memanggil fungsi harian dengan tanggal parameternya sendiri. - Baris 27-29 (uji ganda): backfill dijalankan dua kali penuh — ringkasan kedua duanya identik: 7 partisi, 21 baris. Tidak ada duplikat, tidak ada baris tambahan.
Anti-Pola: Append Tanpa Pembersihan #
Sebagai pembanding, fungsi berikut menulis dengan pola append murni — extend tanpa pernah membuang baris lama.
"""Anti-pola backfill: pemuatan APPEND murni yang tidak idempotent."""
from datetime import date
gudang = {} # simulasi tabel terpartisi: {tanggal_partisi: [baris]}
def muat_append(tanggal):
# anti-pola: baris lama tidak pernah dibuang sebelum menulis
baris = [f"{tanggal:%Y-%m-%d}|pesanan-{i}" for i in range(3)]
gudang.setdefault(tanggal, []).extend(baris)
return len(baris)
tgl = date(2026, 1, 5)
print("run ke-1:", muat_append(tgl), "baris ditulis ->", sum(len(b) for b in gudang.values()), "baris total")
print("run ke-2:", muat_append(tgl), "baris ditulis ->", sum(len(b) for b in gudang.values()), "baris total")Output yang Dihasilkan:
run ke-1: 3 baris ditulis -> 3 baris total
run ke-2: 3 baris ditulis -> 6 baris totalSatu-satunya perbedaan ada di baris gudang.setdefault(tanggal, []).extend(baris): metode extend hanya menambah tanpa pernah menghapus, sehingga run kedua menggandakan isi partisi menjadi 6 baris. Mekanisme backfill di Airflow untuk rentang tanggal terdokumentasi di dokumentasi Apache Airflow
.
Backfill bekerja karena setiap run membawa parameter tanggalnya sendiri — kode yang sama menghasilkan hasil berbeda sesuai parameter. Mekanisme parameter inilah yang membuat workflow reusable untuk dev, produksi, dan historis sekaligus, dan menjadi pokok bahasan Chapter Parameterisasi Workflow.