Data pelanggan TokoKita menumpuk dari tahun ke tahun: pesanan tahun 2019 masih tersimpan lengkap dengan email dan nomor telepon “siapa tahu nanti berguna”. Setiap tabel lama memperbesar dampak bila terjadi kebocoran data, dan setiap kolom pribadi yang tidak terpakai adalah kewajiban yang tidak memberi nilai. Regulasi kini menuntut disiplin: di Indonesia, UU PDP (Undang-Undang Pelindungan Data Pribadi, UU No. 27 Tahun 2022) membatasi pemrosesan data pribadi hanya untuk tujuan yang jelas, sementara GDPR di Uni Eropa menuntut hal serupa bagi pemrosesan data warga Eropa. Bab ini membahas dua sisi disiplin tersebut — mengumpulkan sesedikit mungkin (privacy) dan menyimpan sesingkat mungkin (retention) — lalu mengeksekusi keduanya sebagai kode yang bisa dijalankan dan diaudit.
Data Minimization: Kumpulkan Seminimal yang Perlu #
Data minimization berarti setiap kolom dan setiap tingkat detail harus punya alasan eksplisit untuk dikumpulkan. Prinsip ini paling murah diterapkan saat desain, dan paling mahal setelah data menyebar ke sepuluh tabel turunan. Daftar berikut adalah pemeriksaan yang kita jalankan sebelum tabel baru dibuat di TokoKita:
- Hapus kolom yang jawabannya “mungkin nanti berguna” — kebutuhan bisa diambil saat benar-benar muncul.
- Pilih granularitas paling kasar yang memadai: rentang usia cukup untuk segmentasi promosi, tanpa tanggal lahir lengkap.
- Data pribadi hanya hidup di tabel sumbernya; tabel turunan memakai
pelanggan_idsebagai pseudonym, bukan menyalin email. - Kumpulkan persetujuan (consent) bersama datanya, karena tujuan pemrosesan menjadi tidak sah saat consent dicabut.
- Tetapkan umur simpan (retention) sejak desain, bukan setelah data menumpuk bertahun-tahun.
Anonymization dan Pseudonymization #
Dua teknik ini sering dianggap sama padahal berbeda secara hukum dan teknis. Anonymization mengubah data sehingga identitas asal tidak dapat dipulihkan lagi; pseudonymization mengganti identitas dengan pengganti (pseudonym) tetapi pemetaan kembali masih ada di tangan pihak tertentu. Karena pemetaan itu tersisa, data hasil pseudonymization tetap dihitung sebagai data pribadi menurut GDPR maupun UU PDP.
| Aspek | Anonymization | Pseudonymization |
|---|---|---|
| Contoh hasil | rentang usia 25-34, kode pos | [email protected] menjadi pelanggan_8f3a |
| Dapat dipulihkan | tidak, identitas asal tidak tersisa | ya, oleh pemegang tabel pemetaan |
| Status hukum | bukan lagi data pribadi | tetap data pribadi |
| Cocok untuk | statistik agregat, dataset publik | join antar tabel internal, debugging |
Teknik penggantian nilainya (masking, tokenization) sudah kita bedah di Chapter Masking dan Tokenization. Kolom berisiko tinggi juga sebaiknya terenkripsi seperti pola pada Chapter Encryption at Rest dan In Transit, sehingga akses fisik ke penyimpanan tidak serta-merta membuka isinya.
Retention Policy sebagai Kode #
Retention policy menyatakan berapa lama tiap dataset boleh hidup dan apa yang terjadi setelahnya. Menuliskannya sebagai YAML memberi keuntungan: aturan bisa di-review seperti kode, divalidasi otomatis, dan perubahannya tercatat di version control. Berikut policy TokoKita — tabel transaksi dihapus setelah 90 hari, log layanan lebih pendek lagi, sementara rekaman audit justru diarsipkan lama karena regulasi menuntut jejak panjang:
# Retention policy TokoKita: umur maksimum tiap dataset sebelum diproses.
datasets:
pesanan:
retention_days: 90
action: delete
logs_layanan:
retention_days: 30
action: delete
audit:
retention_days: 1825
action: archivePerhatikan dua aksi yang berbeda: delete menghapus permanen, archive memindahkan ke penyimpanan murah karena data masih wajib ada. Batas retention sebaiknya mengikuti klasifikasi dari Chapter Klasifikasi Data — semakin sensitif datanya, semakin pendek umurnya, dan semakin tegas penghapusannya.
Menjalankan Enforcement Retensi #
Policy tidak berguna tanpa eksekusi. Skrip berikut memvalidasi policy YAML, memindai partisi tanggal=..., menghitung umur tiap partisi terhadap tanggal evaluasi, lalu mencantumkan rencana eksekusi (dry-run) sebelum benar-benar menghapus dan mengarsipkan:
"""Terapkan retention policy: validasi YAML, pindai partisi, proses yang kedaluwarsa."""
import shutil
from datetime import date
from pathlib import Path
import yaml
TANGGAL_EVALUASI = date(2026, 8, 1) # tanggal evaluasi dikunci agar hasil demo konsisten
DASAR = Path(__file__).parent / "data_retensi"
ARSIP = Path(__file__).parent / "arsip_retensi"
KEBIJAKAN = Path(__file__).parent / "retensi_policy.yaml"
def siapkan_data_aset():
"""Bangun ulang partisi contoh agar demo selalu bisa diulang."""
for direktori in (DASAR, ARSIP):
if direktori.exists():
shutil.rmtree(direktori)
contoh = {
"pesanan": ["2026-01-15", "2026-05-01", "2026-05-10", "2026-07-30"],
"logs_layanan": ["2026-06-01", "2026-07-05"],
"audit": ["2020-03-10"],
}
for tabel, tanggalan in contoh.items():
for tanggal in tanggalan:
partisi = DASAR / tabel / f"tanggal={tanggal}"
partisi.mkdir(parents=True)
(partisi / "part-00000.parquet").write_bytes(b"PARQUET-DEMO")
def muat_kebijakan(path):
"""Muat dan validasi retention policy dari YAML."""
aturan_set = yaml.safe_load(path.read_text())["datasets"]
for nama, aturan in aturan_set.items():
hari = aturan.get("retention_days")
if not isinstance(hari, int) or hari <= 0:
raise ValueError(f"{nama}: retention_days harus bilangan bulat positif")
if aturan.get("action") not in ("delete", "archive"):
raise ValueError(f"{nama}: action harus 'delete' atau 'archive'")
return aturan_set
def pindai_partisi():
"""Kumpulkan semua partisi tanggal=... beserta tanggalnya."""
for tabel in sorted(p for p in DASAR.iterdir() if p.is_dir()):
for partisi in sorted(tabel.glob("tanggal=*")):
yield tabel.name, partisi, date.fromisoformat(partisi.name.split("=")[1])
def jalankan():
siapkan_data_aset()
kebijakan = muat_kebijakan(KEBIJAKAN)
semua = list(pindai_partisi())
print(f"Tanggal evaluasi : {TANGGAL_EVALUASI.isoformat()}")
print(f"Partisi ditemukan: {len(semua)}")
print("=== Rencana eksekusi (dry-run) ===")
kedaluwarsa = []
for tabel, partisi, tanggal in semua:
umur = (TANGGAL_EVALUASI - tanggal).days
aturan = kebijakan[tabel]
if umur > aturan["retention_days"]:
aksi = aturan["action"].upper()
kedaluwarsa.append((partisi, aturan["action"]))
print(f" {partisi.relative_to(DASAR)} umur {umur:4d} hari -> {aksi}")
else:
print(f" {partisi.relative_to(DASAR)} umur {umur:4d} hari -> simpan")
print("=== Eksekusi ===")
for partisi, aksi in kedaluwarsa:
if aksi == "delete":
shutil.rmtree(partisi)
else:
tujuan = ARSIP / partisi.parent.name / partisi.name
tujuan.parent.mkdir(parents=True, exist_ok=True)
shutil.move(str(partisi), tujuan)
sisa = list(pindai_partisi())
hapus = sum(1 for _, aksi in kedaluwarsa if aksi == "delete")
arsip = sum(1 for _, aksi in kedaluwarsa if aksi == "archive")
print(f"Sebelum: {len(semua)} partisi")
print(f"Sesudah: {len(sisa)} partisi aktif "
f"({hapus} dihapus, {arsip} diarsipkan)")
if __name__ == "__main__":
jalankan()Output yang Dihasilkan:
Tanggal evaluasi : 2026-08-01
Partisi ditemukan: 7
=== Rencana eksekusi (dry-run) ===
audit/tanggal=2020-03-10 umur 2335 hari -> ARCHIVE
logs_layanan/tanggal=2026-06-01 umur 61 hari -> DELETE
logs_layanan/tanggal=2026-07-05 umur 27 hari -> simpan
pesanan/tanggal=2026-01-15 umur 198 hari -> DELETE
pesanan/tanggal=2026-05-01 umur 92 hari -> DELETE
pesanan/tanggal=2026-05-10 umur 83 hari -> simpan
pesanan/tanggal=2026-07-30 umur 2 hari -> simpan
=== Eksekusi ===
Sebelum: 7 partisi
Sesudah: 3 partisi aktif (3 dihapus, 1 diarsipkan)Mari kita telaah baris-baris kode di atas:
- Baris 14-28 (
siapkan_data_aset): membangun ulang tujuh partisi contoh dari awal sehingga demo selalu bisa diulang dengan hasil yang sama — di produksi, bagian ini digantikan oleh data asli. - Baris 31-40 (
muat_kebijakan): memuat YAML lewatyaml.safe_load, lalu menolak policy yang cacat —retention_daysharus bilangan bulat positif danactionhanya bolehdeleteatauarchive. Policy yang salah gagal keras sebelum menyentuh data. - Baris 43-47 (
pindai_partisi): generator ini berjalan atas direktori tabel, mencocokkan polatanggal=*, dan mengurai tanggalnya dengandate.fromisoformat— pola partisi tanggal inilah yang membuat enforcement retensi cukup murah. - Baris 58-66 (fase dry-run): menghitung umur tiap partisi terhadap
TANGGAL_EVALUASI, lalu hanya mencetak rencana — partisi kedaluwarsa dikumpulkan ke daftarkedaluwarsatanpa dihapus dulu. - Baris 68-80 (fase eksekusi dan laporan): baru pada fase ini
shutil.rmtreemenghapus partisi danshutil.movememindahkannya ke direktori arsip terpisah, ditutup laporan sebelum/sesudah — tujuh menjadi tiga partisi aktif, tiga dihapus, satu diarsipkan.
Partisi pesanan/tanggal=2026-05-01 berumur 92 hari — hanya dua hari melewati batas — tetap dihapus; retention yang tegas tidak mengenal “hampir”. Sebaliknya audit/tanggal=2020-03-10 berumur 2.335 hari tidak dihapus melainkan diarsipkan, karena aksinya archive.
Hak penghapusan (right to be forgotten) menuntut mekanisme serupa pada level individu: pelanggan berhak meminta data pribadinya dihapus, dan kita harus bisa membuktikan penghapusan itu terjadi. Rujukan resmi prinsip minimization ada di teks GDPR di EUR-Lex dan halaman perlindungan data Komisi Eropa ; bab ini menjelaskan praktik teknis secara umum, bukan nasihat hukum.
Eksekusi retensi seperti di atas adalah tindakan sensitif: sekali dijalankan, data tidak kembali. Kita butuh jejak yang menjawab siapa yang menjalankan kebijakan, kapan, dan apa saja yang terpengaruh — persis pekerjaan Chapter Audit Logging.