Eviction, hot key и thundering herd

О лекции

Когда память кончилась, срабатывает политика eviction. Hot key греет один шард и один поток. Thundering herd — согласованный промах: все клиенты одновременно идут в базу.

Кэш и Redis redis собеседование

Три разные аварии часто называют одним словом «Redis лёг». Их лечат по-разному.

  • Eviction — не влезло в память, ключ вытеснили. Для приложения это как истекший TTL: miss.
  • Hot key — ключ влез, но принимает почти весь трафик. Греет один шард и один поток.
  • Thundering herd (cache stampede) — ключ пропал у всех сразу, все пошли в Postgres.

Redis — кэш с ограниченной RAM, если вы сами не поставили «безлимит» и не удивились OOM. maxmemory задаёт потолок. Политика решает, что делать при записи сверх потолка: отказать или кого-то вытеснить. Кэш больше не «полный набор данных», а «что влезло».

Политики eviction

  • noeviction — запись получает ошибку, ключи сами не вытесняются. Честно, если Redis для вас не кэш, а store, и вы обязаны не молча терять данные.
  • allkeys-lru / allkeys-lfu — вытесняют любой ключ, даже без TTL. Классический кэш.
  • volatile-lru (и аналоги volatile-*) — только ключи с TTL. Ключ без expire живёт вечно и может забить память неприкасаемым множеством.

LRU в Redis приближённый, не идеальный двусвязный список на каждый ключ. Для разговора достаточно: «вытесняем давно не использованное, оценка приближённая». После eviction следующий GET — miss, и дальше уже stampede, если ключ популярный.

Hot key

Один ключ (витрина, токен инфлюенсера, глобальный конфиг) принимает 90% GET. В кластере ключ живёт на одном шарде и исполняется одним потоком. CPU этого шарда в потолке, остальные спят. Индекс в Postgres тут ни при чём.

Пример. Ключ product:1 — хит дня. Пятьсот подов делают GET именно его. Шард этого ключа на 100% CPU. Соседние шарды почти пустые. Добавить реплик приложения не поможет: все клиенты попадут на тот же слот.

Лечение: короткий локальный кэш в процессе (singleflight плюс TTL в сотни миллисекунд), копии значения под суффиксами и случайное чтение, read replica Redis для чистого чтения, не класть витрину в один mega-hash. Шардирование клиентов не поможет, если ключ один.

Thundering herd

Популярный ключ протух одновременно у всех — одинаковый TTL без jitter, либо eviction того же ключа. Пятьсот подов делают GET, ловят miss, все идут в Postgres, все делают SET. База ложится, Redis заливают одной и той же записью. Это stampede, не «медленный SQL».

-- stampede: 500 подов одновременно
GET product:1          -- miss
-- все идут в Postgres, потом SET product:1

-- смягчение: один загрузчик
SET product:1:lock 1 NX EX 30
-- кто выиграл NX — читает БД и SET product:1
-- остальные ждут lock или отдают протухшее значение

Согласованный miss: все поды одновременно идут в Postgres. SET NX как лок позволяет грузить базу одному, остальным — ждать или отдать stale.

Смягчение stampede: jitter на TTL (не всем ключам EX 60, а 60 ± 15), soft TTL / stale-while-revalidate (отдать старое и обновить в фоне), singleflight в процессе, лок вокруг загрузки. Eviction популярного ключа даёт тот же эффект, что истечение TTL: готовьте те же щиты.

Типичная ошибка

Смешать три диагноза в один. noeviction «потому что так в образце docker-compose» на кэше, который обязан вытеснять. Одинаковый TTL на горячем ключе без jitter. Лечить hot key добавлением реплик приложения — ключ всё равно на одном шарде Redis.

На собеседовании

Разведите вслух: eviction (не влезло в RAM), hot key (перекос нагрузки на один ключ/шард), stampede (согласованный miss в БД). Политика noeviction против allkeys-lru — осознанный выбор. На stampede назовите jitter, lock/singleflight и stale-while-revalidate.