Un limitador de tasa de Token Bucket: una variante en memoria de 50 líneas y una variante Redis de 95 líneas en TypeScript
En sistemas de software que manejan peticiones de múltiples clientes, el control de tasa (rate limiting) es una de esas piezas de infraestructura que parece sencilla hasta que falla en producción. Muchos equipos empiezan con un contador que dice: si en un segundo este usuario supera X peticiones, se rechaza el resto. Ese enfoque produce dos problemas clásicos: los bursts en los bordes de la ventana (un cliente que lanza el límite justo antes del cambio de segundo y otro justo después puede duplicar la tasa real) y la incapacidad de permitir ráfagas naturales sin falsear la medición. Además, no hay forma de expresar que un usuario puede explotar hasta 100 peticiones en un instante pero solo 10 sostenidas, a menos que se añada otro contador con sus propios problemas de sincronización.
El algoritmo de Token Bucket resuelve estos puntos de forma elegante. Funciona como un depósito que se llena a un ritmo constante (por ejemplo, 10 tokens por segundo) y que tiene una capacidad máxima (por ejemplo, 100 tokens). Cada petición consume uno o más tokens; si hay suficientes, se permite y se descuentan; si no, se rechaza indicando cuándo habrá token disponible. Esto permite ráfagas de hasta la capacidad máxima, y luego vuelve a la tasa media. No hay ventanas fijas, no hay problemas de borde, y la lógica es simple: dos números flotantes por cliente (tokens actuales y timestamp del último relleno). En un servicio Node.js monoproceso, una implementación en memoria con un Map ocupa unas 50 líneas, incluyendo un método opcional para limpiar claves inactivas. Es rápida y no requiere red.
Cuando el servicio crece a varios procesos detrás de un balanceador, la memoria local ya no sirve porque cada instancia tiene su propio estado, y un cliente astuto podría distribuir peticiones entre ellas para multiplicar el límite efectivo. La solución habitual es usar Redis con un script Lua que ejecuta la lectura, cálculo y escritura de forma atómica. Con unas 95 líneas entre el script y la clase TypeScript que lo invoca, se obtiene un limitador distribuido, seguro frente a condiciones de carrera. El script Lua recibe la hora del cliente, y si dos nodos tienen relojes ligeramente diferentes, un clamp evita que el tiempo negativo produzca rellenos incorrectos. Una capa de fallback (fail open) permite que el servicio siga funcionando si Redis no responde, aunque se pierda la protección durante ese lapso. Así, el limitador no se convierte en un punto único de fallo.
Elegir los valores de capacidad y tasa de relleno no debe hacerse por intuición. Lo recomendable es analizar el percentil 99 de peticiones por clave durante los últimos siete días y fijar la tasa de relleno en el doble de ese valor, para dar margen a picos legítimos. La capacidad, típicamente entre cinco y diez veces la tasa de relleno, define el presupuesto de ráfaga. Es importante mantener buckets separados para tráfico autenticado y no autenticado, y por endpoint si la criticidad del recurso varía. Por ejemplo, un POST a pagos merece un límite más restrictivo que un GET de productos.
En entornos reales, es común que el limitador de tasa se integre con otros servicios de infraestructura. Por ejemplo, en una arquitectura cloud con servicios cloud AWS y Azure, Redis puede ser gestionado a través de ElastiCache o Azure Cache for Redis, lo que simplifica el despliegue y la escalabilidad. La misma lógica de Token Bucket puede aplicarse dentro de una aplicación a medida que requiera control de acceso a APIs, ya sea para proteger endpoints de inteligencia artificial, agentes IA, o incluso para gestionar consultas de Power BI que consumen datos con límites de tasa.
Desde una perspectiva más amplia, un rate limiter bien diseñado es una defensa de ciberseguridad esencial contra ataques de fuerza bruta, scraping o denegación de servicio. También es un componente clave en sistemas de inteligencia artificial para empresas, donde los modelos pueden estar expuestos a través de APIs y es necesario evitar que un solo cliente sature el recurso. En Q2BSTUDIO desarrollamos soluciones que integran estos patrones junto con otras capacidades como agentes IA, servicios inteligencia de negocio y automatización de procesos, asegurando que cada capa de la arquitectura cumpla con los requisitos de escalabilidad y seguridad.
En definitiva, el algoritmo de Token Bucket es la respuesta correcta para la mayoría de los casos de rate limiting. Su implementación, ya sea en memoria o con Redis, es directa y probada en producción. El verdadero trabajo está en definir las políticas (qué clave usar, qué límites aplicar) y en integrar el limitador sin que se convierta en un cuello de botella. Cuando se hace bien, el sistema se beneficia de una protección predecible sin sacrificar la experiencia del usuario legítimo.
Comentarios