Estrategias de rate limiting efectivas
El rate limiting por IP protege contra ataques distribuidos pero penaliza usuarios detrás de NATs corporativos donde cientos de empleados comparten una IP. Combiná límites por IP (generosos) con límites por usuario autenticado (estrictos). Por ejemplo: /login permite 5 intentos por IP cada 15 minutos, pero solo 3 por email en la misma ventana. Esto previene fuerza bruta sin bloquear oficinas enteras.
Ventanas deslizantes son superiores a ventanas fijas. Con ventanas fijas de 1 minuto, un atacante puede hacer 100 requests a las 10:00:59 y otros 100 a las 10:01:00, logrando 200 requests en 2 segundos. Ventanas deslizantes cuentan los últimos 60 segundos en cualquier momento, previniendo este burst. Implementalas con sorted sets en Redis: ZADD con timestamp, ZREMRANGEBYSCORE para limpiar entradas viejas.
Los límites deben reflejar patrones de uso reales. Analizá tus logs: ¿cuántos requests hace un usuario promedio? ¿Y el percentil 95? Tu límite debería estar entre el 95 y el 99. Un usuario legítimo nunca debería tocar el límite en uso normal. Si lo hacen regularmente, tu límite es demasiado bajo o tu frontend hace polling ineficiente.
Implementación de rate limiting
Redis es el backend estándar para rate limiting distribuido. El algoritmo token bucket es simple: INCR una key con TTL. Si el valor supera el límite, rechazás. Para ventanas deslizantes, usá ZSET: ZADD key timestamp 1, ZCOUNT key (now-window) now para contar, EXPIRE key window. Esto escala a millones de requests sin problemas de consistencia.
En aplicaciones pequeñas, memory stores en el proceso funcionan. Node tiene rate-limiter-flexible, Python tiene slowapi. Estos usan diccionarios en memoria con timestamps. El problema: no comparten estado entre instancias. Si tenés 3 servidores, cada uno permite 100 req/min independientemente, dándote 300 totales. Funciona si tus límites son generosos; falla si necesitás precisión.
Los headers de respuesta son cruciales: X-RateLimit-Limit (límite total), X-RateLimit-Remaining (requests disponibles), X-RateLimit-Reset (timestamp de reset). Clientes bien diseñados leen estos headers y ajustan su comportamiento automáticamente. Incluí también Retry-After en la respuesta 429 para decirle al cliente cuándo reintentar.
Rate limiting por tipo de endpoint
Endpoints de autenticación necesitan límites agresivos: 5 intentos de login por IP cada 15 minutos previene fuerza bruta sin frustrar usuarios que olvidaron su contraseña. Password reset debe ser aún más restrictivo (3/hora) porque cada request envía un email costoso y atacantes pueden usarlo para enumerar usuarios válidos.
APIs públicas necesitan tiers: gratuito (100 req/min), pro (1000 req/min), enterprise (sin límite). Implementá esto con diferentes API keys vinculadas a planes. El tier gratuito debe ser suficiente para desarrollo pero no para producción seria. Esto incentiva upgrades sin bloquear adopción inicial.
Operaciones costosas (exports, reports, ML inference) merecen límites de largo plazo: 10 requests/hora en lugar de 10/minuto. Un usuario que exporta 10 reports en 1 minuto probablemente es un script automatizado, no un humano. Límites de hora previenen abuso sin afectar uso legítimo espaciado en el tiempo.
Manejo de excepciones y edge cases
Los webhooks de proveedores externos necesitan whitelisting. Si Stripe envía eventos de pago, no podés rate limitarlos estrictamente o perdés eventos. Configurá límites generosos (200 req/min) y validá la firma del webhook. Si la firma falla, aplicá rate limiting agresivo porque probablemente es un atacante.
Usuarios enterprise a menudo necesitan límites custom. En lugar de hardcodear excepciones, implementá un sistema de override: una tabla en tu DB con user_id y custom_limits. Tu middleware de rate limiting checkea esto primero. Esto te permite dar a un cliente 10,000 req/min sin deployar código nuevo.
Durante incidentes, necesitás un kill switch. Un feature flag que reduce todos los límites a 10% de lo normal puede salvarte de caídas. Cuando tu base de datos está muriendo, reducir límites a 10 req/min globalmente es mejor que caerse completamente. Implementá esto en tu config de rate limiter, no en código de aplicación.