Strategi Caching: Cache-Aside, Write-Through, dan Write-Back
Perbandingan strategi caching (cache-aside, read-through, write-through, write-around, write-back), kelebihan, risiko, dan kapan memakai masing-masing.
Strategi caching menentukan kapan data masuk ke cache dan kapan ditulis ke database. Ada lima pola utama. Cache-aside mengisi cache hanya saat data diminta dan tidak ditemukan. Read-through mendelegasikan pengisian itu ke lapisan cache. Write-through menulis ke cache dan database sekaligus. Write-around menulis langsung ke database dan melewati cache. Write-back menulis ke cache dulu, lalu ke database belakangan. Pilihan terbaik bergantung pada rasio baca/tulis dan seberapa besar toleransi Anda terhadap data basi atau data hilang.
Kenapa caching dibutuhkan
Membaca dari memori (misalnya Redis atau Memcached) biasanya selesai dalam hitungan mikrodetik sampai sekitar satu milidetik. Query ke database relasional yang menyentuh disk dan melewati jaringan bisa memakan puluhan milidetik atau lebih. Pada sistem dengan pola baca yang dominan (misalnya halaman produk, profil pengguna, atau konfigurasi), cache menurunkan latensi dan mengurangi beban database sekaligus.
Tapi cache adalah salinan data. Begitu ada salinan, muncul pertanyaan klasik: bagaimana menjaga keduanya tetap sinkron? Setiap strategi di bawah ini menjawab pertanyaan itu dengan trade-off yang berbeda.
Strategi untuk membaca data
Cache-aside (lazy loading)
Aplikasi bertanggung jawab penuh atas cache:
- Cari data di cache.
- Jika miss, baca dari database.
- Simpan hasilnya ke cache dengan TTL, lalu kembalikan ke pemanggil.
async function getUser(id: string) {
const cached = await redis.get(`user:${id}`);
if (cached) return JSON.parse(cached);
const user = await db.users.findById(id);
if (user) {
await redis.set(`user:${id}`, JSON.stringify(user), { EX: 300 }); // TTL 5 menit
}
return user;
}
Kelebihan: hanya data yang benar-benar diminta yang masuk cache, dan jika cache mati, aplikasi tetap bisa berjalan (hanya lebih lambat). Kekurangan: permintaan pertama selalu miss, dan data di cache bisa basi sampai TTL habis atau key dihapus.
Ini pola paling umum dan titik awal yang aman untuk kebanyakan aplikasi.
Read-through
Mirip cache-aside, tetapi logika “miss → baca database → isi cache” dipindahkan ke lapisan cache atau library, bukan di kode aplikasi. Aplikasi cukup memanggil cache.get(key). Hasilnya kode lebih bersih, tapi Anda bergantung pada library/penyedia cache yang mendukung pola ini.
Strategi untuk menulis data
Write-through
Setiap penulisan masuk ke cache dan database secara sinkron, dan baru dianggap sukses setelah keduanya berhasil. Cache selalu berisi data terbaru, sehingga pembacaan berikutnya hampir pasti hit.
Harganya: latensi tulis bertambah (dua penulisan per operasi), dan cache bisa terisi data yang jarang dibaca.
Write-around
Penulisan langsung ke database dan melewati cache. Key yang terkait biasanya dihapus (invalidate) agar pembacaan berikutnya mengambil data segar. Cocok untuk data yang ditulis sekali dan jarang dibaca ulang, seperti log atau arsip.
Write-back (write-behind)
Aplikasi hanya menulis ke cache, lalu cache menyalin ke database secara asinkron, sering dalam batch. Latensi tulis sangat rendah dan database terlindung dari lonjakan tulis.
Perbandingan singkat
| Strategi | Latensi baca | Latensi tulis | Konsistensi | Risiko utama | Cocok untuk |
|---|---|---|---|---|---|
| Cache-aside | Miss pertama lambat | Normal | Bisa basi s.d. TTL | Data basi | Beban baca umum |
| Read-through | Miss pertama lambat | Normal | Bisa basi s.d. TTL | Ketergantungan library | Beban baca, kode lebih rapi |
| Write-through | Cepat (sering hit) | Lebih tinggi | Kuat | Cache penuh data jarang dibaca | Data yang ditulis lalu dibaca |
| Write-around | Miss setelah tulis | Normal | Kuat (setelah miss) | Miss setelah setiap update | Data jarang dibaca ulang |
| Write-back | Cepat | Sangat rendah | Eventual | Kehilangan data | Beban tulis sangat tinggi |
Dalam praktik, kombinasi yang paling sering dipakai adalah cache-aside untuk baca + invalidasi (hapus key) saat tulis.
Masalah yang sering muncul
Cache stampede (thundering herd)
Saat key populer kedaluwarsa, ribuan request bisa miss bersamaan dan menghantam database di waktu yang sama. Beberapa mitigasinya:
- Lock/single-flight: hanya satu request yang mengisi ulang cache, sementara yang lain menunggu.
- TTL dengan jitter: tambahkan variasi acak pada TTL supaya key tidak kedaluwarsa serentak.
- Refresh lebih awal: perbarui key di background sebelum TTL benar-benar habis.
Update vs hapus saat invalidasi
Saat data berubah, menghapus key umumnya lebih aman daripada menimpa nilainya. Dengan dua penulisan paralel, operasi “update cache” bisa tiba dengan urutan terbalik dan meninggalkan nilai lama di cache. Menghapus key memaksa pembacaan berikutnya mengambil data terbaru dari database.
Kebijakan eviction
Memori cache terbatas. Saat penuh, cache membuang key berdasarkan kebijakan seperti LRU (paling lama tidak diakses) atau LFU (paling jarang diakses). Redis, misalnya, mengaturnya lewat opsi maxmemory-policy.
Cara memilih
- Mayoritas beban adalah baca dan data boleh sedikit basi → cache-aside dengan TTL.
- Data harus langsung konsisten setelah ditulis dan sering dibaca → write-through.
- Data ditulis sekali dan jarang dibaca → write-around.
- Tulis sangat intens dan kehilangan sebagian data bisa diterima → write-back.
Mulailah dari yang paling sederhana (cache-aside), ukur hit ratio dan latensi, lalu naikkan kompleksitas hanya jika angka menunjukkan perlu.