Topik ini membandingkan pemuatan penuh dan pemuatan inkremental, lalu memperkenalkan watermark sebagai penanda batas data yang sudah dimuat. Satu skrip demonstrasi dijalankan dalam dua putaran, yaitu run pertama yang memuat seluruh baris awal dan run kedua yang hanya memuat dua baris baru di atas watermark. Setelah membacanya, strategi pemuatan dapat disesuaikan dengan ukuran sumber dan kebutuhan kesegaran target.
Muat penuh dan biayanya#
Pemuatan penuh memindahkan seluruh isi sumber ke target pada setiap run, tanpa menimbang baris mana yang sudah pernah tiba. Keunggulannya jelas, yaitu logikanya paling sederhana, tidak ada kemajuan yang harus disimpan, dan keadaan akhirnya selalu cermin sumber yang lengkap. Untuk tabel dimensi kecil atau snapshot harian, kesederhanaan itu sering menjadi pilihan terbaik.
Biayanya tumbuh mengikuti ukuran sumber, bukan ukuran perubahannya. Sumber dengan puluhan juta baris yang hanya bertambah ribuan per hari tetap dipindahkan utuh pada setiap run, sehingga jendela pemrosesan membengkak dan biaya komputasinya terus berjalan. Pemuatan inkremental membalasnya dengan memindahkan hanya baris yang belum pernah dimuat, sebagaimana dirangkum tabel berikut.
| Aspek | Pemuatan penuh | Pemuatan inkremental |
|---|---|---|
| Data yang dipindah | seluruh isi sumber | hanya baris di atas watermark |
| Biaya per run | tumbuh mengikuti ukuran sumber | relatif tetap, mengikuti data baru |
| Logika | paling sederhana | butuh penyimpanan watermark |
| Risiko utama | lambat pada sumber besar | baris terlambat dapat terlewat |
| Cocok untuk | tabel kecil, snapshot harian | sumber besar dengan kolom yang bertambah monoton |
Pilihan kedua menimbulkan soal baru, yaitu bagaimana pipeline mengingat batas terakhir yang sudah dimuat. Jawabannya adalah watermark.
Watermark sebagai penanda kemajuan#
Watermark adalah nilai terakhir dari kolom sumber yang bertambah monoton, misalnya tanggal kejadian atau ID berurutan, yang menandai batas data yang sudah dimuat pada run sebelumnya. Setiap run menanyakan sumber dengan syarat kolom tersebut lebih besar dari watermark, memuat barisan yang lolos, lalu menaikkan watermark ke nilai maksimum yang baru dimuat. Karena format tanggal ISO tersusun sama dengan urutan waktunya, pembandingan teks sederhana sudah cukup untuk kolom tanggal.
Watermark harus disimpan permanen di luar memori proses, misalnya pada berkas kecil, tabel metadata, atau state milik orkestrator. Watermark yang hilang membuat run berikutnya mengulang seluruh masa lalu atau, lebih buruk, melompat ke depan dan melewatkan baris. Kolom yang dipilih juga harus benar-benar bertambah monoton, karena kolom yang dapat berubah nilainya di sumber membuat baris yang sudah lewat ikut berubah tanpa pernah dimuat ulang.
Demonstrasi dua putaran dalam satu skrip#
Skrip berikut menjalankan dua putaran muat inkremental di dalam satu proses, lengkap dengan penyiapan sumber dan penghapusan artefak lama, sehingga seluruh keadaannya dapat direproduksi dari nol. Run pertama menemui watermark yang kosong dan memuat empat baris awal. Setelah itu sumber bertambah dua baris bertanggal 2026-10-04, dan run kedua hanya menarik dua baris itu.
"""Pemuatan inkremental dengan watermark tanggal pada berkas sumber CSV."""
import csv
from pathlib import Path
import duckdb
SUMBER = Path("pinjaman-sumber.csv")
WATERMARK = Path("watermark.txt")
BERKAS_DB = "pinjaman-inkremental.duckdb"
BARISAN_AWAL = [
("P001", "2026-10-01", 2),
("P002", "2026-10-01", 1),
("P003", "2026-10-02", 3),
("P004", "2026-10-03", 1),
]
BARISAN_BARU = [
("P005", "2026-10-04", 2),
("P006", "2026-10-04", 1),
]
KOLONOM = ["id", "tanggal", "jumlah_buku"]
def tulis_sumber(barisan: list[tuple]) -> None:
"""Tulis berkas sumber CSV dari daftar baris yang diberikan."""
with SUMBER.open("w", newline="", encoding="utf-8") as berkas:
penulis = csv.writer(berkas)
penulis.writerow(KOLONOM)
penulis.writerows(barisan)
def baca_barisan() -> list[tuple]:
"""Baca seluruh baris data sumber tanpa baris kepala kolomnya."""
with SUMBER.open(newline="", encoding="utf-8") as berkas:
pembaca = csv.reader(berkas)
next(pembaca)
return [tuple(b) for b in pembaca]
def muat_inkremental() -> None:
"""Muat hanya baris di atas watermark, lalu perbarui watermarknya."""
con = duckdb.connect(BERKAS_DB)
con.execute(
"CREATE TABLE IF NOT EXISTS peminjaman "
"(id VARCHAR, tanggal VARCHAR, jumlah_buku INTEGER)"
)
watermark = WATERMARK.read_text(encoding="utf-8").strip() if WATERMARK.exists() else ""
barisan = [b for b in baca_barisan() if b[1] > watermark]
con.executemany("INSERT INTO peminjaman VALUES (?, ?, ?)", barisan)
watermark_baru = max(b[1] for b in barisan) if barisan else watermark
WATERMARK.write_text(watermark_baru, encoding="utf-8")
jumlah = con.execute("SELECT count(*) FROM peminjaman").fetchone()[0]
con.close()
print(f" dimuat {len(barisan)} baris (watermark: {watermark or 'kosong'} -> {watermark_baru})")
print(f" target kini berisi {jumlah} baris")
# persiapan: hapus seluruh artefak agar demo selalu bermula dari keadaan nol
for artefak in (SUMBER, WATERMARK):
artefak.unlink(missing_ok=True)
Path(BERKAS_DB).unlink(missing_ok=True)
tulis_sumber(BARISAN_AWAL)
print("Run pertama (sumber berisi 4 baris):")
muat_inkremental()
# sumber bertambah dua baris kejadian pada 2026-10-04
with SUMBER.open("a", newline="", encoding="utf-8") as berkas:
csv.writer(berkas).writerows(BARISAN_BARU)
print("Run kedua (sumber bertambah 2 baris):")
muat_inkremental()Run pertama (sumber berisi 4 baris):
dimuat 4 baris (watermark: kosong -> 2026-10-03)
target kini berisi 4 baris
Run kedua (sumber bertambah 2 baris):
dimuat 2 baris (watermark: 2026-10-03 -> 2026-10-04)
target kini berisi 6 barisRun kedua memperlihatkan sifat inkrementalnya, yaitu dua baris baru dipindahkan dan empat baris lama dibiarkan di tempatnya, sehingga target berakhir dengan enam baris yang lengkap. Skrip menyimpan kemajuannya pada berkas watermark.txt, dan dua baris tambahan yang ditulis skrip menggantikan peran sistem sumber yang tumbuh. Pada pekerjaan sungguhan, penulis baris baru itu adalah sistem sumber atau hasil ekstraksi pada run berikutnya, sedangkan penyimpanan dan pembacaan watermarknya berjalan sama persis.
Kapan penuh tetap dipilih#
Kompleksitas inkremental hanya terbayar bila sumbernya cukup besar. Tabel sumber dengan jumlah baris puluhan ribu yang dimuat harian biasanya lebih murah dimuat penuh daripada dipelihara watermarknya, karena seluruh run berlangsung dalam hitungan detik dan keadaan akhirnya selalu dijamin benar. Inkremental baru memberi manfaat nyata ketika biaya menyalin sumber yang tidak berubah itu terasa, misalnya jendela pemrosesan mulai mendekati SLA atau biaya komputasinya menonjol pada tagihan.
Kebutuhan membangun ulang juga menentukan pilihan. Pemuatan penuh memperbaiki dirinya sendiri setiap run, sedangkan pipeline inkremental yang targetnya rusak perlu diisi ulang dari nol, yang berarti run penuh tetap harus disiapkan sebagai prosedur pemulihan. Karena itu banyak pipeline memakai keduanya sekaligus, yaitu inkremental untuk run harian dan penuh sebagai jaring pengaman mingguan atau saat kehilangan data terdeteksi.
Kesalahan umum#
- Menyimpan watermark di memori proses. Penyebabnya, variabel global terasa cukup untuk skrip kecil. Akibatnya, setiap restart menghapus kemajuan, sehingga run berikutnya memuat ulang seluruh masa lalu atau kehilangan batasnya. Perbaikannya, tulis watermark ke berkas atau tabel metadata setiap kali kemajuannya berubah.
- Memilih kolom yang tidak monoton sebagai watermark. Penyebabnya, kolom tanggal pembaruan di sumber dapat diisi mundur saat koreksi. Akibatnya, baris yang sudah lewat dari watermark berubah tanpa pernah dimuat ulang. Perbaikannya, gunakan kolom yang pasti naik seperti tanggal kejadian atau ID berurutan, dan serahkan koreksi lama pada penanganan baris terlambat.
- Melebarkan syarat menjadi sama dengan atau lebih besar dari watermark. Penyebabnya, kekhawatiran melewatkan baris pada tanggal batas. Akibatnya, baris bertanggal watermark disisipkan ulang pada setiap run dan target menggandakannya. Perbaikannya, pertahankan syarat lebih besar dari, atau pasangkan syarat yang lebih lebar itu dengan pola muat idempoten seperti upsert.
Ringkasan#
- Pemuatan penuh selalu benar tetapi biayanya tumbuh mengikuti ukuran sumber, sedangkan pemuatan inkremental hanya memindahkan baris di atas watermark.
- Watermark adalah nilai terakhir kolom monoton yang sudah dimuat, disimpan permanen di luar memori proses, dan diperbarui pada setiap run.
- Baris yang tiba terlambat di bawah watermark terlewat oleh penyaringan inkremental, sehingga strategi pemuatan yang menutupinya tetap diperlukan.
Referensi#
- Fundamentals of Data Engineering — O’Reilly — Buku Reis dan Housley yang membahas pemrosesan inkremental di dalam siklus hidup data.
- INSERT statements — DuckDB — Dokumentasi resmi pernyataan INSERT DuckDB yang dipakai tahap muat pada demo.