DjangoMiddleSystem design

Как работает система кэширования Django и какие бэкенды она поддерживает?

Django поддерживает бэкенды Memcached, Redis, файловый, in-memory и БД. API: cache.set/get/delete/get_or_set, декораторы @cache_page и @vary_on_headers для view-уровня.

Система кэширования Django

Django предоставляет единый cache API через объект django.core.cache.cache, абстрагирующий конкретный бэкенд. Бэкенд выбирается настройкой CACHES в settings.py.

Поддерживаемые бэкенды

  • django.core.cache.backends.redis.RedisCache — Redis (рекомендуется для прода, Django 4.0+)
  • django.core.cache.backends.memcached.PyMemcacheCache — Memcached
  • django.core.cache.backends.filebased.FileBasedCache — файлы на диске
  • django.core.cache.backends.locmem.LocMemCache — in-process память (по умолчанию, не для прода)
  • django.core.cache.backends.db.DatabaseCache — таблица в БД
  • django.core.cache.backends.dummy.DummyCache — заглушка для разработки

Конфигурация Redis

# settings.py
CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.redis.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/1",
        "TIMEOUT": 300,  # секунды, None = никогда не истекает
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
            "MAX_ENTRIES": 1000,
        },
    },
    "sessions": {
        "BACKEND": "django.core.cache.backends.redis.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/2",
    },
}

Низкоуровневый cache API

from django.core.cache import cache

# Базовые операции
cache.set("user:42:profile", {"name": "Alice"}, timeout=600)
data = cache.get("user:42:profile")  # None если нет

# Атомарный get-or-set
def get_user_stats(user_id):
    key = f"user:{user_id}:stats"
    return cache.get_or_set(
        key,
        lambda: compute_stats(user_id),  # вызывается только при промахе
        timeout=3600,
    )

# Инкремент (атомарный)
cache.set("page:views", 0, timeout=None)
cache.incr("page:views")

# Массовые операции
cache.set_many({"a": 1, "b": 2, "c": 3}, timeout=60)
results = cache.get_many(["a", "b", "c"])  # {"a": 1, "b": 2, "c": 3}
cache.delete_many(["a", "b"])

# Инвалидация по паттерну (только django-redis)
from django_redis import get_redis_connection
con = get_redis_connection("default")
keys = con.keys("user:42:*")
if keys:
    con.delete(*keys)

Кэширование на уровне view

from django.views.decorators.cache import cache_page, never_cache
from django.views.decorators.vary import vary_on_headers, vary_on_cookie

# Кэш на 15 минут
@cache_page(60 * 15)
def article_list(request):
    ...

# Раздельный кэш для аутентифицированных пользователей
@vary_on_cookie
@cache_page(60 * 5)
def dashboard(request):
    ...

# Запрет кэша для приватных страниц
@never_cache
def account_settings(request):
    ...

Кэширование шаблонных фрагментов

{% load cache %}
{% cache 500 sidebar request.user.id %}
    ... дорогой фрагмент ...
{% endcache %}

Подводные камни

  • LocMemCache в проде — значения не разделяются между процессами Gunicorn; каждый воркер имеет собственный кэш, invalidation не работает глобально.
  • Cache stampede — при истечении популярного ключа десятки запросов одновременно вычисляют значение. Решение: cache.get_or_set с блокировкой или django-redis lock.
  • Кэширование объектов QuerySet — кэшируется итерируемый результат, но не ленивый QuerySet. cache.set("qs", qs) выполнит SQL немедленно при сериализации.
  • TIMEOUT=0 означает «немедленно устаревает», а не «никогда». Для бесконечного хранения используйте TIMEOUT=None.
  • vary_on_cookie без логики — если кэш варьируется по cookie, анонимные пользователи с sessionid всё равно получают уникальные ключи, что обнуляет эффективность кэша.
  • Несогласованность при инвалидации — при обновлении модели ключ в кэше остаётся старым. Нужны сигналы post_save/post_delete для явной инвалидации.
  • Конкуренция баз данных кэша — DatabaseCache создаёт write lock при каждой записи; под нагрузкой становится узким местом хуже самих запросов к БД.

Common mistakes

  • Описывать caching только как термин и не показывать механизм на минимальном примере.
  • Игнорировать ошибки, пустые данные, конкурентный доступ или границы транзакции.
  • Не связывать поведение с официальным контрактом Django и реальной эксплуатацией.

What the interviewer is testing

  • Объясняет caching через последовательность действий, а не через набор ключевых слов.
  • Приводит короткий кодовый пример или production-сценарий с ожидаемым поведением.
  • Называет хотя бы один риск: производительность, безопасность, транзакции, память или сопровождение.

Sources

Related topics