π Topik
CDN & edge caching: cache key, purge, dan staleness
π― Tujuan Belajar
Besok bisa:
- Merancang cache key tanpa cache fragmentation.
- Memisahkan cache browser, CDN, dan origin.
- Memilih
purge,invalidate, atau TTL. - Menangani stale content tanpa merusak availability.
- Mencegah kebocoran data personalized melalui CDN.
- Mengukur cache hit ratio dan origin offload.
ποΈ Arsitektur & Alur Teknis
Client
β
βΌ
CDN Edge
ββ Cache HIT βββΊ Response langsung
ββ Cache MISS
β
βΌ
Origin Server
β
ββ 200 + body + cache headers
ββ 304 jika ETag cocok
ββ 5xx jika gagal
Request cache entry ditentukan oleh cache key.
Contoh cache key:
scheme://host/path?query
CDN dapat menambah:
- Query parameter tertentu.
Origin.Accept-Encoding.Accept-Language.- Cookie.
- Header custom.
- Geo atau device type.
- RSC headers pada Next.js.
Cloudflare mendokumentasikan default cache key yang mencakup URL penuh, Origin, beberapa method-override headers, dan forwarding headers [1].
Cache key terlalu sempit
/api/profile
Semua user bisa menerima response user pertama. Risiko: data leak.
Cache key terlalu lebar
/api/catalog?utm_source=x&session_id=abc×tamp=...
Setiap request membuat objek cache baru. Hit ratio turun.
Cache key sehat
/catalog?page=2&sort=price
Tracking parameter seperti utm_source tidak ikut jika tidak memengaruhi response.
Model TTL
Tiga waktu perlu dibedakan:
fresh TTL
β
ββ stale-while-revalidate window
β
ββ stale-if-error window
Contoh:
Cache-Control: public, max-age=60, s-maxage=300, \
stale-while-revalidate=30, stale-if-error=3600
Makna:
- Browser boleh memakai response selama
60detik. - Shared CDN boleh memakai response selama
300detik. - Setelah stale, CDN boleh mengirim response lama selama
30detik sambil revalidate. - Jika origin error, response lama boleh digunakan sampai
3600detik setelah stale.
stale-while-revalidate dan stale-if-error didefinisikan RFC 5861 [2].
max-age vs s-maxage
| Directive | Target | Contoh |
|---|---|---|
max-age |
Browser dan private cache | 60 detik |
s-maxage |
Shared cache seperti CDN | 300 detik |
stale-while-revalidate |
Grace period saat refresh | 30 detik |
stale-if-error |
Fallback saat origin gagal | 3600 detik |
immutable |
Asset URL fingerprinted | 1 tahun |
π» Kode Nyata / Konfigurasi
1. Response header berdasarkan tipe konten
# HTML publik, boleh sedikit stale
Cache-Control: public, max-age=60, s-maxage=300, stale-while-revalidate=30, stale-if-error=3600
ETag: "article-42-v7"
Vary: Accept-Encoding
# JavaScript/CSS dengan content hash
Cache-Control: public, max-age=31536000, immutable
# Data personalized
Cache-Control: private, no-store
# API publik dengan toleransi staleness
Cache-Control: public, max-age=0, s-maxage=10, stale-while-revalidate=30, stale-if-error=300
ETag: "catalog-v19"
Jangan cache response authenticated hanya karena response berstatus 200.
Authorization: Bearer ...
Cache-Control: private, no-store
2. Origin Python minimal yang bisa dijalankan
Python 3.11+:
#!/usr/bin/env python3
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from hashlib import sha256
from email.utils import formatdate
import json
import time
PORT = 8080
VERSION = "catalog-v19"
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path != "/catalog":
self.send_error(404)
return
body = json.dumps(
{"items": [{"id": 1, "name": "Keyboard"}], "version": VERSION},
separators=(",", ":"),
).encode()
etag = '"' + sha256(body).hexdigest()[:16] + '"'
if self.headers.get("If-None-Match") == etag:
self.send_response(304)
self.send_header("ETag", etag)
self.end_headers()
return
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.send_header(
"Cache-Control",
"public, max-age=0, s-maxage=10, "
"stale-while-revalidate=30, stale-if-error=300",
)
self.send_header("ETag", etag)
self.send_header("Date", formatdate(time.time(), usegmt=True))
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
if __name__ == "__main__":
ThreadingHTTPServer(("127.0.0.1", PORT), Handler).serve_forever()
Jalankan:
python3 origin.py
Uji response:
curl -i http://127.0.0.1:8080/catalog
Uji conditional request:
etag="$(curl -sD - http://127.0.0.1:8080/catalog -o /dev/null \
| tr -d '\r' | awk '$1 == "ETag:" {print $2}')"
curl -i -H "If-None-Match: $etag" http://127.0.0.1:8080/catalog
Expected status kedua:
HTTP/1.0 304 Not Modified
304 menghemat body transfer. CDN tetap dapat memperpanjang lifetime object setelah validasi.
3. Cache key dengan allowlist query
from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit
CACHE_QUERY = frozenset({"page", "sort", "filter"})
def cache_key(url: str) -> str:
parts = urlsplit(url)
query = urlencode(
sorted(
(key, value)
for key, value in parse_qsl(parts.query, keep_blank_values=True)
if key in CACHE_QUERY
)
)
return urlunsplit((parts.scheme, parts.netloc, parts.path, query, ""))
assert cache_key(
"https://example.test/catalog?utm_source=x&page=2"
) == "https://example.test/catalog?page=2"
assert cache_key(
"https://example.test/catalog?page=2&sort=price"
) == "https://example.test/catalog?page=2&sort=price"
print("cache key checks passed")
Jangan menghapus query parameter sebelum membuktikan parameter itu tidak mengubah response.
4. Vary
Jika bahasa memengaruhi response:
HTTP/1.1 200 OK
Cache-Control: public, s-maxage=600
Vary: Accept-Language
Content-Type: text/html
Jika device atau user ID memengaruhi response, jangan otomatis menambah header itu ke cache key. High-cardinality header dapat membuat hampir setiap request menjadi cache entry berbeda. Vercel secara eksplisit memperingatkan masalah ini [6].
Cloudflare mendukung normalisasi Accept, Accept-Language, dan Accept-Encoding untuk mengurangi cache fragmentation [3].
5. Cache tags untuk invalidasi grup
Origin:
HTTP/1.1 200 OK
Cache-Control: public, s-maxage=600
Cache-Tag: product-42,products
Saat produk berubah:
curl -X POST \
"https://api.cloudflare.com/client/v4/zones/$ZONE_ID/purge_cache" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{"tags":["product-42"]}'
Cloudflare mendukung sampai 100 tag per operasi purge berdasarkan dokumentasi yang diperbarui 21 Agustus 2026 [4].
Fastly memakai konsep serupa bernama Surrogate-Key dan mendukung soft purge [7].
6. Invalidate vs purge
| Operasi | Object | Request berikutnya | Cocok untuk |
|---|---|---|---|
| Purge | Dihapus | Fetch penuh dari origin | Data salah atau sensitif |
| Invalidate | Tetap ada, ditandai stale | Conditional revalidation | Update normal |
| TTL expiry | Menjadi stale alami | Bergantung konfigurasi | Konten toleran delay |
Cloudflare menjelaskan bahwa invalidation dapat memakai ETag atau Last-Modified; response 304 mempertahankan body cache [5].
π Angka & Benchmark
Formula dasar
cache hit ratio = cache hits / total requests Γ 100%
origin requests = total requests Γ (1 - hit ratio)
Traffic:
100 juta request/bulan
| Hit ratio | Request ke origin |
|---|---|
| 60% | 40 juta |
| 85% | 15 juta |
| 95% | 5 juta |
Tidak ada angka hit ratio universal. Static fingerprinted assets bisa mendekati hit ratio tinggi. Personalized API sering tidak layak di-cache.
Sumber industri melaporkan contoh hit ratio media di atas 90%, tetapi angka itu bukan benchmark universal dan bukan jaminan untuk aplikasi Hilmi [8]. Ukur trafik sendiri berdasarkan:
Age.CF-Cache-Status.X-Cache.x-vercel-cache.- Origin request rate.
- P95/P99 latency.
- Response size.
- Purge frequency.
βοΈ Trade-off & Alternatif
Long TTL + purge
s-maxage=86400
Kelebihan:
- Hit ratio tinggi.
- Origin load rendah.
- Latency stabil.
Kekurangan:
- Purge harus reliable.
- Event purge bisa gagal atau terlambat.
- Deploy tanpa versioning berisiko menyajikan asset lama.
Cocok untuk:
- Product catalog.
- Dokumentasi.
- Public landing page.
Short TTL + revalidation
s-maxage=10, stale-while-revalidate=30
Kelebihan:
- Data relatif fresh.
- Tidak perlu purge setiap perubahan.
Kekurangan:
- Request revalidation lebih banyak.
- Origin tetap menerima conditional requests.
- Behavior CDN berbeda antar provider.
Cocok untuk:
- News feed.
- Public API.
- Stock availability yang tidak real-time.
Versioned URL
/app.8f31c2.js
Kelebihan:
- TTL satu tahun aman.
- Tidak perlu purge asset lama.
- Deployment lebih deterministik.
Kekurangan:
- HTML harus menunjuk hash baru.
- Asset lama tetap tersimpan sampai expire atau purge.
Cocok untuk:
- JS.
- CSS.
- Font.
- Image transform hasil build.
CDN stale fallback
Kelebihan:
- Availability tetap tinggi saat origin down.
- Mengurangi latency saat revalidation.
Kekurangan:
- User dapat melihat data lama.
- Tidak cocok untuk saldo, permission, status pembayaran.
- Harus menentukan batas stale yang dapat diterima.
π Use Case Produksi & Failure Mode Umum
1. Cache poisoning
Penyebab:
- Header atau query yang memengaruhi response tidak masuk cache key.
- CDN dan origin menormalisasi URL secara berbeda.
- Response attacker tersimpan untuk user lain.
Mitigasi:
- Allowlist query parameter.
- Hindari cache response dengan input user.
- Normalisasi path di satu layer.
- Validasi
Host,Forwarded, dan rewrite headers. - Uji cache key dengan request berbeda.
2. Web cache deception
Contoh pola berbahaya:
/account/profile.css
Origin menganggap path sebagai halaman private. CDN menganggap suffix .css sebagai static asset dan menyimpan response.
Cloudflare dan PortSwigger mendokumentasikan risiko perbedaan parsing path antara cache dan origin [9][10].
Mitigasi:
/private/* β no-store
/api/* β no-store kecuali allowlist eksplisit
Jangan menentukan cacheability hanya berdasarkan ekstensi file.
3. Cache stampede
Banyak edge request melihat object expired bersamaan lalu semuanya memanggil origin.
Mitigasi:
stale-while-revalidate.- Request coalescing.
- Lock pada origin.
- TTL jitter untuk cache internal.
- Warm-up hanya untuk route penting.
4. Purge sebelum origin update
Urutan salah:
purge CDN
update origin
Window kosong menyebabkan request pertama meng-cache data lama atau error.
Urutan aman:
update origin
verify origin
purge/invalidate CDN
verify edge
5. RSC cache-key mismatch
Next.js memakai request variant untuk HTML dan React Server Components. Dokumentasi Next.js menyebut _rsc harus dipertahankan dalam cache key untuk static prefetch, bersama header next-router-prefetch pada alur prefetch [11].
Jangan membuat CDN rule yang membuang semua query parameter pada route Next.js tanpa memahami _rsc.
6. Deployment race
HTML baru menunjuk asset baru, tetapi edge berbeda masih menyajikan HTML lama atau RSC payload lama.
Mitigasi:
- Asset immutable dengan content hash.
- Retain asset lama selama masa cache.
- Purge HTML, bukan semua asset.
- Cache key mencakup deployment/build ID jika diperlukan.
π οΈ Latihan Praktis Kecil
Latihan A β Ukur cache behavior
Ambil endpoint publik milik sendiri:
URL='https://example.com/'
for i in 1 2 3; do
curl -sS -D - "$URL" -o /dev/null \
| grep -Ei '^(HTTP/|age:|cache-control:|etag:|vary:|x-cache:|cf-cache-status:|x-vercel-cache:)'
sleep 2
done
Catat:
- Status cache.
Age.Cache-Control.Vary.- Perubahan latency.
Latihan B β Test cache key fragmentation
Kirim URL sama dengan tracking parameter:
for query in \
'page=2' \
'page=2&utm_source=x' \
'page=2&utm_source=y' \
'page=2&sort=price'; do
curl -sS -o /dev/null -w "$query %{http_code} %{time_total}\n" \
"https://example.com/catalog?$query"
done
Jika utm_source membuat cache object terpisah, hapus hanya setelah memastikan tracking tidak memengaruhi response.
Latihan C β Simulasikan conditional request
python3 origin.py
Terminal lain:
curl -i http://127.0.0.1:8080/catalog
Salin nilai ETag, lalu:
curl -i \
-H 'If-None-Match: "PASTE_ETAG"' \
http://127.0.0.1:8080/catalog
Target: status 304 Not Modified.
β Cek Pemahaman
- Mengapa
Vary: Authorizationbiasanya lebih berbahaya daripada berguna? - Kapan memilih
invalidatedibandingpurge? - Mengapa asset fingerprinted dapat memakai
immutable, tetapi HTML biasanya tidak?
π Poin Kunci
- Cache key menentukan correctness dan hit ratio.
- Jangan cache personalized response di shared CDN.
- Query tracking dapat menyebabkan cache fragmentation.
Varyhanya boleh mencakup dimensi yang benar-benar mengubah response.s-maxagemengontrol shared cache;max-agemengontrol browser.stale-while-revalidatemenjaga latency, tetapi content bisa stale.stale-if-errormenjaga availability, tetapi dapat menyembunyikan perubahan.- Purge menghapus; invalidate mempertahankan object untuk revalidation.
- Cache tag lebih aman daripada purge seluruh zone.
- Asset URL dengan content hash mengurangi kebutuhan purge.
- Cache metrics harus diukur dari trafik nyata, bukan angka vendor.
Status pengetahuan
- DIKETAHUI: Format
Cache-Control, RFC 5861, cache tags,ETag/304, cache key behavior. - DIKETAHUI: Cloudflare cache-tag purge mendukung maksimal
100tag per operasi pada dokumentasi 21 Agustus 2026 [4]. - DIKETAHUI: Cloudflare mendokumentasikan cache key dan Vary normalization pada Oktober 2026 [1][3].
- DIKETAHUI: Next.js mendokumentasikan kebutuhan mempertahankan
_rscuntuk CDN caching [11]. - DIINFERENSI: Cache hit ratio naik jika parameter nonfungsional dikeluarkan dari key; hasil aktual bergantung distribusi URL dan TTL.
- BELUM PASTI: Dukungan detail
stale-while-revalidate, purge propagation, dan request coalescing berbeda antar provider, region, plan, serta konfigurasi. Verifikasi pada CDN yang dipakai sebelum menjadikannya kontrak produksi.
β‘οΈ Eksplorasi Lanjut
- CDN cache key untuk Next.js RSC dan PPR.
- Request coalescing serta cache stampede pada Redis/Varnish.
- Cache poisoning testing dengan Burp Suite.
Surrogate-Control,CDN-Cache-Control, danVercel-CDN-Cache-Control.- Edge-side includes dan fragment caching.
- Cost model origin egress berdasarkan hit ratio.
π Sumber
[1] Cloudflare. βCache Keys.β Dokumentasi diperbarui 22 Oktober 2026.
https://developers.cloudflare.com/cache/how-to/cache-keys/
[2] M. Nottingham. βRFC 5861: HTTP Cache-Control Extensions for Stale Content.β Mei 2010.
https://www.rfc-editor.org/info/rfc5861
[3] Cloudflare. βVary.β Dokumentasi Cache.
https://developers.cloudflare.com/cache/concepts/vary/
[4] Cloudflare. βPurge cache by cache-tags.β Dokumentasi diperbarui 21 Agustus 2026.
https://developers.cloudflare.com/cache/how-to/purge-cache/purge-by-tags/
[5] Cloudflare. βInvalidate cached content.β Dokumentasi diperbarui 29 September 2026.
https://developers.cloudflare.com/cache/guides/invalidate-cache/
[6] Vercel. βVercel CDN Cache.β
https://vercel.com/docs/caching/cdn-cache
[7] Fastly. βSoft purges.β
https://www.fastly.com/documentation/guides/full-site-delivery/purging/soft-purges/
[8] BlazingCDN. βHow a CDN Reduces Server Load and Bandwidth Costs.β Angka bersifat contoh industri, bukan benchmark universal.
https://blog.blazingcdn.com/en-us/how-to-use-a-cdn-to-reduce-server-load-and-bandwidth-costs
[9] PortSwigger. βWeb cache deception.β
https://portswigger.net/web-security/web-cache-deception
[10] Cloudflare. βCache Deception Armor.β
https://developers.cloudflare.com/cache/cache-security/cache-deception-armor/
[11] Next.js. βUsing a CDN with Next.js.β
https://nextjs.org/docs/app/guides/cdn-caching