Rate limiting: de la ventana fija al token bucket distribuido

  • arquitectura

Un limitador de tasa decide si una petición cabe dentro de una política. Puede proteger una API de abuso, contener costos o repartir capacidad entre usuarios. La identidad limitada puede ser una cuenta, una API key, un tenant o una dirección IP; cada elección define un alcance distinto.

El problema de las ventanas fijas

Un contador por usuario y minuto parece suficiente: al llegar a 100, se rechazan nuevas solicitudes hasta el siguiente período. Pero un cliente puede enviar 100 justo antes del límite y otras 100 inmediatamente después. Cumple ambos minutos del reloj y concentra casi 200 peticiones en un intervalo muy breve.

Una ventana deslizante mide un período que se mueve con el tiempo. Un token bucket expresa otra política: admite ráfagas acotadas y limita la tasa sostenida.

La capacidad define la ráfaga; la recarga define la tasa sostenida. Los círculos ilustran el saldo, no representan 100 tokens a escala.
La capacidad define la ráfaga; la recarga define la tasa sostenida. Los círculos ilustran el saldo, no representan 100 tokens a escala.

Capacidad y reposición

El bucket tiene capacidad máxima B, saldo T y tasa de recarga r. Al recibir una petición se calcula el tiempo transcurrido desde la última actualización, se repone el saldo hasta la capacidad y se descuenta el costo si hay suficientes tokens.

Pseudocódigo
Δt = max(0, ahora − ultima_actualizacion)
T = min(B, T + r × Δt)
si T >= costo:
    T = T − costo
    permitir
si no:
    rechazar
persistir T y ahora de forma atómica

Con B = 100 y r = 100/60 ≈ 1,67 tokens/s, se permite una ráfaga inicial de 100 y luego una tasa sostenida de aproximadamente 100 por minuto. Esto no equivale a un máximo estricto de 100 en cualquier ventana de 60 segundos: tras una ráfaga se siguen generando tokens. La cota ideal en un intervalo de duración t es B + r·t.

Varias instancias, una decisión

Si cada servidor mantiene su contador independiente, distribuir peticiones entre ellos multiplica la cuota efectiva. Un estado compartido permite aplicar una política común. Sin embargo, leer el saldo y escribirlo después con operaciones separadas permite que dos solicitudes gasten el mismo token.

La lectura, recarga, comprobación y consumo deben ser atómicos. Un script corto de Redis puede ejecutar ese ciclo sin intercalación local, como documentan sus ejemplos de rate limiting. Esto no implica por sí solo un límite global fuerte entre regiones con replicación asíncrona.

Comportamiento operativo

Para una API HTTP puede responderse 429 Too Many Requests e indicar cuándo reintentar. Hay que elegir qué ocurre si el almacén falla: permitir mantiene disponibilidad, pero debilita la cuota; rechazar preserva la política y afecta a usuarios. El límite debe diseñarse junto con esa decisión y con la capacidad real del servicio.

Referencias