Как обновлять кэш при изменениях с админом с глобальным доступом?
Что проверяют
Понимаете ли вы инвалидацию кеша в распределённой системе: админ одним действием меняет данные, закешированные у множества пользователей и на нескольких инстансах. Следом спрашивают, что будет, если сбросить всё разом, и как не оставить пользователю отозванные права.
Как отвечать
Базовая схема cache-aside: чтение из Redis, при промахе запрос в базу и запись в кеш с TTL. При изменении ключ удаляется, а не перезаписывается: при двух параллельных записях удаление не оставит в кеше старое значение.
Для глобальных изменений:
- инвалидация после коммита: в Django
transaction.on_commit(lambda: cache.delete(key)). Удалите до коммита, и соседний запрос успеет перечитать старые данные и снова их закешировать; - версия в ключе: вместо поиска тысяч ключей хранится счётчик
settings:version, ключи строятся какmenu:user42:v17. Админ меняет настройку, счётчик растёт, старые ключи умирают по TTL; - группы ключей: ключи, связанные с сущностью, хранятся в Redis set и удаляются пачкой.
KEYS *в проде не используют, он блокирует Redis, в крайнем случаеSCAN; - локальный кеш процессов: если есть in-memory кеш (
lru_cache,cachetools), каждый инстанс нужно уведомить через Redis Pub/Sub или брокер; - права и роли: когда админ отзывает доступ, инвалидация обязательна или TTL короткий, иначе пользователь сохранит доступ до истечения ключа.
После массового сброса база получает наплыв запросов (cache stampede). Помогают блокировка на пересчёт ключа, случайный разброс TTL и отдача устаревшего значения на время пересчёта.
Где спотыкаются
Вешают инвалидацию на сигнал post_save и забывают, что QuerySet.update() и bulk_create() его не отправляют. Массовая правка проходит мимо.
Предлагают cache.clear() на каждое изменение. Сбрасывается всё, включая сессии, если они живут в том же Redis, а база ложится от наплыва.
Не ставят TTL вовсе, надеясь на идеальную инвалидацию. Одна пропущенная ветка кода, и данные устаревают навсегда.