Cache HTTP & CDN — pengungkit terbesar
Untuk situs bertrafik baca tinggi, tidak ada optimasi lain yang mendekati dampak ini: halaman yang sama bagi semua pembaca cukup dihasilkan sekali per menit, sisanya dilayani CloudFront.
Intisari
s-maxagemengatur CDN,max-agemengatur browser — dan keduanya bisa berbeda.stale-while-revalidatemenyajikan versi lama seketika sambil menyegarkan di latar. Ini yang menghapus lonjakan setelah kedaluwarsa.- Halaman personal wajib
privateatauno-store. Satu kesalahan di sini menyajikan data satu pengguna ke pengguna lain. ETag+If-None-Matchmenghasilkan 304 — hemat bandwidth meski tidak hemat kueri.- Aset ber-hash nama boleh
immutabledan di-cache setahun.
Direktif yang benar-benar dipakai
| Direktif | Artinya |
|---|---|
public | Boleh disimpan cache bersama (CDN) |
private | Hanya browser pengguna — CDN tidak boleh menyimpannya |
no-store | Jangan disimpan di mana pun |
max-age=N | Segar selama N detik (untuk semua cache) |
s-maxage=N | Segar selama N detik khusus cache bersama; menimpa max-age di CDN |
stale-while-revalidate=N | Boleh menyajikan yang basi sampai N detik sambil menyegarkan di latar |
stale-if-error=N | Boleh menyajikan yang basi kalau origin sedang error |
must-revalidate | Jangan pernah sajikan yang basi |
immutable | Tidak akan pernah berubah — jangan pernah validasi ulang |
Resep per jenis halaman
// 1. Halaman publik yang sama untuk semua pembaca — SASARAN UTAMA.
// Browser menyimpan 60 detik; CDN menyimpan 5 menit dan boleh
// menyajikan versi basi sampai 1 jam sambil menyegarkan.
w.Header().Set("Cache-Control",
"public, max-age=60, s-maxage=300, stale-while-revalidate=3600, stale-if-error=86400")
// 2. Aset ber-hash nama (styles.9ea43564.css) — nama baru untuk isi baru
w.Header().Set("Cache-Control", "public, max-age=31536000, immutable")
// 3. Respons API publik, berubah cukup sering
w.Header().Set("Cache-Control", "public, max-age=0, s-maxage=30")
// 4. Halaman personal — WAJIB
w.Header().Set("Cache-Control", "private, no-store")
// 5. Apa pun yang memuat data sensitif
w.Header().Set("Cache-Control", "no-store")
w.Header().Set("Pragma", "no-cache")
Nomor 4 adalah baris yang paling mahal kalau salah. Halaman dasbor yang ditandai
public akan disimpan CloudFront dan disajikan ke pengguna berikutnya yang membuka URL yang
sama — lengkap dengan nama, saldo, dan riwayat pesanan orang lain. Ini bukan skenario teoretis; ia
terjadi setiap tahun di aplikasi produksi. Pasang default aman: private, no-store untuk
semua rute, lalu longgarkan secara eksplisit hanya pada rute publik.
// Default aman lewat middleware
func CacheDefault(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "private, no-store")
next.ServeHTTP(w, r) // handler publik menimpanya secara sadar
})
}
stale-while-revalidate: yang mengubah segalanya
Tanpa SWR (s-maxage=60):
detik 60 → cache kedaluwarsa
detik 60 → 5.000 permintaan bersamaan semuanya menembus ke origin
(cache stampede, versi CDN)
Dengan SWR (s-maxage=60, stale-while-revalidate=600):
detik 60 → CDN menyajikan versi lama SEKETIKA ke semua orang
detik 60 → SATU permintaan latar menyegarkannya
detik 61 → semua orang dapat versi baru
→ origin menerima 1 permintaan, bukan 5.000
Dan stale-if-error adalah asuransi pemadaman. Kalau aplikasimu mati, CloudFront
tetap menyajikan versi terakhir yang berhasil — pembaca anonim tidak melihat apa pun yang salah selama
masa itu. Untuk situs konten, ini mengubah pemadaman total jadi pemadaman yang hanya terasa oleh
penulis.
ETag dan 304
func (h *Handler) Artikel(w http.ResponseWriter, r *http.Request) {
a, err := h.artikel.Ambil(r.Context(), slug)
if err != nil {
h.tulisError(w, r, err)
return
}
etag := fmt.Sprintf(`"%x"`, sha256.Sum256([]byte(
a.Slug+a.Diubah.Format(time.RFC3339))))
w.Header().Set("ETag", etag)
w.Header().Set("Cache-Control", "public, max-age=60, s-maxage=300")
// Klien sudah punya versi ini: balas 304, tanpa body.
if r.Header.Get("If-None-Match") == etag {
w.WriteHeader(http.StatusNotModified)
return
}
...
}
304 menghemat bandwidth, bukan pekerjaan server. Kamu tetap harus mengambil data untuk
menghitung ETag-nya. Kalau tujuanmu mengurangi beban database, s-maxage yang menyelesaikannya
— permintaannya tidak pernah sampai. ETag melengkapi, bukan menggantikan.
Kunci cache CDN
Kunci cache CloudFront = host + path + (query/header/cookie yang DIPILIH)
❌ Meneruskan SEMUA query string:
/artikel?utm_source=fb&utm_campaign=x → entri cache terpisah
/artikel?utm_source=ig → entri lain lagi
→ rasio hit hancur; hampir semua permintaan tembus ke origin
✅ Teruskan hanya yang MEMENGARUHI keluaran:
/artikel?halaman=2 → ya
?utm_* , ?fbclid , ?gclid → abaikan
❌ Meneruskan semua cookie:
setiap pengunjung punya cookie analitik unik → 0% hit rate
✅ Teruskan hanya cookie sesi, DAN hanya pada rute yang membutuhkannya
Rasio hit CDN di bawah 90% pada situs konten hampir selalu berarti kunci cache-nya salah — biasanya karena query pelacakan atau cookie ikut masuk kunci. Ini pemeriksaan pertama saat trafik naik dan servermu ikut naik padahal seharusnya tidak.
Header Vary
// Konten berbeda per bahasa atau per encoding
w.Header().Set("Vary", "Accept-Encoding, Accept-Language")
// ❌ Vary: Cookie pada halaman publik = satu entri cache per pengunjung
// ❌ Vary: User-Agent = ribuan entri untuk konten yang sama
Urutan pemeriksaan saat trafik naik
- Rasio hit CDN. Di bawah 90% untuk situs konten berarti kunci cache bermasalah.
- Kueri per permintaan. Naik seiring jumlah baris = N+1 (Fase 4).
- p95 database. Kueri teratas biasanya menunjuk index yang hilang.
- Rasio hit cache aplikasi.
- Antrean pool database (
WaitCount). - Baru tambah task, dan baru pikirkan arsitektur.
Satu hal yang tidak akan diselesaikan infrastruktur: halaman yang butuh empat puluh kueri dan mengirim dua megabita HTML tetap mahal di runtime mana pun, di kelas instans apa pun, di belakang CDN mana pun. Ukur dulu, baru belanja.
Latihan: pasang Cache-Control: public, s-maxage=60, stale-while-revalidate=600
pada satu halaman publik dan verifikasi header X-Cache: Hit from cloudfront pada permintaan
kedua. Lalu tambahkan ?utm_source=x dan lihat apakah ia menghasilkan miss — kalau ya,
perbaiki kebijakan kunci cache-mu.
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.