harness-kit wiki

Rate Limiter

Serie System Design #2. Topic skill: skills/rate-limiter/. Diseñar rate limiting distribuido a escala, del algoritmo a la operación.

1. El problema y por qué engaña

De lejos, rate limiting parece "un contador con TTL en Redis". Eso resuelve una parte. No resuelve el problema entero. Cuando el sistema crece, las preguntas que importan son más grandes que el algoritmo:

La pregunta que ordena todo, la primera:

¿Qué estoy protegiendo exactamente, y cuánta imprecisión acepto para protegerlo sin destruir latency y simplicidad?

Esa sola pregunta guía casi cada decisión de acá.

2. Por qué existe rate limiting (cinco objetivos a la vez)

  1. Proteger la capacidad finita de un sistema.
  2. Equidad entre clientes, tenants, usuarios.
  3. Reducir el radio de daño: un cliente en loop, un deploy generando tráfico anómalo.
  4. Sostener modelos comerciales: planes free / pro / enterprise.
  5. Controlar costo: servicios que llaman dependencias caras (LLM, búsqueda, terceros).

Un buen diseño es un conjunto de límites superpuestos (global, tenant, usuario, endpoint, seguridad por IP), no un contador único. Primero la política, después Redis.

3. Dónde aplicarlo (defensa en profundidad)

Capa Buena para Tipo de límite Salvedad
Edge / CDN absorber abuso volumétrico, DDoS L7, bloquear temprano grueso le falta contexto de negocio rico
API Gateway el lugar más común; por token/usuario/tenant/endpoint API genérico si se vuelve cuello de botella, lo siente toda la plataforma
Dentro del servicio el límite depende del contexto de dominio (reporte caro, inferencia de LLM) semántico / costo lo más cerca de la verdad, lo más lejos del edge

La arquitectura madura es en capas: edge para protección gruesa, gateway para límites genéricos de API, servicio para límites semánticos y de costo. Cada capa atrapa lo que la capa de arriba no puede ver. Es la misma idea de "defensa en profundidad" del apilamiento de seguridad.

4. La clave del límite

Elija la dimensión (o las dimensiones) de forma consciente: usuario, token, IP, tenant, endpoint, método, región, muchas veces una composición. La elección de la clave define:

Las claves compuestas (tenant + endpoint) localizan límites con precisión, pero multiplican la cantidad de contadores.

5. Teoría, los algoritmos

Esta es la parte que todos nombran y pocos explican. Cada fila es una respuesta distinta a "¿cómo contamos?".

Fixed window counter

Cuenta requests en un balde fijo de reloj (por ejemplo, por minuto) y resetea en el corte. Barato, un contador por clave. Defecto: ráfaga de frontera. Un cliente puede mandar una ventana llena a las 11:59:59 y otra ventana llena a las 12:00:00, ~2x la tasa pretendida cruzando la frontera.

Sliding window log

Guarda un timestamp por request y cuenta los que caen dentro de la ventana hacia atrás. Exacto y justo. Costo: memoria y CPU crecen con el tráfico, porque se conserva cada timestamp de la ventana. Tranquilo con poco volumen, caro a escala.

Sliding window counter

Aproxima la sliding window con dos baldes fijos (actual + anterior), ponderados por la fracción ya transcurrida de la ventana actual:

estimate = current_count + previous_count * (1 - elapsed_fraction)

Suaviza la ráfaga de frontera, uno o dos contadores por clave, una sola operación atómica. Es el compromiso habitual en producción entre lo barato de la fixed window y la exactitud del sliding log.

Leaky bucket

Los requests entran en una cola que drena a tasa constante; lo que desborda se rechaza. Produce una tasa de salida suave y constante, buena para moldear tráfico hacia un downstream que quiere flujo estable.

Token bucket ← el default de la industria

Un balde guarda hasta capacity tokens. Los tokens se reponen a una rate constante. Cada request consume un token; si el balde está vacío, rechaza (o encola). La propiedad clave: separa la ráfaga permitida (capacity) de la tasa sostenida (refill). Un cliente puede disparar hasta capacity de golpe y después queda sujeto a rate. Esto coincide con cómo quieren comportarse las plataformas: "puede tener un pico chico, pero su régimen permanente tiene techo."

on request:
  now = clock()
  tokens = min(capacity, tokens + (now - last_refill) * rate)
  last_refill = now
  if tokens >= 1: tokens -= 1; allow
  else: reject (429, Retry-After)

Token bucket es el algoritmo clásico de traffic shaping que viene de redes (ver Tanenbaum, Computer Networks); es lo que exponen la mayoría de las plataformas de API y los proveedores de nube.

Cómo elegir

Use token bucket por defecto cuando quiera tasa sostenida + ráfaga controlada; sliding window counter cuando quiera un límite deslizante simple y casi exacto, con un chequeo por debajo del milisegundo. Haga que el chequeo sea atómico: un script Lua en Redis ejecuta leer-decidir-escribir en una sola operación del lado del servidor, así las réplicas concurrentes no compiten entre sí.

6. Estado y almacenamiento

7. Teoría, hot keys

Un tenant con tráfico desproporcionado concentra carga en un shard del store (una hot key). Mitigaciones:

8. Fail-open vs fail-closed

Cuando el store del rate limit falla, decida de forma deliberada.

La mayoría de las plataformas hacen fail open en el camino genérico y fail closed solo donde el límite protege un techo duro de capacidad o de costo. Declare qué postura toma cada capa, y por qué. Combínelo con un circuit breaker (Nygard) para que un store lento no le sume latency a cada request: apenas se detecta que el store está insano, el limiter deja de llamarlo y aplica la postura de fallback al instante.

9. Límites hard vs soft

Un límite hard rechaza (429 Too Many Requests + Retry-After). Un límite soft avisa, degrada o encola, pero igual atiende. Los límites de costo y de equidad suelen ser soft con fricción creciente; los de seguridad y capacidad son hard.

10. Multi-región

Una exactitud global estricta entre regiones exigiría un salto síncrono cross-region en cada request, lo que destruye latency. El compromiso realista son presupuestos regionales con reconciliación: cada región aplica una porción local del límite global y reconcilia de forma asíncrona. Se acepta una sobre-admisión acotada (un límite global de N podría admitir por un momento un poco más que N) a cambio de latency baja. Reserve el conteo global estricto para los pocos límites que de verdad lo necesitan. Es el trade-off de CAP/PACELC hecho concreto: en operación normal, se cambia consistencia por latency.

11. Shadow mode (la práctica de mayor apalancamiento)

Antes de aplicar un límite hard, córralo en modo observación: el sistema calcula la decisión de bloqueo pero no la aplica, y emite lo que habría bloqueado. Esto atrapa políticas mal configuradas antes de que causen un incidente: usted ve "este límite nuevo le habría dado 429 al 40% del tráfico legítimo del tenant X" en un dashboard en vez de en una alerta a las 3 de la mañana. Promueva un límite de shadow a enforce solo después de que las métricas del shadow se vean bien.

12. Observabilidad

Métricas esenciales:

A las 3 de la mañana alguien tiene que poder responder: ¿qué límite se disparó, en qué dimensión, estaba mal la política, hubo una regresión de tráfico? Si el diseño no puede responder eso, no es gobernable en operación, y "gobernable en operación" es la vara real, no un algoritmo lindo.

13. Modos de falla

Falla Impacto Mitigación
Store caído sin conteo central postura elegida (fail-open por defecto) + presupuesto local de fallback + circuit breaker + alerta
Hot key satura un shard pico de CPU en el shard, latency prefiltro local + claves compuestas + presupuestos locales
Política mala publicada 429 falsos masivos shadow mode primero; rollback rápido de config; alerta de tasa de bloqueo por política
El limiter del gateway se vuelve el cuello de botella latency en toda la plataforma empuje los límites gruesos al edge, mantenga el chequeo del gateway en O(1)

14. Plan incremental

  1. Rebanada vertical: una capa de aplicación (gateway), fixed o sliding window, un solo Redis, una clave de límite, 429 + Retry-After, métricas de allow/deny.
  2. Corrección / operación: token bucket vía Lua atómico, fail-open + circuit breaker, shadow mode, config con hot-reload, métricas por política.
  3. Escala: store con sharding, presupuestos locales + prefiltro para hot key, aplicación en capas (edge/gateway/servicio).
  4. Global: presupuestos regionales con reconciliación, políticas compuestas, límites semánticos de costo/LLM en el servicio.

15. Trade-offs que hay que declarar explícitamente

Exactitud vs latency (conteo global exacto vs presupuesto local); fail-open vs fail-closed; store central vs presupuestos locales; una clave de límite vs compuesta; aplicar ya vs shadow primero; ubicación por capa (atrapar temprano en el edge vs contexto rico en el servicio).

16. Un ejemplo completo, ya llenado

El repo trae un SDD completo y llenado para un rate limiter distribuido (variante sliding-window-counter, 200k QPS, presupuesto por debajo del milisegundo, fail-open) como el buen ejemplo del agent: good-system-design-example.md.

Referencias

Traducción manual de Rate Limiter, la página original en inglés en la wiki.