Cada vez que tu aplicación le pega a la API de Stripe, GitHub o Twitter y recibe un error 429 Too Many Requests, detrás hay casi siempre el mismo algoritmo trabajando: el token bucket. Es la técnica de rate limiting más usada en la web, y entenderla cambia la forma en que diseñás tanto los clientes que consumen APIs como los servidores que las protegen.
En esta guía vas a entender qué problema resuelve el token bucket, cómo funciona paso a paso, y vas a poder implementarlo vos mismo en unas veinte líneas de código.
TL;DR
- El token bucket es el algoritmo de rate limiting que decide cuántas peticiones por segundo acepta una API.
- Un "balde" guarda fichas (tokens) hasta una capacidad máxima; cada petición consume una ficha.
- El balde se recarga a un ritmo fijo (r tokens por segundo), lo que define la tasa promedio permitida.
- Su gran ventaja sobre otros métodos: tolera ráfagas cortas mientras el promedio se mantenga bajo el límite.
- Cuando no quedan tokens, la API responde HTTP 429 Too Many Requests, a veces con cabecera Retry-After.
- Stripe, GitHub, Cloudflare y la mayoría de los gateways usan variantes de este algoritmo.
- Se implementa en unas 20 líneas de código y es trivial de distribuir con Redis para varios servidores.
Qué es el rate limiting y por qué existe
Antes de entrar en el token bucket conviene entender el problema que resuelve. Rate limiting es, simplemente, poner un tope a cuántas operaciones puede hacer un cliente en una ventana de tiempo. Sin ese tope, cualquier servicio expuesto a internet queda a merced de quien le envíe más peticiones: un script con un bucle infinito, un cliente mal configurado que reintenta sin parar, o un ataque deliberado de denegación de servicio.
Pensalo como la entrada a un concierto. Si dejás pasar a todos al mismo tiempo, la puerta colapsa y nadie entra. Un portero que deja pasar a un ritmo controlado mantiene el flujo ordenado y protege lo que hay del otro lado. El rate limiting es ese portero: protege la base de datos, la CPU y el ancho de banda del servidor, reparte los recursos de forma justa entre todos los clientes, y permite construir planes de precios escalonados (gratis: 60 peticiones por minuto; de pago: 600).
El reto está en cómo medir ese ritmo. La forma ingenua es contar peticiones en ventanas fijas: "máximo 100 por minuto, contando desde el segundo cero de cada minuto". Funciona, pero tiene un problema clásico en el borde de la ventana: un cliente puede enviar 100 peticiones en el último segundo de un minuto y otras 100 en el primer segundo del siguiente. Resultado: 200 peticiones en dos segundos, el doble del límite, sin violar técnicamente ninguna regla. Aquí es donde el token bucket brilla.
El rate limiting actúa como un portero que regula el ritmo de acceso.
Cómo funciona el algoritmo token bucket
La idea del token bucket es hermosa por lo concreta. Imaginá un balde con una capacidad máxima de fichas, digamos diez. Un grifo deja caer fichas nuevas dentro del balde a un ritmo constante, por ejemplo una ficha cada segundo. Si el balde ya está lleno, las fichas que sobran se desbordan y se pierden.
Cada vez que llega una petición, el sistema intenta sacar una ficha del balde. Si hay al menos una, la petición se acepta y se descuenta esa ficha. Si el balde está vacío, la petición se rechaza (el famoso 429) o se pone en cola. Eso es todo. Solo hay dos parámetros que gobiernan el comportamiento:
- Capacidad (b) — cuántas fichas caben en el balde. Define el tamaño máximo de una ráfaga: cuántas peticiones se pueden hacer de golpe tras un período de inactividad.
- Tasa de recarga (r) — cuántas fichas se añaden por segundo. Define la tasa promedio sostenible a largo plazo.
La consecuencia es elegante: si un cliente estuvo callado un rato, el balde se llenó, y puede gastar una ráfaga de hasta b peticiones de inmediato. Pero si insiste, el ritmo queda limitado a r por segundo, porque es lo único que el grifo repone. Esto respeta el comportamiento real de las aplicaciones, que suelen alternar momentos de actividad intensa con pausas, en lugar de un goteo perfectamente uniforme.
graph LR
A["Petición llega"] --> B{"¿Hay tokens?"}
B -->|"Sí"| C["Consume 1 token"]
C --> D["Petición aceptada (200)"]
B -->|"No"| E["Petición rechazada (429)"]
F["Grifo: r tokens/seg"] -.-> G["Balde (capacidad b)"]
G -.-> B
Un detalle de implementación que enamora: no hace falta un proceso que añada fichas en segundo plano cada cierto tiempo. En la práctica se calcula "perezosamente". Se guarda el momento de la última recarga y, cuando llega una petición, se mira cuánto tiempo pasó y se añaden las fichas correspondientes de un solo cálculo, sin pasar nunca de la capacidad máxima. Así el algoritmo es puro y barato: una resta de tiempos y una comparación.
💭 Clave: el token bucket separa dos cosas que la gente suele confundir: la tasa promedio (la recarga r) y el tamaño de ráfaga permitido (la capacidad b). Podés permitir 100 peticiones de golpe pero solo 10 por segundo sostenidas.
Implementación en Python
Veamos el token bucket en código real. La implementación cabe cómodamente en una clase pequeña. Usamos time.monotonic() en lugar de time.time() porque es un reloj que nunca retrocede, incluso si alguien ajusta la hora del sistema:
import time
class TokenBucket:
def __init__(self, capacity, refill_rate):
self.capacity = capacity # maximo de fichas en el balde
self.refill_rate = refill_rate # fichas que entran por segundo
self.tokens = capacity # arranca lleno
self.last_refill = time.monotonic()
def _refill(self):
now = time.monotonic()
elapsed = now - self.last_refill
# anade las fichas acumuladas, sin pasar de la capacidad
self.tokens = min(self.capacity, self.tokens + elapsed * self.refill_rate)
self.last_refill = now
def allow(self, cost=1):
self._refill()
if self.tokens >= cost:
self.tokens -= cost
return True
return False
Y así se usa para proteger un endpoint que acepta como máximo una ráfaga de cinco peticiones y luego una por segundo:
# 5 fichas de capacidad, recarga de 1 ficha/segundo
bucket = TokenBucket(capacity=5, refill_rate=1)
for i in range(8):
if bucket.allow():
print(f"Peticion {i}: aceptada")
else:
print(f"Peticion {i}: rechazada (429)")
# Las primeras 5 pasan (la rafaga), las siguientes se rechazan
# hasta que el grifo reponga fichas.
Fijate que el parámetro cost permite que distintas operaciones "pesen" distinto: una consulta simple puede costar una ficha y una exportación masiva, cincuenta. Muchas APIs serias usan exactamente este truco para cobrar por complejidad y no solo por número de llamadas.
El algoritmo completo cabe en una clase de unas veinte líneas.
Token bucket frente a leaky bucket
El primo cercano del token bucket es el leaky bucket (balde con fuga). La diferencia es sutil pero importante. En el leaky bucket, las peticiones entran a un balde y salen a un ritmo perfectamente constante por un agujero en el fondo, como agua goteando. Si entra más rápido de lo que sale, el balde se llena y las peticiones nuevas se descartan.
El leaky bucket suaviza el tráfico: a la salida siempre tenés un flujo uniforme, sin importar lo irregular que sea la entrada. Es ideal cuando lo que protegés necesita un ritmo estrictamente constante, como un sistema de cola de mensajes. La contrapartida es que no permite ráfagas: aunque hayas estado inactivo una hora, no podés hacer diez peticiones de golpe, porque la salida sigue goteando a ritmo fijo.
El token bucket, en cambio, sí premia la inactividad acumulando fichas. Por eso es el favorito para APIs públicas, donde los clientes legítimos tienen patrones a ráfagas (cargás una página y se disparan veinte peticiones juntas, luego silencio). Como regla práctica: usá token bucket cuando querés tolerar ráfagas y leaky bucket cuando necesitás un caudal de salida uniforme.
Casos de uso reales
El token bucket no es teoría de pizarrón; es la infraestructura silenciosa de medio internet. Estos son algunos lugares donde lo vas a encontrar:
- APIs de pago como Stripe — implementan limitadores basados en token bucket sobre Redis para proteger su backend y devolver 429 cuando un cliente se pasa, documentado en su blog de ingeniería.
-
GitHub y Twitter/X — sus APIs devuelven cabeceras como
X-RateLimit-Remainingque reflejan, en esencia, cuántas fichas te quedan en el balde. - Cloudflare y los API gateways — aplican rate limiting en el borde de la red para frenar abuso y ataques antes de que lleguen a tu servidor.
- Calidad de servicio en redes (QoS) — los routers usan token bucket para regular el ancho de banda asignado a distintos tipos de tráfico.
- Reintentos en clientes — librerías de cliente usan token bucket internamente para no martillar un servicio que ya respondió 429, combinándolo con backoff exponencial.
Cuando una API devuelve un 429, lo correcto del lado del cliente es respetar la cabecera Retry-After si está presente, y si no, esperar con backoff exponencial: esperar 1s, luego 2s, luego 4s, con un poco de aleatoriedad (jitter) para que todos los clientes no reintenten en el mismo instante.
💡 Tip: en una API, derivá el valor de
Retry-Afterdirectamente del balde:tiempo = (cost - tokens) / refill_rate. Le decís al cliente exactamente cuántos segundos faltan para que tenga fichas suficientes.
Cómo escalarlo a varios servidores
La implementación en memoria que vimos funciona perfecto en un solo proceso, pero se rompe si tenés varias instancias del servidor detrás de un balanceador: cada una tendría su propio balde y el límite real sería N veces mayor del esperado. La solución estándar es centralizar el estado del balde en Redis, que es rápido y atómico.
El patrón habitual es un script Lua ejecutado en Redis (atómico, sin condiciones de carrera) que guarda dos valores por cliente: las fichas restantes y el momento de la última recarga. Cada petición ejecuta el script, que recalcula las fichas según el tiempo transcurrido y decide si acepta. Como Redis es de un solo hilo para esa operación, no hay riesgo de que dos servidores consuman la misma ficha al mismo tiempo.
Ventajas y desventajas
Como toda decisión de diseño, el token bucket tiene compromisos que conviene conocer antes de adoptarlo.
- Ventaja: memoria mínima. Solo guardás dos números por cliente (fichas y última recarga), frente a algoritmos como el sliding window log, que guardan un registro de cada petición.
- Ventaja: tolera ráfagas legítimas sin penalizar a clientes que tienen picos naturales de actividad.
- Ventaja: barato de calcular y trivial de distribuir con Redis.
- Desventaja: no es perfectamente suave. Si necesitás un caudal de salida estrictamente constante, el leaky bucket encaja mejor.
- Desventaja: elegir los parámetros b y r requiere conocer bien tu carga; mal calibrados, o ahogan a usuarios legítimos o dejan pasar abuso.
- Desventaja: en sistemas distribuidos, la dependencia de Redis añade un punto de fallo y latencia que hay que tener en cuenta.
📖 Resumen en Telegram: Ver resumen
Preguntas frecuentes
¿Cuál es la diferencia entre token bucket y un contador de ventana fija?
El contador de ventana fija cuenta peticiones en bloques de tiempo discretos (cada minuto desde cero), lo que sufre el problema del borde: se pueden enviar el doble del límite a caballo entre dos ventanas. El token bucket usa una recarga continua, así que el límite se respeta de forma deslizante y suave.
¿Qué significa exactamente el error HTTP 429?
El código 429 "Too Many Requests" indica que superaste el límite de peticiones permitido. Una API bien hecha incluye una cabecera Retry-After que te dice cuántos segundos esperar antes de reintentar. Tu cliente debería respetarla en lugar de reintentar de inmediato.
¿El token bucket sirve para proteger contra ataques DDoS?
Ayuda contra abuso desde una sola fuente o un número limitado de fuentes, pero un DDoS distribuido real, con millones de IP, necesita defensas adicionales en la capa de red y un CDN. El token bucket es una capa de protección, no la única.
¿Por qué usar Redis y no una base de datos normal?
Porque el rate limiting se consulta en cada petición y debe ser extremadamente rápido y atómico. Redis vive en memoria, ejecuta scripts Lua de forma atómica y responde en microsegundos, algo que una base de datos relacional en disco no puede igualar a esa frecuencia.
¿Qué valores de capacidad y recarga debería elegir?
Depende de tu carga real. Una regla inicial razonable: fijá la recarga (r) en la tasa promedio sostenible que tu backend aguanta cómodamente, y la capacidad (b) en el tamaño de ráfaga típico de un cliente legítimo (a menudo 2x o 3x la tasa por segundo). Después ajustá observando métricas reales.
¿Se puede aplicar token bucket del lado del cliente?
Sí, y es muy recomendable. Un cliente que sabe el límite de la API puede usar su propio token bucket para autolimitarse y no llegar nunca al 429, ahorrando reintentos y latencia. Es una práctica común en SDKs oficiales.
Referencias
- Stripe Engineering — Scaling your API with rate limiters — implementación real en producción sobre Redis.
- Wikipedia — Token bucket — definición formal del algoritmo y sus parámetros.
- Wikipedia — Leaky bucket — el algoritmo comparado y sus diferencias.
- Google Cloud — Rate-limiting strategies and techniques — comparativa de estrategias de rate limiting.
📱 ¿Te gusta este contenido? Únete a nuestro canal de Telegram @programacion donde publicamos a diario lo más relevante de tecnología, IA y desarrollo. Resúmenes rápidos, contenido fresco todos los días.
Top comments (0)