Привет! Меня зовут Дима, я 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;
🔑 Главное — не кешировать всё подряд, а осознанно подбирать стратегию под конкретный сценарий. Учитывайте частоту обновления данных, их критичность, тип клиентов и требования к производительности. Немного кеша — это быстро. Хорошее кеширование — это искусство.
Мы умеем внедрять кеширование в продуктах с разной архитектурой и нагрузкой — от проектирования стратегии до настройки и обкатки. Обращайтесь, и мы разработаем решение, которое впишется в вашу инфраструктуру.