Инвалидация

Что это такое

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

Почему это важно

Устаревший кэш даёт ошибки худшего класса: система выглядит здоровой, каждый запрос успешен, а данные неверны. Ни алерта, ни стека вызовов тут нет, и о симптоме обычно сообщает пользователь, который уже трижды попробовал одно и то же действие. Отказ тихий и нерегулярный, поэтому инвалидации стоит уделять при проектировании больше внимания, чем самому кэшированию; честное решение по умолчанию — предпочесть истечение всякой хитрости.

Ключевые идеи

Истечение — тот запасной механизм, который делает остальные необязательными

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

Инвалидируйте при записи и примите, что это может не сработать

Удаление или обновление кэшированной записи при изменении источника сокращает окно. Однако операция может не удаться: кэш бывает недоступен, а процесс — упасть между записью и инвалидацией. Значит, механизм этот работает по мере сил (best-effort), а страховкой остаётся истечение; считать вызов инвалидации гарантированным — частое и ничем не обоснованное допущение.

запись в источник -> удалить ключ кэша (может не удаться) -> ttl ограничивает устарелость

Версионированные ключи делают старое значение недостижимым

Вместо удаления кэшированной записи можно поменять ключ так, что старая запись больше никогда не будет найдена: номер версии, хеш отметки времени обновления строки источника или счётчик поколений. Устаревшее значение остаётся до истечения, но становится недостижимым — и гонка между записью и одновременным чтением, которое наполняет кэш старым значением, исчезает.

Чтение промахивается, получает старое значение и сохраняет его; но между получением и сохранением может произойти запись, которая инвалидирует ключ, — и тогда чтение запишет обратно то самое устаревшее значение, которое инвалидация должна была убрать. Окно узкое, а устарелость получается долгой. Версионированные ключи избегают этого по построению, а короткие сроки жизни её ограничивают.

Никогда не инвалидируйте сканированием

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

Инвалидируйте только то, на что запись действительно влияет

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

Кэш не источник и никогда не решает вопрос корректности

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

Практика

Ограничьте устарелость одной записи

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

Готово, когда: ответ — конечное время, выведенное из срока жизни, и вы можете сказать, приемлемо ли оно для пользователя функции.

Сделайте инвалидацию полной

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

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

Частые ошибки

  • Опора на явную инвалидацию без срока жизни — пропущенный вызов означает неограниченную устарелость; исправить: всегда задавайте срок жизни.
  • Допущение, что вызов инвалидации не может не удаться — неудавшееся удаление оставляет старое значение на месте; исправить: считайте это работой по мере сил и ограничьте ущерб сроком жизни.
  • Удаление ключей по шаблону — операция дорогая и может блокировать хранилище; исправить: инвалидируйте известный набор или используйте версию в ключе.
  • Очистка всего кэша из-за одной записи — доля попаданий падает, и кэш перестаёт помогать; исправить: инвалидируйте только затронутое изменением.
  • Отказ кэша как отказ запроса — кэш становится единой точкой отказа; исправить: откатывайтесь к источнику истины.

Что читать дальше