Estrategias de Limitación de Velocidad para APIs

La limitación de velocidad controla la frecuencia de solicitudes de clientes, protegiend APIs del abuso, asegurando asignación justa de recursos y previniendo fallos en cascada. Diferentes algoritmos e implementaciones ofrecen niveles variados de precisión, rendimiento y complejidad operacional. Esta guía cubre algoritmos de token bucket y ventana deslizante, limitación de velocidad de Nginx y HAProxy, análisis de encabezado de solicitud, límites por cliente y estándares de encabezado de respuesta de velocidad.

Tabla de Contenidos

  1. Descripción General de Limitación de Velocidad
  2. Algoritmo Token Bucket
  3. Algoritmo Ventana Deslizante
  4. Algoritmo Leaky Bucket
  5. Limitación de Velocidad de Nginx
  6. Limitación de Velocidad de HAProxy
  7. Análisis de Encabezado de Solicitud
  8. Límites de Velocidad Por Cliente
  9. Encabezados de Respuesta de Límite de Velocidad
  10. Limitación de Velocidad Distribuida
  11. Pruebas y Monitoreo

Descripción General de Limitación de Velocidad

Estrategias de limitación de velocidad:

  1. Por IP: Limitar por dirección IP del cliente
  2. Por Usuario: Limitar por usuario autenticado
  3. Por Clave API: Limitar por clave de API
  4. Por Endpoint: Diferentes límites para diferentes endpoints
  5. Distribuida: Estado compartido a través de múltiples servidores

Algoritmos comunes:

  • Token Bucket: Acumula tokens, consume uno por solicitud
  • Ventana Deslizante: Cuenta solicitudes en ventana de tiempo
  • Leaky Bucket: Encola solicitudes, filtra a velocidad fija
  • Ventana Fija: Contador simple por período de tiempo

Beneficios:

  • Previene abuso de API y ataques DDoS
  • Asegura uso justo de recursos
  • Protege infraestructura backend
  • Mejora estabilidad del servicio

Costos:

  • Latencia agregada (especialmente distribuida)
  • Complejidad en implementación
  • Costo de almacenamiento para rastreo
  • Potencial para falsos positivos

Algoritmo Token Bucket

Token bucket mantiene un cubo de tokens, consumiendo uno por solicitud:

Algoritmo:
1. Comience con N tokens
2. Cada segundo, agregue R tokens (hasta máximo N)
3. Cada solicitud consume 1 token
4. Rechace solicitudes cuando cubo esté vacío
5. Los tokens no utilizados se acumulan (capacidad de ráfaga)

Ventajas:

  • Permite tráfico de ráfaga (búfer de tokens)
  • Simple de implementar
  • Comportamiento predecible
  • Costo bajo de CPU

Ejemplo: 100 solicitudes/seg con 50 capacidad de ráfaga

Inicial: 50 tokens
Segundo 1: 50 + 100 = 150 (limitado a 150) → 49 tokens después de solicitudes
Segundo 2: 49 + 100 = 149 (limitado a 150) → 48 tokens después de solicitudes

Algoritmo Ventana Deslizante

La ventana deslizante cuenta solicitudes en una ventana de tiempo móvil:

Algoritmo:
1. Mantenga conteo de solicitudes en últimos T segundos
2. Para nueva solicitud, compruebe si conteo < límite
3. Agregue timestamp de solicitud
4. Elimine timestamps más antiguos que T segundos
5. Permita si conteo < límite

Ventajas:

  • Limitación de velocidad justa (no ventanas fijas)
  • Sin capacidad de ráfaga (límites estrictos)
  • Previene comportamiento de caso límite

Desventajas:

  • Uso de memoria más alto
  • Más intensivo en CPU
  • Requiere rastreo preciso de timestamp

Ejemplo: 100 solicitudes por 60 segundos

Tiempo 0.00s: Solicitud 1 → [0.00] → conteo=1 ✓
Tiempo 0.05s: Solicitud 2 → [0.00, 0.05] → conteo=2 ✓
...
Tiempo 1.00s: Solicitud 100 → [0.00, 0.05, ..., 1.00] → conteo=100 ✓
Tiempo 1.01s: Solicitud 101 → Timestamps antiguos eliminados, conteo < 100 ✓
Tiempo 0.05s (eliminado): conteo cae, solicitudes nuevas permitidas

Algoritmo Leaky Bucket

Leaky bucket encola solicitudes y procesa a velocidad fija:

Algoritmo:
1. Encole solicitudes entrantes
2. Procese solicitudes de cola a velocidad fija R
3. Si cola llena, rechace solicitudes nuevas
4. Suavice ráfagas de tráfico en flujo constante

Ventajas:

  • Velocidad de salida suave
  • Uso de recursos predecible
  • Previene sobrecarga de ráfaga

Desventajas:

  • Latencia más alta para solicitudes iniciales
  • Costo de gestión de cola
  • No apropiado para cargas de trabajo de ráfaga

Limitación de Velocidad de Nginx

Nginx proporciona limitación de velocidad de token bucket:

Limitación de Velocidad Básica

# Define rate limit zones
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=100r/s;
limit_req_zone $http_x_api_key zone=api_key_limit:10m rate=1000r/s;

server {
    listen 80;
    server_name api.example.com;
    
    # Apply rate limit to entire API
    limit_req zone=api_limit burst=50 nodelay;
    
    location /api {
        proxy_pass http://backend;
    }
}

Parámetros:

  • $binary_remote_addr: IP del cliente (representación binaria)
  • zone=nombre:tamaño: Nombre y tamaño de zona (10m = 10 megabytes)
  • rate=100r/s: 100 solicitudes por segundo
  • burst=50: Permita 50 solicitudes de ráfaga (cola)
  • nodelay: No retrase solicitudes de ráfaga

Limitación de Velocidad Por Endpoint

limit_req_zone $binary_remote_addr zone=strict_limit:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=normal_limit:10m rate=100r/s;

server {
    listen 80;
    server_name api.example.com;
    
    # Strict limit for expensive endpoints
    location /api/expensive {
        limit_req zone=strict_limit burst=5;
        proxy_pass http://backend;
    }
    
    # Normal limit for standard endpoints
    location /api/standard {
        limit_req zone=normal_limit burst=20;
        proxy_pass http://backend;
    }
    
    # High limit for fast endpoints
    location /api/fast {
        # No rate limit
        proxy_pass http://backend;
    }
}

Limitación de Velocidad por Clave de API

# Extract API key from header
map $http_x_api_key $api_client {
    default "unknown";
    "~^key_(.+)$" $1;
}

limit_req_zone $api_client zone=api_key_limit:10m rate=1000r/s;

server {
    listen 80;
    server_name api.example.com;
    
    location /api {
        # Check for valid API key
        if ($http_x_api_key = "") {
            return 401 '{"error": "Missing API key"}';
        }
        
        limit_req zone=api_key_limit burst=100 nodelay;
        proxy_pass http://backend;
        proxy_set_header X-API-Key $http_x_api_key;
    }
}

Exclusiones de Lista Blanca

geo $whitelist {
    default 0;
    10.0.0.0/8 1;
    192.168.0.0/16 1;
    203.0.113.0/24 1;  # Partner network
}

limit_req_zone $binary_remote_addr zone=api_limit:10m rate=100r/s;

server {
    listen 80;
    
    location /api {
        if ($whitelist = 1) {
            access_log off;
            # Skip rate limiting for whitelisted IPs
            proxy_pass http://backend;
        }
        
        limit_req zone=api_limit burst=50;
        proxy_pass http://backend;
    }
}

Límites de Velocidad Dinámicos

# Map user tier to rate limit
map $http_x_user_tier $rate_limit_zone {
    "premium" $binary_remote_addr:premium;
    "standard" $binary_remote_addr:standard;
    default $binary_remote_addr:free;
}

limit_req_zone $rate_limit_zone zone=premium_limit:10m rate=5000r/s;
limit_req_zone $rate_limit_zone zone=standard_limit:10m rate=500r/s;
limit_req_zone $rate_limit_zone zone=free_limit:10m rate=50r/s;

server {
    listen 80;
    server_name api.example.com;
    
    location /api {
        if ($http_x_user_tier = "premium") {
            limit_req zone=premium_limit burst=500;
        }
        if ($http_x_user_tier = "standard") {
            limit_req zone=standard_limit burst=50;
        }
        if ($http_x_user_tier = "free") {
            limit_req zone=free_limit burst=5;
        }
        
        proxy_pass http://backend;
    }
}

Respuesta Personalizada de Estado de Límite de Velocidad

limit_req_zone $binary_remote_addr zone=api_limit:10m rate=100r/s;

server {
    listen 80;
    server_name api.example.com;
    
    # Handle rate limit errors
    error_page 429 = @rate_limit_error;
    
    location /api {
        limit_req zone=api_limit burst=50 nodelay;
        limit_req_status 429;
        proxy_pass http://backend;
    }
    
    location @rate_limit_error {
        default_type application/json;
        return 429 '{"error": "Too many requests", "retry_after": 60}';
    }
}

Limitación de Velocidad de HAProxy

HAProxy usa tablas sticky para limitación de velocidad distribuida:

Limitación de Velocidad Básica

global
    stats socket /run/haproxy/admin.sock

defaults
    mode http
    timeout client 30s
    timeout server 30s

frontend api_in
    bind *:80
    
    # Stick table for rate limiting
    stick-table type ip size 100k expire 1h store http_req_rate(10s)
    
    # Track client IP
    http-request track-sc0 src
    
    # Limit to 100 requests per 10 seconds
    http-request deny if { sc_http_req_rate(0) gt 100 }
    
    default_backend api_servers

backend api_servers
    balance roundrobin
    server srv1 192.168.1.100:8000 check
    server srv2 192.168.1.101:8000 check

Limitación de Velocidad Por Clave de API

frontend api_in
    bind *:80
    
    # Extract API key from header
    http-request set-var(req.api_key) req.hdr(X-API-Key)
    
    # Stick table tracking by API key
    stick-table type string len 64 size 100k expire 1h store http_req_rate(10s)
    http-request track-sc0 var(req.api_key)
    
    # Enforce limit
    http-request deny if { sc_http_req_rate(0) gt 1000 }
    
    default_backend api_servers

Limitación de Velocidad Escalonada

frontend api_in
    bind *:80
    
    stick-table type string len 64 size 100k expire 1h store http_req_rate(10s)
    http-request track-sc0 req.hdr(X-API-Key)
    
    # Different limits based on user tier
    acl is_premium_tier req.hdr(X-Tier) -i "premium"
    acl is_standard_tier req.hdr(X-Tier) -i "standard"
    
    http-request deny if is_premium_tier !{ sc_http_req_rate(0) lt 5000 }
    http-request deny if is_standard_tier !{ sc_http_req_rate(0) lt 500 }
    http-request deny if !is_premium_tier !is_standard_tier !{ sc_http_req_rate(0) lt 50 }
    
    default_backend api_servers

Limitación de Velocidad con Backoff Exponencial

frontend api_in
    bind *:80
    
    stick-table type ip size 100k expire 1h store http_req_rate(10s), gpc0
    http-request track-sc0 src
    
    # First tier: 100 req/s
    http-request deny if { sc_http_req_rate(0) gt 100 }
    
    # Mark repeat offenders
    http-request set-var(proc.offender) sc0_gpc0 if { sc_http_req_rate(0) gt 100 }
    http-request sc-inc-gpc0(0) if { sc_http_req_rate(0) gt 100 }
    
    # Higher penalty for repeat offenders
    http-request set-header X-Client-Warning "Rate-limited" if { sc0_gpc0 gt 3 }
    http-request deny if { sc0_gpc0 gt 10 }
    
    default_backend api_servers

Análisis de Encabezado de Solicitud

Analice encabezados de solicitud para limitación de velocidad inteligente:

# Rate limit based on User-Agent
map $http_user_agent $is_bot {
    default 0;
    ~*bot 1;
    ~*crawler 1;
    ~*spider 1;
}

limit_req_zone $binary_remote_addr zone=normal:10m rate=100r/s;
limit_req_zone $binary_remote_addr zone=bot:10m rate=10r/s;

server {
    listen 80;
    
    location /api {
        if ($is_bot = 1) {
            limit_req zone=bot burst=5;
        }
        if ($is_bot = 0) {
            limit_req zone=normal burst=50;
        }
        
        proxy_pass http://backend;
    }
}

Limitar por nombre de host:

limit_req_zone $host zone=per_host:10m rate=100r/s;

server {
    listen 80;
    server_name api.example.com other.example.com;
    
    location / {
        limit_req zone=per_host burst=50;
        proxy_pass http://backend;
    }
}

Limitar por ruta de solicitud:

map $request_uri $request_limit_zone {
    ~*/api/expensive strict_limit;
    ~*/api/standard normal_limit;
    default no_limit;
}

limit_req_zone $request_limit_zone zone=strict_limit:10m rate=10r/s;
limit_req_zone $request_limit_zone zone=normal_limit:10m rate=100r/s;

server {
    listen 80;
    location / {
        limit_req zone=$request_limit_zone;
        proxy_pass http://backend;
    }
}

Límites de Velocidad Por Cliente

Implemente límites por cliente sofisticados:

# Combine multiple factors
map "$binary_remote_addr:$http_x_user_id:$http_x_api_key" $rate_limit_key {
    default $binary_remote_addr;
    ~*^(.+):([^:]+):(.+)$ $3;  # Prefer API key
    ~*^(.+):([^:]+)$ $2;       # Then user ID
}

limit_req_zone $rate_limit_key zone=client_limit:10m rate=500r/s;

server {
    listen 80;
    
    location /api {
        limit_req zone=client_limit burst=100 nodelay;
        proxy_pass http://backend;
        
        # Pass identified client to backend
        proxy_set_header X-Client-ID $rate_limit_key;
    }
}

Encabezados de Respuesta de Límite de Velocidad

Incluya encabezados estándar de límite de velocidad en respuestas:

# Add rate limit headers to responses
add_header X-RateLimit-Limit 100 always;
add_header X-RateLimit-Remaining $limit_req_status always;
add_header X-RateLimit-Reset $msec always;

# Custom response when rate limited
error_page 429 = @rate_limited;

location @rate_limited {
    default_type application/json;
    add_header X-RateLimit-Limit 100 always;
    add_header X-RateLimit-Remaining 0 always;
    add_header X-RateLimit-Reset 60 always;
    add_header Retry-After 60 always;
    
    return 429 '{
        "error": "Too Many Requests",
        "message": "Rate limit exceeded. Retry after 60 seconds.",
        "status": 429
    }';
}

Encabezados de límite de velocidad de HAProxy:

http-response set-header X-RateLimit-Limit "1000"
http-response set-header X-RateLimit-Remaining "999"
http-response set-header X-RateLimit-Reset "%T"

# When rate limited
http-response set-header X-RateLimit-Limit "100" if rate_limited
http-response set-header X-RateLimit-Remaining "0" if rate_limited
http-response set-header Retry-After "60" if rate_limited

Limitación de Velocidad Distribuida

Comparta estado de límite de velocidad a través de múltiples servidores usando Redis:

import redis
import time
from flask import Flask, request, jsonify

app = Flask(__name__)
r = redis.Redis(host='localhost', port=6379, db=0)

def check_rate_limit(client_id, limit=100, window=60):
    key = f"rate_limit:{client_id}"
    
    # Increment counter
    current = r.incr(key)
    
    # Set expiration on first request
    if current == 1:
        r.expire(key, window)
    
    # Check if exceeded
    if current > limit:
        return False, current - limit
    
    return True, limit - current

@app.route('/api/resource')
def get_resource():
    client_id = request.remote_addr
    allowed, remaining = check_rate_limit(client_id)
    
    if not allowed:
        return jsonify({'error': 'Rate limit exceeded'}), 429
    
    response = {'data': 'resource data'}
    response.headers['X-RateLimit-Remaining'] = str(remaining)
    return response

Pruebas y Monitoreo

Pruebe limitación de velocidad:

# Simple rate limit test
for i in {1..150}; do
    curl -s -o /dev/null -w "%{http_code} " http://api.example.com/resource
    echo -n "$i "
done
echo

# Measure response time under load
ab -n 1000 -c 100 http://api.example.com/api/resource

# Check rate limit headers
curl -i http://api.example.com/api/resource | grep X-RateLimit

# Test with custom headers
for i in {1..5}; do
    curl -s -H "X-API-Key: mykey" http://api.example.com/api/resource | head -1
done

Monitoree limitación de velocidad:

# Check Nginx rate limit stats
grep "limiting requests" /var/log/nginx/error.log | wc -l

# Monitor HAProxy stick table
echo "show table api_limit" | socat - /run/haproxy/admin.sock

# Monitor Redis rate limits
redis-cli --scan --pattern "rate_limit:*"
redis-cli DBSIZE

Conclusión

La limitación de velocidad es esencial para protección de API y asignación justa de recursos. Elija algoritmos basados en requisitos: token bucket para tráfico de ráfaga, ventana deslizante para límites estrictos, leaky bucket para rendimiento suave. Implemente estrategias por cliente, por endpoint y escalonadas con encabezados de respuesta claros. Los sistemas distribuidos se benefician de limitación de velocidad respaldada por Redis para consistencia. Monitoree y ajuste límites continuamente basado en patrones de uso real y capacidad del sistema.