Caching menyimpan data yang sering diakses di tempat yang lebih cepat, mengurangi beban database dan mempercepat response.
Di mana cache bisa ditaruh:
Client (Browser) → CDN → Load Balancer → App Server → Cache (Redis) → Database
↑ ↑ ↑
Browser cache App-level cache DB query cache
Caching patterns:
1. Cache-Aside (Lazy Loading) Paling umum. App cek cache dulu, jika miss baru query database, lalu simpan di cache.
async function getUser(userId) {
// 1. Cek cache
const cached = await redis.get("user:" + userId);
if (cached) return JSON.parse(cached); // Cache hit!
// 2. Cache miss — query database
const user = await db.query("SELECT * FROM users WHERE id = ?", [userId]);
// 3. Simpan di cache (TTL 1 jam)
await redis.set("user:" + userId, JSON.stringify(user), "EX", 3600);
return user;
}
Kelebihan: hanya data yang dibutuhkan yang di-cache Kekurangan: cache miss pertama selalu lambat, data bisa stale
2. Write-Through Data ditulis ke cache dan database secara bersamaan.
async function updateUser(userId, data) {
// Tulis ke database
await db.query("UPDATE users SET ... WHERE id = ?", [userId]);
// Tulis ke cache juga
await redis.set("user:" + userId, JSON.stringify(data), "EX", 3600);
}
Kelebihan: cache selalu up-to-date Kekurangan: setiap write lebih lambat (2 operasi), cache bisa berisi data yang jarang dibaca
3. Write-Behind (Write-Back) Data ditulis ke cache dulu, database di-update secara asinkron.
Write → Cache (instant) → Queue → Database (later)
Kelebihan: write sangat cepat Kekurangan: risiko kehilangan data jika cache crash sebelum sync ke database
4. Read-Through Mirip cache-aside tapi cache-nya yang bertanggung jawab mengambil data dari database (bukan app).
Cache eviction policies: Ketika cache penuh, data lama harus dihapus:
| Policy | Strategi |
|---|---|
| LRU | Hapus yang paling lama tidak diakses (paling umum) |
| LFU | Hapus yang paling jarang diakses |
| FIFO | Hapus yang paling dulu masuk |
| TTL | Hapus berdasarkan waktu hidup |
Cache invalidation: Salah satu masalah tersulit di computer science: kapan menghapus cache?
// Strategi 1: TTL (Time to Live) — expired otomatis
await redis.set("key", value, "EX", 3600); // hapus setelah 1 jam
// Strategi 2: Event-based — hapus saat data berubah
async function updateArticle(id, data) {
await db.updateArticle(id, data);
await redis.del("article:" + id); // hapus cache spesifik
await redis.del("article-list"); // hapus cache list juga
}
// Strategi 3: Version-based
await redis.set("articles:v2", data); // ganti key saat data berubah
Thundering herd problem: Jika cache expired dan 1000 request datang bersamaan, semua akan query database.
Solusi: lock — hanya satu request yang query database, sisanya tunggu cache di-refresh.
Redis vs Memcached:
| Aspek | Redis | Memcached |
|---|---|---|
| Data types | String, list, set, hash, dll | Hanya string |
| Persistence | Bisa persist ke disk | Hanya memory |
| Pub/Sub | Ada | Tidak ada |
| Popularitas | Lebih populer | Sederhana dan cepat |
🎭 Analogi sehari-hari: Cache = laci atas meja kerja. Pulpen yang sering dipakai taruh di laci (cepat ambil). Yang jarang dipakai di gudang (database). Mau ambil pulpen? Cek laci dulu. Gak ada? Buka gudang, ambil, simpan di laci untuk next time. Itulah Cache-Aside.
💡 "There are only 2 hard things in CS: cache invalidation and naming things" — Phil Karlton. Cache invalidation susah karena: kapan invalidate? key apa yang affected? race condition antara update DB dan invalidate? Strategi paling aman: TTL pendek (5-15 menit) + invalidate eksplisit untuk operasi critical.
⚠️ Jebakan klasik:
- Stale data — TTL terlalu lama, user lihat data outdated
- Thundering herd — cache expire bareng = 1000 request hit DB sekaligus. Pakai lock/single-flight
- Cache stampede — restart Redis = semua cache miss = DB collapse
- Cache hot key — 1 key jadi bottleneck (misal homepage). Solusi: replicate hot key di multi-instance
- Inconsistency — write ke DB sukses, invalidate cache gagal. Pakai event/CDC untuk safety
🎯 Pilih cache pattern:
- Read-heavy + tolerate stale → Cache-Aside (default)
- Read-heavy + need fresh → Cache-Aside + short TTL + event invalidation
- Write-heavy + read jarang → skip cache (hampir gak guna)
- Real-time critical → Write-Through (sync) atau no-cache
- Throughput maksimal, OK loss → Write-Behind (async)
TL;DR: Cache = simpan data di tempat lebih cepat (Redis/Memcached/CDN/browser). Cache-Aside paling umum. Wajib strategi invalidation. Awas thundering herd, hot key, stampede. TTL + event invalidation = balance.