Какие механизмы используются для автоматического удаления устаревших данных из кэша
Что проверяют
Различаете ли вы три разные вещи, которые часто сваливают в одну: вытеснение при нехватке места, протухание по времени и явную инвалидацию при изменении данных. Дальше почти всегда идёт вопрос о том, что произойдёт, когда у тысячи ключей срок жизни истечёт одновременно.
Как отвечать
Сначала разделите механизмы:
- вытеснение по размеру: кэш ограничен, при переполнении что-то выбрасывается. LRU выкидывает давно не использованное, LFU редко используемое, FIFO самое старое. Caffeine, стандарт для JVM, работает по
W-TinyLFU: гибрид держит и частые, и недавние ключи и обычно даёт лучший hit rate, чем чистый LRU; - срок жизни:
expireAfterWriteсчитает от записи,expireAfterAccessот последнего обращения,refreshAfterWriteне выбрасывает значение, а обновляет его в фоне, отдавая старое до готовности нового; - инвалидация: явное удаление ключа при изменении данных (
@CacheEvictв Spring), версия внутри самого ключа или рассылка сообщения об изменении на все инстансы.
Важная деталь про реализацию: ни Caffeine, ни Redis не удаляют записи ровно в момент истечения. Caffeine чистит по ходу операций чтения и записи, Redis сочетает ленивое удаление при обращении с фоновым выборочным проходом. То есть TTL это гарантия «не отдадим просроченное», а не «освободим память в секунду X».
Для Redis назовите maxmemory-policy: по умолчанию там noeviction, то есть запись начнёт падать с ошибкой, а не молча вытеснять старое. Осознанный выбор это allkeys-lru или allkeys-lfu для чистого кэша и volatile-ttl, если рядом лежат данные без срока жизни.
Где спотыкаются
Прогревают кэш пачкой с одинаковым TTL. Через час всё протухает разом, и вся нагрузка уходит в базу. Лечится разбросом времени жизни и загрузкой в один поток на ключ, чтобы сто запросов не полезли за одним значением.
Держат локальный кэш в кластере и забывают, что инстансов пять. Один обновил данные, остальные четыре ещё десять минут отдают старое.
Считают WeakHashMap кэшем. Он держит ключи по слабым ссылкам и сравнивает их по ссылочному равенству, поэтому собранный заново строковый ключ туда просто не попадёт.