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.

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.