Tangani stampede dan hot key
Saat satu key populer kedaluwarsa, ratusan request menghitung ulang hal yang sama. Gabungkan, sebar, atau sajikan versi usang yang dibatasi.
Pertanyaan
Apa yang terjadi di sumber data saat satu key yang sangat populer, atau banyak key sekaligus, kedaluwarsa pada waktu yang sama?
Setelah baca ini, kamu bisa menghitung ukuran lonjakan akibat satu key kedaluwarsa dan memilih cara meredamnya.
Prasyarat dan contoh
Kamu sudah paham TTL dan pengisian ulang (M06.04), serta konkurensi request dan batas koneksi (M02). Pada sistem ini satu aplikasi bisa menangani ratusan request bersamaan, tiap request bekerja sendiri.
Contoh: halaman utama sebuah situs berita memakai satu key beranda:v2 dengan TTL 5 menit.
Membangunnya butuh 400 ms dan beberapa query berat. Trafik ke key itu 1.500 request per detik.
Pada detik kedaluwarsa, semua request yang tiba selama 400 ms berikutnya melihat miss:
requestMiss ≈ rps × waktuBangun = 1.500 × 0,4 = 600 build bersamaan
Enam ratus build berat jalan serentak, basis data melambat, build jadi makin lama (misalnya 2 detik), yang berarti lebih banyak request miss, dan seterusnya. Itu stampede (thundering herd).
Mekanisme
1. Gabungkan request (single-flight). Hanya satu pemanggil yang membangun, yang lain menunggu
hasilnya. Dalam satu proses, cukup peta key -> promise yang sedang berjalan. Antar proses
butuh kunci pendek di cache bersama:
if cache.setnx("lock:" + k, id, ttl=5s): bangun, set cache, lepas kunci
else: tunggu sebentar lalu baca cache lagi
TTL pada kunci itu wajib. Tanpa itu, proses yang mati saat membangun mengunci key selamanya. Pemanggil yang menunggu juga perlu batas waktu: setelah 1 sampai 2 detik, kembalikan versi usang atau error, jangan menunggu tanpa akhir.
2. Jitter pada TTL. Ini untuk masalah banyak key sekaligus. Kalau 200.000 key diisi bersama saat deploy dengan TTL tepat 300 detik, semuanya habis di detik yang sama. Tambahkan acak:
ttl = 300 × (1 + acak(−0,1 ; +0,1)) // 270 sampai 330 detik
Kedaluwarsa tersebar di jendela 60 detik, sehingga lonjakan puncak turun sekitar 60 kali lipat dibanding serentak, dengan asumsi sebaran seragam.
3. Pemanasan cache. Isi key yang pasti panas sebelum trafik datang: saat deploy, setelah cache dikosongkan, atau sebelum event terjadwal. Isi bertahap dengan laju terbatas, bukan sekaligus.
4. Stale-while-revalidate yang dibatasi. Simpan dua waktu: segarSampai dan usangSampai.
Setelah segarSampai, kembalikan nilai lama ke pembaca dan picu satu pembaruan di latar belakang.
Batas usangSampai (misalnya 60 detik setelahnya) wajib ada. Tanpa batas, kegagalan terus-menerus
berarti nilai lama tampil tanpa akhir.
Hot key. Satu key bisa membebani satu node cache walau total kapasitas masih longgar. Kalau
beranda:v2 menerima 50.000 request per detik dan satu node Redis nyaman di sekitar 100.000
operasi sederhana, angkanya masih ada ruang, tapi nilai 200 KB dikali 50.000 = 10 GB/detik, yang
jauh melampaui bandwidth satu node. Redamannya: cache lokal di proses untuk key itu dengan TTL
beberapa detik, atau menyalin key ke beberapa node (beranda:v2#0..#7) dan membaca salah satu
secara acak.
Saat rancangan ini gagal
- Single-flight hanya per proses. Dengan 30 instance, tiap instance membangun sekali: 30 build bersamaan, bukan 1. Lebih baik dari 600 tapi masih bisa membebani. Pakai kunci bersama kalau build-nya berat.
- Kunci tidak punya TTL atau TTL lebih pendek dari waktu bangun. Kunci kedaluwarsa saat build berjalan dan pemanggil kedua mulai membangun juga.
- Pembangun gagal. Semua penunggu menunggu pembangun yang error. Cache negatif untuk error (beberapa detik) atau kembalikan nilai usang supaya penunggu tidak antre tanpa batas.
- Jitter terlalu kecil. ±1% pada 300 detik hanya menyebar 6 detik, belum meratakan lonjakan.
- Usang tanpa batas. Stale-while-revalidate tanpa
usangSampaimembuat harga lama tampil berjam-jam saat sumber data bermasalah. - Pemanasan serentak. Memuat 2 juta key sekaligus justru membuat stampede buatan sendiri.
Contoh tandingan: tim menambahkan kunci tapi tidak memberi batas tunggu. Saat pembangun macet karena query lambat, 1.500 request per detik menumpuk sebagai koneksi yang menunggu. Dalam 10 detik ada 15.000 koneksi tertahan, pool habis, dan seluruh situs mati padahal hanya satu key yang bermasalah.
Latihan
Key promo:aktif menerima 500 request per detik, TTL 120 detik, dan membangunnya butuh 300 ms.
Aplikasi punya 10 instance dan connection pool DB total 40 koneksi.
- Hitung build bersamaan saat key kedaluwarsa tanpa pengaman.
- Hitung dengan single-flight per proses, lalu dengan kunci bersama.
- Tentukan satu pengaman tambahan yang menjaga pembaca tetap cepat saat DB lambat.
Lihat pembahasan
Tanpa pengaman: 500 × 0,3 = 150 build bersamaan. Pool DB cuma 40, jadi 110 build antre dan waktu bangun memanjang, yang menaikkan jumlah miss lagi.
Single-flight per proses: tiap instance membangun sekali, jadi 10 build bersamaan. Muat dalam 40 koneksi, dan menambah 7 permintaan DB per detik pada rata-rata (10 build per 120 detik tidak berarti). Dengan kunci bersama: 1 build.
Pengaman tambahan: stale-while-revalidate dengan usangSampai 30 detik. Pembaca selama masa itu
menerima nilai lama sementara satu pembangun bekerja. Kalau pembangun gagal, pembaca tetap cepat
dan setelah 30 detik sistem mengembalikan error yang jelas, bukan data lama selamanya.
Terkait
- Sebelumnya: M06.04 Batasi data usang dan penggunaan memori.
- Lanjut ke M06.06 Bertahan saat cache mati.
- Retry dan penggandaan beban (M03) serta batas koneksi (M02) belum tersedia di sini. Hot key pada partisi dibahas di M07.
Catatan
- Asumsi: waktu bangun tetap, dan request datang merata selama jendela miss. Pada kenyataan waktu bangun naik saat DB terbebani.
- Yang tidak dibahas: probabilistic early expiration dan cache klaster dengan replikasi.
- Perkiraan 60 kali lipat dari jitter berlaku untuk sebaran seragam, bukan jaminan.
- Terakhir ditinjau: 26 Oktober 2026.