Архитектура

Кэш: как ускорить приложение и не начать показывать чужие данные

01.09.2026 · TitarX · 👁 4

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

Прежде чем кэшировать

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

Кэш уместен, когда данные дороги в получении, часто запрашиваются и меняются заметно реже, чем читаются.

Уровни

Кэшировать можно на разной высоте, и цена ошибки везде разная.

  • Результат запроса к базе — самый безопасный уровень: данные ещё не смешаны с контекстом пользователя.
  • Фрагмент страницы — блок «популярные статьи», одинаковый для всех.
  • Страница целиком — быстрее всего и опаснее всего: именно здесь чаще всего утекают приватные данные.
  • HTTP-кэш (заголовки Cache-Control, ETag) — работает на стороне браузера и CDN.

Ключ решает всё

Большинство аварий с кэшем — это неполный ключ. Если в ключе не учтён пользователь, первый посетитель наполнит кэш своими данными, а остальные их увидят:

# Опасно: один ключ на всех
key = "dashboard"

# Правильно: всё, от чего зависит результат, входит в ключ
key = f"dashboard:v2:user:{user.id}:lang:{language}"

Префикс версии (v2) — дешёвый приём: при изменении структуры данных достаточно поднять номер, и старые записи перестают использоваться без ручной очистки.

Приватные данные разумнее вообще не класть в общий кэш. Если всё же приходится — короткий TTL и обязательный идентификатор пользователя в ключе.

Инвалидация

Два основных подхода, и обычно нужны оба.

По времени (TTL). Просто и надёжно: запись живёт заданное время. Цена — устаревшие данные в течение этого окна. Подходит там, где небольшая задержка допустима: счётчики, списки, агрегаты.

По событию. Кэш сбрасывается при изменении данных. Точно, но требует помнить обо всех местах, где данные меняются — включая админку, консольные команды и миграции. В Django для этого удобны сигналы:

from django.core.cache import cache
from django.db.models.signals import post_delete, post_save
from django.dispatch import receiver


@receiver([post_save, post_delete], sender=Article)
def drop_article_cache(sender, instance, **kwargs):
    cache.delete(f"article:{instance.slug}")
    cache.delete("articles:latest")

Разумная стратегия — сбрасывать по событию и всё равно ставить TTL как страховку: если сброс где-то забыт, данные протухнут сами.

Одновременный промах

Популярный ключ истёк — и сотня запросов одновременно обнаружила пустой кэш. Все они идут в базу с одинаковым тяжёлым запросом. Это cache stampede, и он способен уронить базу именно в момент пиковой нагрузки.

Простое средство — блокировка: первый запрос считает, остальные ждут готового результата:

def get_popular_articles():
    data = cache.get("articles:popular")
    if data is not None:
        return data

    # add() отработает только у одного процесса — он и пересчитает
    if cache.add("lock:articles:popular", "1", timeout=30):
        try:
            data = expensive_query()
            cache.set("articles:popular", data, timeout=300)
        finally:
            cache.delete("lock:articles:popular")
        return data

    time.sleep(0.05)
    return cache.get("articles:popular") or expensive_query()

Второй приём — разброс времени жизни: если у тысячи ключей TTL ровно 300 секунд, они истекут одновременно. Случайная добавка (300 + random.randint(0, 60)) размазывает нагрузку.

Чек-лист перед выкладкой

  • Входит ли в ключ всё, от чего зависит результат: пользователь, язык, права, версия структуры?
  • Что увидит пользователь, если кэш вернёт данные минутной давности?
  • Работает ли приложение, если кэш недоступен целиком?
  • Сбрасывается ли кэш при изменении через админку и команды?
  • Есть ли TTL как страховка от забытой инвалидации?

Что в итоге

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