API Engineering

Mitigación de límites de tasa de API sin activar heurísticas anti-abuso

4 min read Published Updated 808 words

La mayoría de las implementaciones de límite de tasa de API se eluden trivialmente con una rotación ingenua de proxies, pero esa misma rotación activa las heurísticas antiahuso en cuestión de minutos. El verdadero desafío no es solo mantenerse por debajo del límite, sino hacerlo sin parecer un bot. Esto requiere comprender los dos ámbitos distintos de límite de tasa (por IP y por clave), aplicar retroceso con fluctuación aleatoria (jitter), aleatorizar la rotación del pool y modelar la temporización de las solicitudes para imitar el tráfico orgánico. El patrón de cubeta de tokens (token bucket), superpuesto sobre un pool de proxies, proporciona una base sólida.

Por IP vs. Por Clave: Los Dos Ejes del Límite de Tasa

Los límites de tasa operan en al menos dos dimensiones independientes: la IP de origen y la clave de API (o token). Una sola clave puede permitir 5,000 solicitudes por hora (el límite autenticado de GitHub), pero la misma clave desde una sola IP puede ser limitada con un techo de ráfaga más bajo. Las solicitudes no autenticadas están aún más restringidas — típicamente 60 solicitudes por hora por IP. Ignorar la dimensión IP es la forma más rápida de provocar una respuesta 429 Too Many Requests (RFC 6585). Un pool de proxies debe distribuir las solicitudes entre múltiples IPs para evitar saturar un solo origen, pero cada IP sigue compartiendo la misma clave. Si el límite global de la clave es de 5,000/hora y tienes 50 proxies, cada proxy solo puede hacer 100 solicitudes por hora antes de que se active el contador a nivel de clave. Mapea ambos límites antes de escribir una sola línea de código.

Retroceso con Fluctuación Aleatoria: La Diferencia entre Ser Cortés y Ser Predecible

El retroceso exponencial sin fluctuación aleatoria es una huella digital. Los servidores ven solicitudes que llegan en intervalos perfectamente duplicados y las marcan como automatizadas. La solución es la fluctuación aleatoria completa: sleep(random.uniform(0, min(cap, base * 2 ** attempt))). Esto dispersa los reintentos a lo largo de la ventana de tiempo, haciendo que el patrón sea indistinguible de una ráfaga de usuarios reales. La propia documentación de AWS sobre retroceso exponencial recomienda la fluctuación aleatoria precisamente por esta razón. El siguiente fragmento de Python implementa una cubeta de tokens que se rellena a una tasa fija, duerme con fluctuación aleatoria cuando está vacía y rota el proxy en cada adquisición exitosa:

import time
import random
from collections import deque

class TokenBucketProxyPool:
    def __init__(self, proxies, rate, burst):
        self.proxies = deque(proxies)
        self.rate = rate          # tokens per second
        self.burst = burst
        self.tokens = burst
        self.last_refill = time.monotonic()

    def refill(self):
        now = time.monotonic()
        elapsed = now - self.last_refill
        self.tokens = min(self.burst, self.tokens + elapsed * self.rate)
        self.last_refill = now

    def acquire(self):
        while True:
            self.refill()
            if self.tokens >= 1:
                self.tokens -= 1
                self.proxies.rotate(1)   # simple rotation, but see section below
                return self.proxies[0]
            else:
                sleep_time = (1 - self.tokens) / self.rate
                jitter = random.uniform(0, sleep_time * 0.5)
                time.sleep(sleep_time + jitter)

# Usage: pool = TokenBucketProxyPool(proxies, rate=2.0, burst=10)
#         proxy = pool.acquire()   # blocks until token available

Rotación del Pool de Proxies: Aleatoriza, No Hagas Round-Robin

El round-robin secuencial a través de una lista de proxies es detectable. Un servidor que ve solicitudes desde 1.2.3.4, luego 5.6.7.8, luego 9.10.11.12 en paso sincronizado correlacionará el patrón entre IPs. En su lugar, elige proxies aleatoriamente con reemplazo y añade un tiempo de espera por proxy. Los directorios públicos de proxies reportan tasas de fallo del 60–80% — un proxy que devuelve un 429 o un error de conexión debe ser degradado a una cola de muertos durante al menos 60 segundos. Implementa una selección aleatoria ponderada donde los proxies que han tenido éxito recientemente tengan más probabilidades de ser elegidos. La cubeta de tokens anterior usa un simple rotate(1) por claridad; en producción reemplázalo con random.choice() y lleva un registro de los contadores de fallos por proxy.

Modelado de Solicitudes: Imita la Media Sin Cruzar la Línea

Incluso con una cubeta de tokens y un pool de proxies perfectos, una tasa de solicitud constante de exactamente 2.0 por segundo no es natural. Los usuarios reales hacen pausas, agrupan y varían los intervalos entre solicitudes. Añade un pequeño retardo aleatorio (por ejemplo, time.sleep(random.uniform(0.1, 0.5))) antes de cada llamada a acquire(), y varía la tasa de la cubeta de tokens en ±10% cada pocos minutos. Sin embargo, no añadas retardos que excedan la ventana de límite de tasa documentada de la API — eso anularía el propósito. El objetivo es mantenerse dentro del límite mientras se aparenta ser un grupo de clientes legítimos. Un modelado excesivo (por ejemplo, insertar retardos similares a la escritura humana) es esfuerzo desperdiciado; a la API le importa la frecuencia de las solicitudes, no el tiempo entre pulsaciones de teclas.

Mitigación Ética: Sepa Dónde Está la Línea

Cada técnica descrita aquí es una optimización legítima — hasta que viola los Términos de Servicio de la API. La mayoría de los ToS prohíben explícitamente "eludir los límites de tasa" o "usar medios automatizados para acceder al servicio". Leer los ToS no es opcional. Si la API permite 100 solicitudes por minuto por clave, usar 50 proxies que hagan 2 solicitudes por minuto cada uno cumple con las normas. Usar 100 proxies que hagan 100 solicitudes por minuto cada uno no cumple — es abuso, sin importar lo ingeniosa que sea tu fluctuación aleatoria. La distinción radica en la intención y el volumen. Una cubeta de tokens superpuesta sobre un pool de proxies es una herramienta; la misma herramienta que extrae éticamente un directorio público también puede usarse para hacer DDoS a una API pequeña. Documenta tus objetivos de límite de tasa, audita tus registros en busca de 429 y nunca superes el techo por clave documentado. Si necesitas más rendimiento, solicita un límite más alto o paga por un plan dedicado.