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

Запись в базе уже обновилась, но приложение продолжает отдавать старое значение из Redis, локального кеша или CDN. Удалить один ключ недостаточно: копии данных могут находиться в нескольких слоях, а параллельный запрос способен записать устаревший результат сразу после очистки. Поэтому кеш-инвалидация — это не отдельная команда, а согласование записи, чтения и срока жизни данных.
Почему удалить ключ недостаточно
В простой схеме приложение читает данные из базы и кладёт их в Redis. При изменении записи оно удаляет ключ, а следующий запрос строит кеш заново. Такая модель работает, пока есть один источник кеша, один процесс записи и нет параллельных чтений в момент обновления.
В реальном веб-приложении путь длиннее: браузер может хранить ответ, прокси — собственную копию, CDN — объект на edge-узле, приложение — значение в локальной памяти, а Redis — ещё одну версию. Очистка Redis не удалит уже выданный HTTP-ответ из CDN. Очистка CDN не изменит локальный кеш процесса.
Инвалидация — это действие, после которого система перестаёт считать кешированное значение актуальным и должна получить новую версию данных. На практике она может означать удаление ключа, сокращение времени жизни, смену версии ключа или явную публикацию нового значения.
Кеш ломается не тогда, когда данные устарели. Он ломается, когда система не умеет точно сказать, где ещё лежит старая копия и кто имеет право её заменить.
Три источника сложности
Слои кеша не знают друг о друге
Представьте страницу товара. Браузер получил ответ с заголовком Cache-Control, CDN сохранил его на периферийном узле, приложение положило объект в Redis, а ORM использует внутренний кеш соединения или подготовленных результатов. Обновление цены в базе не является сигналом для всех этих слоёв.
Чем больше уровней, тем важнее карта владения кешем. Для каждого значения нужно знать:
- кто его создаёт;
- где оно хранится;
- как определяется ключ;
- какое событие делает его устаревшим;
- кто отправляет сигнал на очистку или замену;
- сколько времени система может показывать старую версию.
Если хотя бы один слой не попал в эту схему, пользователь может видеть старую цену или статус заказа даже после успешного обновления базы.
Гонка между чтением и записью
Классическая ошибка возникает в последовательности «удалить, затем записать». Один процесс обновляет базу и удаляет ключ. Почти одновременно второй процесс читает базу в момент, когда транзакция ещё не завершена, получает старое значение и записывает его в кеш. После фиксации новой записи кеш снова содержит устаревшие данные.
Обратная последовательность — «записать, затем удалить» — тоже не универсальна. Если удаление задержалось, читатель может получить старое значение между записью в базу и очисткой. Если удаление потерялось из-за сбоя сети, устаревшая копия останется до истечения TTL.
Важна не только последовательность операций, но и момент, когда другие процессы видят транзакцию. При репликации база может подтвердить запись на основном узле, а чтение уйдёт на реплику, которая ещё не догнала изменения.
Согласованность стоит денег
Можно отказаться от кеша после каждой записи и читать всё напрямую из базы. Тогда данные будут свежее, но возрастут задержка и нагрузка на хранилище. Можно очищать кеш агрессивно, но получить всплеск запросов после массового сброса. Можно держать старые значения дольше, выиграв в производительности, но приняв временную рассинхронизацию.
Это не только технический выбор. Для каталога товаров задержка в несколько секунд может быть допустима, если цена проверяется перед оплатой. Для баланса, лимита или статуса платежа такая же задержка способна привести к неверному решению.
Основные стратегии
| Стратегия | Как работает | Плюс | Цена и риск |
|---|---|---|---|
| Удаление ключа | После изменения источника кеш удаляется, следующий запрос читает данные заново | Простая логика и низкая стоимость хранения | Гонка при параллельном чтении; нужно пережить потерю сигнала |
| Обновление значения | После записи приложение сразу записывает новую версию в кеш | Меньше промахов кеша после изменения | Нужно поддерживать формат и все связанные ключи |
| Версионирование ключа | Ключ содержит версию сущности или набора данных | Старая копия перестаёт находиться по новому адресу | Старые ключи физически живут до TTL и занимают память |
| Короткий TTL | Данные автоматически устаревают через ограниченный срок | Есть безопасный верхний предел устаревания | Мгновенная свежесть не обеспечивается и возникают промахи |
| Событие об изменении | Сервис записи публикует событие, подписчики очищают свои кеши | Подходит для нескольких сервисов | Появляются очереди, повторы, порядок событий и мёртвые подписки |
Стратегии часто комбинируют. Например, обновляют горячий ключ после записи, задают TTL как предохранитель, а для связанных списков используют версию набора. Универсального варианта нет: критерий выбора — допустимый срок устаревания и цена промаха, а не привычка команды.
Где подходы ломаются
Связанные ключи
Изменение товара затрагивает не только ключ product:42, но и список категории, результаты поиска, рекомендации и страницу корзины. Очистка одного объекта не делает остальные представления свежими. Чем больше производных данных, тем опаснее вручную перечислять ключи: новый экран может появиться без добавления его в список инвалидации.
Практичнее группировать кеш по понятным наборам или использовать версию сущности. Тогда изменение версии делает старые производные ключи недействительными логически, даже если они ещё физически находятся в Redis.
Повторная доставка событий
Событие «товар изменён» может прийти дважды, позже ожидаемого или не прийти вовсе. Обработчик должен быть идемпотентным: повторное выполнение не должно портить результат. Для очистки это обычно означает безопасное повторное удаление, а для обновления — проверку версии записи перед публикацией нового значения.
Массовый сброс и stampede
Когда тысячи ключей истекают одновременно, множество запросов одновременно обращается к базе за одним и тем же объектом. Это называют cache stampede — лавиной промахов кеша. Она способна перегрузить источник именно в момент, когда команда пытается восстановить данные.
Для защиты используют разнесённые сроки жизни, блокировку перестроения одного ключа, предварительный прогрев и ограничение числа одновременных запросов. Конкретный механизм зависит от клиента Redis и архитектуры приложения; не стоит копировать настройку из другой библиотеки без проверки документации её версии.
CDN и HTTP-кеш
Если ответ уже попал в CDN, приложение может исправно отдавать свежие данные, а пользователь продолжит получать старый HTTP-ответ. Здесь нужно отдельно управлять заголовками кеширования и механизмом очистки CDN. Время жизни в браузере также ограничивает результат: сервер не может мгновенно удалить копию, которая уже сохранена на устройстве клиента.
Для критичных данных безопаснее не кешировать персонализированный ответ публично. Для публичного контента можно использовать короткий TTL, условные запросы и версионирование URL или ресурса. Инвалидация CDN — отдельная операция, её стоимость и ограничения нужно проверять у конкретного провайдера.
Что измерять вместо ощущения «всё работает»
Без замеров невозможно понять, стала ли схема надёжнее. Для каждого кеша полезно собирать не только hit rate, но и возраст возвращённого значения, число промахов после записи, задержку перестроения и ошибки обработчика событий.
- Запишите идентификатор версии данных в лог чтения и сравнивайте его с версией в базе.
- Разделите операции чтения, удаления и обновления по метрикам.
- Измеряйте время от фиксации записи до первого чтения новой версии.
- Проверьте поведение при повторной доставке, задержке и потере события.
- Отдельно протестируйте массовое изменение записей и одновременный доступ к одному ключу.
Тест должен учитывать реальную топологию: Redis 7.x, используемый клиент, число экземпляров приложения, репликацию базы и CDN, если он есть. Результат локального теста с одним процессом не показывает поведение распределённой системы.
Как выбрать безопасную схему
Для редко меняющихся публичных страниц обычно достаточно TTL и явного сброса после публикации. Для объектов, которые меняются часто, но не влияют на деньги, подойдёт cache-aside с удалением ключа и предохранительным TTL. Для платежей, остатков и прав доступа кеш должен либо иметь очень короткий срок жизни и повторную проверку, либо не использоваться как окончательный источник решения.
Если данные читают несколько сервисов, границу инвалидации лучше привязать к событию изменения, а не к внутреннему вызову одного приложения. При этом событие не заменяет контроль версий и повторную обработку: оно лишь доставляет информацию о том, что кеш пора пересмотреть.
Что делать дальше
Начните с одной сущности, например товара или профиля пользователя. Нарисуйте путь значения от базы до браузера, перечислите все ключи и задайте допустимый возраст данных. Затем добавьте тест на гонку чтения и записи, проверку повторной доставки события и метрику времени до появления новой версии.
После этого отдельно проверьте HTTP-кеш и CDN. Если система не может ответить, где находится копия и кто отвечает за её очистку, сначала исправьте эту наблюдаемость, а уже потом меняйте TTL или добавляйте ещё один слой кеша.
Тест и проверочная: Быстрые ответы с объяснением, почему остальные варианты неверны
Спросить за 10 ₽Источники: Redis Documentation: Cache-Aside Pattern, MDN Web Docs: HTTP caching
Частые вопросы
Можно ли просто удалить ключ Redis после изменения записи?
Что лучше: удалять кеш или сразу записывать новое значение?
Почему после очистки Redis пользователь всё ещё видит старую страницу?
Что будет, если событие об инвалидации не дойдёт?
Как понять, что проблема вызвана гонкой, а не неверным ключом?
Нужно ли вообще кешировать остатки, баланс и статус платежа?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.