Pemuatan inkremental

Membandingkan pemuatan penuh dan inkremental beserta watermark sebagai penandanya.

Daftar isi

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.

AspekPemuatan penuhPemuatan inkremental
Data yang dipindahseluruh isi sumberhanya baris di atas watermark
Biaya per runtumbuh mengikuti ukuran sumberrelatif tetap, mengikuti data baru
Logikapaling sederhanabutuh penyimpanan watermark
Risiko utamalambat pada sumber besarbaris terlambat dapat terlewat
Cocok untuktabel kecil, snapshot hariansumber 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.

python muat-inkremental.py
"""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()
text Keluaran
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 baris

Run 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#

  1. 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.
  2. 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.
  3. 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#