Кеш это самый дешёвый способ ускорить сервис и самый простой способ показать пользователю устаревшие данные. На собесе спрашивают, где и что кешировать, как инвалидировать и что делать, когда кеш одновременно протух у тысячи запросов.
Уровни кеша
- Клиент и CDN: HTTP-заголовки
Cache-Control,ETag,Last-Modified. - Обратный прокси: nginx может кешировать ответы.
- Приложение: Redis или Memcached, общий для всех процессов и серверов.
- Процесс:
functools.lru_cache, словарь в памяти. Быстро, но у каждого воркера свой и не инвалидируется извне. - База данных: свой буферный кеш, материализованные представления.
Кеш Django
from django.core.cache import cache
key = f'catalog:v2:category:{category_id}:page:{page}'
data = cache.get(key)
if data is None:
data = build_catalog_page(category_id, page)
cache.set(key, data, timeout=300)
Бэкенды: Redis (django.core.cache.backends.redis.RedisCache, встроен с Django 4.0), Memcached, база, файлы, LocMemCache (только для одного процесса и разработки). Есть кеш целых страниц (cache_page) и фрагментов шаблонов. В FastAPI используют redis.asyncio напрямую или обёртки.
Стратегии
- Cache-aside (ленивая загрузка): читаем из кеша, при промахе из базы и кладём в кеш. Самая частая.
- Write-through: при записи в базу сразу обновляем кеш.
- Write-behind: пишем в кеш, в базу асинхронно позже. Быстро, но риск потери.
- TTL ограничивает, насколько данные могут устареть, даже если инвалидацию забыли.
Инвалидация
«Есть две сложные вещи в программировании: инвалидация кеша и именование». Подходы:
- удалять ключ при изменении данных (
cache.delete(key)после коммита); - версионировать ключи: хранить версию сущности и включать её в ключ, при изменении увеличивать версию, старые ключи умрут по TTL;
- короткий TTL там, где точность не критична.
Удаление ключа до коммита транзакции опасно: параллельный запрос успеет прочитать старые данные из базы и положить их в кеш снова.
Cache stampede
Популярный ключ истёк, и тысяча запросов одновременно идут в базу пересчитывать одно и то же. Защита:
- блокировка на пересчёт: первый запрос берёт lock в Redis (
SET key NX EX), остальные ждут или отдают старое значение; - раннее обновление: пересчитывать чуть раньше истечения, вероятностно или фоновой задачей;
- джиттер TTL: разбросать время истечения, чтобы ключи не протухали одновременно;
- stale-while-revalidate: отдавать устаревшее значение, пока одно обновление идёт в фоне.
Что кешировать, а что нет
Кешируют дорогие и часто читаемые данные: каталог, настройки, агрегаты, ответы внешних API. Осторожно с персональными данными: ключ должен включать пользователя или права, иначе один пользователь увидит данные другого. Не кешируют то, что должно быть строго актуальным: остатки при оформлении заказа, баланс перед списанием.
Redis не только кеш
Счётчики (INCR), лимиты частоты запросов, блокировки, очереди, сессии, pub/sub. Если Redis используется ещё и как хранилище, нужно продумать персистентность (RDB/AOF) и политику вытеснения (maxmemory-policy), иначе важные ключи могут быть вытеснены.
Частые ошибки
LocMemCacheв проде с несколькими воркерами.- Ключ кеша без пользователя или прав: утечка чужих данных.
- Нет TTL: устаревшие данные живут вечно.
- Инвалидация до коммита транзакции.
- Кеширование
Noneне различается с промахом, и отсутствующие объекты каждый раз идут в базу. - Большие объекты в кеше и сериализация pickle, несовместимая между версиями кода.
Что спросят на собесе
- Какие уровни кеша бывают, где кешировать в вашем сервисе.
- Что такое cache-aside, write-through, TTL.
- Как инвалидировать кеш, почему это сложно.
- Что такое cache stampede и как защититься.
- Чем кеш в процессе отличается от Redis.