Jawaban singkat untuk pertanyaan yang paling sering diajukan pembaca tentang topik ini.
01Apa perbedaan cache-aside dan write-through?
Pada cache-aside, aplikasi memuat data ke cache saat miss dan menghapus entry saat menulis database, sehingga cache bisa stale sampai read berikutnya. Pada write-through setiap write memperbarui database dan cache sekaligus, jadi reader langsung melihat nilai baru. Harganya adalah penulisan cache tambahan di setiap update dan satu kasus gagal saat penulisan kedua itu error.
02Apakah read-through sama dengan cache-aside?
Keduanya memakai alur baca yang sama, tetapi logika pemuatannya berada di tempat berbeda. Pada cache-aside kode aplikasi Anda yang mengecek cache dan query ke database saat miss. Pada read-through lapisan cache yang melakukannya sendiri, sehingga pemanggil hanya berbicara dengan cache.
03Kapan sebaiknya memakai write-back caching?
Pakai write-back untuk data write-heavy yang boleh kehilangan beberapa detik update, seperti counter view atau flag presence. Update berulang ke key yang sama menyatu menjadi satu write ke database saat flush. Hindari untuk uang, stok, atau data audit, karena cache memegang satu-satunya salinan sampai flush selesai.
04Apa itu write-around caching dan kapan berguna?
Write-around mengirim write langsung ke database dan melewati cache, sehingga cache baru terisi saat ada reader yang memintanya. Berguna untuk record yang ditulis sekali dan jarang dibaca, seperti invoice atau baris audit, karena tidak mendorong entry berguna keluar dari cache yang kecil. Read pertama setelah setiap write selalu miss.
05Pola caching mana yang paling baik sebagai default?
Cache-aside dengan invalidation database-first dan TTL adalah default paling aman untuk data read-heavy. Pola ini tahan terhadap cache yang mati atau kosong, tidak butuh infrastruktur khusus, dan membatasi stale lewat expiry. Tambahkan write-through hanya untuk key yang pembacanya harus langsung melihat hasil write mereka.
Cache-Aside vs Write-Through vs Write-Back: Mana yang Cocok?
Perbandingan cache-aside, read-through, write-through, write-back, dan write-around: cara baca-tulisnya, cara gagalnya, lengkap dengan kode TypeScript dan Redis.
Pakai cache-aside sebagai default: aplikasi membaca cache, memuat dari database saat miss, dan meng-invalidate entry saat write. Pilih write-through bila pembaca butuh nilai segar tepat setelah write, write-back hanya untuk counter write-heavy yang boleh hilang, dan write-around untuk data yang ditulis sekali dan jarang dibaca.
Bayangkan POS carwash yang membaca daftar harga layanan di setiap tiket, padahal harganya diubah mungkin dua kali sebulan. Menaruh Redis di depan Postgres adalah keputusan yang mudah. Yang sulit adalah pertanyaan yang jarang dituliskan: siapa yang memasukkan data ke cache, dan apa yang terjadi pada cache saat harga diedit.
Tulisan ini membandingkan lima pola yang menjawab pertanyaan itu: cache-aside, read-through, write-through, write-back, dan write-around. Masing-masing punya snippet TypeScript dengan ioredis, satu failure mode, dan tempat yang cocok. Klaim perilaku bersumber dari Azure Architecture Center, panduan Amazon ElastiCache, Wikipedia, dan dokumentasi Redis yang tercantum di akhir. Perhitungan pada contoh bersifat ilustrasi, bukan hasil pengukuran.
Apa itu pola cache-aside dan bagaimana cara kerjanya?
Pada cache-aside, aplikasi memegang seluruh logika cache. Aplikasi membaca cache lebih dulu, saat miss ia membaca database lalu menyimpan hasilnya, dan saat write ia commit ke database kemudian meng-invalidate entry di cache. Azure Architecture Center menyebutnya memuat data ke cache sesuai kebutuhan. Berikut versi TypeScript-nya dengan TTL sebagai pengaman.
import Redis from "ioredis";
const redis = new Redis(); // one long-lived client, never one per request
const TTL_SECONDS = 300; // the backstop: no entry stays wrong for longer
const key = (id: string) => "service:" + id;
export async function getService(id: string): Promise<Service | null> {
const hit = await redis.get(key(id));
if (hit !== null) return JSON.parse(hit) as Service; // cache hit
const row = await db.findService(id); // cache miss: 3 trips
if (row) {
// SET key value EX seconds: the value and its expiry in one command.
await redis.set(key(id), JSON.stringify(row), "EX", TTL_SECONDS);
}
return row;
}
export async function updateService(id: string, patch: Partial<Service>) {
// Wrong: await redis.del(key(id)) first. A reader can slip in between the
// delete and the commit, miss, reload the OLD row and re-cache it.
// Right: commit to the database first, then invalidate.
await db.updateService(id, patch);
await redis.del(key(id));
}
Ada dua detail yang penting. Urutan saat write menentukan: dokumentasi Azure meminta data store diperbarui sebelum item cache dihapus, karena kalau dihapus lebih dulu, reader yang bersamaan bisa memuat row lama dan menaruhnya kembali. Lalu satu miss memakan tiga perjalanan menurut hitungan panduan ElastiCache: permintaan ke cache, query ke database, dan penulisan ke cache. Cache-aside juga tahan terhadap cache yang mati, sebab node yang dingin atau baru diganti hanya berarti lebih banyak miss, bukan outage.
Apa bedanya read-through dengan cache-aside?
Alur baca read-through sama, tetapi logika pemuatan pindah dari pemanggil ke lapisan cache. Dokumentasi Azure mencatat bahwa banyak sistem cache komersial menyediakan read-through secara native, dan cache-aside adalah cara aplikasi menirunya saat cache tidak menyediakannya. Redis tidak punya loader native, jadi read-through berupa class kecil yang membungkus get, load, dan set dalam satu method.
// Read-through: the loader lives INSIDE the cache layer.
// Callers never see the database, so the miss logic exists exactly once.
export class ReadThroughCache<T> {
constructor(
private readonly redis: Redis,
private readonly prefix: string,
private readonly ttlSeconds: number,
private readonly loader: (id: string) => Promise<T | null>,
) {}
async get(id: string): Promise<T | null> {
const k = this.prefix + id;
const hit = await this.redis.get(k);
if (hit !== null) return JSON.parse(hit) as T;
const value = await this.loader(id);
if (value !== null) {
await this.redis.set(k, JSON.stringify(value), "EX", this.ttlSeconds);
}
return value;
}
}
const services = new ReadThroughCache<Service>(redis, "service:", 300, (id) =>
db.findService(id),
);
const svc = await services.get("svc_42"); // caller has no idea the DB exists
Keuntungannya, penanganan miss hanya ada di satu tempat dan tidak disalin ke setiap method service. Biayanya, pembungkus itu menjadi dependency yang harus dilewati semua reader, dan ia tidak mengurus write sama sekali. Anda tetap harus memutuskan apa yang dilakukan write, yaitu pertanyaan yang dijawab tiga pola berikutnya.
Apa itu write-through caching dan kapan layak dipakai?
Write-through memperbarui cache setiap kali database ditulis. Panduan ElastiCache menyebut keuntungannya sebagai data yang tidak pernah stale karena cache diperbarui di setiap write, dengan harga dua perjalanan per write, satu ke cache dan satu ke database. Snippet di bawah menjadikan database sebagai system of record dan menempatkan penulisan cache sebagai langkah kedua.
export async function saveService(s: Service): Promise<Service> {
// 1. The database is the system of record: it commits first, always.
const saved = await db.upsertService(s);
// 2. Then the cache is overwritten in the same request. Readers see the new
// value immediately instead of waiting for a miss.
try {
await redis.set(key(saved.id), JSON.stringify(saved), "EX", TTL_SECONDS);
} catch {
// The database commit already happened and cannot be undone. Drop the
// entry so a later read reloads it, and let the TTL cover the rest.
await redis.del(key(saved.id)).catch(() => undefined);
// Log it: a silent failure here is a stale cache nobody can explain.
}
return saved;
}
Pakai di tempat reader harus melihat nilai baru segera setelah write berhasil, dan jumlah read jauh melebihi write. Arsitektur referensi write-through dari Azure menyatakan pola ini hanya untuk jalur baca yang berat dan butuh nilai segar, sedangkan data yang jarang dibaca setelah ditulis sebaiknya dibaca langsung dari database. Panduan itu juga memperingatkan cache churn: sebagian besar data tidak pernah dibaca, sehingga write-through mengisi cache dengan entry yang tak diminta siapa pun kecuali TTL memangkasnya.
Write-through bukan transaksi lintas dua sistem. Jika database sudah commit tetapi penulisan cache gagal, cache menjadi stale dan pemanggil menerima error atau informasi yang keliru. Desain referensi Azure menjawabnya dengan outbox yang durable, repair job, dan TTL sebagai pengaman. Kalau Anda belum punya semuanya, anggap penulisan cache sebagai best effort dan andalkan invalidation plus TTL.
Apa itu write-back caching dan mengapa berisiko?
Write-back, disebut juga write-behind, menulis ke cache lebih dulu lalu mem-flush ke database belakangan. Wikipedia menjelaskan gagasan yang sama di level hardware: pada awalnya penulisan hanya dilakukan ke cache, dan datanya ditulis ke backing store kemudian. Jalur request secepat Redis, dan update berulang ke satu key menyatu menjadi satu write ke database.
const DIRTY = "dirty:counter";
const FLUSH_MS = 10_000;
// The request path touches Redis only. MULTI makes the value and the
// dirty marker land together, so a counter is never changed but unmarked.
export async function bumpCounter(id: string, value: number) {
await redis.multi().hset("counter:" + id, "v", value).sadd(DIRTY, id).exec();
}
// A background loop writes dirty keys back to the database in batches.
export async function flushDirty() {
const ids = await redis.srandmember(DIRTY, 100); // peek, do NOT pop yet
for (const id of ids) {
const v = await redis.hget("counter:" + id, "v");
if (v === null) continue;
await db.setCounter(id, Number(v)); // if this throws, id stays dirty
await redis.srem(DIRTY, id); // forget it only after the DB commit
}
}
setInterval(() => flushDirty().catch(console.error), FLUSH_MS);
Contoh hitungan menunjukkan keuntungannya. Counter yang diupdate 50 kali dalam semenit dan di-flush tiap 10 detik menghasilkan paling banyak 60 dibagi 10 sama dengan 6 write ke database, bukan 50. Contoh yang sama menunjukkan harganya. Crash bisa menghilangkan semua yang dirty sejak flush terakhir, sekitar 50 dibagi 6 sama dengan kira-kira 8 update, karena selama jendela itu cache memegang satu-satunya salinan.
Write-back memindahkan source of truth ke cache sampai flush selesai. Jangan pernah memakainya untuk uang, pergerakan stok, atau apa pun yang akan ditanyakan auditor. Pakai untuk counter, tally view, dan data presence, yang kehilangan beberapa detik update masih bisa diterima, dan pastikan loop flush baru menghapus penanda dirty setelah commit database.
Kapan sebaiknya memakai write-around?
Write-around mengirim write langsung ke database dan melewati cache, sehingga write tidak pernah mengusir atau mengisi apa pun. Wikipedia menyebut padanan hardware-nya no-write allocate: data di lokasi missed-write ditulis langsung ke backing store dan tidak dimuat ke cache. Pola ini cocok berpasangan dengan read cache-aside, sehingga row hanya masuk cache saat ada yang membacanya.
// Write-around: the write goes to the database and the cache is not touched.
export async function createInvoice(inv: NewInvoice): Promise<Invoice> {
return db.insertInvoice(inv); // no redis call on purpose
}
// Reads still use cache-aside, with a short TTL, so an invoice only enters
// the cache if somebody actually asks for it.
export async function getInvoice(id: string): Promise<Invoice | null> {
const hit = await redis.get("invoice:" + id);
if (hit !== null) return JSON.parse(hit) as Invoice;
const row = await db.findInvoice(id);
if (row) await redis.set("invoice:" + id, JSON.stringify(row), "EX", 60);
return row;
}
Cocok untuk data yang ditulis sekali dan dibaca jarang atau jauh kemudian, seperti invoice, baris audit, atau batch import. Pola ini mencegah cache kecil tercemar entry yang akan tergusur sebelum sempat dibaca. Kekurangannya ada pada read pertama setelah write yang pasti miss, jadi jangan dipakai bila read pertama itulah yang paling sering terjadi.
Cache-aside vs write-through vs write-back: bagaimana perbandingannya?
Tabel berikut menyandingkan kelima pola. Kolom yang penting saat review adalah jalur write, apa yang bisa salah, dan jenis data yang cocok. Latensi sengaja dijelaskan secara kualitatif karena angka sebenarnya bergantung pada jaringan dan database Anda.
Pola
Jalur write
Risiko data stale atau hilang
Paling cocok untuk
Cache-aside
Tulis database, lalu hapus entry cache
Stale sampai miss berikutnya atau TTL; tidak ada data hilang
Default untuk data read-heavy yang toleran terhadap stale singkat
Read-through
Tidak didefinisikan; pasangkan dengan pola write lain
Sama dengan pola write yang dipilih di sampingnya
Banyak service yang berbagi satu aturan pemuatan
Write-through
Tulis database, lalu timpa cache
Segar setelah sukses; stale bila penulisan cache gagal
Read-after-write pada row panas yang read-heavy
Write-back
Tulis cache, flush ke database belakangan
Data yang belum di-flush hilang saat crash
Counter dan tally write-heavy yang boleh kehilangan beberapa detik
Write-around
Tulis database saja; biarkan cache
Read pertama setelah write pasti miss
Record yang ditulis sekali dan jarang dibaca
Baca tabel ini sebagai dua pilihan yang terpisah: pola read (cache-aside atau read-through) dan pola write (invalidate, write-through, write-back, atau around). Kebanyakan sistem memasangkan read cache-aside dengan invalidate-on-write, lalu menambahkan write-through hanya untuk beberapa key yang kesegarannya menjadi syarat.
Apa saja consistency dan failure mode tiap pola?
Setiap pola adalah jawaban berbeda atas pertanyaan apa yang terjadi saat dua store tidak sepakat, dan masing-masing punya kegagalan yang bernama. Halaman cache-aside Azure menyatakan terus terang bahwa pola ini tidak menjamin konsistensi antara data store dan cache. Berikut kegagalan yang layak diuji sebelum production.
Cache-aside: reader memuat row lama di antara delete yang terlalu awal dan commit, atau proses di luar mengedit database langsung sehingga cache tidak pernah tahu. Hanya TTL yang membatasi hal ini.
Write-through: commit database berhasil tetapi penulisan cache gagal, meninggalkan entry stale dan error yang membingungkan. Node cache baru atau pengganti juga mulai kosong, dan panduan ElastiCache menyarankan menggabungkan write-through dengan lazy loading untuk menutupinya.
Write-back: node cache mati sebelum flush, dan write yang belum di-flush hilang karena tidak ada salinan lain. Flush yang menghapus penanda dirty sebelum commit database membuat kehilangan itu tidak terlihat.
Write-around: read pertama setelah setiap write selalu miss, sehingga rentetan write baru yang diikuti read berperilaku seperti sistem tanpa cache sampai cache hangat.
Redis mati adalah kasus tersendiri. Pada cache-aside dan write-through, sistem seharusnya turun ke read database, jadi bungkus pemanggilan cache dengan timeout pendek dan anggap setiap error sebagai miss. Write-back tidak punya fallback seperti itu, yang menjadi alasan lain untuk menyimpannya bagi data yang boleh hilang.
Selalu beri TTL pada entry cache, bahkan dengan write-through. Panduan ElastiCache dan desain referensi Azure sama-sama menyarankan expiry sebagai pengaman terhadap kegagalan parsial dan edit database langsung, dan Redis mengatur nilai beserta expiry secara atomik lewat SET key value EX seconds.
Pola caching mana yang sebaiknya saya pilih?
Putuskan per jenis data, bukan per aplikasi. ERP carwash, misalnya, wajar memakai cache-aside untuk katalog layanan, write-through untuk satu key status shift yang di-poll semua terminal, dan write-around untuk tiket yang sudah selesai. Telusuri checklist ini untuk setiap jenis data.
Apakah data ini boleh hilang beberapa detik tanpa ada yang peduli? Jika ya, write-back dimungkinkan. Jika itu uang, stok, atau data audit, coret write-back sekarang juga.
Apakah reader butuh nilai baru segera setelah write berhasil? Jika ya, pakai write-through, dengan TTL dan rencana untuk kegagalan penulisan cache.
Apakah data ditulis sekali dan jarang dibaca? Pakai write-around dengan read cache-aside dan TTL pendek.
Selain itu pakai cache-aside dengan invalidate-on-write: database dulu, hapus cache kedua, TTL selalu ada.
Jika tiga service atau lebih mengulang logika miss yang sama, bungkus dalam class read-through agar aturannya hanya ada satu.
Apa pun pilihannya, taruh cache hit ratio di dashboard. Hitungannya sederhana: pada 2.000 read per detik, hit ratio 95 persen mengirim 2.000 kali 0,05 sama dengan 100 read per detik ke database, sedangkan 80 persen mengirim 400. Pola yang tidak bisa menjaga rasio yang Anda rencanakan adalah pola yang salah.
Perlakukan caching sebagai dua keputusan terpisah, pola read dan pola write. Jadikan cache-aside dengan invalidation database-first dan TTL sebagai default, tambahkan write-through hanya bila kesegaran read-after-write adalah syarat yang tertulis, simpan write-back untuk data yang boleh hilang, dan sisakan write-around untuk record yang ditulis sekali dan jarang dibaca.