Marcio Cunha

¿API con picos de tráfico? Cómo implementar rate limiting con Redis

Descubre cómo proteger tu API contra picos de tráfico y ataques de denegación de servicio utilizando Redis y el algoritmo de ventana deslizante en microservicios.

Marcio Cunha•5 min
También disponible en:PortuguêsEnglish
Resumen
  • El control de tráfico evita que picos repentinos derriben servidores backend y defiende contra ataques de denegación de servicio.
  • Redis ofrece alto rendimiento en memoria para almacenar contadores de solicitudes en una arquitectura distribuida.
  • El algoritmo de ventana deslizante ofrece mayor precisión matemática en comparación con los modelos tradicionales de contadores fijos.
  • Los scripts de Lua ejecutan operaciones atómicas dentro de Redis, eliminando condiciones de carrera entre múltiples instancias.
  • Ajustar los límites por usuario o IP preserva la experiencia de los clientes legítimos incluso durante sobrecargas severas.

Por qué fallan las APIs y cómo el control de tráfico salva tu backend

Imagina que tu aplicación recibe repentinamente diez veces su tráfico normal, ya sea debido a una campaña de marketing exitosa o a un ataque malicioso automatizado. Sin una barrera de protección, el servidor backend agota sus conexiones a la base de datos y simplemente deja de responder. En la práctica, esto significa que los clientes legítimos se quedan sin servicio y tu infraestructura sufre una inestabilidad severa. Para evitar este colapso, recurrimos al rate limiting, que consiste en limitar el número máximo de solicitudes que un cliente puede hacer en un intervalo de tiempo específico.

Al construir sistemas distribuidos modernos basados en microservicios, contar solicitudes en la memoria local de una sola máquina deja de funcionar porque las solicitudes llegan a diferentes nodos. Aquí es exactamente donde surge la necesidad de una solución centralizada y ultrarrápida. Como vimos en nuestra discusión anterior sobre cómo usar colas asíncronas, desacoplar el flujo de datos ayuda a absorber volumen, pero el control de entrada debe ocurrir en el borde antes de que comience el procesamiento pesado. Proteger tu aplicación requiere una sólida estrategia basada en caché distribuida y contadores atómicos.

El papel de Redis y el desafío del almacenamiento distribuido

Redis es una base de datos en memoria extremadamente rápida, utilizada frecuentemente como caché o intermediario de mensajes. En la arquitectura de control de tráfico, actúa como la única fuente de verdad para registrar cuántas veces un usuario o dirección IP ha realizado solicitudes recientemente. En lugar de consultar discos duros lentos, la aplicación valida los permisos en fracciones de milisegundo. Esta velocidad es indispensable porque el mecanismo de protección no debe añadir latencia perceptible al ciclo de vida de la API.

Sin embargo, gestionar contadores concurrentes en un entorno distribuido introduce dificultades sutiles. Si dos servidores intentan leer y actualizar el mismo contador exactamente al mismo tiempo, puede ocurrir una condición de carrera donde se pierdan actualizaciones. Para resolver este problema con elegancia, utilizamos scripts escritos en Lua que se ejecutan directamente dentro de Redis de forma atómica, asegurando que ninguna solicitud escape a un conteo correcto. Esta diligencia técnica garantiza que la política de restricción funcione con precisión quirúrgica bajo cualquier nivel de concurrencia.

Cómo funciona el algoritmo de ventana deslizante en la práctica

Existen varios enfoques para calcular los límites de tráfico, siendo la ventana fija la más simple pero la más problemática. En una ventana fija, si el límite es de cien solicitudes por minuto, el contador se reinicia exactamente al inicio de cada minuto. Esto crea un fallo grave conocido como efecto de borde: un usuario puede gastar las cien solicitudes en los últimos segundos de un minuto y otras cien solicitudes justo en el primer segundo del minuto siguiente, acumulando doscientas llamadas en un lapso muy corto. Para eliminar este vacío legal, adoptamos el algoritmo de ventana deslizante.

La ventana deslizante calcula el tráfico considerando una proporción del minuto anterior sumada al minuto actual, suavizando el flujo de solicitudes a lo largo del tiempo. En la práctica, el algoritmo previene picos artificiales en los cambios de periodo, distribuyendo el límite de forma continua y justa. Esta precisión es comparable al rigor técnico que aplicamos al gestionar validaciones estrictas en sistemas de inteligencia artificial para bloquear comportamientos inesperados. La ganancia en estabilidad operativa compensa con creces la ligera complejidad adicional de implementación.

Implementando la ventana deslizante con Redis y scripts de Lua

Para llevar la teoría al código funcional, utilizaremos una estructura de datos que combina hashes y conjuntos ordenados en Redis, gestionados por un script de Lua atómico. Este script recibe el identificador de usuario, el límite máximo permitido y la ventana de tiempo en segundos, devolviendo si la solicitud puede continuar o debe ser rechazada con el código de estado HTTP adecuado.

local key = KEYS[1]local now = tonumber(ARGV[1])local window = tonumber(ARGV[2])local limit = tonumber(ARGV[3])local clear_before = now - windowredis.call('ZREMRANGEBYSCORE', key, 0, clear_before)local current_requests = redis.call('ZCARD', key)if current_requests < limit then    redis.call('ZADD', key, now, now)    redis.call('EXPIRE', key, window)    return 1else    return 0end

En este código de Lua, primero eliminamos todos los registros de solicitudes más antiguos que la ventana de tiempo actual utilizando el comando de eliminación por puntuación. A continuación, contamos cuántos registros quedan en el conjunto ordenado. Si el número es inferior al límite establecido, agregamos el registro actual con la marca de tiempo vigente, renovamos el tiempo de expiración de la clave para evitar un consumo infinito de memoria y permitimos el paso. De lo contrario, rechazamos la llamada de inmediato.

Buenas prácticas para mitigar falsos positivos y proteger clientes legítimos

Implementar rate limiting requiere precaución para no castigar a clientes legítimos que comparten conexiones corporativas detrás de la misma dirección IP. Una práctica recomendada es combinar la dirección IP con tokens de autenticación o identificadores de usuario con sesión iniciada en la clave de Redis. De esta manera, si toda una empresa accede a tu API desde el mismo enrutador, cada empleado tendrá su propio límite aislado, evitando que el uso intensivo de un colega bloquee el acceso de los demás.

Otro punto crítico es definir claramente qué respuesta HTTP devolver cuando se alcanza el límite. El estándar de la industria es utilizar el código de estado HTTP 429 Too Many Requests, acompañado de cabeceras informativas como Retry-After, indicando exactamente cuántos segundos debe esperar el cliente antes de volver a intentarlo. Esta transparencia mejora la experiencia de los desarrolladores que consumen tu API y previene reintentos automáticos agresivos que empeorarían aún más la carga sobre el backend. Con un monitoreo adecuado y ajustes precisos basados en el comportamiento real del tráfico, tu infraestructura se mantiene resiliente bajo cualquier circunstancia.