Setiap commit pada table format menghasilkan satu snapshot yang tidak berubah. Properti sederhana ini berdampak besar: tabel tidak hanya menyimpan keadaan terbaru, tetapi seluruh riwayatnya. Query versi lampau — time travel — kemudian menjadi fitur bawaan, bukan tambahan. Chapter ini menguji mekanisme tersebut di Delta Lake dengan empat percobaan nyata: membuka kembali setiap versi, membaca sambil penulis lain bekerja (snapshot isolation), membersihkan berkas tidak terpakai (vacuum), dan memulihkan keadaan lama (rollback).
Tiga Penulisan, Tiga Keadaan #
Blok pertama melakukan tiga penulisan berturut-turut, lalu membuka ketiga versinya. Penulisan pertama juga menyetel delta.logRetentionDuration — konfigurasi berapa lama berkas log versi lama disimpan sebelum boleh dibersihkan:
import os
import shutil
import pyarrow as pa
from deltalake import DeltaTable, write_deltalake
BASE = "delta-versi/pesanan"
if os.path.exists(BASE):
shutil.rmtree(BASE)
skema = pa.schema(
[
pa.field("id", pa.int64()),
pa.field("nama", pa.string()),
pa.field("jumlah", pa.int64()),
]
)
def tabel(rows):
return pa.Table.from_arrays(
[
pa.array([r[0] for r in rows], pa.int64()),
pa.array([r[1] for r in rows]),
pa.array([r[2] for r in rows], pa.int64()),
],
schema=skema,
)
# tiga penulisan -> tiga versi; atur juga retensi log
konfigurasi = {"delta.logRetentionDuration": "interval 30 days"}
write_deltalake(BASE, tabel([(1, "Budi", 2), (2, "Siti", 1), (3, "Dewi", 5)]), configuration=konfigurasi)
write_deltalake(BASE, tabel([(4, "Ani", 3)]), mode="append")
write_deltalake(BASE, tabel([(5, "Rina", 2)]), mode="append")
for versi in range(3):
dt_v = DeltaTable(BASE, version=versi)
isi = dt_v.to_pyarrow_table()
nama = [b["nama"] for b in sorted(isi.to_pylist(), key=lambda r: r["id"])]
print(f"versi {versi}: {isi.num_rows} baris -> {', '.join(nama)}")
print("pertumbuhan baris: 3 -> 4 -> 5")Output yang Dihasilkan:
versi 0: 3 baris -> Budi, Siti, Dewi
versi 1: 4 baris -> Budi, Siti, Dewi, Ani
versi 2: 5 baris -> Budi, Siti, Dewi, Ani, Rina
pertumbuhan baris: 3 -> 4 -> 5Mari kita telaah baris-baris kode di atas:
- Baris 32 (
konfigurasi):configurationmenerima properti tabel Delta;interval 30 daysadalah nilai bawaan protokol untuk retensi log, ditulis eksplisit agar mudah diubah saat bereksperimen. - Baris 33-35 (tiga
write_deltalake): tiga commit menghasilkan tiga berkas log —...00000.json,...00001.json,...00002.json— dan tiga berkas Parquet. - Baris 37-41 (
for versi in range(3)): setiap iterasi membuat objekDeltaTableterikat pada satu versi, lalu membaca isi versi itu; hasilnya diurutkan berdasarkanidagar output stabil.
Snapshot Isolation #
Pembaca tidak terganggu oleh penulis baru. Objek DeltaTable menahan snapshot yang dibacanya sampai dipanggil update_incremental() atau dibuat ulang — perilaku yang dikenal sebagai snapshot isolation:
import pyarrow as pa
from deltalake import DeltaTable, write_deltalake
BASE = "delta-versi/pesanan"
skema = pa.schema(
[
pa.field("id", pa.int64()),
pa.field("nama", pa.string()),
pa.field("jumlah", pa.int64()),
]
)
def tabel(rows):
return pa.Table.from_arrays(
[
pa.array([r[0] for r in rows], pa.int64()),
pa.array([r[1] for r in rows]),
pa.array([r[2] for r in rows], pa.int64()),
],
schema=skema,
)
# pembaca menahan snapshot versi 2
pembaca_lama = DeltaTable(BASE, version=2)
print("handle versi 2:", pembaca_lama.version(), "|", pembaca_lama.to_pyarrow_table().num_rows, "baris")
# penulis lain menambahkan versi 3
write_deltalake(BASE, tabel([(6, "Joko", 4)]), mode="append")
print("handle lama tetap:", pembaca_lama.version(), "|", pembaca_lama.to_pyarrow_table().num_rows, "baris")
pembaca_baru = DeltaTable(BASE)
print("handle baru:", pembaca_baru.version(), "|", pembaca_baru.to_pyarrow_table().num_rows, "baris")Output yang Dihasilkan:
handle versi 2: 2 | 5 baris
handle lama tetap: 2 | 5 baris
handle baru: 3 | 6 barisLaporan panjang yang dibaca selama sepuluh menit tetap konsisten dari awal sampai akhir meskipun penulis lain meng-commit di tengah proses — inilah dasar reproduksibilitas: hasil query terikat ke versi, bukan ke jam dinding.
Vacuum dan Retensi Versi #
Time travel memakan tempat: berkas Parquet versi lama tidak dihapus otomatis. Vacuum membersihkan berkas yang sudah tidak dirujuk versi mana pun, dengan dua pengaman: mode dry_run hanya mendaftar tanpa menghapus, dan durasi retensi melindungi berkas yang masih terlalu baru. Blok ini juga mendemonstrasikan restore() — memulihkan keadaan versi 3 setelah sebuah overwrite:
import pyarrow as pa
from deltalake import DeltaTable, write_deltalake
BASE = "delta-versi/pesanan"
skema = pa.schema(
[
pa.field("id", pa.int64()),
pa.field("nama", pa.string()),
pa.field("jumlah", pa.int64()),
]
)
def tabel(rows):
return pa.Table.from_arrays(
[
pa.array([r[0] for r in rows], pa.int64()),
pa.array([r[1] for r in rows]),
pa.array([r[2] for r in rows], pa.int64()),
],
schema=skema,
)
# overwrite mengganti seluruh isi tabel dengan satu baris
write_deltalake(BASE, tabel([(7, "Tuti", 1)]), mode="overwrite")
dt = DeltaTable(BASE)
print("setelah overwrite: versi", dt.version(), "|", dt.to_pyarrow_table().num_rows, "baris")
# vacuum dry-run: daftar berkas yang akan dihapus, tanpa benar-benar menghapus
terlindung = dt.vacuum(dry_run=True)
paksa = dt.vacuum(retention_hours=0, dry_run=True, enforce_retention_duration=False)
print("vacuum dry-run (retensi bawaan):", len(terlindung), "berkas (semua masih terlalu baru)")
print("vacuum dry-run (retensi 0 jam):", len(paksa), "berkas parquet tidak terpakai")
# rollback: pulihkan keadaan versi 3
hasil_restore = dt.restore(target=3)
print("restore ke versi 3:", hasil_restore)
dt = DeltaTable(BASE)
isi = dt.to_pyarrow_table()
nama = [b["nama"] for b in sorted(isi.to_pylist(), key=lambda r: r["id"])]
print("setelah restore: versi", dt.version(), "|", isi.num_rows, "baris ->", ", ".join(nama))
print("konfigurasi retensi:", dt.metadata().configuration)Output yang Dihasilkan:
setelah overwrite: versi 4 | 1 baris
vacuum dry-run (retensi bawaan): 0 berkas (semua masih terlalu baru)
vacuum dry-run (retensi 0 jam): 4 berkas parquet tidak terpakai
restore ke versi 3: {'numRemovedFile': 1, 'numRestoredFile': 4}
setelah restore: versi 5 | 6 baris -> Budi, Siti, Dewi, Ani, Rina, Joko
konfigurasi retensi: {'delta.logRetentionDuration': 'interval 30 days'}Tiga angka pada output tersebut layak dibaca pelan. Pertama, vacuum dengan retensi bawaan mengembalikan nol berkas — protokol Delta melindungi berkas yang lebih muda dari tujuh hari, dan seluruh berkas kita baru berumur beberapa detik. Kedua, memaksa retensi 0 jam memperlihatkan empat berkas Parquet yang sudah tidak dirujuk versi mana pun setelah overwrite menghapus empat commit sebelumnya. Ketiga, restore menulis commit baru (versi 5) yang mengembalikan rujukan ke berkas versi 3 — rollback tidak menghapus riwayat, justru menambah satu entri lagi; baris numRestoredFile: 4 mencatat empat berkas yang dirujuk kembali. Nilai bawaan delta.logRetentionDuration adalah 30 hari dan delta.deletedFileRetentionDuration tujuh hari — keduanya bisa diubah per tabel seperti konfigurasi pada blok pertama; rincian protokolnya ada di dokumentasi Delta Lake
dan dokumentasi delta-rs
.
Use Case: Audit, Reproduksi, dan Rollback #
- Audit —
history()menyebut siapa, kapan, dan berapa baris per commit; versi menjadi bukti yang tidak bisa diringkas. - Debugging — ketika angka hari ini mencurigakan, bandingkan versi sebelum dan sesudah perubahan untuk menemukan commit yang mengubah hasil.
- Reproduksi laporan — laporan tertanggal 15 bulan lalu diterbitkan ulang dengan query versi saat laporan itu dibuat, bukan versi hari ini.
- Rollback —
restore()membatalkan kesalahan penulisan tanpa menyalin data secara manual.
Semua use case tersebut berdiri di atas satu syarat: retensi. Simpan log lebih lama dari kebutuhan audit Anda, jadwalkan vacuum setelah periode retensi terlewati, dan catat nomor versi pada setiap artefak (laporan, model) yang diterbitkan.
Dengan transaksi, schema, dan versi yang lengkap, semua bahan arsitektur lakehouse sudah ada di meja. Tersisa satu pertanyaan penutup section ini: arsitektur mana yang paling cocok untuk konteks kita — pertanyaan yang dijawab pada Chapter Memilih Arsitektur Lakehouse.