Topik ini membahas strategi pemuatan ke tabel target, yaitu truncate-reload, upsert, dan penanganan data terlambat. Ketiganya dibandingkan dari sisi data yang dibutuhkan, biaya per run, dan risikonya, kemudian satu skrip demonstrasi menjalankan ketiga skenario pada tabel target yang sama. Setelah membacanya, strategi pemuatan dapat dipilih dengan alasan yang terukur, bukan karena kebiasaan.
Truncate-reload#
Truncate-reload mengosongkan tabel target sepenuhnya, lalu mengisinya ulang dari data sumber pada setiap run. Pernyataan yang lazim dipakai adalah TRUNCATE atau DELETE tanpa syarat, diikuti penyisipan penuh dari staging, yaitu tabel perantara yang memuat data yang sudah divalidasi. Hasil akhirnya selalu cermin penuh sumber, sehingga duplikat mustahil terjadi dan logikanya paling mudah ditelusuri.
Strategi ini cocok untuk tabel dimensi kecil, snapshot harian, dan target yang boleh dibangun ulang sepenuhnya dalam hitungan menit. Biayanya berbanding lurus dengan ukuran target, sehingga pada tabel besar yang hanya berubah sedikit, strategi ini membuang sebagian besar pekerjaannya untuk menulis ulang baris yang tidak berubah. Risiko yang lebih berbahaya ada pada kegagalan di tengah run, karena target yang sudah dikosongkan menunggu pengisian, dan pembaca yang datang tepat pada momen itu melihat tabel kosong.
Ada dua cara menutup celah itu, dan keduanya menyasar sifat atomiknya. Transaksi membuat pengosongan dan pengisian berjalan sebagai satu kesatuan yang berhasil semuanya atau tidak sama sekali, sedangkan pola tabel baru menulis hasil ke tabel pengganti lalu menukarnya dalam satu langkah. Dengan salah satu keduanya, pembaca tidak pernah menemui tabel yang setengah jadi.
Upsert#
Upsert (update or insert) menyisipkan baris baru dan memperbarui baris lama dalam satu pernyataan, sehingga hanya baris yang berubah atau baru yang menyentuh target. DuckDB dan PostgreSQL menyediakannya sebagai INSERT ... ON CONFLICT (kolom) DO UPDATE SET, sedangkan DuckDB juga menawarkan bentuk pendeknya INSERT OR REPLACE. Nilai dari tabel masukan diambil lewat alias EXCLUDED, misalnya jumlah_buku = EXCLUDED.jumlah_buku, yang merujuk baris yang sedang mencoba masuk.
Syaratnya tegas, yaitu kolom konflik harus dilindungi PRIMARY KEY atau UNIQUE, karena tanpa kunci itu basis data tidak punya dasar memutuskan baris mana yang digantikan. Data masukannya juga harus bebas kunci kembar, sebab dua baris staging dengan kunci sama berarti memperbarui baris target yang sama dua kali dalam satu pernyataan, dan basis data menolaknya. Karena baris yang sama ditimpa alih-alih ditumpuk, upsert sekaligus membuat run ulang menjadi aman.
Upsert paling cocok untuk target besar yang perubahannya terbatas, sebagaimana batch inkremental yang hanya membawa sebagian kecil baris berubah. Pada target semacam itu, menulis ulang seluruh tabel demi puluhan baris yang berubah adalah pemborosan yang mudah dihindari. Biaya yang dibayar adalah disiplin kunci, yaitu kunci konflik yang stabil dan data masukan yang bebas kunci kembar.
Data terlambat#
Data terlambat (late arriving data) adalah baris yang tanggal kejadiannya lebih lama dari watermark pemrosesan karena baru tiba di sumber setelah jendelanya lewat. Contohnya, koreksi peminjaman bertanggal 2026-10-01 yang baru diketik petugas pada 2026-10-05, sementara pipeline sudah berjalan melewati tanggal tersebut. Penyaringan inkremental yang hanya menarik baris di atas watermark tidak akan pernah melihat baris itu, sehingga koreksinya tidak pernah tiba di target.
Tiga penanganan melengkapi satu sama lain. Upsert berbasis kunci bisnis memperbarui baris lama tanpa peduli urutan kedatangan, sehingga baris terlambat cukup dimuat begitu sampai di staging. Jendela proses ulang menarik ulang beberapa hari terakhir pada setiap run, sekitar tiga sampai tujuh hari, untuk menangkap koreksi yang datang dalam jendela itu. Pemuatan penuh berkala berperan sebagai rekonsiliasi yang menyetarakan target dengan sumber ketika keduanya disyaki menyimpang.
Demonstrasi ketiga skenario#
Skrip berikut menjalankan truncate-reload, upsert batch inkremental, lalu pemrosesan baris terlambat pada satu tabel peminjaman yang sama, dan mencetak isi tabel setelah setiap langkah. Staging lengkapnya berisi tiga baris, batch inkrementalnya mengoreksi P003 dan menambah P004, sedangkan baris terlambatnya mengoreksi P002 dengan tanggal kejadian yang lama. Basis datanya dihapus di awal skrip, sehingga demo selalu bermula dari keadaan nol.
"""Perbandingan truncate-reload, upsert, dan baris terlambat pada target yang sama."""
from pathlib import Path
import duckdb
BERKAS_DB = "sasaran.duckdb"
STAGING_LENGKAP = [
("P001", "2026-10-01", 2),
("P002", "2026-10-01", 1),
("P003", "2026-10-02", 3),
]
BATCH_INKREMEN = [
("P003", "2026-10-02", 5),
("P004", "2026-10-03", 1),
]
TERLAMBAT = ("P002", "2026-10-01", 4)
UPSERT = (
"INSERT INTO peminjaman VALUES (?, ?, ?) "
"ON CONFLICT (id) DO UPDATE SET "
"tanggal = EXCLUDED.tanggal, jumlah_buku = EXCLUDED.jumlah_buku"
)
def tampil(con: duckdb.DuckDBPyConnection, judul: str) -> None:
"""Cetak isi tabel target setelah satu langkah pemuatan."""
print(judul)
for id_, tanggal, jumlah in con.execute(
"SELECT id, tanggal, jumlah_buku FROM peminjaman ORDER BY id"
).fetchall():
print(f" {id_} {tanggal} {jumlah}")
# persiapan: hapus basis data lama agar demo selalu bermula dari keadaan nol
Path(BERKAS_DB).unlink(missing_ok=True)
con = duckdb.connect(BERKAS_DB)
con.execute(
"CREATE TABLE peminjaman "
"(id VARCHAR PRIMARY KEY, tanggal VARCHAR, jumlah_buku INTEGER)"
)
# strategi 1: truncate-reload, seluruh isi target ditulis ulang dari staging
con.execute("TRUNCATE peminjaman")
con.executemany("INSERT INTO peminjaman VALUES (?, ?, ?)", STAGING_LENGKAP)
tampil(con, "Setelah truncate-reload dari staging lengkap:")
# strategi 2: upsert, cukup batch baris yang berubah atau baru
con.executemany(UPSERT, BATCH_INKREMEN)
tampil(con, "Setelah upsert batch inkremental (P003 dikoreksi, P004 baru):")
# data terlambat: koreksi P002 bertanggal lama tetap memperbarui barisnya
con.executemany(UPSERT, [TERLAMBAT])
tampil(con, "Setelah baris terlambat untuk P002 diproses:")
con.close()Setelah truncate-reload dari staging lengkap:
P001 2026-10-01 2
P002 2026-10-01 1
P003 2026-10-02 3
Setelah upsert batch inkremental (P003 dikoreksi, P004 baru):
P001 2026-10-01 2
P002 2026-10-01 1
P003 2026-10-02 5
P004 2026-10-03 1
Setelah baris terlambat untuk P002 diproses:
P001 2026-10-01 2
P002 2026-10-01 4
P003 2026-10-02 5
P004 2026-10-03 1Langkah pertama menulis ulang seluruh target dari staging lengkap, dan ketiga barisnya menandakan hasil yang benar. Langkah kedua hanya menyentuh dua baris batch, yaitu memperbarui P003 dari 3 menjadi 5 dan menyisipkan P004, tanpa menulis ulang P001 dan P002. Langkah ketiga memperlihatkan perlakuan upsert pada data terlambat, karena koreksi P002 bertanggal 2026-10-01 tetap memperbarui baris yang sudah ada menjadi 4 meskipun tanggalnya paling tua di tabel. Tanpa pola ini, koreksi semacam itu harus menunggu pemuatan penuh berikutnya.
Kesalahan umum#
- Truncate-reload tanpa transaksi. Penyebabnya, pernyataan
TRUNCATEdan penyisipan dianggap selalu berjalan berdampingan. Akibatnya, kegagalan di antara keduanya meninggalkan target kosong sampai run berikutnya. Perbaikannya, bungkus keduanya dalam satu transaksi atau muat ke tabel baru lalu tukar dalam satu langkah. - Staging dengan kunci kembar untuk upsert. Penyebabnya, batch dari sumber tidak dideuplikasi lebih dulu. Akibatnya, pernyataan
ON CONFLICTmenolak berjalan karena baris target yang sama diperbarui dua kali dalam satu pernyataan. Perbaikannya, ambil satu baris per kunci dari staging sebelum upsert, biasanya yang terbaru. - Memakai kunci teknis sumber sebagai kunci konflik. Penyebabnya, ID baris sumber terasa paling mudah dipakai. Akibatnya, koreksi yang datang dengan ID baris baru menghasilkan duplikat logis pada target. Perbaikannya, tetapkan kunci bisnis yang stabil, misalnya kombinasi id peminjaman dan tanggalnya, sebagai kunci konflik upsert.
Ringkasan#
- Truncate-reload menulis ulang seluruh target dan cocok untuk tabel kecil, dengan syarat pengosongan dan pengisian berjalan atomik.
- Upsert memperbarui baris lama dan menyisipkan baris baru dalam satu pernyataan berbasis kunci unik, sehingga hemat untuk target besar yang perubahannya terbatas.
- Baris terlambat ditangani upsert berbasis kunci bisnis, jendela proses ulang beberapa hari, dan pemuatan penuh berkala sebagai rekonsiliasi.
Referensi#
- INSERT statements — DuckDB — Dokumentasi resmi DuckDB untuk INSERT beserta ON CONFLICT DO UPDATE dan INSERT OR REPLACE.
- INSERT — PostgreSQL — Dokumentasi resmi PostgreSQL untuk pernyataan INSERT dan klausa ON CONFLICT.