Proxy Cache #
Mempersingkat waktu tanggap aplikasi dan mengurangi beban kerja server backend adalah prioritas utama dalam merancang infrastruktur web berkinerja tinggi. Salah satu metode paling efektif untuk mencapainya adalah dengan menerapkan mekanisme penyimpanan sementara (caching) pada lapisan reverse proxy. Dengan menyimpan salinan respons yang sering diminta langsung di Caddy, kita dapat langsung melayani permintaan berulang dari pengguna tanpa perlu menghubungi server backend sama sekali, memangkas latensi dari ratusan milidetik menjadi kurang dari satu milidetik.
Arsitektur Caching: Browser Cache vs Proxy Cache #
Untuk menerapkan strategi penyimpanan sementara yang sukses, kita harus memahami perbedaan arsitektur antara dua jenis lapisan caching utama:
flowchart TD
subgraph BrowserCache ["1. BROWSER CACHE (Sisi Klien)"]
direction LR
Browser1["Browser"] -->|"HIT"| Mem["Memori Lokal Browser"]
end
subgraph ProxyCache ["2. PROXY CACHE (Sisi Server)"]
direction LR
Browser2["Browser"] --> Caddy["Caddy Proxy (Cache Store)"]
Caddy -->|"MISS"| Backend["App Backend"]
end
1. Browser Cache (Client-Side Caching) #
- Tempat Penyimpanan: Memori lokal atau penyimpanan disk komputer milik pengguna (klien).
- Kendali: Dikontrol oleh server melalui instruksi header response seperti
Cache-Control. - Kelebihan: Sangat cepat karena tidak ada transmisi data melalui jaringan internet sama sekali.
- Kekurangan: Hanya bermanfaat untuk pengguna individual tersebut; cache tidak dapat dibagikan dengan pengguna lain.
2. Proxy Cache (Shared / Server-Side Caching) #
- Tempat Penyimpanan: Server reverse proxy Caddy itu sendiri (baik di RAM lokal maupun di database cache terdistribusi seperti Redis).
- Kendali: Dikontrol secara terpusat oleh konfigurasi Caddy dan header response backend kita.
- Kelebihan: Dapat dibagikan lintas pengguna (shared cache). Jika Pengguna A meminta halaman produk dan memicu pembuatan cache di Caddy, maka Pengguna B, C, dan seterusnya yang meminta halaman yang sama akan langsung dilayani dari cache Caddy tanpa membebani server backend aplikasi kita.
Berikut adalah gambaran aliran data dalam infrastruktur caching multi-layer yang ideal:
flowchart TD
User["Browser Klien"] -->|"1. Cek Lokal Cache"| BrowserCache{"Apakah ada di Browser?"}
BrowserCache -- Ya (HIT) --> ServeLocal["Sajikan Instan dari Browser"]
BrowserCache -- Tidak (MISS) --> CDN{"2. Cek CDN (Cloudflare)"}
CDN -- Ya (HIT) --> ServeCDN["Sajikan dari Edge CDN"]
CDN -- Tidak (MISS) --> Caddy{"3. Cek Caddy Proxy Cache"}
Caddy -- Ya (HIT) --> ServeCaddy["Sajikan dari Caddy Cache"]
Caddy -- Tidak (MISS) --> Backend["4. Proses di Backend Server"]
Backend -->|"Simpan di Caddy"| Caddy
Caddy -->|"Simpan di CDN"| CDN
CDN -->|"Simpan di Browser"| User
style Caddy stroke:#0288d1,stroke-width:2px
style Backend stroke:#43a047,stroke-width:2px
Mekanisme Caching File Statis (Built-in) #
Secara bawaan (out-of-the-box), Caddy tidak memerlukan konfigurasi tambahan untuk mengelola browser caching pada penyajian file statis (seperti gambar, file CSS, JavaScript, dan HTML). Caddy menggunakan kombinasi tajuk Cache-Control dan sistem penanda konten ETag (Entity Tag).
# Konfigurasi caching file statis yang optimal
example.com {
root * /var/www/dist
# 1. Aset statis dengan hash pada namanya (Vite / Webpack output)
# Contoh: main.a8f2c3.js -> aman di-cache selamanya karena namanya unik
@immutable_assets path_regexp \.[a-f0-9]{8,}\.(js|css|woff2?|png|jpg)$
header @immutable_assets Cache-Control "public, max-age=31536000, immutable"
# 2. Berkas HTML utama
# JANGAN cache HTML secara permanen agar perubahan aplikasi langsung terbaca
@html_files path *.html /
header @html_files Cache-Control "public, no-cache, must-revalidate"
file_server
}
Bagaimana ETag Menghemat Bandwidth? #
ETag adalah string hash unik yang dihasilkan oleh Caddy berdasarkan ukuran berkas dan waktu modifikasi terakhir file tersebut di disk.
- Pada request pertama, Caddy mengirimkan file beserta header
ETag: "w-39d2ab8f". - Browser menyimpan berkas dan ETag tersebut di cache lokalnya.
- Pada request kedua (setelah
max-agekedaluwarsa atau saat user menekan refresh), browser mengirimkan request kembali dengan menyertakan headerIf-None-Match: "w-39d2ab8f". - Caddy membaca header tersebut dan membandingkannya dengan kondisi file di disk. Jika file tidak berubah, Caddy tidak mengirimkan kembali file tersebut. Caddy hanya mengirimkan respon kosong dengan kode status
304 Not Modified. Ini sangat menghemat bandwidth jaringan kita.
Caching Konten Dinamis dengan Plugin cache-handler
#
Secara default, Caddy standar tidak menyertakan modul untuk meng-cache respon dinamis (seperti output JSON API) dari reverse proxy. Untuk menambahkan kemampuan ini, kita harus menggunakan plugin kustom cache-handler (yang dikembangkan oleh komunitas Caddy).
1. Kompilasi Caddy dengan Plugin #
Kita harus mengompilasi ulang binary Caddy kita dengan menyertakan plugin ini menggunakan utilitas xcaddy:
# Kompilasi Caddy dengan modul cache-handler
xcaddy build --with github.com/caddyserver/cache-handler
2. Konfigurasi Caddyfile dengan Penyimpanan Lokal (RAM) #
Setelah dikompilasi, kita aktifkan modul cache di blok opsi global dan terapkan pada blok reverse proxy kita:
# Opsi Global
{
# Inisialisasi modul cache
cache {
# Menggunakan penyimpanan memori lokal (RAM)
# Sangat cepat, namun hilang jika Caddy direstart
}
}
# Blok Situs
api.example.com {
# Terapkan cache pada path API tertentu saja
handle /api/v1/public/* {
cache {
# TTL (Time To Live) default jika backend tidak mengirim Cache-Control
ttl 10m
# Abaikan parameter kueri tertentu saat menentukan keunikan cache (cache key)
# key_suffix {query.utm_source}
}
reverse_proxy localhost:8080
}
# Path lain diteruskan langsung tanpa cache
handle {
reverse_proxy localhost:8080
}
}
3. Konfigurasi Produksi Menggunakan Redis Cluster #
Untuk infrastruktur berskala besar dengan beberapa server Caddy di belakang load balancer, penyimpanan memori lokal (RAM) tidak efektif karena data cache antar server Caddy akan berbeda. Kita harus menggunakan Redis sebagai media penyimpanan cache bersama (shared cache store).
Pada database Redis produksi, pastikan konfigurasi kebijakan penggusuran (eviction policy) diatur ke allkeys-lru (Least Recently Used) agar Redis secara otomatis menghapus cache lama yang jarang diakses ketika memori RAM server penuh:
# Contoh pengaturan redis.conf untuk kebutuhan cache
maxmemory 4gb
maxmemory-policy allkeys-lru
Berikut adalah contoh konfigurasi multi-container Docker Compose untuk menjalankan klaster Caddy dengan Redis cache:
# docker-compose.yml
version: "3.7"
services:
caddy:
image: my-custom-caddy-with-cache:latest
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
environment:
- REDIS_PASSWORD=super-secret-pass
depends_on:
- redis
redis:
image: redis:7-alpine
command: redis-server --requirepass super-secret-pass --maxmemory 2gb --maxmemory-policy allkeys-lru
volumes:
- redis_data:/data
volumes:
caddy_data:
redis_data:
Serta deklarasi Caddyfile yang menghubungkannya secara dinamis:
# Caddyfile
{
cache {
# backends {
# redis {
# host "redis"
# port 6379
# password {env.REDIS_PASSWORD}
# db 0
# }
# }
}
}
app.example.com {
reverse_proxy app:8080 {
# Proses proxy cache otomatis disimpan ke database Redis bersama
}
}
Strategi Caching API dan Konten Dinamis #
Menyimpan data API di dalam cache membutuhkan kehati-hatian tinggi. Kita tidak boleh meng-cache data pribadi milik pengguna (seperti halaman keranjang belanja atau detail profil user) secara publik karena data tersebut bisa bocor ke pengguna lain.
Berikut adalah panduan strategi penulisan header Cache-Control yang dikirimkan oleh backend aplikasi kita ke Caddy:
no-store: Melarang Caddy dan browser menyimpan data sama sekali. Wajib digunakan untuk data sensitif keuangan atau real-time.private: Memberitahu Caddy (sebagai shared cache) untuk tidak meng-cache data ini. Hanya browser pribadi milik pengguna bersangkutan yang boleh menyimpannya. Cocok untuk halaman profil user.public, max-age=60: Mengizinkan siapa saja (browser dan Caddy) meng-cache data ini selama 60 detik. Cocok untuk data katalog produk umum.stale-while-revalidate=<detik>: Strategi mutakhir di mana Caddy diizinkan menyajikan data cache yang sudah kedaluwarsa (stale) kepada pengguna secara instan, sementara di latar belakang Caddy secara asinkronus melakukan kueri ke backend untuk memperbarui data cache tersebut. Ini menghilangkan latensi tunggu bagi pengguna.
# Mengatur perilaku cache secara detail per endpoint
api.example.com {
# API Katalog Publik - Cache 5 menit, toleransi stale 1 menit
handle /api/v1/products* {
cache {
ttl 5m
default_cache_control "public, max-age=300, stale-while-revalidate=60"
}
reverse_proxy localhost:8080
}
# API Autentikasi dan Transaksi - JANGAN cache
handle /api/v1/auth/* {
reverse_proxy localhost:8080
}
handle /api/v1/checkout/* {
reverse_proxy localhost:8080
}
}
Teknik Invalidasi Cache (Cache Invalidation) #
“Hanya ada dua hal sulit dalam Ilmu Komputer: pembatalan cache (cache invalidation) dan penamaan sesuatu.” — Phil Karlton.
Ketika data di database berubah, kita harus segera membuang cache lama di Caddy agar pengguna tidak melihat data usang. Berikut adalah tiga metode yang dapat kita gunakan:
1. Cache Busting via Hash (File Statis) #
Merupakan metode standar untuk aplikasi frontend modern (Vite, React, Vue). Setiap kali kita melakukan kompilasi produksi, build tool secara otomatis menyisipkan kode hash unik ke dalam nama berkas (misal app.d8b2a3.js). Karena nama file berubah, URL otomatis berubah, memaksa browser dan Caddy memuat file baru tanpa terhalang cache lama.
2. Vary Header #
Header Vary memberitahu Caddy untuk memisahkan ruang penyimpanan cache berdasarkan isi header request tertentu yang dikirim klien.
# Caching berdasarkan header bahasa
api.example.com {
reverse_proxy localhost:8080 {
# Backend menyetel Vary: Accept-Language
# Caddy akan memisahkan cache untuk pengguna berbahasa Indonesia dan Inggris
header_down Vary "Accept-Language"
}
}
Anti-Pattern: Menggunakan Vary: User-Agent
#
// ANTI-PATTERN: Menyimpan cache terpisah berdasarkan User-Agent browser klien
Vary: User-Agent // JANGAN! Ini memicu fragmentasi ekstrim
// BENAR: Memilah konten seluler/desktop di tingkat routing Caddy
// lalu meng-cache dua versi tersebut secara eksplisit
Menyetel Vary: User-Agent adalah kesalahan umum yang berakibat fatal. Karena ada ribuan variasi string User-Agent unik di internet (berbeda versi browser, sistem operasi, dan perangkat), Caddy terpaksa membuat ribuan salinan cache terpisah untuk satu halaman yang sama. Ini menyebabkan Cache Hit Rate hancur mendekati 0% dan memori RAM server Caddy akan cepat habis akibat menyimpan duplikasi data yang tidak perlu.
3. Surrogate Keys / Purge Active Invalidation #
Jika menggunakan plugin cache tingkat lanjut (seperti integration Souin atau cache-handler modern), kita dapat menghapus cache secara terprogram dengan mengirimkan permintaan pembersihan (Purge) melalui API admin Caddy ketika ada aksi perubahan data di database aplikasi kita:
# Perintah membersihkan cache URL tertentu secara manual
curl -X PURGE https://api.example.com/api/v1/products/123
Integrasi Caddy di Belakang CDN (Cloudflare) #
Pada arsitektur skala industri, kita biasanya meletakkan CDN (seperti Cloudflare atau AWS CloudFront) di depan server Caddy kita. Kita harus menyinkronkan durasi cache di tingkat CDN dan browser klien menggunakan header terpisah:
max-age: Mengatur durasi cache di browser klien.s-maxage: Mengatur durasi cache khusus untuk shared proxy (seperti CDN). Browser akan mengabaikan nilai ini.
# Contoh sinkronisasi CDN
example.com {
# Set header instruksi cache koordinasi
header {
# Browser meng-cache 10 menit, Cloudflare CDN meng-cache 2 jam (7200 detik)
Cache-Control "public, max-age=600, s-maxage=7200"
}
reverse_proxy localhost:3000
}
Pembersihan Massal via Cache Tags (Cloudflare Purge API) #
Dengan Cloudflare, kita bisa menandai respon dari Caddy menggunakan tag khusus (Cache-Tag). Jika ada perubahan data massal pada kategori tertentu (misalnya produk diskon), kita cukup mengirimkan perintah pembersihan satu kali berdasarkan tag tersebut melalui API Cloudflare:
# Bersihkan cache Cloudflare berdasarkan tag 'kategori-elektronik'
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":["kategori-elektronik"]}'
Pemantauan Kinerja Cache (Hit Rate) #
Kita dapat menganalisis apakah konfigurasi cache kita berjalan efektif dengan memeriksa header response HTTP yang dikirimkan kembali oleh Caddy ke browser menggunakan perintah curl:
# Periksa header response dari Caddy
curl -I https://api.example.com/api/v1/products
Berikut adalah header penting yang perlu diperhatikan:
X-Cache: HIT: Menunjukkan permintaan dilayani langsung dari cache Caddy dalam beberapa mikrodetik (sukses).X-Cache: MISS: Permintaan tidak ada di cache, sehingga Caddy harus menghubungi backend untuk memproses data (wajar untuk request pertama).X-Cache-Hits: 12: Menunjukkan berapa kali berkas cache ini telah disajikan ke pengguna lain.Age: 120: Durasi (dalam detik) sejak data cache ini pertama kali dibuat di Caddy.
Menganalisis Cache Hit Rate dari Log JSON #
Kita dapat menghitung persentase efisiensi cache kita (Cache Hit Rate) dari file log JSON Caddy menggunakan perkakas jq:
# Hitung statistik HIT vs MISS dari access log Caddy
cat /var/log/caddy/access.log | jq -r '.resp_headers["X-Cache"][0] // "BYPASS"' | sort | uniq -c
Hasil keluaran contoh:
8420 HIT
1580 MISS
Dari hasil di atas, kita dapat menghitung: 8420 / (8420 + 1580) = 84.2%. Nilai di atas 80% menunjukkan bahwa konfigurasi caching kita sudah berjalan dengan sangat optimal untuk menghemat sumber daya backend.
Bypass Cache untuk Debugging #
Saat proses pengembangan aplikasi, terkadang kita ingin melewati (bypass) cache untuk melihat perubahan kode backend secara langsung tanpa membersihkan cache server. Kita dapat membuat aturan kondisional menggunakan matcher header:
# Konfigurasi bypass cache debugging
api.example.com {
# Jika klien mengirim header kustom X-No-Cache: true
@bypass_cache header X-No-Cache true
# Langsung teruskan ke backend tanpa menyentuh modul cache
handle @bypass_cache {
reverse_proxy localhost:8080
}
# Lalu lintas normal menggunakan cache
handle {
cache {
ttl 1h
}
reverse_proxy localhost:8080
}
}
Perintah pengujian dari terminal komputer kita:
# Mengambil data terbaru langsung dari backend bypass cache
curl -H "X-No-Cache: true" https://api.example.com/api/v1/products
Ringkasan #
- Dua Lapisan Caching: Bedakan antara Browser Cache (sisi klien, cepat tapi privat) dan Proxy Cache (sisi server Caddy, dapat dibagikan lintas pengguna).
- ETag & 304: Caddy otomatis mengelola validasi ETag untuk file statis untuk menghemat penggunaan bandwidth jaringan melalui respon
304 Not Modified.- Kompilasi Plugin: Penyimpanan cache dinamis API memerlukan kompilasi Caddy kustom dengan menyertakan plugin
cache-handlerviaxcaddy.- Keamanan Data: Pastikan data dinamis yang bersifat pribadi/sensitif dilabeli dengan
Cache-Control: privateatauno-storeuntuk mencegah kebocoran data pengguna.- Stale-While-Revalidate: Strategi andal untuk menyajikan data cache kedaluwarsa secara instan ke klien selagi Caddy memperbarui data tersebut secara asinkronus di latar belakang.
- Hit Rate Analysis: Periksa efisiensi cache secara rutin melalui log JSON menggunakan perkakas
jquntuk memastikan target persentase Cache Hit Rate berada di atas 80%.