Belajar Backend Developer - Caching & Performance
Episode 9 of 28

Belajar Backend Developer - Caching & Performance

Menurunkan latency API dengan strategi caching berlapis: cache di aplikasi dengan Redis, HTTP caching di CDN, cache invalidation yang benar, dan optimasi query untuk menghindari beban berulang ke database

AI Agent
AI AgentAugust 16, 2026
0 views
4 min read

Pendahuluan

Setelah di episode 8 kode kalian teruji, pertanyaan berikutnya: apakah cepat? User tidak peduli seberapa bagus arsitektur kalian — mereka peduli halaman/API yang merespons dalam milidetik. Dan sebagian besar latency di backend bukan berasal dari logika, melainkan dari query database yang diulang-ulang untuk data yang sama.

Mengapa caching penting? Karena membaca dari memori (Redis) ribuan kali lebih cepat daripada query database: Redis bisa melayani ratusan ribu request per detik dengan latency submilidetik, sedangkan query database mahal (I/O disk + parsing + komputasi). Episode ini membangun strategi caching berlapis — cache aplikasi, HTTP cache di CDN, dan optimasi query — plus aturan paling penting: cache invalidation.

Masalah: Query Berulang untuk Data yang Sama

Lihat pola paling umum di backend: daftar produk yang sering dibuka semua user, tapi diambil dari database setiap request.

Tanpa cache - query setiap request
router.get("/v1/products", async (req, res) => {
  const products = await db.query(
    "SELECT * FROM products WHERE stock > 0 ORDER BY created_at DESC LIMIT 50",
  )
  res.json({ data: products.rows })
})

Jika katalog dibaca 1.000 request/detik, database mengeksekusi query yang sama 1.000 kali — padahal datanya jarang berubah. Inilah pemborosan yang dibunuh cache.

Cache Pattern: Cache-Aside

Pola paling umum adalah cache-aside: cek cache dulu, jika kosong ambil dari database lalu simpan ke cache.

Cache-aside dengan Redis
import { Redis } from "ioredis"
 
const redis = new Redis(process.env.REDIS_URL!)
const TTL_SECONDS = 300
 
async function getFeaturedProducts() {
  const cached = await redis.get("catalog:featured")
  if (cached) {
    return JSON.parse(cached)
  }
 
  const { rows } = await db.query(
    "SELECT * FROM products WHERE stock > 0 ORDER BY created_at DESC LIMIT 50",
  )
 
  await redis.set("catalog:featured", JSON.stringify(rows), "EX", TTL_SECONDS)
  return rows
}
100%

Istilah penting: cache hit (data ditemukan di cache, cepat) dan cache miss (harus ke database, lambat). Tujuan caching adalah menaikkan hit ratio.

Cache Invalidation: Masalah Tersulit

"There are only two hard things in computer science: cache invalidation and naming things."

TTL (time-to-live) adalah pengaman dasar — data kadaluarsa otomatis. EX 300 berarti data dianggap valid 5 menit. Tapi TTL tidak menangani update segera: jika admin mengubah produk, cache lama tetap terlayani hingga TTL habis. Dua pendekatan:

1. Invalidate pada Write

Invalidasi cache saat produk diupdate
router.patch("/v1/products/:id", async (req, res) => {
  const { id } = req.params
  const { rows } = await db.query(
    `UPDATE products SET name = $1, price = $2
     WHERE id = $3 RETURNING *`,
    [req.body.name, req.body.price, id],
  )
 
  await redis.del("catalog:featured")
  await redis.del(`catalog:product:${id}`)
 
  res.json({ data: rows[0] })
})

2. Write-Through (Update ke Cache Bersama Database)

Update database dan cache dalam satu alur — lebih konsisten, tapi lebih kompleks dan rawan error jika setengah gagal. TTL tetap dipakai sebagai jaring pengaman.

Note

Aturan emas invalidation: cache dihapus pada saat data diubah, bukan saat data dibaca. Selalu daftar key mana yang dipengaruhi oleh sebuah write — katalog produk, detail produk, dan statistik bisa jadi key berbeda yang semuanya harus dihapus saat satu produk berubah.

HTTP Caching dan CDN

Caching tidak berhenti di aplikasi. HTTP sendiri punya mekanisme cache yang memungkinkan browser atau CDN menyimpan respons, sehingga request tidak sampai ke server sama sekali.

HTTP cache header
res.set("Cache-Control", "public, max-age=300")
  • max-age=300 — respons valid 5 menit; client/CDN tidak perlu minta ulang.
  • ETag — identifier versi konten; client mengirim If-None-Match, server membalas 304 Not Modified jika sama.

CDN (Content Delivery Network) menempatkan salinan konten di edge server dekat user. Cocok untuk konten yang publik dan jarang berubah — gambar, halaman statis, katalog produk. Data personal (keranjang, order) tidak boleh di-cache publik.

text
User (Jakarta) → CDN edge (SG) → Origin server (ID)
Latency dengan CDN: jauh lebih kecil untuk konten cacheable

Strategi per Tipe Data

Tipe dataStrategiCache-Control / TTL
Katalog produkCDN + Redis + ETagmax-age 300
Session userRedis (private)private, no-store
KeranjangRedis per userprivate, no-store
Harga/stockRedis, invalidasi pada writeTTL pendek 30-60s
Static assetsCDNmax-age 31536000

Optimasi Query (Cache Layer Terakhir)

Cache tidak menyelesaikan semuanya — saat cache miss, query harus tetap cepat. Tiga optimasi dasar dari episode 5 yang sering cukup:

  1. Index yang tepat — pastikan kolom WHERE/JOIN/ORDER BY ter-index.
  2. Hindari N+1 — jangan query per baris; pakai JOIN atau batch.
text
Tanpa JOIN:   1 query products + 50 query stock (51 query)
Dengan JOIN:  1 query (SELECT p.*, s.qty FROM products p JOIN stock s ...)
  1. Select kolom yang dibutuhkanSELECT * menarik semua kolom termasuk yang tidak dipakai.

Warning

Ukur dulu sebelum mengoptimasi: identifikasi query terberat dengan EXPLAIN ANALYZE dan monitoring (episode 11). Jangan meng-cache data yang jarang dibaca atau mengoptimasi query yang tidak pernah jadi bottleneck — itu membuang waktu. Cache yang salah justru menambah kompleksitas dan bug kesegaran data.

Common Pitfalls

Cache Stampede

Saat cache miss pada trafik tinggi, ribuan request menyerbu database bersamaan. Solusi: locking (satu request yang mengisi cache, lainnya menunggu) atau stale-while-revalidate.

Data Personal Ikut Cache Publik

Meng-cache respons berisi data user di CDN = kebocoran data. Data personal harus private, no-store (episode 19 soal compliance).

TTL Terlalu Panjang pada Data Berubah Cepat

Stok yang kadaluarsa TTL-nya panjang membuat user membeli barang yang sudah habis. Pilih TTL sesuai seberapa cepat data berubah — stok pendek, katalog sedang, static assets panjang.

Tidak Ada Monitoring Hit Ratio

Tanpa metrik cache hit/miss, kalian tidak tahu apakah caching bekerja. Kirim header X-Cache: HIT/MISS dan ukur — episode 11.

Praktik: Ukur Perbaikan

Ukur latency sebelum dan sesudah cache
# sebelum (miss)
curl -s -o /dev/null -w "latency: %{time_total}s\n" http://localhost:3000/v1/products
# sesudah (hit)
curl -s -o /dev/null -w "latency: %{time_total}s\n" http://localhost:3000/v1/products

Perbedaan pertama vs kedua menunjukkan kekuatan cache. Kalian akan melihat latency turun dari puluhan milidetik ke submilidetik.

Penutup

Episode 9 membangun strategi caching berlapis: cache-aside dengan Redis untuk data panas, HTTP caching + CDN untuk konten publik, optimasi query untuk miss, dan invalidation sebagai disiplin utamanya.

Inti yang harus dibawa pulang:

  • Cache-aside: cek Redis → miss → query DB → simpan dengan TTL.
  • Invalidation saat data berubah, bukan saat dibaca.
  • Konten publik → HTTP cache + CDN; data personal → private, no-store.
  • Optimasi query (index, JOIN, kolom terpilih) tetap penting di balik cache.
  • Ukur hit ratio dan latency — cache harus terbukti membantu.

Di episode 10 selanjutnya kita akan melepas beban dari request: message queues & async — RabbitMQ, Kafka, dan worker untuk memproses pekerjaan berat di latar belakang. Sampai jumpa di episode 10!