Chapter sebelumnya memperkenalkan incremental ingestion dengan watermark sebagai jawaban atas mahalnya menyalin seluruh tabel. Namun incremental bukan jawaban untuk semua tabel: memelihara state watermark menambah kompleksitas, dan untuk tabel kecil kompleksitas tersebut tidak sebanding dengan hematnya. Pada chapter ini kita bandingkan dua strategi — full load yang menyalin seluruh tabel setiap run, dan incremental load yang hanya menyalin perubahan — lalu menentukan kapan masing-masing masuk akal lewat demo dengan data nyata.
Dua Strategi Memuat Data #
Full load membaca seluruh isi tabel sumber dan menimpa tabel tujuan pada setiap run — pola umumnya TRUNCATE lalu INSERT, atau tulis ke tabel baru lalu tukar nama. Hasil tujuan selalu mencerminkan keadaan terkini sumber; run berikutnya otomatis memperbaiki kesalahan run sebelumnya karena semuanya ditulis ulang dari awal.
Incremental load hanya membaca baris baru atau berubah sejak run terakhir, memakai watermark seperti updated_at. Run menjadi cepat dan ringan di sumber, tetapi pipeline kini punya state yang harus dipelihara: watermark yang salah membuat baris terlewat, dan mekanisme deduplikasi menjadi wajib.
Tabel Perbandingan #
| Aspek | Full Load | Incremental Load |
|---|---|---|
| Kompleksitas implementasi | Rendah: satu query tanpa state | Sedang: watermark, state, deduplikasi |
| Durasi dan beban run | Sebanding ukuran tabel penuh | Sebanding jumlah perubahan |
| Histori perubahan | Hanya keadaan terkini per run | Setiap perubahan tercatat sebagai baris |
| Baris terhapus di sumber | Ikut hilang dari tujuan di run berikutnya | Tidak terdeteksi — butuh mekanisme lain |
| Pemulihan dari galat | Otomatis: run ulang menulis ulang semua | Perlu menjalankan ulang jendela waktu terdampak |
| Use case alami | Tabel dimensi kecil | Tabel fakta besar yang terus bertambah |
Kapan Full Load Masuk Akal #
Full load masuk akal ketika ukuran data kecil dan kesederhanaan bernilai lebih daripada kecepatan. Tabel dimensi seperti produk dan kategori di toko online biasanya hanya berisi ratusan hingga ribuan baris — menyalin seluruhnya tiap malam selesai dalam hitungan detik, sementara perbaikan otomatis dari penulisan ulang total menyelamatkan tim dari galat yang menumpuk. Full load juga menjadi pilihan saat sumber tidak menyediakan kolom pembeda yang andal: tanpa updated_at yang terpelihara, watermark tidak bisa dipercaya.
Satu catatan operasional: full load yang menimpa tabel tujuan sebaiknya tidak menulis langsung ke tabel yang sedang dibaca analitik. Pola yang aman adalah menulis ke tabel versi baru — misalnya pesanan_v2 — lalu menukar nama kedua tabel dalam satu transaksi singkat. Pembaca selalu melihat tabel utuh: versi lama atau versi baru, tidak pernah keadaan setengah tertulis.
Demo: Membandingkan Jumlah Baris yang Dibaca #
Kita buktikan selisih keduanya dengan tabel pesanan berisi 5.000 baris: 4.988 baris histori September dan 12 baris baru tanggal 3 Oktober. Skrip berikut menjalankan kedua strategi terhadap sumber yang sama:
import sqlite3
PELANGGAN = ["Budi Santoso", "Siti Rahma", "Dewi Lestari",
"Andi Wijaya", "Farhan Pratama"]
SKEMA = """
CREATE TABLE pesanan (
id INTEGER PRIMARY KEY,
pelanggan TEXT NOT NULL,
total INTEGER NOT NULL,
updated_at TEXT NOT NULL
)
"""
# 4.988 baris histori September + 12 baris baru 3 Oktober
sumber = sqlite3.connect(":memory:")
sumber.execute(SKEMA)
histori = [
(i, PELANGGAN[i % 5], 50000 + (i * 7) % 400000,
f"2026-09-{(i % 30) + 1:02d}T{i % 24:02d}:00:00")
for i in range(1, 4989)
]
baru = [
(4988 + j, PELANGGAN[j % 5], 100000 + j * 5000,
f"2026-10-03T{j + 8:02d}:00:00")
for j in range(1, 13)
]
sumber.executemany("INSERT INTO pesanan VALUES (?, ?, ?, ?)", histori + baru)
sumber.commit()
total_sumber = sumber.execute("SELECT COUNT(*) FROM pesanan").fetchone()[0]
def salin(query, parameter=()):
tujuan = sqlite3.connect(":memory:")
tujuan.execute(SKEMA)
baris = sumber.execute(query, parameter).fetchall()
tujuan.executemany(
"INSERT OR REPLACE INTO pesanan VALUES (?, ?, ?, ?)", baris
)
tujuan.commit()
return len(baris)
print(f"Jumlah baris di sumber: {total_sumber}")
print(f"Full load : "
f"{salin('SELECT id, pelanggan, total, updated_at FROM pesanan')} baris disalin")
WATERMARK = "2026-09-30T23:59:59"
jumlah_baru = salin(
"SELECT id, pelanggan, total, updated_at FROM pesanan WHERE updated_at > ?",
(WATERMARK,),
)
persen = jumlah_baru / total_sumber * 100
print(f"Incremental load : {jumlah_baru} baris disalin "
f"({persen:.2f}% dari sumber)")Output yang Dihasilkan:
Jumlah baris di sumber: 5000
Full load : 5000 baris disalin
Incremental load : 12 baris disalin (0.24% dari sumber)Mari kita telaah baris-baris kode di atas:
- Baris 17-21 (
histori): list comprehension membangkitkan 4.988 baris dengan tanggal tersebar di September — mensimulasikan tabel yang sudah berjalan berbulan-bulan. - Baris 22-26 (
baru): dua belas baris baru semuanya bertanggal 3 Oktober, setelah batas watermark yang akan dipakai. - Baris 32-40 (
salin): satu fungsi melayani kedua strategi — yang membedakan hanyalah query: full load tanpa filter, incremental denganWHERE updated_at > ?. Fungsi mengembalikan jumlah baris yang benar-benar dibaca. - Baris 47-52 (
WATERMARKdan pemanggilan): watermark dipatok akhir September, sehingga incremental hanya menangkap 12 baris Oktober — 0,24% dari tabel.
Angka 0,24% inilah inti keputusannya: pada tabel besar, selisih 5.000 berbanding 12 baris memperbesar durasi run, pemakaian jaringan, dan beban sumber secara drastis. Prinsip serupa diimplementasikan secara bawaan oleh tool modern seperti dbt lewat materialization incremental, yang dokumentasinya ada di docs.getdbt.com
.
Pendekatan Hybrid #
Praktik di lapangan memadukan keduanya: tabel dimensi kecil (produk, kategori) di-full load setiap malam, sementara tabel fakta besar (pesanan) berjalan incremental. Strategi per tabel, bukan per pipeline — ditentukan oleh ukuran, pola perubahan, dan kebutuhan histori masing-masing tabel.
Kedua strategi ini mengasumsikan kita menarik data dari sumber. Mulai chapter berikutnya kita masuk ke pola konkret per jenis sumber, dimulai dari yang paling umum di chapter Ingestion dari File.