awesome-everything EN
↑ Обратно к восхождению

Кеширование

Детектирование stampede и дизайн TTL для продакшена

Суть Observability-отпечаток cache stampede, минимально-жизнеспособные dashboard-метрики, TTL jitter для размазывания границ, negative caching и стратегии pre-warming для выживания при cold-start перезапусках.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на middle-высоте — в небе
◷ 13 min

Команда деплоит single-flight и локи. Три недели спустя срабатывает on-call алерт: CPU БД спайкует каждые 5 минут. Защита на месте — но она защищает не те ключи. Спайк идёт от другого ключа с TTL=300 с, который никто не инструментировал.

Observability-отпечаток

Cache stampede оставляет характерную сигнатуру в метриках:

  • Ставка запросов к БД: пилообразный паттерн — почти нуль между TTL-границами, резкий спайк на каждой границе. Ширина спайка равна длительности rebuild.
  • Периодичность: спайки повторяются с интервалами, совпадающими с TTL. TTL=60 с → спайки каждые 60 с. TTL=300 с → каждые 5 минут.
  • p99 латентность: спайки на тех же интервалах, что и ставка запросов к БД.
  • cache_miss_total rate: резкие периодические увеличения на границах вместо ровного низкого baseline.

Без этих метрик stampede выглядит как общая «медленная БД» без очевидной причины.

Минимально-жизнеспособный dashboard

Шесть метрик покрывают все stampede-сценарии:

МетрикаУсловие алертаЧто сигнализирует
cache_miss_total rateПериодические спайки > 5× steady-stateStampede в процессе
db_query_rate p99Пилообразный паттернDownstream stampede от кеш-границ
cache_rebuild_duration_seconds p99Длинный хвост на границахContention при rebuild
cache_lock_wait_seconds p99Выше rebuild p99Очередь лока растёт — голодание ожидающих
singleflight_subscriber_count p99> 1 (coalescing активен)Single-flight срабатывает — нормально под нагрузкой
cache_swr_stale_serve_totalНенулевой на границахSWR поглощает истечение — ожидаемо

Алерт 1: cache_miss_total rate выше 5× steady-state → stampede формируется. Алерт 2: db_query_rate p99 выше 10× p50 → пилообразная нагрузка БД → boundary-спайки. Алерт 3: cache_lock_wait_seconds p99 выше длительности rebuild → глубина очереди лока растёт.

TTL jitter

Single-flight и локи обрабатывают per-key stampede. Но что, если 1 000 ключей имеют TTL=300 с и все были закешированы одновременно? Все истекают вместе, производя 1 000 одновременных per-key stampede — single-flight корректно обрабатывает каждый, но сумма 1 000 параллельных rebuild — спайк БД.

TTL jitter: вместо фиксированного TTL использовать случайное значение в диапазоне:

ttl = base_ttl * (1 + jitter_fraction * (rand() - 0.5))
# Пример: base=300, jitter=0.25 → TTL в диапазоне [225, 375]

Флот из 1 000 ключей с ±25% jitter размазывает истечения на 150 с вместо одновременного срабатывания. Нагрузка БД становится плавной низкой кривой вместо спайка.

Большинство кеш-библиотек поддерживают jitter нативно (Caffeine в Java, Redis через application-level вычисление). Стандартный ±15–25% достаточен для большинства нагрузок.

Негативное кеширование

Та же stampede-форма применима, когда БД отвечает «нет такой строки». Если приложение не кеширует null-результаты, каждый запрос несуществующего ключа бьёт в БД — и под высокой параллельностью это miss-storm, перегружающий БД так же полно, как положительный stampede.

Фикс: кешировать sentinel “missing” с коротким TTL.

# При DB miss:
SET key:missing "" EX 10  # 10 с негативный TTL

# При чтении:
val = GET key
if val == "":
  return NOT_FOUND  # из кеша, без обращения к БД

Короткий негативный TTL (5–30 с) ограничивает расход памяти. Положительный TTL может быть намного длиннее (60–300 с). Write-through invalidation должна удалять негативную запись при INSERT реальной строки.

Замечание по безопасности: без негативного кеширования атакующий может чеканить случайные несуществующие ключи (случайные UUID в URL-пути) для амплификации нагрузки БД на порядки — задокументированный паттерн, поразивший CDN-сайты в 2024.

Pre-warming после перезапусков

Кеш, перезапущенный холодным (deploy, eviction, отказ машины), стартует пустым. Каждый входящий запрос промахивается и бьёт в БД — полный origin-спайк. При перезапуске на пиковом трафике этот спайк равен по величине полному stampede.

Процедура pre-warming:

  1. До приёма публичного трафика проиграть топ-N наиболее accessed ключей из audit-лога или access-лога.
  2. Прогреть кеш сначала, затем переключить трафик.
  3. Для blue-green деплоев: прогреть green кеш-инстанс до steady-state перед переключением load balancer.

Edge-ноды Cloudflare pre-warm-ятся из соседних POP. Redis-backed сервисы используют startup-скрипт, читающий «топ 1 000 ключей» из audit-таблицы. Правило: никогда не перезапускать кеш под живым трафиком без pre-warming.

Почему это работает

Pre-warming — наиболее часто пропускаемый шаг в runbook-ах апгрейда кеш-tier. Команды тестируют логику лока и SWR, но пренебрегают cold-start окном. Cold-start stampede обычно в 3–10 раз хуже нормального TTL-boundary stampede, потому что 100% ключей холодные одновременно. Runbook-и должны включать шаг «прогреть новый кеш перед роутингом трафика» как жёсткий gate.

Викторина

Ставка запросов к БД сервиса показывает резкие спайки каждые 60 секунд с почти нулевой нагрузкой между ними. Какова наиболее вероятная причина?

Викторина

Кеш хранит 5 000 product-страниц, все закешированных одновременно с TTL=300 с. Что происходит на секунде 300, даже с single-flight защитой?

Расставь шаги по порядку

Поставь шаги диагностики и фикса 60-секундного периодического DB спайка:

  1. 1 Проверить ставку запросов к БД по времени — подтвердить пилообразный паттерн с 60-секундной периодичностью
  2. 2 Определить ключи кеша с TTL=60 с на горячем пути
  3. 3 Задеплоить in-process single-flight как первую митигацию
  4. 4 Проверить, что спайки падают с 4 000 QPS до 50 QPS (один rebuild на ноду × 50 нод)
  5. 5 Добавить TTL jitter ±20% для десинхронизации будущих истечений
  6. 6 Добавить dashboard-алерт: cache_miss_total rate выше 5× steady-state
  7. 7 Запустить синтетический stampede-тест в CI: впрыснуть 5 000 misses, asserting db_query_count под порогом
Викторина

Сервис кеширует lookups профилей пользователей на 5 минут. Атакующий запрашивает 100 000 случайных несуществующих user ID в секунду. Что происходит без негативного кеширования?

Вспомните перед уходом
  1. 01
    У команды задеплоены single-flight и Redis локи. Что означает cache_lock_wait_seconds p99 выше rebuild p99, и каков правильный фикс?
  2. 02
    Объясни, почему pre-warming — наиболее важный шаг перед переключением трафика при blue-green деплое кеша, и что происходит, если его пропустить.
Итог

Детектирование cache stampede в продакшене требует инструментированных метрик: пилообразный db_query_rate паттерн с периодичностью, совпадающей с TTL — канонический отпечаток. Минимально-жизнеспособная observability включает шесть метрик: miss rate, DB query rate, rebuild duration, lock wait, single-flight subscriber count и SWR stale serve count. TTL jitter (±15–25%) предотвращает синхронизированное multi-key истечение, размазывая границы по времени. Негативное кеширование (short-TTL sentinel для пустых строк) предотвращает miss-storm amplification атаки. Pre-warming кеша перед приёмом живого трафика после перезапуска предотвращает cold-start stampede. Вместе эти operational практики закрывают разрыв между «митигации задеплоены» и «stampede реально предотвращены в продакшене».

Связанные уроки
встречается в202
Продолжить восхождение ↑Метастабильный сбой, fencing-токены и production-постмортемы
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources4
expand
  1. 01
  2. 02
  3. 03
  4. 04

Trademarks belong to their respective owners. Editorial reference only.