Урок по треку Python для веба: Django и FastAPI 7 мин

Кеширование в Python-бэкенде: Redis, стратегии и инвалидация

Redis и кеш Django, стратегии и инвалидация, cache stampede, кеш на уровне HTTP.

Кеш это самый дешёвый способ ускорить сервис и самый простой способ показать пользователю устаревшие данные. На собесе спрашивают, где и что кешировать, как инвалидировать и что делать, когда кеш одновременно протух у тысячи запросов.

Уровни кеша

  • Клиент и 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.

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.