Перейти к содержанию
Бэкенд и системы

Redis Cache-Aside: защита от stampede и устаревшего заполнения

Попадание в кеш обычно не вызывает сложностей. Проблемы начинаются, когда популярный ключ истекает, Redis отвечает медленно или обновление базы пересекается со старой загрузкой. Спроектируйте эти пути до оптимизации hit ratio.

Автор: Опубликовано

5 мин чтенияРедакционный разборОбновлено
  • Redis
  • Cache-aside
  • Cache stampede
  • Согласованность
Главная мысль

Объединяйте дорогие загрузки, ограничивайте ожидание и обращения к базе, задавайте допустимую устарелость. Блокировка заполнения сокращает лишнюю работу, но не обеспечивает строгую согласованность cache-aside.

Начните с одного товара и TTL

Для товара 42 источником истины остаётся база. Прочитайте catalog:product:42; при промахе получите запись из базы, положите её в кеш и верните. Команды показывают заполнение и удаление ключа в локальном Redis через redis-cli. Приложение также должно сериализовать данные и отличать отсутствующий товар от сбоя запроса к базе.

Записывайте значение и срок хранения одной командой SET EX. При отдельных SET и EXPIRE остановка клиента между командами может оставить бессрочный ключ. Выбирайте TTL по допустимой устарелости и цене загрузки; 60 секунд здесь — учебное значение. Если доступные данные зависят от арендатора или представления, включите эту идентичность в ключ.

redis-cli SET 'catalog:product:42' '{"version":11,"price":25}' EX 60
redis-cli GET 'catalog:product:42'
redis-cli TTL 'catalog:product:42'
redis-cli DEL 'catalog:product:42'

Пример: после истечения пришли 200 читателей

Предположим, сразу после истечения ключа его запросили 200 одновременных читателей. Без координации каждый может выполнить один и тот же дорогой запрос. Нагрузка на базу растёт именно тогда, когда кеш перестал её защищать. Числа описывают условное упражнение, а не измерение производительности.

Сначала объединяйте запросы внутри процесса: один загружает, остальные ждут его результат. Если дублирование между репликами всё ещё дорого, добавьте общую координацию. Случайный разброс TTL распределяет истечение разных ключей, но не объединяет читателей одного уже исчезнувшего ключа. Популярные данные иногда выгодно обновлять заранее.

Защита зависит от сценария сбоя
ПроблемаМеханизмОграничение
Истёк горячий ключSingleflight или блокировкаОграничить ожидание
Истекает много ключейРазброс TTLНе объединяет читателей
Redis недоступенОграниченный fallbackБеречь базу
Допустим старый ответОбновление в фонеЛимит устарелости

Повторно читайте кеш после захвата блокировки

Читатель мог обнаружить промах, подождать и получить блокировку уже после чужого заполнения. Повторное чтение под блокировкой исключает лишнюю загрузку. Ограничьте запрос к базе дедлайном, освобождайте владение в блоке очистки и предусмотрите выход из ожидания. Ниже — псевдокод приложения, а не готовый Redis-клиент.

Для stale-while-refreshing нужно хранить значение дольше мягкого срока свежести и отдельно задать окончательное истечение. Когда Redis удалил единственную копию, старый ответ из неё уже не получить. Заранее определите, где устарелость допустима. Права доступа, остатки и платёжные решения могут требовать проверки по базе.

прочитать товар из кеша
если найден: вернуть товар
получить короткую блокировку с уникальным токеном
если получена:
  повторно прочитать кеш
  если всё ещё промах:
    прочитать базу с дедлайном
    записать результат с небольшим разбросом TTL
  освободить блокировку, только если она всё ещё наша
  вернуть товар
иначе:
  немного подождать со случайной задержкой и проверить кеш
  вернуть допустимый старый ответ, ограничить запрос к базе
  или завершить запрос с ошибкой

Удаляйте только собственную блокировку

SET NX PX выдаёт короткую аренду, только если ключа блокировки ещё нет. Ответ OK означает захват, nil-ответ — отказ. При сетевом таймауте владение неизвестно: проясните результат, прежде чем действовать как владелец. Создавайте новый непредсказуемый токен для каждого захвата. Значение в примере подходит только для ручной демонстрации.

Если медленный читатель пережил свою аренду, ключ мог получить другой читатель. Обычный DEL от первого удалит чужую блокировку. Lua проверяет токен и удаляет ключ атомарно, исключая эту ошибку. Но истечение аренды всё ещё допускает повторную работу, а смена Redis-узла влияет на гарантии. Используйте механизм как оптимизацию идемпотентного чтения, а не как гарантию корректности записи.

redis-cli SET 'lock:catalog:product:42' 'demo-owner-token' NX PX 2000
redis-cli EVAL 'if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("DEL", KEYS[1]) else return 0 end' 1 'lock:catalog:product:42' 'demo-owner-token'

После успешного удаления могут вернуться старые данные

Рассмотрим порядок: читатель A видит промах и получает версию 11 из базы; писатель B сохраняет версию 12 и удаляет ключ; затем A кладёт в кеш версию 11. Обновление базы перед удалением кеша — полезная основа, но такую гонку оно не исключает. Блокировка заполнения, в которой писатели не участвуют, тоже не помогает.

При допустимой ограниченной устарелости может хватить короткого TTL. Более строгие требования нуждаются в протоколе: общей координации чтений и записей, отклонении заполнения после смены поколения или обходе кеша для критичных чтений. Поле version само по себе гонку не предотвращает. Если база обновилась, а удаление Redis не удалось, надёжная инвалидация также требует повторов или outbox.

Измеряйте пути, которые перегружают базу

Помимо доли промахов отслеживайте одновременные загрузки, ожидание блокировки, задержку базы и возраст старых ответов. Высокий hit ratio может скрывать один дорогой ключ. Ограничьте таймаут Redis и параллелизм fallback-запросов: недоступность кеша не должна перегрузить базу.

Проверьте истечение, медленную загрузку, неудачную инвалидацию и недоступность кеша относительно принятого требования к свежести. Убедитесь, что ожидающие запросы завершаются, а работа базы ограничена. Эти наблюдения дают основу для ответов на более общие вопросы о кешировании.

Коротко

Частые вопросы

Устраняет ли разброс TTL любой cache stampede?

Он распределяет истечение разных ключей. Для множества читателей одного отсутствующего ключа нужны объединение загрузок, короткая аренда или допустимая выдача старых данных.

Гарантирует ли Redis-блокировка согласованность?

Нет. Аренда может истечь, при смене узла действуют дополнительные ограничения, а писатели могут участвовать в отдельной гонке. Сокращение работы и требования к свежести проектируйте отдельно.

Удалять кеш до обновления базы или после?

Обычно сначала фиксируют изменение базы, затем удаляют ключ. Иначе читатель может заполнить кеш старой записью. Но и обычный порядок требует решения для конкурентного заполнения и ошибок инвалидации.

Можно ли при сбое Redis направить все запросы в базу?

Только если у базы хватает ресурсов. Ограничьте параллелизм и ожидание, затем используйте контролируемую ошибку или допустимые старые данные при исчерпании бюджета.

Источники

Источники и редакционная политика

Сведения проверены 4 октября 2026 г. Ссылки рядом с разделами указывают источники фактов и технических объяснений. Выводы, учебные сценарии и рекомендации по подготовке — редакционная работа RecallDeck.

От чтения к воспроизведению

Отрепетируйте полный цикл интервью.

RecallDeck возвращает сложные темы по расписанию и помогает удерживать в памяти язык, SQL, архитектуру и поведенческие истории.

Начать подготовку

Продолжить подготовку