Полное руководство по DRF Throttling: зачем он нужен и как настроить, применить и кастомизировать
Наличие limit_req в nginx‑обратном прокси безусловно эффективен. Однако, когда хочется задать разные политики для разных представлений/действий (например, логин – 5 раз в минуту, загрузка – 20 раз в день, просмотр – 1000 раз на пользователя), полагаться только на сервер/инфраструктуру становится рискованно. Throttling в DRF позволяет создавать ограничения, адаптированные к особенностям эндпоинта, что делает его обязательным знанием.
Зачем нужен Throttling
Throttling в DRF, как и разрешения (permissions), решает вопрос разрешить ли запрос, но в отличие от них он временный. Документация описывает его как «временное состояние, контролирующее скорость запросов клиента к API».
Практическая необходимость выглядит так:
- Смягчение злоупотреблений/атак: перебор логина, спам‑запросы, сканирование, простые DoS.
- Защита ресурсов/затрат: загрузки, внешние API‑вызовы, тяжёлые запросы, вызовы генеративного ИИ.
- Справедливое использование: предотвращение доминирования одного пользователя/ключа.
- Кодирование политики: правила вроде «этот API – N запросов в минуту» управляются в коде, а не в инфраструктуре.
Важно: throttling может применяться глобально или на уровне конкретного представления/действия – именно здесь nginx может оказаться недостаточным.
Как работает DRF Throttling (основные концепции)
1) Строка rate
Ограничения задаются в виде строк, например "100/day", "60/min".
2) Кому ограничение
UserRateThrottle: для аутентифицированных пользователей – поuser id, для анонимных – по IP.AnonRateThrottle: только анонимные запросы ограничиваются по IP.ScopedRateThrottle: применяет политику по scope (например,uploads).
IP‑идентификация берётся из X-Forwarded-For или REMOTE_ADDR; при работе за прокси важно корректно настроить NUM_PROXIES.
3) Хранение состояния
Базовый механизм использует Django cache backend. В однопроцессном окружении LocMemCache подходит, но в многопроцессных/мульти‑репликационных системах необходим общий кеш, например Redis.
Глобальная настройка: быстрый старт
REST_FRAMEWORK = {
"DEFAULT_THROTTLE_CLASSES": [
"rest_framework.throttling.AnonRateThrottle",
"rest_framework.throttling.UserRateThrottle",
],
"DEFAULT_THROTTLE_RATES": {
"anon": "100/day",
"user": "1000/day",
},
}
После такой конфигурации «базовая политика» применяется ко всем API‑эндпоинтам. При превышении лимита DRF возвращает HTTP 429 (Too Many Requests).
Применение к конкретному представлению
1) Класс‑основанное представление (APIView)
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.throttling import UserRateThrottle
class ExpensiveView(APIView):
throttle_classes = [UserRateThrottle]
def get(self, request):
return Response({"ok": True})
2) Функциональное представление (@api_view)
from rest_framework.decorators import api_view, throttle_classes
from rest_framework.throttling import UserRateThrottle
from rest_framework.response import Response
@api_view(["GET"])
@throttle_classes([UserRateThrottle])
def ping(request):
return Response({"pong": True})
3) ViewSet – только для конкретного действия (@action)
from rest_framework.decorators import action
from rest_framework.throttling import UserRateThrottle
from rest_framework.viewsets import ViewSet
from rest_framework.response import Response
class ItemViewSet(ViewSet):
@action(detail=True, methods=["post"], throttle_classes=[UserRateThrottle])
def purchase(self, request, pk=None):
return Response({"purchased": pk})
Атрибут throttle_classes на уровне действия имеет приоритет над настройками на уровне ViewSet.
ScopedRateThrottle: политика по «области» (рекомендовано)
Scoped throttling позволяет разделить правила по смысловым областям, например login, uploads, search.
REST_FRAMEWORK = {
"DEFAULT_THROTTLE_CLASSES": [
"rest_framework.throttling.ScopedRateThrottle",
],
"DEFAULT_THROTTLE_RATES": {
"login": "5/min",
"uploads": "20/day",
"search": "60/min",
},
}
В представлении задаём только throttle_scope:
from rest_framework.views import APIView
from rest_framework.response import Response
class LoginView(APIView):
throttle_scope = "login"
def post(self, request):
return Response({"ok": True})
DRF создаёт уникальный ключ из scope + user id или IP и хранит счётчик в кеше.
Создание собственного Throttle: ключ – ключевой момент
Хотя встроенные throttles покрывают большинство случаев, в реальных проектах часто требуется:
- Ограничение по комбинации IP + username.
- Ограничение по API‑ключу.
- Ограничение по заголовку/тенанту/организации.
- Использование другого кеша (например, Redis‑кластер).
1) Самый распространённый способ: наследование SimpleRateThrottle
from rest_framework.throttling import SimpleRateThrottle
class LoginBurstThrottle(SimpleRateThrottle):
scope = "login"
def get_cache_key(self, request, view):
username = (request.data.get("username") or "").lower().strip()
ident = self.get_ident(request) # IP‑идентификатор
if not username:
return None
return f"throttle_login:{ident}:{username}"
Регистрация в настройках:
REST_FRAMEWORK = {
"DEFAULT_THROTTLE_CLASSES": [
"path.to.LoginBurstThrottle",
],
"DEFAULT_THROTTLE_RATES": {
"login": "5/min",
},
}
2) Использование другого кеша (cache attribute)
from django.core.cache import caches
from rest_framework.throttling import AnonRateThrottle
class CustomCacheAnonThrottle(AnonRateThrottle):
cache = caches["alternate"]
Что важно знать перед деплоем
1) Проблема одинакового IP за прокси
IP‑идентификация основана на X-Forwarded-For/REMOTE_ADDR. Если NUM_PROXIES неверно настроен, все пользователи могут восприниматься как один.
2) LocMemCache слаб в многопроцессных/мульти‑серверных средах
Кеш локальный для процесса, поэтому счётчики могут расходиться. Для надёжной работы используйте Redis или аналогичный общий кеш.
3) Условие гонки (race condition)
Встроенный механизм может позволить несколько запросов пройти сверх лимита при высокой конкуренции. Для критичных операций (платёж, купон) рассмотрите атомарный счётчик, например Redis INCR + EXPIRE.
4) Клиентская дружелюбность: 429 и Retry-After
По умолчанию DRF возвращает 429. Реализовав wait() в кастомном throttle, можно добавить заголовок Retry-After.
Итоги: nginx + DRF throttling – двойная защита

- nginx – первый щит, отсекающий массовый трафик и атаки.
- DRF throttling – точечные ограничения, учитывающие смысл и стоимость эндпоинта.
Особенно полезно ограничивать каждый эндпоинт индивидуально: DRF позволяет это сделать быстро и без изменения инфраструктуры.
Комментариев нет.