intermediate Por Mathias Paulenko

Patron de Throttling

Limita la tasa a la que un sistema procesa solicitudes o consume recursos para prevenir sobrecarga, asegurar uso justo y mantener rendimiento predecible.

Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.

Resumen

El Patron de Throttling controla la tasa a la que un sistema procesa solicitudes o consume recursos para prevenir sobrecarga y asegurar asignacion justa. En lugar de aceptar todas las solicitudes inmediatamente, el sistema limita la tasa segun capacidad, tiers de usuario o disponibilidad de recursos.

Previene fallos en cascada asegurando que servicios downstream y recursos compartidos no sean abrumados.

Cuando Usar

  • For alternatives, see Content Delivery Network (CDN) Pattern.

  • Proteger servicios downstream de picos de trafico

  • Aplicar rate limits en APIs

  • Controlar agotamiento de pool de conexiones a base de datos

  • Gestionar costos con APIs de terceros por uso

  • Asegurar asignacion justa en sistemas multi-tenant

  • Prevenir DDoS o abuso accidental

Cuando Evitar

  • Servicios internos con carga predecible dentro del mismo dominio
  • Sistemas donde rechazar solicitudes viola requisitos de negocio
  • Cuando el cuello de botella es tamano de datos, no tasa

Solucion

Python (Token Bucket)

import time
import threading

class TokenBucket:
    def __init__(self, capacity, refill_rate):
        self.capacity = capacity
        self.refill_rate = refill_rate
        self.tokens = capacity
        self.last_refill = time.time()
        self.lock = threading.Lock()

    def acquire(self, tokens=1):
        with self.lock:
            now = time.time()
            elapsed = now - self.last_refill
            self.tokens = min(self.capacity, self.tokens + elapsed * self.refill_rate)
            self.last_refill = now
            if self.tokens >= tokens:
                self.tokens -= tokens
                return True
            return False

bucket = TokenBucket(capacity=10, refill_rate=2)
if not bucket.acquire():
    raise Exception("Rate limit exceeded")

Java (Guava RateLimiter)

import com.google.common.util.concurrent.RateLimiter;

public class ThrottledService {
    private final RateLimiter limiter = RateLimiter.create(10.0);
    public String process(String request) {
        limiter.acquire();
        return "Processed: " + request;
    }
}

JavaScript (Ventana Deslizante)

class SlidingWindowThrottle {
    constructor(windowMs, maxRequests) {
        this.windowMs = windowMs;
        this.maxRequests = maxRequests;
        this.requests = new Map();
    }
    isAllowed(clientId) {
        const now = Date.now();
        const start = now - this.windowMs;
        const recent = (this.requests.get(clientId) || [])
            .filter(t => t > start);
        if (recent.length < this.maxRequests) {
            recent.push(now);
            this.requests.set(clientId, recent);
            return true;
        }
        return false;
    }
}

Explicacion

Los algoritmos de throttling balancean justicia y eficiencia:

  • Token bucket: Tokens se agregan a tasa fija. Las solicitudes consumen tokens. Permite rafagas cortas manteniendo tasa promedio.
  • Leaky bucket: Solicitudes entran en cola fija y gotean a tasa constante. Suaviza trafico pero descarte overflow.
  • Ventana fija: Cuenta solicitudes en intervalos de tiempo. Simple pero permite rafagas en limites de ventana.
  • Ventana deslizante: Mas precisa rastreando timestamps exactos dentro de una ventana rodante.

Variantes

VarianteComportamientoIdeal Para
Token bucketRafagas permitidas hasta capacidadAPIs que necesitan tolerancia a rafagas
Leaky bucketTasa de salida constanteSuavizado de trafico hacia downstream
Ventana fijaContador reseteado por intervaloImplementaciones simples
Ventana deslizanteVentana de tiempo rodanteRate limits precisos por cliente

Lo que funciona

  • Retornar 429 Too Many Requests con header Retry-After
  • Diferenciar tiers de usuario con limites distintos
  • Monitorear tasas de rechazo como senal temprana
  • Implementar backoff para clientes que reciben throttling
  • Considerar rate limiting distribuido para despliegues multi-instancia

Errores Comunes

  • Throttling sin comunicar limites a clientes
  • Mismos limites para todos los usuarios sin importar tier
  • No manejar desviacion de reloj en sistemas distribuidos
  • Olvidar limpiar entradas expiradas en algoritmos basados en ventana

Ejemplos del Mundo Real

  • GitHub API: Rate limits por usuario autenticado (5000/hora) y por IP (60/hora). Exceder limites retorna 403 con X-RateLimit-Reset.
  • AWS API Gateway: Throttling a nivel de cuenta, stage y metodo usando token bucket, con capacidad de rafaga.

Notas de Producción

  • Despliega gradualmente usando canary o blue-green para detectar regresiones temprano.
  • Configura alertas para errores, latencia p99 y tasa de fallos antes de habilitar en producción.
  • Documenta el rollback en el runbook; prueba el procedimiento en staging al menos una vez por trimestre.
  • Revisa logs estructurados con correlation IDs para trazar requests end-to-end en incidentes.

Puntos Clave

  • Aplica patron de throttling cuando necesites una solución práctica para tu caso de uso.
  • Monitorea el rendimiento después de implementar; mide latencia, errores y uso de recursos antes y después.
  • Revisa la sección de Troubleshooting ante errores comunes; la mayoría tienen causa raíz documentada con solución.
  • Mantén dependencias actualizadas y ejecuta tests en CI para prevenir regresiones en producción.

Temas Avanzados

Escenario: Throttling para API de Geolocalizacion

// Throttling pattern: max 10 requests por segundo
class Throttle {
  private requests: number[] = [];
  constructor(private maxRequests: number, private windowMs: number) {}

  canProceed(): boolean {
    const now = Date.now();
    // Eliminar requests fuera de la ventana
    this.requests = this.requests.filter(t => now - t < this.windowMs);
    if (this.requests.length < this.maxRequests) {
      this.requests.push(now);
      return true;
    }
    return false;
  }
  timeUntilNextSlot(): number {
    if (this.requests.length < this.maxRequests) return 0;
    const oldest = this.requests[0];
    return this.windowMs - (Date.now() - oldest);
  }
}

// Uso: API de Google Maps (limit 10 req/s)
const throttle = new Throttle(10, 1000);

async function geocode(address: string): Promise<LatLng> {
  if (!throttle.canProceed()) {
    const wait = throttle.timeUntilNextSlot();
    await new Promise(resolve => setTimeout(resolve, wait));
  }
  const response = await fetch(`https://maps.googleapis.com/maps/api/geocode/json?address=${address}`);
  return response.json();
}

// Comparacion: Throttle vs Rate Limit vs Debounce
  | Patron | Proposito | Ejemplo |
  |--------|-----------|---------|
  | Throttle | Max N requests por ventana | 10 req/s |
  | Rate Limit | Rechazar si excede | 429 Too Many Requests |
  | Debounce | Esperar a que pare el input | Search autocomplete |
  | Token Bucket | Tokens se reponen over time | Burst + sustained |
  | Leaky Bucket | Cola con salida constante | Suavizar picos |

Lecciones:

  • Throttle limita la tasa de requests: no rechaza, espera
  • Rate limit rechaza: 429 con Retry-After header
  • Debounce agrupa llamadas: espera inactividad
  • Token bucket permite burst: util para APIs con quotas
  • Mide el throughput real: no asumas que el limite es exacto

### Como elijo entre throttle y rate limit?

Usa throttle cuando el cliente debe esperar (ej: llamar API externa con limite). Usa rate limit cuando el cliente debe ser rechazado (ej: proteger tu API de abuso). Throttle es cooperativo: el cliente se auto-limita. Rate limit es impuesto: el servidor rechaza. Para APIs publicas, usa rate limit (429 + Retry-After). Para integraciones internas, throttle es suficiente.
















































End of document. Review and update quarterly.

## Troubleshooting

- **Pattern does not fit the problem**: re-evaluate the forces (performance, scalability, team size, coupling).   A pattern is only appropriate when its trade-offs match your constraints.
- **Too many abstractions**: if adding a pattern increases complexity without a clear benefit, simplify.   Not every module needs a factory, decorator, or strategy.
- **Tight coupling after refactoring**: check that interfaces are stable and dependencies point inward.
- **Tests break when the design changes**: favor stable contracts over internal structure.
- **Performance regression from indirection**: measure before and after.   Layers, decorators, and adapters can add latency; cache or inline hot paths if needed.

## Errores Comunes en Producción

- Aplicar el patrón donde no se necesita abstracción, agregando complejidad accidental.
- Dejar que el patrón se filtre en módulos no relacionados y confundir los límites de responsabilidad.
- Sobre-ingeniería en la primera implementación en lugar de comenzar simple y medir el dolor.
- Saltar los tests de contrato, de modo que las refactorizaciones rompan consumidores en silencio.
- Ignorar modos de fallo que el patrón no cubre.
- Usar el patrón como opción por defecto en lugar de elegir la herramienta adecuada para la escala actual.
- Olvidar documentar cuándo dejar de usar el patrón y qué lo reemplaza.
- Carecer de observabilidad sobre rendimiento y propagación de errores del patrón.

Preguntas frecuentes

¿Es este patrón adecuado para proyectos pequeños?
Para proyectos pequeños con pocos componentes, este patrón puede añadir complejidad innecesaria. Empieza simple e introduce el patrón cuando sientas el problema que resuelve.
¿Cómo se compara este patrón con alternativas?
Cada patrón hace diferentes trade-offs. Revisa la tabla de variantes arriba y considera tus restricciones específicas: tamaño del equipo, requisitos de rendimiento y planes de escalado.
¿Puedo aplicar este patrón parcialmente?
Sí. Muchos equipos adoptan patrones incrementalmente. Empieza con la idea central y añade sofisticación según sea necesario. El patrón es una guía, no un blueprint estricto.