Caching #
Dalam dunia optimasi performa web, ada satu aturan emas yang tidak terbantahkan: operasi tercepat adalah operasi yang tidak pernah dilakukan. Caching adalah perwujudan nyata dari aturan emas ini. Daripada membiarkan server backend kita memproses kueri database yang sama, merender halaman HTML yang sama, atau membaca file statis yang sama secara berulang-ulang untuk setiap request pengunjung, kita dapat menyimpan hasil pemrosesan tersebut di tempat yang cepat diakses dan menyajikannya secara instan.
Nginx menyediakan sistem caching yang sangat kuat dan fleksibel, terbagi dalam dua kategori utama yang saling melengkapi: Client-Side Browser Caching (mengarahkan browser pengguna untuk menyimpan aset lokal) dan Server-Side Proxy Caching (menyimpan respons dari server backend hulu di dalam penyimpanan Nginx). Di artikel ini, kita akan membahas strategi penerapan kedua jenis caching ini secara mendalam, membedah konfigurasi proxy_cache_path, merancang aturan bypass cache untuk data dinamis sensitif, memanfaatkan fitur stale cache saat backend down, serta mencegah masalah thundering herd di server produksi.
Strategi Caching: Dua Lapis Pertahanan #
Untuk mengoptimalkan performa secara maksimal, kita harus menerapkan caching pada dua lapis pertahanan yang berbeda:
- Lapis Pertama: Browser Caching (Klien): Respons disimpan langsung di memori komputer pengguna. Browser tidak perlu mengirimkan request jaringan sama sekali ke internet saat pengguna berpindah halaman, sehingga waktu pemuatan terasa instan (0 milidetik latensi).
- Lapis Kedua: Proxy Caching (Server): Jika browser klien terpaksa mengirimkan request ke server kita (misalnya karena cache browser kosong atau pengguna menekan reload), Nginx akan memeriksa penyimpanan cache lokalnya terlebih dahulu. Jika data tersedia, Nginx langsung mengembalikan respons tanpa membebani server backend aplikasi kita.
Client-Side Browser Caching #
Browser caching dikontrol sepenuhnya dengan mengirimkan header respons HTTP tertentu dari Nginx ke browser klien. Header utama yang kita gunakan adalah Cache-Control dan Expires.
Berikut adalah konfigurasi Nginx untuk menginstruksikan browser agar menyimpan aset statis kita secara efisien:
server {
listen 80;
server_name example.com;
root /var/www/html;
# 1. Aset Statis yang Tidak Pernah Berubah (Gaya & Kode Program)
# File ini dihasilkan dengan hash konten unik (misal: main.a7f8b9.js) oleh build tool
location ~* \.(css|js|woff2|woff|ttf|otf|eot)$ {
expires 1y; # Berlaku 1 tahun
add_header Cache-Control "public, no-transform, immutable";
access_log off;
}
# 2. File Media & Gambar (Jarang Berubah)
location ~* \.(jpg|jpeg|png|gif|webp|svg|ico)$ {
expires 30d; # Berlaku 30 hari
add_header Cache-Control "public, no-transform";
access_log off;
}
# 3. Halaman HTML Dinamis & File Manifest (Wajib Selalu Fresh)
location ~* \.(html|htm|json)$ {
expires -1; # Kadaluwarsa instan
add_header Cache-Control "no-store, no-cache, must-revalidate";
}
}
Membedah Direktif Cache-Control #
public: Menyatakan bahwa respons boleh disimpan oleh browser klien maupun oleh proxy publik perantara (seperti CDN atau ISP caching).no-transform: Melarang proxy perantara untuk mengompres atau mengubah format gambar/aset kita secara sepihak untuk menghemat bandwidth mereka.immutable: Memberitahu browser bahwa file ini tidak akan pernah diubah selama masa aktifnya. Browser modern tidak akan mengirim request validasiIf-None-Match(304 Not Modified) ke server kita sama sekali meskipun pengguna menekan tombol F5/Refresh. Ini menghemat pemrosesan koneksi SSL/TCP di Nginx.no-store: Browser dilarang keras menyimpan berkas ini ke dalam cache lokal (sangat krusial untuk halaman admin atau transaksi bank).no-cache: Browser boleh menyimpan berkas, namun wajib mengirimkan request validasi ke server pada setiap kunjungan untuk memastikan apakah berkas telah berubah di server.
Server-Side Proxy Caching (proxy_cache)
#
Ketika request terpaksa harus masuk ke server kita, kita ingin meminimalkan beban di sisi aplikasi backend. Nginx dapat meng-cache respons dari backend HTTP di penyimpanan disk lokal server kita.
Berikut adalah diagram alur keputusan caching di Nginx:
flowchart TD
Request["Klien Mengirim Request"] --> CheckBypass{"Apakah Request Memenuhi<br/>Syarat Bypass Cache?"}
CheckBypass -->|Ya: Login/Cookie/Non-GET| PassBackend["Minta Data Langsung ke Backend"]
CheckBypass -->|Tidak| CheckCache{"Apakah Data Ada di Cache Disk?"}
CheckCache -->|Ya: Cache HIT| ReturnClient["Kembalikan Respons dari Cache"]
CheckCache -->|Tidak: Cache MISS| FetchBackend["Minta Data ke Backend"]
FetchBackend --> SaveCache["Simpan Respons ke Cache Disk"]
SaveCache --> ReturnClient2["Kembalikan Respons ke Klien"]
PassBackend --> ReturnClient2
classDef default fill:#f9f9f9,stroke:#d1d5db,stroke-width:1px,color:#111827;
classDef hitStyle fill:#d1fae5,stroke:#10b981,stroke-width:2px,color:#065f46;
classDef missStyle fill:#fee2e2,stroke:#ef4444,stroke-width:2px,color:#991b1b;
classDef processStyle fill:#eff6ff,stroke:#3b82f6,stroke-width:2px,color:#1e3a8a;
class ReturnClient hitStyle;
class CheckCache,CheckBypass processStyle;
class FetchBackend,PassBackend missStyle;1. Mendefinisikan Zona Caching (proxy_cache_path)
#
Sama seperti direktif log format kustom, konfigurasi zona penyimpanan cache wajib dideklarasikan di level konteks http global. Kita menentukan folder penyimpanan lokal di disk server serta alokasi memori RAM untuk meng-indeks kunci cache tersebut.
http {
# Konfigurasi Zona Cache Proxy
proxy_cache_path /var/cache/nginx/app_cache
levels=1:2
keys_zone=my_cache_zone:10m
max_size=2g
inactive=60m
use_temp_path=off;
}
Mari kita bedah parameter di atas:
/var/cache/nginx/app_cache: Direktori fisik di disk server kita tempat file cache akan disimpan. Pastikan user Nginx (www-data) memiliki izin tulis (write permission) ke folder ini.levels=1:2: Menentukan struktur subdirektori penyimpanan cache. Nilai1:2berarti Nginx akan membuat folder bertingkat (misal:/app_cache/c/29/filename). Ini sangat penting demi performa sistem berkas Linux, karena menaruh ratusan ribu file di dalam satu direktori tunggal akan memperlambat operasi I/O disk secara ekstrem.keys_zone=my_cache_zone:10m: Membuat zona memori bersama (shared memory zone) bernamamy_cache_zonesebesar 10 Megabyte. Zona memori ini digunakan oleh Nginx untuk menyimpan daftar indeks metadata kunci cache (cache keys). Memori 10MB cukup untuk menampung sekitar 80.000 kunci indeks.max_size=2g: Batas maksimum ukuran penyimpanan cache di disk (2 Gigabyte). Jika ukuran cache melebihi batas ini, proses pembasmi cache internal Nginx (cache manager) akan otomatis menghapus file cache yang paling jarang diakses menggunakan algoritma LRU (Least Recently Used).inactive=60m: Menentukan batas kedaluwarsa ketidakaktifan data. Jika suatu berkas cache tidak diakses sama sekali selama 60 menit oleh klien, berkas tersebut akan langsung dihapus dari disk, terlepas dari masa validitas resminya.use_temp_path=off: Memaksa Nginx menulis file cache langsung ke direktori tujuan akhir tanpa menulisnya ke folder temp terlebih dahulu. Ini menghemat pemborosan siklus penulisan I/O disk.
2. Mengaktifkan Cache pada Server Block #
Setelah dideklarasikan di level http, kita dapat mengaktifkan cache pada server block virtual host kita:
server {
listen 80;
server_name api.unisbadri.com;
location /api/v1/public/ {
# Aktifkan zona cache yang telah kita buat
proxy_cache my_cache_zone;
# Tentukan format kunci cache (digunakan untuk mencocokkan request)
proxy_cache_key "$request_method$scheme$host$request_uri";
# Atur validitas waktu simpan cache berdasarkan status code respons backend
proxy_cache_valid 200 301 302 10m; # Status 200, 301, 302 disimpan selama 10 menit
proxy_cache_valid 404 1m; # Status 404 disimpan selama 1 menit
# Tambahkan header HTTP debug ke klien untuk memantau status cache
add_header X-Cache-Status $upstream_cache_status always;
proxy_pass http://backend_upstream;
}
}
Cache Bypass: Kapan Mengabaikan Cache? #
Penerapan cache secara agresif dapat memicu kebocoran data pribadi jika kita salah mengaturnya. Kita dilarang keras menyajikan cache untuk request yang berisi data sensitif dinamis (seperti keranjang belanja pengguna, halaman profil akun bank, atau request yang membutuhkan otentikasi JWT).
Nginx menyediakan dua direktif untuk mengontrol pengecualian ini secara dinamis:
proxy_cache_bypass: Menentukan kondisi di mana Nginx tidak akan membaca dari cache dan akan meneruskan request langsung ke backend. Namun, hasil respons dari backend tetap boleh disimpan ke cache untuk request lain.proxy_no_cache: Menentukan kondisi di mana respons dari backend tidak boleh ditulis ke dalam penyimpanan cache di disk.
Kedua direktif ini menerima variabel. Jika nilai variabel tersebut tidak kosong ("") dan tidak bernilai 0, maka aturan pengecualian aktif.
Konfigurasi Keamanan Caching API Produksi #
Berikut adalah contoh konfigurasi standar industri untuk memisahkan lalu lintas yang boleh di-cache dengan lalu lintas dinamis terproteksi:
location /api/ {
proxy_cache my_cache_zone;
proxy_cache_key "$request_method$scheme$host$request_uri";
proxy_cache_valid 200 5m;
# 1. BYPASS cache jika klien mengirimkan Session Cookie atau Token Otorisasi
proxy_cache_bypass $http_authorization $cookie_session_id;
proxy_no_cache $http_authorization $cookie_session_id;
# 2. BYPASS cache untuk seluruh request selain GET dan HEAD (POST, PUT, DELETE, dll.)
# (Kita tidak boleh men-cache request yang mengubah data database)
set $bypass_caching 0;
if ($request_method != GET) {
set $bypass_caching 1;
}
if ($request_method != HEAD) {
set $bypass_caching 1;
}
proxy_cache_bypass $bypass_caching;
proxy_no_cache $bypass_caching;
add_header X-Cache-Status $upstream_cache_status always;
proxy_pass http://backend_upstream;
}
Stale Caching untuk Ketersediaan Tinggi (proxy_cache_use_stale)
#
Salah satu fitur Nginx yang paling revolusioner dalam meningkatkan keandalan sistem (reliability) adalah kemampuannya untuk menyajikan berkas cache yang sudah kedaluwarsa (stale cache) ketika server backend aplikasi kita sedang bermasalah.
Misalkan kita menetapkan validitas cache selama 5 menit. Pada menit ke-6, backend aplikasi Node.js kita mengalami crash total. Secara default, Nginx akan mengembalikan error 502 Bad Gateway ke seluruh pengunjung. Namun, dengan mengaktifkan stale caching, Nginx akan mendeteksi masalah tersebut dan mengembalikan versi cache yang dimilikinya, sehingga pengguna tetap mendapatkan data halaman web dengan normal.
location / {
proxy_cache my_cache_zone;
proxy_cache_valid 200 5m;
# Sajikan cache kedaluwarsa jika backend mengalami error, timeout, atau sedang di-update
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
# Hubungi backend kembali untuk memvalidasi cache jika browser memaksa bypass
proxy_cache_revalidate on;
proxy_pass http://backend_upstream;
}
Mencegah Kebanjiran Request (Thundering Herd / Cache Stampede) #
Ketika sebuah berkas cache yang sangat populer kedaluwarsa (misalnya halaman beranda situs berita dengan 10.000 pengunjung aktif), secara default Nginx akan meneruskan seluruh 10.000 request yang masuk secara simultan tersebut langsung ke backend server. Lonjakan trafik tiba-tiba ini dikenal sebagai Thundering Herd atau Cache Stampede, yang sering kali membuat database backend kewalahan dan langsung mengalami crash.
Nginx mengatasi masalah ini dengan menyediakan direktif proxy_cache_lock.
location / {
proxy_cache my_cache_zone;
proxy_cache_valid 200 5m;
# Aktifkan penguncian cache
proxy_cache_lock on;
# Batas waktu tunggu maksimal request lainnya di antrean Nginx
proxy_cache_lock_timeout 5s;
proxy_pass http://backend_upstream;
}
Bagaimana proxy_cache_lock Bekerja?
#
Ketika cache kedaluwarsa dan 10.000 request masuk bersamaan:
- Nginx mengunci antrean, mengambil satu request pertama saja, dan meneruskannya ke backend untuk mengambil data baru dan memperbarui cache.
- Sedangkan 9.999 request lainnya ditahan di dalam antrean memori Nginx.
- Setelah request pertama selesai dan cache baru ditulis ke disk, Nginx segera melepas kunci dan menyajikan data cache baru tersebut untuk 9.999 request yang sedang mengantre tadi.
- Ini menjamin backend kita hanya menerima tepat satu request pembaruan saja, menjaga server kita tetap stabil.
Memantau dan Menganalisis Status Caching #
Kita dapat memantau efektivitas konfigurasi cache kita dengan menganalisis nilai dari variabel $upstream_cache_status yang kita sisipkan ke dalam respons header:
HIT: Respons berhasil diambil langsung dari cache di disk Nginx tanpa menyentuh backend sama sekali (performa terbaik).MISS: Data tidak ditemukan di cache (karena request baru atau cache sudah dihapus). Request diteruskan ke backend dan hasilnya disimpan ke cache.EXPIRED: Data ditemukan di cache, namun masa validitasnya sudah kedaluwarsa. Request diteruskan ke backend untuk mengambil data terbaru dan memperbarui cache.BYPASS: Request melewati cache karena memenuhi syarat direktifproxy_cache_bypass(misal karena user sedang login).STALE: Backend mengalami error/timeout, dan Nginx mengembalikan data cache kedaluwarsa yang dimilikinya demi menjaga situs tetap aktif.UPDATING: Cache sedang diperbarui oleh request lain yang memegang kunciproxy_cache_lock. Klien saat ini disajikan stale cache sementara waktu.
Analisis Hit Rate dari Terminal #
Kita dapat menghitung rasio keefektifan cache (cache hit rate) situs kita dengan mengekstrak data access log (jika kita merekam status cache di access log kita):
# Hitung frekuensi masing-masing status cache
awk '{print $NF}' /var/log/nginx/access.log | sort | uniq -c | sort -rn
Rasio hit rate produksi yang sehat untuk endpoint yang bisa di-cache adalah di atas 80%. Jika hit rate kita rendah, periksa apakah format kunci $request_uri kita terlalu spesifik (misalnya menyertakan parameter query string acak unik user) yang membuat Nginx memperlakukan setiap request sebagai data yang berbeda.
Ringkasan dan Praktik Terbaik #
- Gunakan Immutable pada Aset Ber-Hash: Terapkan
Cache-Control "public, immutable"hanya pada berkas statis yang nama filenya menyertakan hash unik untuk memangkas lalu lintas jaringan validasi.- Aktifkan proxy_cache_lock: Selalu gunakan
proxy_cache_lock on;pada server dengan lalu lintas tinggi untuk melindungi server backend dari serangan Cache Stampede.- Terapkan stale caching: Gunakan
proxy_cache_use_staleuntuk meningkatkan stabilitas ketersediaan situs web kita ketika backend mengalami kegagalan.- Amankan Caching dengan Bypass: Selalu bypass cache untuk request non-GET (POST, PUT, DELETE) dan request yang menyertakan token otentikasi klien.
- Gunakan Disk Cepat: Tempatkan direktori
proxy_cache_pathpada media penyimpanan SSD atau memori RAM (tmpfs) untuk kecepatan baca/tulis data cache maksimal.