Cache Stampede — este o creștere avalansată a cererilor către sursa de date care apare atunci când mai mulți clienți sau fire de execuție ratează simultan cache-ul. În aplicațiile mobile, Cache Stampede apare atunci când o intrare populară din cache expiră și sute de dispozitive încearcă simultan să reîncarce datele, provocând supraîncărcarea serverului. Conform cercetării Medium Engineering (2024), protecția configurată corect împotriva Cache Stampede reduce încărcarea de vârf a serverului cu până la 95%.
Principalele puncte
Cache Stampede (cunoscut și ca cache thundering herd) — este o situație în care un număr mare de cereri descoperă simultan o ratare a cache-ului și se îndreaptă către sursa de date. Acest lucru creează o încărcare de vârf care poate duce la degradarea sau defectarea completă a sistemului.
Scenariu tipic: o aplicație mobilă stochează în cache o listă comună de produse cu TTL = 5 minute. La ora 14:00 cache-ul expiră. 500 de utilizatori activi deschid simultan catalogul, fiecare descoperă cache-ul gol și toate cele 500 de cereri merg către server. Baza de date sau API-ul extern nu face față fluxului brusc, pagina se încarcă 10-15 secunde, o parte din cereri ajung în timeout.
Conform datelor Vattani et al. (SOCC, 2015), Cache Stampede apare în orice strat de cache cu intrări care expiră, inclusiv CDN, Redis, Memcached, cache-ul HTTP al browserului și cache-ul în memorie al aplicației. Cu cât intrarea este mai populară — cu atât potențialul prejudiciu de la stampede este mai mare.
def mutex_get(key, lock_timeout=5):
cached = cache.get(key)
if cached is not None:
return cached
if lock.acquire(key, lock_timeout):
data = source.load(key)
cache.set(key, data)
lock.release(key)
return data
sleep(0.01)
return mutex_get(key, lock_timeout)
Acest exemplu demonstrează protecția prin mutex: doar primul fir de execuție actualizează cache-ul, celelalte așteaptă rezultatul gata. Mecanismul de blocare — cea mai simplă, dar eficientă metodă de prevenire a stampede-ului pentru încărcări moderate.
Expirarea în masă a TTL — principala cauză a Cache Stampede. Când o intrare populară din cache are același TTL pentru toți clienții, toți descoperă simultan absența acesteia. Acest lucru este tipic pentru datele comune: cursuri de schimb, lista țărilor, configurația de bază a aplicației.
Colapsul la repornirea serverului — dacă Redis sau Memcached este resetat, întregul cache devine gol. La primul vârf de trafic, toate cererile merg către baza de date. Conform Amazon AWS Architecture Blog (2024), se recomandă încălzirea cache-ului după repornire: încărcarea treptată a intrărilor populare pentru a evita creșterea bruscă a încărcării.
Eroare în logica de invalidare — când dezvoltatorul curăță cache-ul pentru toți utilizatorii la modificarea unui element. De exemplu, într-o aplicație de știri, la publicarea unui articol, întreaga listă de știri este invalidată. Sute de utilizatori descoperă simultan cache-ul gol — și serverul cade. Invalidarea punctuală (doar a intrării modificate) elimină această problemă.
Pornirea la rece a aplicației — pe dispozitivele mobile, cache-ul există doar în memoria procesului. După repornirea aplicației, cache-ul este gol și toate cererile merg către server. Soluție: salvarea cache-ului pe disc între sesiuni (Room, SQLite) și utilizarea încărcării prealabile a datelor cheie la pornire.
Ideea metodei — la ratarea cache-ului, primul fir de execuție preia blocarea (lock) și începe încărcarea datelor din sursă. Celelalte fire așteaptă finalizarea blocării și citesc cache-ul actualizat. Blocarea poate fi implementată prin Redis SETNX, ZooKeeper sau chiar un mutex în memorie.
Parametrul cheie — lock_timeout. Dacă este prea scurt, primul fir poate să nu reușească să actualizeze cache-ul, iar al doilea fir va încerca și el să încarce datele, creând un mini-stampede. Dacă este prea lung — clienții așteaptă mai mult decât necesar. Valoare recomandată: 2-5 secunde pentru cereri tipice de bază de date.
Problema unui singur fir — la defectarea primului fir (excepție, timeout), blocarea rămâne preluată și toate celelalte fire așteaptă până la expirarea lock_timeout. Soluție: ștergerea blocării în blocul finally. Conform Redis Best Practices (2025), se recomandă utilizarea scripturilor Lua pentru setarea și eliberarea atomică a blocărilor.
-- Atomic lock acquisition script for Redis
if redis.call("SET", KEYS[1], "locked", "NX", "EX", ARGV[1]) then
return true
else
return false
end
Acest script Lua verifică și setează atomic blocarea cu TTL. Atomicitatea garantează că două cereri simultane nu pot primi blocarea — doar una, ceea ce previne complet Cache Stampede.
Probabilistic Early Expiration (PEE) — o metodă elegantă fără blocări. Ideea este că fiecare client, cu o anumită probabilitate, recalculează cache-ul înainte de expirarea sa efectivă. Cu cât intrarea este mai aproape de expirare — cu atât probabilitatea este mai mare. Acest lucru distribuie natural reîncărcările în timp, iar încărcarea de vârf este netezită.
Algoritmul XFetch — propus de Vattani, Chierichetti și Panconesi (2015). Formula probabilității: p = max(0, 1 - (beta * age / ttl)), unde beta — parametrul de neuniformitate (de obicei 1.0). La age = 0 → p = 1 (actualizare garantată la vârstă zero, ceea ce este incorect). De aceea se folosește modificarea: p = max(0, (ttl - age) / (ttl * beta)).
Conform datelor Etsy Engineering (2024), implementarea XFetch în Memcached a redus numărul de evenimente stampede de la 12 pe zi la 0, iar încărcarea de vârf a bazei de date a scăzut cu 78%. Actualizarea randomizată nu necesită blocări și sincronizare, ceea ce face XFetch ideal pentru arhitectura de microservicii.
Pentru clienții mobili, PEE poate fi aplicat în partea de aplicație: clienții inițiază aleator actualizarea cache-ului înainte de expirarea oficială. De exemplu, la age > 80% TTL, fiecare cerere cu 20% probabilitate actualizează datele. Dispozitivele mobile distribuite creează un zgomot natural care reduce suplimentar probabilitatea unui impact sincron.
Ideea abordării — actualizarea cache-ului înainte de expirare, eliminând complet ratarea. Un proces de fundal (cron, scheduler în Kubernetes) verifică periodic cheile populare și le rescrie cu un nou TTL. Clienții găsesc întotdeauna cache-ul valid — stampede este imposibil prin definiție.
Time-To-Refresh (TTR) — parametrul care determină cu cât timp înainte de expirare să înceapă actualizarea. De exemplu, TTL = 300 secunde, TTR = 60 secunde: la marcajul de 240 secunde, procesul de fundal rescrie datele. Aceasta garantează că clienții nu văd niciodată cache-ul gol.
Dezavantajul actualizării preventive — încărcare constantă a sursei de date, chiar dacă nimeni nu solicită intrarea. Pentru datele rar utilizate, acest lucru este ineficient. Soluție: monitorizarea frecvenței cererilor către cheie (access frequency) și actualizarea doar a intrărilor populare. TTR adaptiv — o tehnică avansată în care intervalul de actualizare este calculat dinamic pe baza istoricului cererilor.
Blocarea cu Mutex — potrivită pentru sisteme cu încărcare moderată (până la 10 000 rps per cheie). Simplă de implementat și garantează că sursa primește o singură cerere de actualizare. Dezavantaj — blocările creează întârziere pentru firele care așteaptă.
Probabilistic Early Expiration / XFetch — optim pentru încărcări mari și sisteme distribuite. Nu necesită blocări, se scalează orizontal, încărcarea de vârf este netezită natural. Recomandat pentru clustere Redis și Memcached cu mii de clienți.
Actualizarea preventivă — ideală pentru date critice cu model de acces previzibil (configurații, dicționare, metadate de bază). Necesită infrastructură suplimentară pentru procesul de fundal.
Cache pe disc la client — cel mai important nivel de protecție pentru aplicațiile mobile. Chiar dacă cache-ul serverului este invalidat, clientul mobil poate afișa datele de pe disc în timp ce se execută o nouă cerere. Room + Stale-While-Revalidate — modelul recomandat de Google (2025), care elimină complet Cache Stampede la client.
Întrebări frecvente
Cache Stampede — rezultatul comportamentului normal al clienților legitimi care au descoperit simultan cache-ul gol. DDoS — un atac intenționat. Pentru Stampede sunt suficienți algoritmi de protecție; pentru DDoS este necesară o infrastructură suplimentară de filtrare a traficului.
Monitorizează graficele RPS pe sursa de date (bază de date, API). Dacă vezi vârfuri regulate de încărcare sincronizate cu momentul expirării cache-ului — acesta este Cache Stampede. Adaugă metrica cache miss rate pentru fiecare cheie populară.
Da, dacă mai multe fire de execuție în aplicație folosesc un cache comun în memorie. De exemplu, 10 corutini solicită simultan profilul utilizatorului — primul blochează, celelalte 9 pot duplica cererea. Biblioteca Kotlin kotlinx.coroutines rezolvă aceasta prin CoroutineCache sau Flow.distinctUntilChanged.
beta = 1.0 — valoarea implicită, care oferă o distribuție uniformă a recalculărilor. Pentru o protecție mai agresivă (probabilitate mai mică de ratare), crește beta la 1.5–2.0. Pentru economisirea resurselor sursei — reduce la 0.5. Intervalul recomandat: 0.8–1.2.
Da, prin mecanismul Cache-Control: stale-while-revalidate și Cache-Control: stale-if-error. CDN returnează versiunea învechită și în fundal actualizează cache-ul, ceea ce este echivalentul CDN al PEE. Cloudflare și Fastly suportă aceste directive din 2023.
Rezumat
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și