Как обеспечивать инвалидность кэша при его невалидности
Что проверяют
Понимаете ли вы, что инвалидация кэша это вопрос согласованности данных, а не размера памяти. Интервьюеру важно, как вы гарантируете, что после изменения в базе пользователь не увидит старое значение, и что будет при гонке запросов. Следом часто спрашивают про лавину запросов в базу, когда протух популярный ключ.
Как отвечать
Разведите три вещи: вытеснение (LRU, LFU) решает проблему памяти, TTL ограничивает время жизни устаревших данных, а инвалидация по событию убирает их сразу. Дальше перечислите подходы:
- TTL как страховка:
r.set(key, value, ex=300)в redis-py илиcachetools.TTLCache. Просто, но между изменением и истечением срока данные неверны; - удаление при записи (cache-aside): после изменения в базе удалить ключ, следующий читатель заново заполнит кэш. Удалять надо после коммита транзакции, в Django для этого есть
transaction.on_commit; - версионированные ключи: в ключ входит версия или
updated_at, напримерuser:42:v17. Старые записи никто не читает, они сами уйдут по TTL. Удобно, когда один объект лежит под многими ключами; - события: изменение публикуется в Redis Pub/Sub или Kafka, и все экземпляры сервиса сбрасывают свои локальные кэши. Для изменений в обход приложения используют CDC, например Debezium.
Защита от лавины: блокировка на пересчёт через SET key NX EX, случайный разброс TTL, чтобы ключи не истекали одновременно, или ранний пересчёт до истечения срока.
Про стандартную библиотеку: у functools.lru_cache нет TTL, сбросить можно только весь кэш через cache_clear(), и живёт он внутри одного процесса.
Где спотыкаются
Называют LRU способом инвалидации. LRU выбрасывает давно не читанные записи, а часто читаемый объект, который только что изменили, как раз останется в кэше со старым значением.
Удаляют ключ до коммита. Параллельный запрос успевает прочитать из базы старые данные и положить их обратно, и кэш остаётся устаревшим до конца TTL.
Используют lru_cache в приложении под Gunicorn с несколькими воркерами. У каждого процесса свой кэш, и сброс в одном воркере остальные не видят.