Maîtriser le throttling DRF : pourquoi c’est nécessaire et comment le configurer, l’appliquer et le personnaliser
Limiter le trafic avec limit_req dans un reverse proxy nginx est efficace, mais lorsqu’on veut appliquer des politiques différentes par vue/action (ex. login 5/min, upload 20/jour, API de recherche 60/min), compter sur la couche serveur devient fragile. Le throttling de DRF permet de créer des limites au niveau de l’application adaptées aux caractéristiques de chaque endpoint, ce qui en fait une compétence indispensable.
Pourquoi le throttling est indispensable
Le throttling de DRF décide, comme les permissions, si une requête est autorisée, mais il s’agit d’une restriction temporaire (fréquence) plutôt que permanente. Le guide officiel le décrit comme un état temporaire qui contrôle la vitesse à laquelle un client peut appeler l’API.
Les besoins courants sont les suivants :
- Atténuation des abus/attaques : force brute, spam, crawling, DoS simple
- Protection des coûts/ressources : uploads, appels à des API externes, requêtes lourdes, appels à des modèles génératifs
- Équité (fair use) : empêcher qu’un utilisateur ou une clé monopolise les ressources
- Codification des politiques : gérer des règles comme "5 requêtes/min" dans le code plutôt que dans l’infrastructure
Le throttling peut être appliqué globalement ou par vue/action, ce qui dépasse souvent les possibilités d’un seul proxy.
Comment fonctionne le throttling DRF (concepts clés)
1) Chaîne de taux (Rate string)
DRF utilise des chaînes comme "100/day" ou "60/min" pour définir les limites.
2) Qui est limité (identification du client)
UserRateThrottle: limite par ID utilisateur si authentifié, sinon par IPAnonRateThrottle: limite uniquement les requêtes anonymes par IPScopedRateThrottle: applique une politique par scope (ex.uploads,login)
L’identification IP se fait via X-Forwarded-For ou REMOTE_ADDR. Si vous êtes derrière un proxy, configurez NUM_PROXIES correctement.
3) Stockage de l’état : cache
Les compteurs sont stockés dans le backend cache de Django. Sur un seul processus, LocMemCache suffit, mais dans un environnement multi‑workers ou multi‑replicas, un cache partagé comme Redis est indispensable.
Configuration globale : démarrage rapide
REST_FRAMEWORK = {
"DEFAULT_THROTTLE_CLASSES": [
"rest_framework.throttling.AnonRateThrottle",
"rest_framework.throttling.UserRateThrottle",
],
"DEFAULT_THROTTLE_RATES": {
"anon": "100/day",
"user": "1000/day",
},
}
Cette configuration applique les règles par défaut à l’ensemble de l’API. En cas de dépassement, DRF renvoie automatiquement un HTTP 429 (Too Many Requests).
Appliquer le throttling à une vue : règles différentes par endpoint
1) Vue basée sur classe (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) Vue fonctionnelle (@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) Action spécifique d’un 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})
Les throttles définis sur une action prévalent sur ceux du niveau ViewSet.
Utiliser ScopedRateThrottle pour des politiques par vue (recommandé)
Le throttling par scope permet de nommer les politiques de façon significative, ce qui rend la gestion plus propre.
REST_FRAMEWORK = {
"DEFAULT_THROTTLE_CLASSES": [
"rest_framework.throttling.ScopedRateThrottle",
],
"DEFAULT_THROTTLE_RATES": {
"login": "5/min",
"uploads": "20/day",
"search": "60/min",
},
}
Dans la vue, il suffit de déclarer le 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 applique ScopedRateThrottle uniquement aux vues qui déclarent throttle_scope, en créant une clé unique basée sur le scope + ID utilisateur ou IP.
Créer un throttle personnalisé : l’essentiel est la clé
Les throttles intégrés suffisent souvent, mais certains cas exigent une personnalisation.
- Limiter le login par combinaison IP + nom d’utilisateur
- Limiter par clé API
- Limiter par en-tête/tenant/organisation
- Utiliser un cache différent (ex. cluster Redis)
1) Hériter de 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}"
Ajoutez le scope dans les paramètres :
REST_FRAMEWORK = {
"DEFAULT_THROTTLE_CLASSES": [
"path.to.LoginBurstThrottle",
],
"DEFAULT_THROTTLE_RATES": {
"login": "5/min",
},
}
2) Utiliser un cache différent
from django.core.cache import caches
from rest_framework.throttling import AnonRateThrottle
class CustomCacheAnonThrottle(AnonRateThrottle):
cache = caches["alternate"]
Points à retenir avant le déploiement
1) Problème d’IP identique derrière un proxy
Si NUM_PROXIES n’est pas correctement configuré, toutes les requêtes peuvent être vues comme provenant du même IP.
2) LocMemCache n’est pas fiable en multi‑workers
Chaque worker possède son propre cache local, ce qui peut entraîner des compteurs incohérents. Utilisez un cache partagé comme Redis.
3) Condition de concurrence (race condition)
Les throttles intégrés peuvent laisser passer quelques requêtes supplémentaires en haute concurrence. Pour des cas critiques (paiement, coupon), envisagez une implémentation atomique (Redis INCR + EXPIRE).
4) Expérience client : 429 et Retry‑After
DRF renvoie 429 par défaut. En implémentant wait() dans votre throttle, vous pouvez ajouter l’en‑tête Retry-After pour informer le client quand il peut réessayer.
Conclusion : combinez nginx et DRF throttling

- nginx : filtre le trafic massif et les attaques dès la première couche
- DRF throttling : applique des règles fines basées sur le sens et le coût de chaque endpoint
En particulier, limiter chaque vue selon ses spécificités est la force de DRF : la logique reste dans le code, indépendante de l’infrastructure.
Aucun commentaire.