Как стать автором
Поиск
Написать публикацию
Обновить
63.59

Меньше нагрузки — больше запросов: искусство кеширования API

Уровень сложностиСредний
Время на прочтение11 мин
Количество просмотров2.2K

Привет! Меня зовут Дима, я 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 с новым содержимым.

Когда использовать условные запросы

Сценарий

Что использовать

Точное отслеживание версии ресурса

ETag + If-None-Match

Простая проверка по дате изменения

Last-Modified + If-Modified-Since

Преимущества

  • Экономия трафика (особенно на медленных сетях)

  • Снижение нагрузки на сервер

  • Более быстрые ответы (по сети передаётся меньше данных)

  • Совместимость с прокси, браузерами и 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 без увеличения серверных ресурсов или переработки логики приложения. Оно позволяет не только снизить нагрузку на базу данных и сервер, но и обеспечить быстрый отклик пользователю — особенно при высокой конкуренции за миллисекунды.

В этой статье мы рассмотрели кеширование на всех уровнях:

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

Мы умеем внедрять кеширование в продуктах с разной архитектурой и нагрузкой — от проектирования стратегии до настройки и обкатки. Обращайтесь, и мы разработаем решение, которое впишется в вашу инфраструктуру.

Дополнительные ресурсы

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Какие уровни кеширования вы используете чаще всего?
62.5% Клиентский (браузер, прокси)10
62.5% Серверный (Redis, Memcached)10
25% Промежуточный (CDN, reverse proxy)4
12.5% Пока не использую2
Проголосовали 16 пользователей. Воздержавшихся нет.
Теги:
Хабы:
+9
Комментарии4

Публикации

Информация

Сайт
doubletapp.ai
Дата регистрации
Дата основания
Численность
51–100 человек
Местоположение
Россия