Лавина кешу (cache stampede, thundering herd) - популярний ключ кешу закінчується, і сотні одночасних запитів не знаходять значення, всі разом ідуть у базу й обчислюють одне й те саме. База, що спокійно працювала завдяки кешу, раптом отримує навантаження, від якого падає. А поки вона повільна, ключ ще довше не з'являється в кеші.
Захист:
1. Блокування - обчислює лише один.
$value = Cache::get('report');
if ($value === null) {
$value = Cache::lock('report:lock', 10)->block(5, function () {
return Cache::remember('report', now()->plus(minutes: 10), fn () => buildReport());
});
}
Один процес обчислює, інші чекають і отримують готове значення.
2. Stale-while-revalidate - віддавати застаріле, оновлювати у фоні. У Laravel - Cache::flexible():
$stats = Cache::flexible('dashboard:stats', [300, 900], fn () => computeStats());
- перші 300 секунд значення свіже - віддається з кешу;
- з 300 до 900 секунд - застаріле, але прийнятне: віддається одразу, а перерахунок запускається після відправки відповіді (лише один, із блокуванням);
- після 900 секунд - обчислюється заново синхронно.
Користувачі майже ніколи не чекають на обчислення, а база не отримує лавини.
3. Ймовірнісне дострокове оновлення - кожен запит з невеликою ймовірністю, що зростає ближче до кінця TTL, оновлює значення заздалегідь. Лише один-два запити підуть у базу до закінчення терміну.
4. Розкид TTL (jitter) - ключі, створені одночасно (наприклад, після деплою чи прогріву), отримують трохи різний термін - і не закінчуються в одну секунду.
5. Прогрів кешу - фонова задача оновлює важливі ключі за розкладом, і користувацькі запити кеш ніколи не «пропускають».
Схожа проблема - холодний старт: після очищення всього кешу (деплой, перезапуск Redis) - лавина по всіх ключах одразу. Тому cache:clear на продакшені під навантаженням - ризикована операція.