# Domina el Throttling de DRF: ¿Por qué es necesario y cómo configurarlo, aplicarlo y personalizarlo? El uso de `limit_req` en un proxy inverso nginx es una forma efectiva de limitar el tráfico. Sin embargo, cuando se necesita aplicar políticas distintas por vista o acción (por ejemplo, 5 inicios de sesión por minuto, 20 cargas al día, 1000 consultas por usuario), depender únicamente de la configuración del servidor puede resultar insuficiente. El throttling de DRF permite crear límites basados en las características de cada endpoint a nivel de aplicación, lo que lo convierte en una habilidad esencial. --- ## ¿Por qué necesitamos throttling? {#sec-c97ae217f027} El throttling de DRF decide si se permite o no una solicitud, al igual que los permisos, pero la diferencia es que los permisos son permanentes y el throttling es temporal (basado en la frecuencia de las solicitudes). La documentación de DRF describe el throttling como un estado temporal que controla la velocidad a la que un cliente puede enviar peticiones a la API. Las necesidades reales se pueden resumir en: * **Mitigación de abusos/ataques**: fuerza bruta de inicio de sesión, spam, crawling, DoS simples. * **Protección de costos y recursos**: cargas, llamadas a APIs externas, consultas pesadas, llamadas a IA generativa. * **Uso justo (fair use)**: evitar que un usuario o clave monopolice los recursos. * **Codificación de políticas**: reglas como "esta API permite N peticiones por minuto" se gestionan en código en lugar de infraestructura. Además, el throttling puede aplicarse globalmente o a vistas/acciones específicas, algo que nginx por sí solo no cubre completamente. --- ## Cómo funciona el throttling de DRF (conceptos clave) {#sec-a5c59acab781} ### 1) Cadena de tasa {#sec-a2424ad14298} DRF suele usar formatos como `"100/day"` o `"60/min"` para establecer límites. ### 2) ¿A quién se aplica el límite? (identificación del cliente) {#sec-9cf8b4c551fc} * `UserRateThrottle`: si el usuario está autenticado, se basa en el ID de usuario; si no, en la IP. * `AnonRateThrottle`: solo solicita anónimos, basándose en la IP. * `ScopedRateThrottle`: aplica políticas por *scope* (por ejemplo, "uploads"). La identificación de IP utiliza `X-Forwarded-For` o `REMOTE_ADDR`; si hay proxies, `NUM_PROXIES` es crucial. ### 3) Almacenamiento de estado: uso de caché {#sec-7b9c8a453729} El throttling de DRF almacena los contadores en el backend de caché de Django. En entornos de un solo proceso/servidor, `LocMemCache` funciona, pero en entornos con múltiples workers o réplicas, un caché compartido como Redis es esencial. --- ## Configuración global: el inicio más rápido {#sec-c94dcae484bb} `settings.py`: ```python REST_FRAMEWORK = { "DEFAULT_THROTTLE_CLASSES": [ "rest_framework.throttling.AnonRateThrottle", "rest_framework.throttling.UserRateThrottle", ], "DEFAULT_THROTTLE_RATES": { "anon": "100/day", "user": "1000/day", }, } ``` Con esto, la política predeterminada se aplica a toda la API. Cuando se alcanza el límite, DRF devuelve **HTTP 429 (Too Many Requests)** por defecto. --- ## Aplicar throttling a vistas: límites por endpoint {#sec-491e225aa51d} ### 1) Vistas basadas en clases (APIView) {#sec-f32b57a3aba1} ```python 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}) ``` La documentación también indica el uso de `throttle_classes` para aplicar throttling a nivel de vista. ### 2) Vistas basadas en funciones (@api_view) {#sec-7a28800ffd4e} ```python 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) Aplicar a una acción específica de un ViewSet (@action) {#sec-f2285f7a57f9} ```python 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}) ``` El throttling definido en la acción tiene prioridad sobre la configuración a nivel de ViewSet. --- ## Crear políticas por *scope* con ScopedRateThrottle (recomendado) {#sec-69d1c4813e63} El throttling por *scope* permite separar políticas con nombres significativos (por ejemplo, "uploads", "login"). `settings.py`: ```python REST_FRAMEWORK = { "DEFAULT_THROTTLE_CLASSES": [ "rest_framework.throttling.ScopedRateThrottle", ], "DEFAULT_THROTTLE_RATES": { "login": "5/min", "uploads": "20/day", "search": "60/min", }, } ``` En la vista, solo se declara el *scope*: ```python 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 aplica `ScopedRateThrottle` solo a vistas con `throttle_scope`, creando una clave única basada en *scope* + ID de usuario o IP. --- ## Crear un throttling personalizado: la clave es `get_cache_key` {#sec-892da9d3fb29} Aunque los throttles incorporados suelen ser suficientes, en la práctica surgen requisitos como: * Limitar el inicio de sesión por combinación IP + nombre de usuario. * Limitar por API Key. * Limitar por encabezado/tenant/organización. * Usar un caché distinto (por ejemplo, un clúster Redis). ### 1) La forma más común: heredar de SimpleRateThrottle {#sec-759164135e01} ```python 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) # identificación basada en IP if not username: return None # si no hay username, no se aplica el throttling return f"throttle_login:{ident}:{username}" ``` Y registrar el *scope* en `settings`: ```python REST_FRAMEWORK = { "DEFAULT_THROTTLE_CLASSES": [ "path.to.LoginBurstThrottle", ], "DEFAULT_THROTTLE_RATES": { "login": "5/min", }, } ``` ### 2) Cambiar el caché (atributo `cache`) {#sec-99ae4b21e2b9} ```python from django.core.cache import caches from rest_framework.throttling import AnonRateThrottle class CustomCacheAnonThrottle(AnonRateThrottle): cache = caches["alternate"] ``` --- ## Antes de desplegar: puntos críticos a considerar {#sec-68bf2bd36ec0} ### 1) Problema de IP en entornos con proxies {#sec-55e331e40267} La identificación de IP se basa en `X-Forwarded-For`/`REMOTE_ADDR`. Si no se configura correctamente `NUM_PROXIES`, todos los usuarios pueden ser tratados como uno solo. ### 2) LocMemCache es débil en entornos multi-worker/multi-server {#sec-238e19948ccf} Un caché local puede llevar a contadores independientes por worker. En producción, un caché compartido como Redis garantiza la coherencia. ### 3) Condición de carrera (race condition) {#sec-d87294622fb5} El throttling incorporado puede permitir que un número limitado de solicitudes adicionales pasen en situaciones de alta concurrencia. Para casos críticos (pago, cupón), considere una implementación atómica basada en Redis (INCR + EXPIRE). ### 4) Amigabilidad del cliente: 429 y Retry-After {#sec-f405894aac21} DRF devuelve 429 por defecto. Implementar `wait()` en el throttling permite incluir la cabecera `Retry-After`. --- ## Conclusión: combina nginx y throttling de DRF {#sec-b08cdd5cec16} ![Robots Club entry restriction and 429 neon sign](/media/editor_temp/6/c96bb1d6-7cde-48a0-98aa-07396570391d.png) * **nginx**: filtra tráfico masivo y ataques en la capa más externa. * **Throttling de DRF**: aplica políticas finas basadas en el significado y costo de cada endpoint. En particular, limitar cada vista según su naturaleza es la fortaleza de DRF, manteniendo la lógica en código incluso cuando cambia la infraestructura.