Setelah data bersih, pekerjaan belum selesai: hasil transformasi harus disimpan agar bisa diquery oleh analis dan dashboard. Cara menyimpannya menentukan tiga hal sekaligus — kesegaran data di tujuan, durasi proses pemuatan, dan apa yang terjadi ketika pipeline dijalankan ulang. Memuat 20.000 baris dengan teknik yang salah bisa memakan waktu puluhan kali lebih lama daripada yang benar. Chapter ini membahas empat pola pemuatan yang umum, transaksi sebagai jaminan keutuhan saat load, lalu uji nyata perbandingan kecepatan di SQLite.
Empat Pola Pemuatan #
| Pola | Cara kerja | Kapan dipakai |
|---|---|---|
| Load penuh | seluruh isi tabel tujuan diganti setiap proses | tabel kecil dengan sumber yang selalu tersedia lengkap |
| Append | baris baru ditambahkan di belakang baris lama | data historis yang terus bertambah, mis. fakta penjualan |
| Truncate-and-load | tabel tujuan dikosongkan, lalu diisi ulang | dimensi kecil yang isinya memang dibangun ulang dari nol |
| Upsert | baris baru di-insert; kunci yang sudah ada di-update | dimensi pelanggan/produk yang isinya bisa berubah |
Load penuh dan truncate-and-load sebenarnya saudara: keduanya membangun tabel tujuan dari awal, sehingga hasilnya selalu konsisten dengan sumber. Append paling hemat — hanya baris baru yang ditulis — tetapi menuntut mekanisme penanda supaya baris lama tidak dimuat dua kali. Upsert adalah pola paling toleran terhadap perubahan: pembaruan domisili pelanggan cukup menimpa baris lama tanpa membangun ulang tabel.
Konsekuensi ketiganya berbeda pada biaya dan risiko. Full load selalu konsisten dengan sumber, tetapi membayar pindai penuh setiap proses; append paling murah, namun menuntut penanda batas (watermark) supaya proses berikutnya tahu mulai dari mana; upsert paling kompleks karena harus menetapkan kunci dan aturan penimpaan. Watermark dibahas lebih lanjut pada chapter Incremental Transformation.
Uji Nyata: Satu per Satu Lawan executemany #
Pada database relasional, satu baris INSERT dijalankan sebagai satu statement. Python menyediakan dua cara mengirim banyak baris: memanggil execute() berulang kali, atau mengirim seluruh batch dalam satu panggilan executemany(). Ditambah satu dimensi lagi — apakah COMMIT dijalankan per baris atau sekali di akhir — kita mendapatkan tiga kombinasi berikut.
import sqlite3
import time
from pathlib import Path
DB = Path("toko_eksperimen.db")
if DB.exists():
DB.unlink()
N = 200_000
data = [(i, f"P{i:06d}", i * 1000) for i in range(1, N + 1)]
koneksi = sqlite3.connect(DB)
def siapkan_tabel():
koneksi.execute("DROP TABLE IF EXISTS eksperimen")
koneksi.execute(
"CREATE TABLE eksperimen (id INTEGER PRIMARY KEY, nama TEXT, nilai INTEGER)")
# Cara 1: INSERT satu per satu, COMMIT pada setiap baris
siapkan_tabel()
mulai = time.perf_counter()
for baris in data:
koneksi.execute("INSERT INTO eksperimen VALUES (?, ?, ?)", baris)
koneksi.commit()
cara1 = time.perf_counter() - mulai
# Cara 2: INSERT satu per satu, seluruh baris dalam satu transaksi
siapkan_tabel()
mulai = time.perf_counter()
with koneksi:
for baris in data:
koneksi.execute("INSERT INTO eksperimen VALUES (?, ?, ?)", baris)
cara2 = time.perf_counter() - mulai
# Cara 3: executemany, seluruh baris dalam satu transaksi
siapkan_tabel()
mulai = time.perf_counter()
with koneksi:
koneksi.executemany("INSERT INTO eksperimen VALUES (?, ?, ?)", data)
cara3 = time.perf_counter() - mulai
jumlah = koneksi.execute("SELECT COUNT(*) FROM eksperimen").fetchone()[0]
print(f"Cara 1 (commit per baris) : {cara1:6.3f} detik")
print(f"Cara 2 (satu transaksi) : {cara2:6.3f} detik")
print(f"Cara 3 (executemany) : {cara3:6.3f} detik")
print("baris tersimpan :", jumlah)
koneksi.close()
DB.unlink()Output yang Dihasilkan:
Cara 1 (commit per baris) : 6.157 detik
Cara 2 (satu transaksi) : 0.191 detik
Cara 3 (executemany) : 0.104 detik
baris tersimpan : 200000Mari kita telaah baris-baris kode di atas:
- Baris 5-7 (bersihkan sisa): berkas database lama dihapus agar pengukuran tidak terganggu data terdahulu.
- Baris 9-11 (persiapan):
databerisi 200.000 tuple — barisnamadannilaidibangkitkan dari pola agar contoh tetap realistis tanpa mengetik 200.000 baris literal. - Baris 14-17 (
siapkan_tabel): tabel dijatuhkan lalu dibuat ulang sebelum setiap pengukuran, sehingga ketiga cara menghadapi kondisi awal yang sama. - Baris 22-26 (cara 1): setiap
INSERTlangsung diikuticommit(); SQLite harus menulis hasil ke berkas untuk setiap baris. - Baris 30-34 (cara 2): blok
with koneksimembuka satu transaksi untuk seluruh 200.000 statement;COMMITterjadi sekali saat blok selesai. - Baris 38-41 (cara 3):
executemanymengirim seluruh baris dalam satu panggilan Python ke C — perulangan pemanggilan per baris hilang dari sisi Python.
Ringkasan ketiganya:
| Cara | COMMIT | Pengiriman | Durasi |
|---|---|---|---|
| 1 | per baris | execute berulang | paling lambat |
| 2 | sekali di akhir | execute berulang | puluhan kali lebih cepat |
| 3 | sekali di akhir | executemany | tercepat |
Transaksi sebagai Jaminan Keutuhan #
Perbedaan cara 1 dan cara 2 pada uji di atas bukan sekadar soal kecepatan. Transaksi memberi jaminan all-or-nothing: bila proses pemuatan terhenti di tengah — listrik terputus, disk penuh, kode error — seluruh perubahan sejak transaksi dibuka dibatalkan, sehingga tabel tujuan tidak berisi separuh batch. Pada skrip di atas, blok with koneksi: adalah bentuk singkatnya: bila blok selesai normal, COMMIT dijalankan; bila terjadi galat, ROLLBACK dijalankan otomatis. Tanpa transaksi, tabel tujuan bisa berisi setengah data dan konsumen laporan membacanya seolah lengkap.
Kombinasi yang dianjurkan untuk pipeline produksi: executemany dalam blok transaksi, dipanggil per batch berukuran ribuan baris — bukan satu transaksi raksasa untuk jutaan baris, agar penggunaan memori dan waktu kunci (lock) tetap terkendali. Detail API pemuatan tersedia di dokumentasi sqlite3
.
Selama empat chapter terakhir, seluruh logika transformasi kita ditulis sebagai perulangan Python eksplisit. Pola itu jelas dan mudah dibedah, tetapi Python menyediakan dua gaya penulisan yang lebih ringkas — fungsi murni yang dirangkai, atau pustaka pandas. Chapter berikutnya, Transformasi dengan Python, membandingkan keduanya.