Exponer una API pública sin mecanismos de control es una invitación abierta al caos. Un solo cliente mal configurado, o un ataque malintencionado de bots, puede saturar tus servidores en cuestión de segundos, degradando la experiencia de todos los usuarios y disparando los costes de infraestructura. El Rate Limiting (o limitación de tasa) es la barrera defensiva esencial que define cuántas solicitudes puede realizar un usuario en un periodo de tiempo determinado.

¿Por qué es vital implementar Rate Limiting?

  • Mitigación de ataques DoS y fuerza bruta: Bloquea intentos masivos de inicio de sesión o inundaciones de peticiones maliciosas.
  • Protección de recursos del sistema: Evita que un solo "vecino ruidoso" consuma toda la CPU o memoria disponible del servidor.
  • Garantía de equidad (Fairness): Asegura que todos los clientes tengan acceso equitativo a los servicios, sin monopolios de ancho de banda.
  • Monetización y gestión de cuotas: Permite establecer planes de precios (Tiered Pricing) basados en el volumen de uso de la API.
  • Optimización de costes: Reduce el gasto en operaciones costosas como inferencias de IA o consultas complejas a bases de datos.

Algoritmos de Rate Limiting más utilizados

1. Ventana Fija (Fixed Window Counter)

Divide el tiempo en bloques estáticos (ej. 1 minuto). Las peticiones se cuentan dentro de ese bloque y se rechazan si superan el umbral hasta que comienza el siguiente intervalo.

Ejemplo de Ventana Fija
Ventana: 1 minuto | Límite: 100 peticiones

12:00:00 - 12:00:59 → Petición 1...100 ✅ | Petición 101 ❌
12:01:00 - 12:01:59 → El contador se reinicia → Petición 1...100 ✅

⚠️ Riesgo: Un usuario puede enviar 100 peticiones al final de una ventana 
y 100 al principio de la otra, procesando 200 en apenas segundos.

2. Registro de Ventana Deslizante (Sliding Window Log)

Almacena la marca de tiempo (timestamp) de cada solicitud. Para cada nueva petición, el sistema cuenta cuántos registros existen en el intervalo de tiempo previo exacto. Es el método más preciso pero requiere mucha memoria al guardar cada evento.

3. Contador de Ventana Deslizante (Sliding Window Counter)

Es una solución híbrida eficiente. Calcula una media ponderada basada en el contador de la ventana actual y el de la anterior. Ofrece una gran precisión sin el coste computacional del log completo.

4. Cubo de Tokens (Token Bucket) - El estándar de la industria

Se utiliza un "cubo" virtual que se llena de tokens a una tasa constante. Cada petición consume un token. Si no hay tokens, la petición se rechaza. Permite manejar picos de tráfico (bursts) de forma controlada mientras se mantiene un promedio estable a largo plazo.

Concepto de Token Bucket
Capacidad: 10 tokens
Recarga: 1 token por segundo

Segundo 0: Cubo lleno (10 tokens)
           → El usuario envía 5 peticiones → Quedan 5 tokens
Segundo 1: Se añade 1 token → Quedan 6 tokens
Segundo 5: Se añaden 4 tokens → Cubo tiene 10 tokens
           → Ráfaga de 10 peticiones enviada → Quedan 0 tokens
Segundo 6: Se añade 1 token → Usuario puede hacer 1 petición

Implementación Práctica con Redis

Para sistemas distribuidos, Redis es la herramienta ideal debido a su velocidad y soporte para operaciones atómicas. Aquí un ejemplo simplificado usando Node.js:

Express.js + Redis (Ventana Deslizante)
import Redis from 'ioredis';
const redis = new Redis();

async function rateLimiter(req, res, next) {
  const key = `api_limit:${req.ip}`;
  const limit = 50;            // peticiones máximas
  const windowMs = 60 * 1000;  // ventana de 1 minuto

  const now = Date.now();
  const windowStart = now - windowMs;

  const pipeline = redis.pipeline();
  pipeline.zremrangebyscore(key, 0, windowStart); // Limpia datos antiguos
  pipeline.zadd(key, now, `${now}-${Math.random()}`); // Registra petición actual
  pipeline.zcard(key);                             // Obtiene el total actual
  pipeline.expire(key, 60);

  const results = await pipeline.exec();
  const totalRequests = results[2][1];

  // Configuración de cabeceras estándar
  res.set({
    'X-RateLimit-Limit': limit,
    'X-RateLimit-Remaining': Math.max(0, limit - totalRequests),
    'X-RateLimit-Reset': Math.ceil((now + windowMs) / 1000),
  });

  if (totalRequests > limit) {
    res.set('Retry-After', '60');
    return res.status(429).json({
      error: 'Too Many Requests',
      message: 'Límite de peticiones excedido. Inténtalo más tarde.',
      retryAfter: 60
    });
  }

  next();
}

app.use('/api/v1/', rateLimiter);

Cabeceras HTTP de Rate Limit

Es fundamental comunicar el estado de los límites al cliente para que pueda autogestionar sus peticiones:

Respuesta HTTP Estándar
HTTP/1.1 200 OK
X-RateLimit-Limit: 100          // Límite total en la ventana
X-RateLimit-Remaining: 45       // Peticiones restantes
X-RateLimit-Reset: 1720396800   // Timestamp de reinicio

HTTP/1.1 429 Too Many Requests
Retry-After: 30                 // Segundos para reintentar
{
  "status": 429,
  "error": "Rate limit exceeded"
}

Mejores Prácticas para Desarrolladores

  • Identificación flexible: Usa la API Key para usuarios autenticados y la dirección IP para usuarios anónimos.
  • Diferenciación de endpoints: Aplica límites más estrictos en rutas críticas (ej. `/login`, `/checkout`) que en rutas de solo lectura.
  • Arquitectura distribuida: Implementa el limitador en la capa de API Gateway o usa Redis para compartir el estado entre múltiples nodos.
  • Excepciones estratégicas: No apliques rate limit a endpoints de "health check" o servicios internos de monitoreo.
  • Comunicación clara: Incluye siempre el encabezado Retry-After para indicar exactamente cuándo el cliente puede reintentar la operación.
  • Límites por niveles: Crea tiers (Free, Pro, Enterprise) para ajustar los límites según las necesidades del negocio.

Herramientas Gratuitas para Desarrolladores

Valida y formatea las respuestas JSON de tu API de forma profesional.