Lompat ke konten
19 menit bacamateri 35 dari 96

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.

Ditinjau 26 Oktober 2026

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 usangSampai membuat 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.

  1. Hitung build bersamaan saat key kedaluwarsa tanpa pengaman.
  2. Hitung dengan single-flight per proses, lalu dengan kunci bersama.
  3. 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

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.

Isi · System Design