Инвалидация кэша это процесс удаления данных из кэша при изменении состояния источника. Причем очень желательно, чтобы кэш сбрасывался сразу же за изменением. Например, мы закэшировали выборку из БД, рано или поздно исходные данные таблицы изменятся, и кэш перестает быть валидным.
Способы инвалидации:
- По истечению срока действия - TTL.
- Самая простая реализация.
- При малом TTL будет высокий CacheMissRate
- При большом TTL данные могут быть не консистентны
- По событию
- Опасно из-за риска мгновенной инвалидации и сопутствующей перестройки кэша
- Использование алгоритмов вытеснения
Дополнительно для работы со статическими файлами можно отменить Fingerprint.
Инвалидация по TTL
Один из подходов это инвалидация по времени. Для кэшированных данных устанавливается TTL, и по прошествию времени кэш удаляется. Такое вариант подходит для редко изменяемых данных, устаревание которых не приводит к серьезным проблемам в бизнес-логике, например словари.
При таком варианте важно подобрать оптимальное время жизни кэша, слишком маленькое время жизни будет давать плохую производительность, слишком большое ухудшит опыт пользователей. В оценке эффективности кэша поможет метрика CacheMissRate.
Инвалидация по событию
В этом случае мы удаляем данные кэша по какому-то событию. Например, при вызове метода обновления данных можно удалить кэш, который связан с этими данными. В некоторых случаях можно не удалять, а сразу класть новые данные в кэш, если понимаете, что они скоро потребуются.
Инвалидацию по событию можно комбинировать с TTL. В таком случае при запросе данных из кэша можно проверять оставшееся время жизни, и если оно меньше, чем некоторое заданное значение, то установить TTL заново. Таким образом данные остаются в кэше, если ими активно пользуются.
Комбинация TTL + событийная инвалидация
На практике наиболее надёжен комбинированный подход:
- Событийная инвалидация как основной механизм — данные обновляются мгновенно при мутации
- TTL как safety net — страхует от пропущенных инвалидаций (баг, сбой, новый путь мутации)
При таком подходе TTL можно ставить длинный (1-2 часа), потому что он срабатывает только при пропуске инвалидации. Без событийной инвалидации TTL — единственная защита, и его приходится делать коротким (5-30 минут), что увеличивает CacheMissRate.
Подводные камни инвалидации
Race condition с inflight запросами
Если при инвалидации есть запрос, который уже вычисляет значение для того же ключа (factory in-flight), то после DEL из кэша factory завершится и запишет устаревшие данные обратно. Решение: при инвалидации удалять ключ не только из кэша, но и из in-flight Map (см. Redis как кэш для API).
Открытое пространство ключей
Если ключ кэша включает произвольный пользовательский параметр (например ?period=42), то при инвалидации невозможно перечислить все варианты. Решения:
- Whitelist параметров — кэшировать только для известного набора значений
- Wildcard инвалидация через
SCAN+DEL(дорого при большом количестве ключей) - Короткий TTL вместо инвалидации для таких ключей
Scope-aware инвалидация
Если данные фильтруются по scope (регион, tenant), при мутации нужно инвалидировать и конкретный scope, и агрегат “all”:
// Мутация в регионе 5
invalidate("leaders:5:month", "leaders:all:month")
Забыть “all” — частая ошибка, после которой страница “все регионы” показывает stale данные.
Пример обновления TTL на Lua
local key = KEYS[1];
local threshold = tonumber(ARGV[1]);
local new_ttl = tonumber(ARGV[2]);
local value = redis.call('get', key);
if value then
local ttl = redis.call('pttl', key);
if ttl >= 0 and ttl <= threshold then
redis.call('pexpire', key, new_ttl);
end;
end;
return value;Мета информация
Область:: 00 HighLoad
Родитель:: Кэширование
Источник::
Автор::
Создана:: 2024-06-18