Привет! Меня зовут Дима, я Backend-разработчик в Doubletapp. В этой статье расскажу про кеширование API (на примере Django Ninja): чем оно полезно бизнесу и когда его стоит внедрять.
Содержание
Почему кеширование может понадобиться
Когда ваш продукт начинает расти, а пользователей становится всё больше, любой повторяющийся запрос к серверу — это лишняя нагрузка. Даже если человек просто обновил страницу или несколько пользователей задали один и тот же вопрос приложению, сервер отвечает заново — и тратит на это ресурсы.
А теперь представьте: вы можете обрабатывать одновременно в несколько раз больше запросов пользователей без расширения ресурсов и без переписывания ядра продукта. Как? С помощью кеширования — подхода, который «запоминает» одинаковые запросы и снижает нагрузку на сервер.

Почему советы подойдут не только для Django
Хотя в статье используются примеры на Django Ninja, принципы кеширования универсальны и применимы к любым технологиям — будь то Flask, FastAPI, Express.js, Laravel или Spring.
Это связано с тем, что кеширование опирается на стандартные механизмы и уровни взаимодействия в вебе:
-
HTTP-протокол: заголовки вроде Cache-Control, ETag, Last-Modified и коды ответов (304 Not Modified) работают одинаково в любом языке или фреймворке.
-
Хранилища «ключ-значение»: Redis, Memcached и другие in-memory базы данных используются во всех современных приложениях.
-
Кеш на клиенте: браузеры и промежуточные прокси (например, Cloudflare, Nginx) одинаково интерпретируют HTTP-заголовки вне зависимости от того, на чём написан сервер.
Поэтому, даже если вы используете другой стек, советы из этой статьи останутся актуальными и легко адаптируемыми под ваш проект. Django здесь выступает лишь как конкретный и понятный пример.
Обзор типов кеша
Эффективное кеширование API возможно на разных уровнях — от браузера до серверного хранилища. Каждый тип кеша решает свою задачу и может быть использован как по отдельности, так и в комбинации. Рассмотрим отдельно каждый тип.
Серверный кеш (хранилища «ключ-значение»)
Серверный кеш — временное in-memory хранилище на стороне backend-приложения, например как Redis, Memcached или аналогичные key-value решения. Это один из самых гибких и контролируемых способов кеширования, поскольку он полностью находится под управлением разработчика.
На практике это означает сохранение результатов API-запросов, промежуточных вычислений или подготовленных данных в память, чтобы при повторных обращениях не выполнять те же самые действия заново.
Плюсы:
-
Мгновенный доступ к данным из памяти
-
Гибкое управление временем жизни (TTL) и ключами
-
Хорошо масштабируется и легко интегрируется в любой стек
-
Подходит для кеширования любых данных — от сериализованных JSON-ответов до SQL-запросов
Минусы:
-
Может потребовать синхронизации при работе в распределенных системах
-
Нужно следить за объемом хранимых данных, чтобы не перегрузить память
Серверный кеш отлично сочетается с фреймворками, такими как Django, через встроенный модуль django.core.cache.
Пример: кеширование эндпоинта
from django.core.cache import cache from ninja import Router import hashlib import json router = Router() def cache_response(timeout=60): def decorator(func): async def wrapper(request, *args, **kwargs): # Формируем ключ на основе URL и параметров key_source = f"{request.path}?{json.dumps(dict(request.GET), sort_keys=True)}" key = "api_cache:" + hashlib.md5(key_source.encode()).hexdigest() # Пытаемся достать из кеша cached = cache.get(key) if cached: return cached # Вызываем обработчик и кладём результат в кеш response = await func(request, *args, **kwargs) cache.set(key, response, timeout) return response return wrapper return decorator @router.get("/public-data") @cache_response(timeout=300) # кеш на 5 минут async def public_data(request): return {"data": "expensive computation result"}
Такой подход подходит для GET-запросов с детерминированными результатами. Для POST/PUT/DELETE — с осторожностью.
Кеширование запросов в ORM
Если ваш API активно использует ORM-запросы, можно дополнительно ускорить работу, кешируя сами SQL-запросы. Для этого существует библиотека django-cachalot, которая автоматически сохраняет результаты ORM-запросов в кеш и переиспользует их.
Установка:
pip install django‑cachalot
Настройка:
Добавьте в INSTALLED_APPS:
INSTALLED_APPS = [ ... 'cachalot', ]
Обязательно настройте кеш-бэкенд (например, Redis):
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.redis.RedisCache', 'LOCATION': 'redis://127.0.0.1:6379/1', } }
Пример использования:
from myapp.models import Product def get_products(): # Этот запрос будет автоматически закеширован return list(Product.objects.filter(is_active=True).order_by("name"))
django-cachalot автоматически инвалидирует кеш при изменении данных в базе и отлично работает для часто повторяющихся SELECT-запросов. Это хороший способ оптимизировать API, не переписывая бизнес-логику.
Тут также стоит обратить внимание на нюанс: не используйте django-cachalot, если в таблице происходит более 50 изменений в минуту. Это не приведёт к ошибкам, но производительность django-cachalot снизится, и он начнёт замедлять проект, вместо того чтобы его ускорять. Подробнее об этом — в документации проекта.
Инвалидация кеша, ключи и TTL
При кешировании API важно не только сохранить результат в кеше, но и грамотно управлять его жизненным циклом. Кеш сам по себе не является «умным» — он не знает, когда данные на сервере устарели или изменились. Поэтому вам как разработчику нужно продумать, как, когда и на основе чего очищать или обновлять кеш. Это включает в себя два ключевых аспекта:
-
Инвалидацию — принудительное удаление устаревших данных при изменении состояния (например, при обновлении записи в БД).
-
TTL (Time To Live) — автоматическое истечение срока жизни кешированных данных, чтобы они не оставались в памяти вечно.
Если эти элементы не продуманы, кеш из помощника превращается в источник ошибок: пользователи видят старые данные, система загружается бессмысленными запросами, а отладка становится хаотичной.
Клиентский кеш (браузер, прокси)

Клиентский кеш работает на стороне пользователя — в браузере, в мобильном приложении, в промежуточных HTTP-проксирующих серверах (например, локальный CDN). Он позволяет избежать повторных запросов к серверу, если запрашиваемые данные уже были получены ранее и считаются актуальными.
Этот тип кеша управляется с помощью стандартных HTTP-заголовков, которые сервер указывает в ответе.
Ключевые заголовки:
-
Cache-Control: управляет правилами хранения и актуальности (например,
max-age,no-cache,public,private) -
ETag: хэш или версия ресурса, позволяющая сравнивать старую и новую копии
-
Last-Modified: дата последнего изменения ресурса
-
Expires: дата/время, до которого кеш считается свежим
Если браузер получает ответ с такими заголовками, он может, не обращаться к серверу вовсе, если ресурс ещё не устарел.
Преимущества:
-
Ускорение загрузки данных на клиенте
-
Снижение количества обращений к API
-
Простота использования — всё основано на стандартах HTTP
Ограничения:
-
Подходит только для публичных и стабильных данных
-
Не рекомендуется использовать для персонализированных или конфиденциальных данных без дополнительных мер (
private,no-store) -
Ошибки в конфигурации могут привести к тому, что клиент будет использовать устаревшую информацию
Клиентский кеш особенно полезен для кеширования метаданных, справочников, общедоступных коллекций и других редко изменяющихся ресурсов. При правильной настройке он обеспечивает мгновенный отклик и экономию трафика без участия сервера. Кроме того, его можно эффективно использовать для периодически обновляющихся данных, где не требуется мгновенная актуальность — например, для отображения рейтингов, статистики, агрегатов или данных аналитики, которые можно кешировать на несколько минут или часов без ущерба для пользовательского опыта.
HTTP-заголовки: Cache-Control, Expires

Рассмотрим подробнее ключевые заголовки, которые отвечают за контроль кеша.
Cache-Control
Это основной заголовок для управления поведением кеша. Он указывает, кто может кешировать ответ, как долго, и при каких условиях.
Основые директивы:
-
public — ответ может кешироваться кем угодно (браузером, прокси, CDN)
-
private — кешировать может только клиент (например, браузер)
-
no-cache — требует повторной валидации при каждом запросе
-
no-store — запрещает хранение ответа вообще (например, для токенов)
-
max-age=3600 — указывает, что ответ считается свежим в течение 3600 секунд
Пример заголовка:
Cache-Control: public, max-age=300
Expires
Механизм, указывающий дату и время, до которых ответ считается свежим.
Пример заголовка:
Expires: Wed, 12 Jul 2025 12:00:00 GMT
Если Cache-Control и Expires заданы одновременно, приоритет имеет Cache-Control.
Совместное использование этих заголовков даёт полный контроль над кешированием — от простой настройки сроков хранения до продвинутой валидации и обновления данных без избыточного трафика.
Условные HTTP‑запросы (Conditional Requests)

Условные запросы — это механизм, позволяющий клиенту узнать, изменился ли ресурс на сервере с момента последнего получения. Если ресурс остался прежним, сервер отвечает кодом 304 Not Modified и не передаёт тело ответа, экономя трафик и ускоряя работу приложения. Это особенно важно для часто используемых, но редко обновляемых данных: списков товаров, публичных профилей, метаданных, категорий и т.д.
Как это работает
Клиент сохраняет некоторую «версию» ресурса (например, ETag или Last Modified). При следующем запросе он отправляет её в заголовке If-None-Match или If-Modified-Since.
Если данные не изменились — сервер возвращает 304 Not Modified.
Если изменились — клиент получает обычный 200 OK с новым содержимым.
Когда использовать условные запросы
|
Сценарий |
Что использовать |
|
Точное отслеживание версии ресурса |
|
|
Простая проверка по дате изменения |
|
Преимущества
-
Экономия трафика (особенно на медленных сетях)
-
Снижение нагрузки на сервер
-
Более быстрые ответы (по сети передаётся меньше данных)
-
Совместимость с прокси, браузерами и CDN
ETag
ETag (Entity Tag) — это уникальный идентификатор версии ресурса. Когда клиент получает ответ с ETag, он может при следующем запросе передать его в заголовке If-None-Match. Если содержимое не изменилось, сервер возвращает 304 Not Modified, и тело ответа не пересылается.
Когда использовать
Используйте ETag, если:
-
Вам нужна более точная проверка изменений, чем позволяет
Last-Modified(вплоть до байта или логического состояния) -
Вам важно контролировать версионирование ресурса на уровне содержимого, а не только времени
ETag особенно полезен в случаях, когда точность имеет значение: файлы, конфигурации, бинарные ресурсы, вложенные JSON-структуры и всё, что может измениться «незаметно» с точки зрения времени.
Пример на Django
import hashlib, json from django.http import JsonResponse from django.utils.http import quote_etag def generate_etag(data): return quote_etag( hashlib.md5(json.dumps(data, sort_keys=True).encode()).hexdigest() ) @api.get("/status") def get_status(request): data = {"version": "1.2.3", "uptime": "120h"} etag = generate_etag(data) if request.headers.get("If-None-Match") == etag: return JsonResponse({}, status=304) response = JsonResponse(data) response["ETag"] = etag return response
Пример запроса
Первый ответ от сервера:
GET /api/products/42 HTTP/1.1 HTTP/1.1 200 OK ETag: "v42-abcdef" Content-Type: application/json { "id": 42, "name": "Product A", "price": 199.00 }
Повторный запрос от клиента сIf-None-Match:
GET /api/products/42 HTTP/1.1 If-None-Match: "v42-abcdef"
Серверный ответ, если данных не меняли:
HTTP/1.1 304 Not Modified
Серверный ответ, если данные обновились:
HTTP/1.1 200 OK ETag: "v43-xyz123" Content-Type: application/json { "id": 42, "name": "Product A", "price": 189.00 }
Last-Modified
Заголовок Last-Modified сообщает клиенту, когда ресурс последний раз изменялся. Он используется в паре с заголовком If-Modified-Since, который клиент может отправить в следующем запросе, чтобы узнать, актуальные ли у него данные. Если данные не изменились — сервер отвечает кодом 304 Not Modified и не отправляет тело ответа, экономя ресурсы и трафик.
Этот механизм проще и легче реализуется, чем ETag, и отлично подходит для кеширования публичных и редко обновляемых ресурсов — например, статей, профилей, товаров, новостей и т.д.
Когда использовать
Используйте Last-Modified, если:
-
У вас есть поле
updated_at, обновляемое при каждом изменении -
Данные не чувствительны к миллисекундам изменений
Пример использования в Django
from datetime import datetime from django.utils.http import http_date, parse_http_date_safe from django.http import JsonResponse from myapp.models import Article from ninja import Router router = Router() @router.get("/articles/{article_id}") def get_article(request, article_id: int): article = Article.objects.get(id=article_id) last_modified = article.updated_at # Проверяем заголовок If-Modified-Since since = request.headers.get("If-Modified-Since") if since: since_dt = parse_http_date_safe(since) if since_dt and last_modified.timestamp() <= since_dt: return JsonResponse({}, status=304) response = JsonResponse({ "id": article.id, "title": article.title, "content": article.content, }) response["Last-Modified"] = http_date(last_modified.timestamp()) return response
Пример ответа сервера
HTTP/1.1 200 OK Content-Type: application/json Last-Modified: Wed, 10 Jul 2025 12:45:00 GMT { "id": 42, "title": "Как работает Last-Modified", "content": "Это пример статьи с поддержкой условных запросов." }
Пример повторного запроса клиента
HTTP/1.1 304 Not Modified
Если данные изменились, сервер вернёт новый контент с обновлённым Last-Modified.
Промежуточное кеширование (CDN, reverse proxy)

Промежуточное кеширование происходит между клиентом и вашим сервером. Его реализуют с помощью CDN (Content Delivery Network) или обратного прокси — чаще всего через Nginx, Varnish, Cloudflare, Fastly и др.
Это особенно эффективный уровень кеширования, потому что он разгружает API сервер до того, как запрос вообще до него дойдёт. Ответы на часто запрашиваемые ресурсы (например, публичные API, изображения, JSON-коллекции) могут кешироваться прямо на границе сети, ближе к пользователю.
Когда использовать промежуточный кеш
Использовать CDN или reverse proxy кеширование стоит, если:
-
Вы отдаёте публичные и часто повторяющиеся данные (например, справочники, каталоги)
-
Вы хотите уменьшить задержку для пользователей по всему миру
Преимущества
-
Снижение нагрузки на сервер
-
Быстрые ответы (не нужно проксировать до API)
-
Меньшая задержка для географически удалённых пользователей
-
Простая интеграция с существующей инфраструктурой на базе Cloud Provider
Недостатки
-
Сложнее управлять инвалидированием кеша (особенно при частых изменениях данных)
-
Не подходит для персональных данных без настройки
private,no-store -
Требуется точная настройка заголовков, иначе кеш может оказаться не там, где нужно — или наоборот, вообще не будет работать
-
Сложнее тестировать
Пример кеширования API через Nginx
Если у вас есть API, отдающий JSON-данные, вы можете настроить Nginx для кеширования ответов следующим образом.
proxy_cache_path /tmp/nginx_cache levels=1:2 keys_zone=api_cache:10m inactive=5m max_size=100m; server { listen 80; location /api/ { proxy_pass http://127.0.0.1:8000; proxy_cache api_cache; proxy_cache_valid 200 302 10m; # кешировать 200/302 ответы на 10 минут proxy_cache_valid 404 1m; # ошибки кешируем на 1 минуту proxy_cache_key "$scheme$request_method$host$request_uri"; add_header X-Cache-Status $upstream_cache_status; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }
Что здесь происходит:
-
proxy_cache_path— указывает, где хранить кеш и на каких условиях -
proxy_cache— включает кеш для текущегоlocation -
proxy_cache_valid— задаёт TTL для разных типов ответов -
proxy_cache_key— формирует ключ (по умолчанию чувствителен к query params) -
X-Cache-Status— можно использовать для отладки (будетHIT,MISS,BYPASS)
Чтобы ответы кешировались, ваш API должен разрешать это либо через HTTP-заголовки (Cache-Control: public, max-age=600), либо на уровне логики. Если в ответах присутствует Cache-Control: no-store или private — Nginx (и большинство CDN) их кешировать не будет. Также стоит учитывать, что POST-запросы не кешируются по умолчанию, кеш применим в основном к GET-запросам.
Промежуточное кеширование — мощный инструмент в арсенале оптимизации API. Оно особенно полезно в случаях высокой нагрузки и геораспределённых пользователей. При этом, как и любой инструмент, требует внимательной настройки и понимания его ограничений. Правильно настроенный reverse proxy может обрабатывать большинство трафика вообще без участия приложения, обеспечивая мгновенный отклик и масштабируемость.
Заключение
Кеширование — это один из самых мощных инструментов для масштабирования и ускорения API без увеличения серверных ресурсов или переработки логики приложения. Оно позволяет не только снизить нагрузку на базу данных и сервер, но и обеспечить быстрый отклик пользователю — особенно при высокой конкуренции за миллисекунды.
В этой статье мы рассмотрели кеширование на всех уровнях:
-
На сервере — через key-value хранилища и ручную инвалидацию;
-
На стороне клиента — с помощью HTTP-заголовков и условных запросов (
ETag,Last-Modified); -
На промежуточных уровнях — используя CDN и reverse proxy;
🔑 Главное — не кешировать всё подряд, а осознанно подбирать стратегию под конкретный сценарий. Учитывайте частоту обновления данных, их критичность, тип клиентов и требования к производительности. Немного кеша — это быстро. Хорошее кеширование — это искусство.
Мы умеем внедрять кеширование в продуктах с разной архитектурой и нагрузкой — от проектирования стратегии до настройки и обкатки. Обращайтесь, и мы разработаем решение, которое впишется в вашу инфраструктуру.
Дополнительные ресурсы
ссылка на оригинал статьи https://habr.com/ru/articles/928874/
Добавить комментарий