Por qué aumentar la concurrencia provoca errores HTTP 429: tasa de inicio y límites por host
Aumentar el número de trabajadores sin establecer una tasa de inicio ni límites por host genera picos que provocan errores 429. El rendimiento proviene de una concurrencia controlada, mecanismos de retraso y un diseño honesto de colas.
En resumen: Aumentar la concurrencia se presenta como un paralelismo gratuito: más trabajadores, más solicitudes, más datos. Diez suena bien, así que veinte debe ser mejor y cincuenta irresistible. El problema es que elevar el número de recursos no solo aumenta la concurrencia, sino que también determina con qué intensidad el cliente se conecta al servidor desde el primer instante, y —en cargas de trabajo con varios servidores— qué nivel de concurrencia por servidor se genera sin que nadie lo configure. Cuando estos mecanismos ocultos fallan, el código de estado suele ser HTTP 429, que indica exactamente “demasiadas solicitudes concurrentes”. Entonces los equipos reducen el número de recursos o añaden proxies y siguen adelante. Al no identificar la causa real, pierden capacidad de procesamiento útil.
Parte I — Concurrencia vs tasa de inicio: un parámetro controla dos límites
El tamaño del grupo de trabajadores (p-limit(n), un semáforo, el número de hilos) limita cuántas operaciones pueden estar en ejecución. No limita la rapidez con la que puede comenzar un nuevo trabajo. En t=0, un grupo sin trabajos previos con cincuenta tareas en espera puede iniciar cincuenta solicitudes en un solo intervalo de tiempo, lo que genera un aumento repentino en la tasa de inicio; aunque la concurrencia en estado estable parecerá “solo cincuenta” más tarde. Los limitadores de tasa tienen en cuenta ese aumento inicial tanto como el paralelismo sostenido.
Midiendo el aumento repentino en la tasa de inicio
Un entorno reproductible ayuda. Tome un pequeño conjunto de URLs de artículos de la Wiki de Arch Linux:
https://wiki.archlinux.org/title/Arch_Linux
https://wiki.archlinux.org/title/Installation_guide
https://wiki.archlinux.org/title/Pacman
https://wiki.archlinux.org/title/Systemd
...and so on
Cree una carga de trabajo con 100 solicitudes recorriendo la lista:
function buildWorkload(urls, n) {
return Array.from({ length: n }, (_, i) => urls[i % urls.length]);
}
async function runPooledFetch(urls, concurrencyLimit, agent, options = {}) {
const limit = pLimit(concurrencyLimit);
const results = await Promise.all(
urls.map((url) => limit(() => fetchOne(url, agent)))
);
// ...
}
Ejecuta pruebas con diferentes tamaños de grupo, clasifica cada resultado como correcto o 429 (y otros fallos), y registra las solicitudes completadas por segundo junto con el conteo de resultados correctos. Esta distinción es importante: un 429 rápido sigue contribuyendo a las métricas superficiales de “solicitudes completadas por segundo”, aunque no se entregue ninguna página.
El rendimiento útil disminuye a medida que aumenta la concurrencia global
Con una carga de trabajo fija de 100 solicitudes y solo el tamaño del grupo cambiando, una conexión directa muestra un rendimiento deficiente. A una concurrencia de 1, esencialmente todas las 100 solicitudes tienen éxito y la tasa de solicitudes completadas se sitúa cerca de 2.4/s; hay que esperar a que se completen todas las generaciones sin poder iniciar procesos en paralelo. A una concurrencia de 10, los datos útiles disminuyen drásticamente: solo unas 42 páginas tienen éxito y el 58% de las solicitudes devuelve un error 429, mientras que la tasa de solicitudes completadas sube a aproximadamente 23.5/s. A concurrencias de 25 y 50, la cantidad de solicitudes exitosas sigue disminuyendo (alrededor de 24/100, luego 16/100) mientras que la tasa de solicitudes completadas se mantiene en los veinte o veintitrés por segundo (~18.2/s y 21.8/s).
El pico de vanidad se da con una concurrencia de 10 a 23.5 solicitudes completadas por segundo, el valor más alto de la tabla, acompañado de uno de los peores conteos de respuestas “ok”. Optimizar para solicitudes completadas por segundo recomendaría el ajuste que consume la mayor parte del presupuesto en prohibiciones. El rendimiento útil es respuestas “ok” por segundo, no las completaciones brutas.
Agregar un límite a la tasa de inicio con la misma concurrencia lo soluciona
Mantenga el tamaño del grupo sin cambios y controle cuán pronto puede iniciarse la próxima solicitud. Determine un intervalo mínimo a partir de un límite máximo de solicitudes por segundo:
// minGapMs derived from --max-rps; pool concurrency is untouched
if (minGapMs > 0) {
const now = Date.now();
const waitMs = Math.max(0, nextStartAt - now);
if (waitMs > 0) await sleep(waitMs);
nextStartAt = Math.max(Date.now(), nextStartAt) + minGapMs;
}
Con este control, la misma concurrencia que anteriormente sobrecargaba el servidor puede completar casi todas las páginas, ya que desaparece el aumento repentino de solicitudes. El modelo mental correcto consiste en dos controles: concurrencia (solicitudes en proceso) y tasa de inicio (solicitudes admitidas por segundo). Fusionarlos en un único número oculta el modo de fallo.
Por qué un cliente HTTP en pool envía una ráfaga de solicitudes en t=0
Los pools no conocen el concepto de ritmo moderado. Solo saben de los espacios disponibles. Al iniciar, cada espacio está libre, por lo que todas las tareas en cola que pueden ejecutarse lo hacen. El reuso de conexiones y el multiplexing HTTP/2 pueden hacer que esa ráfaga sea aún más intensa en la red. Solo un controlador de admisión —como el bucket de tokens, el intervalo mínimo o el bucket con fugas— puede regular los inicios independientemente de los límites establecidos para las solicitudes en tránsito.
¿Corrigen los proxies residenciales la ráfaga en la tasa de inicio?
El control del ritmo es la solución para garantizar la correctitud: recopilar datos sin sobrecargar al destino. La realidad técnica a veces exige una velocidad que un límite de 2,4 solicitudes por segundo no puede satisfacer. El mismo enfoque ingenuo —sin límite en la tasa de inicio y con ráfagas en t=0— cuando se envía a través de proxies residenciales puede lograr aproximadamente 98–99/100 de éxitos prácticamente sin bloqueos en diferentes niveles de concurrencia, mientras que la ruta directa sigue teniendo grandes problemas.
Eso no significa que los proxies eliminen la necesidad de comprender la tasa de inicio. Ellos cambian la reputación de la IP y la forma en que un sitio atribuye el tráfico. Pueden ocultar un aumento repentino de tráfico que podría causar el bloqueo de una sola IP de salida. Si el requisito del producto es “recopilar datos sin que parezca una avalancha”, el control de ritmo sigue siendo el principal; los proxies son una capa de capacidad y reputación, no un sustituto para medir las admisiones.
La configuración del agente proxy generalmente obtiene las credenciales del entorno:
function buildProxyAgent() {
const user = process.env.BRIGHT_DATA_PROXY_RESIDENTIAL_USERNAME;
const pass = process.env.BRIGHT_DATA_PROXY_RESIDENTIAL_PASSWORD;
if (!user || !pass) return null;
const host = process.env.BRIGHT_DATA_PROXY_HOST || 'brd.superproxy.io';
const port = process.env.BRIGHT_DATA_PROXY_PORT || '33335';
const proxyUrl = `http://${encodeURIComponent(user)}:${encodeURIComponent(pass)}@${host}:${port}`;
return new HttpsProxyAgent(proxyUrl);
}
const residentialAgent = buildProxyAgent();
await runPooledFetch(tasks, 50, residentialAgent);
Úsalas de manera intencionada, registra si una ejecución fue directa o a través de un proxy, y sigue anotando la tasa de inicio de solicitudes observada para saber qué método influyó en el resultado.
Parte II — Concurrency por host
Los grupos globales también ocultan un segundo límite que surge. Considera lo siguiente:
const limit = pLimit(50);
await Promise.all(urls.map((url) => limit(() => fetchOne(url))));
Cincuenta espacios globales compartidos entre varios hosts no implica que cada host vea como máximo cincuenta solicitudes en ejecución. El orden de la cola y las diferencias en el tiempo de respuesta determinan cuántas solicitudes absorbe realmente un nombre de host. Una lista mixta puede parecer equilibrada en teoría:
1. arch
2. github
3. arch
4. mdn
5. npm
6. arch
7. cloudflare
pero aún así se producen picos de carga en el host estricto cuando sus URLs se agrupan dentro del conjunto ejecutable.
Pruebas de rendimiento de concurrencia por host
Reutilice el entorno con dos hosts:
- Un host estricto: Arch Linux Wiki (10 URLs de artículos) que se bloquea bajo presión, como en la Parte I.
- Un host flexible: las páginas de catálogo de
books.toscrape.com(10 URLs) que rara vez se bloquean, y que sirven como control. Si la sandbox falla, el cliente está dañado.
Una lista alternada genera 20 URLs × 5 repeticiones = 100 solicitudes con una concurrencia global de 50. Las listas de elementos fijos y los generadores se encuentran en el repositorio open concurrency-trap-bench (por ejemplo, urls-mixed-arch-books.txt).
https://wiki.archlinux.org/title/Arch_Linux
https://books.toscrape.com/catalogue/page-1.html
https://wiki.archlinux.org/title/Installation_guide
https://books.toscrape.com/catalogue/page-2.html
https://wiki.archlinux.org/title/Pacman
https://books.toscrape.com/catalogue/page-3.html
...and so on
Las cargas de trabajo ordenadas pueden seguir el método round-robin, bloquearse por host o mezclarse con un valor de semilla:
function buildOrderedWorkload(urls, pattern, { repeatsPerUrl = 5, seed = null } = {}) {
const n = urls.length * repeatsPerUrl;
let list = Array.from({ length: n }, (_, i) => urls[i % urls.length]);
if (pattern === 'block') {
// AxN, BxN, and so on. Repeat each URL before advancing.
const out = [];
for (const url of urls) {
for (let i = 0; i < repeatsPerUrl; i++) out.push(url);
}
return out;
}
if (pattern === 'shuffle') {
// Fisher–Yates with a fixed seed so the run is reproducible
for (let i = list.length - 1; i > 0; i--) {
seed = (Math.imul(1664525, seed) + 1013904223) >>> 0;
const j = seed % (i + 1);
[list[i], list[j]] = [list[j], list[i]];
}
}
// 'round-robin' leaves the alternating list as-is
return list;
}
Mida el pico de solicitudes en curso por host a partir de los eventos de inicio y finalización:
function maxConcurrentPerHost(results) {
const eventsByHost = new Map();
for (const r of results) {
const host = new URL(r.url).hostname;
if (!eventsByHost.has(host)) eventsByHost.set(host, []);
const end = r.startedAt + r.ms;
eventsByHost.get(host).push({ t: r.startedAt, delta: 1 }, { t: end, delta: -1 });
}
// sort events by time, sweep: +1 on start, -1 on finish, track max
}
La concurrencia global permanece fija; solo cambia el orden. Esto isola la presión en cada host del tamaño del grupo.
Cómo el orden de la cola modifica la concurrencia por host
Los rastreadores reales nunca siguen perfectamente el método round-robin de forma permanente. Los mapas del sitio, los grafos de dependencias y las colas de reintentos redistribuyen el trabajo. Por lo tanto, los mismos ajustes globales pueden generar picos diferentes en cada host.
1. Round-robin: distribución equitativa entre hosts alternos
La lista alternada (A B A B …) distribuye el trabajo. El pico de solicitudes en proceso en el host estricto se mantiene relativamente moderado porque el otro host sigue ocupando espacios.
2. Bloqueo: agrupación de solicitudes del mismo host
Al agrupar todas las URLs de Arch y luego todas las URLs de libros, se le da al host estricto una larga secuencia continua de solicitudes por procesar. El pico de solicitudes en proceso en ese host aumenta según el tamaño total del conjunto global, aunque “la concurrencia siga siendo del 50 %”.
3. Mezcla (semilla 42): orden aleatorio
Una mezcla con semilla se sitúa entre los extremos y refleja el orden aleatorio que ocurre en producción. Los picos varían según la semilla; la lección es la sensibilidad al parámetro, no una permutación mágica.
Misma concurrencia global, diferente presión por host
A través de esos patrones, las tasas de éxito en el modo estricto siguen más de cerca su pico medido durante las solicitudes en curso que el valor global constante c=50. El modo indulgente mantiene su estado saludable. Reducir el conjunto global para “arreglar” el modo estricto también perjudicaría al indulgente y, aun así, no lograría controlar el pico del modo estricto en casos de ordenamiento desafortunado.
Solución: limitar por separado la concurrencia por host
En la Parte I se añadió un límite a la tasa de inicio junto al conjunto global. En la Parte II se introduce un límite por host adicional: un límite anidado que determina cuántas solicitudes en curso puede tener cualquier nombre de host en particular.
Recuerde los tres términos:
- Concurrencia global — tamaño del conjunto compartido (por ejemplo
p-limit(50)). Usted lo establece. - Concurrencia por host — lo que realmente experimenta un host concreto (
peak_inflight). Usted lo mide.
Si falta este límite a nivel de host, cualquier URL disponible en el pool global puede ser utilizada, lo que podría causar una sobrecarga en un origen específico. Al añadir el límite anidado, cada nombre de host dispone de su propia puerta de cola:
async function runPooledFetch(urls, concurrencyLimit, agent, { perHostLimit } = {}) {
const globalLimit = pLimit(concurrencyLimit);
const hostLimiters = new Map();
async function fetchWithLimits(url, execute) {
return globalLimit(async () => {
if (perHostLimit && perHostLimit >= 1) {
const host = new URL(url).hostname;
if (!hostLimiters.has(host)) hostLimiters.set(host, pLimit(perHostLimit));
return hostLimiters.get(host)(execute);
}
return execute();
});
}
// ...
}
Ahora un grupo de URLs con restricciones estrictas no puede ocupar todos los espacios del pool global al mismo tiempo. Los hosts con restricciones más flexibles siguen pudiendo utilizar la capacidad restante. Las prohibiciones están relacionadas con la variable de límite que pretendía controlar.
Por qué un pool de trabajadores compartido concentra la carga en un solo host
La piscina llena los espacios disponibles con todo lo que puede ejecutarse. Los hosts rápidos liberan los espacios con rapidez y se encargan de más tareas propias, hasta que un grupo de URLs de hosts estrictos puede ejecutarse juntos y hereda una carga repentina. El tamaño de esta carga depende del orden, la latencia y la combinación de elementos, ninguno de los cuales se refleja en el único valor entero de concurrencia. Por eso, un limitador por host es mejor que reducir ciegamente la piscina global.
Una lista de verificación práctica
- Trate la tasa de inicio como un parámetro independiente. Un semáforo no es un limitador de RPS. Añada un límite explícito para la tasa de inicio además del de concurrencia.
- Trate la concurrencia por host como un parámetro independiente. Las cargas de trabajo multihost necesitan un limitador por host dentro de la piscina global.
- Registre
observed_start_rpsypeak_inflightpor host. No se pueden establecer límites sin haber medido primero esos valores.
Los umbrales exactos en una ejecución específica de un blog no son portables: los limitadores tienen estado y dependen del tiempo, del historial de tráfico y de la reputación de la IP. El patrón portable es el aislamiento. Un fallo que parece deberse a “demasiada concurrencia” podría ser un problema de tasa de inicio, un problema de programación por host o ambos. Mientras no se separen esas variables, reducir el conjunto de recursos solo trata un síntoma, y a menudo el incorrecto.
Leer las métricas sin engañarse
La cantidad de solicitudes completadas por segundo aumenta siempre que el cliente puede abrir sockets rápidamente, incluso cuando la mayoría de las respuestas son rechazos. Los paneles de control que celebran los picos de concurrencia sin un filtro de aprobación recomendarán configuraciones perjudiciales. Combine cada gráfico de rendimiento con una tasa de aprobación y, cuando sea posible, con la cantidad de bytes de contenido útil conservados. Si su pipeline vuelve a intentar solicitudes con código 429, cuente las reintentos por separado para que una ola de intentos no se confunda con un paralelismo productivo.
Los registros de tasa de inicio deben capturar la marca de tiempo de cada admisión, no solo de cada finalización. A partir de las admisiones se pueden reconstruir los picos en los primeros 100–500 ms, que es cuando muchos clientes agrupados parecen idénticos independientemente del tamaño del grupo en estado estable que haya configurado. Si dos configuraciones presentan el mismo pico de inicio, no se sorprenda si comparten también el mismo patrón de bloqueos.
Proxies, reputación y honestidad sobre los compromisos
Los proxies residenciales o de centro de datos redistribuyen la identidad. Pueden convertir un flujo masivo desde una única IP en varios flujos más discretos. Esto ayuda a los proyectos de recolección y puede ocultar un deficiente control de acceso ante un destino que impone límites en las IPs. No elimina las obligaciones éticas y contractuales hacia los sitios desde los que se obtiene la información, ni tampoco la necesidad técnica de comprender a su propio cliente. Prefiera controlar el ritmo primero cuando usted maneja al cliente; utilice proxies cuando el producto realmente requiera un mayor ancho de banda total a través de múltiples identidades, y siga midiendo la carga por identidad y por servidor para no actuar a ciegas detrás de la capa de proxy.
Uniendo las dos partes
Los rastreadores de producción suelen necesitar ambos tipos de límites: un pool global para garantizar la seguridad de los recursos por tu parte, un tope en la tasa de inicio para ser cortés al admitir solicitudes, y límites por host cuando varios destinos comparten ese pool. Omitir cualquiera de ellos vuelve a generar un patrón de fallo que “parece concurrencia” en los códigos de estado HTTP. La solución no es mística; se trata de instrumentación además de un segundo limitador dirigido a la variable que realmente observaste.
Notas de diseño para que las comparaciones sean justas
Mantenga idéntica la taxonomía de éxitos en todas las pruebas: los resultados OK, HTML, HTTP 429, otros códigos 4xx/5xx, tiempos de espera y errores de análisis deben etiquetarse de la misma manera en cada ejecución. Cambie únicamente la variable que se está probando: el tamaño del grupo, el intervalo mínimo de inicio, la activación/desactivación del proxy o el orden de la cola. Utilice DNS y TLS ya calentados siempre que sea posible, para que los primeros puntos de la curva no estén influenciados por ruidos relacionados con el handshake inicial, a menos que ese inicio en frío forme parte explícita del estudio.
Repita las cargas de trabajo suficientes veces para reducir el ruido, pero no tantas como para que el limitador adaptativo de un sitio cambie permanentemente durante el experimento. Cuando los limitadores tengan estado, anote la hora del día y si las prohibiciones anteriores podrían seguir teniendo efecto. Publique los valores iniciales utilizados para los mezcladores, de modo que otros puedan reproducir los efectos relacionados con el orden.
Las URLs de los archivos fijos deben ser estables. Los títulos de la wiki y las páginas del catálogo de entorno de pruebas que desaparecen durante el estudio dañan las cuentas de “ok”. Coloque listas de fijación en el control de versiones junto al conjunto de herramientas, como lo hace el proyecto concurrency-trap-bench, para que los gráficos hagan referencia a entradas conocidas.
Así es el “rendimiento útil” en una tubería de procesamiento
A los sistemas posteriores les importan los documentos aceptados, no el número de conexiones establecidas. Si un raspador alimenta a un indexador, cuente los documentos indexados por minuto. Si lo hace a una base de datos de precios, cuente las filas validadas. Alinee el objetivo de optimización con esa unidad de negocio. De lo contrario, el equipo de ingeniería maximizará una métrica indirecta: los intercambios HTTP completados, que incluyen grandes cantidades de respuestas con código 429.
Las reintentos interfieren negativamente con los grupos sin control de ritmo. Un aumento repentino que genere prohibiciones, seguido de reintentos inmediatos, puede aumentar aún más la tasa de inicio. Aplique estrategias de retroceso ante códigos 429 con variabilidad temporal, respete el campo Retry-After cuando esté presente, y nunca permita que las tormentas de reintentos evadan la restricción de tasa de inicio. El controlador de admisión debe considerar los reintentos como nuevos intentos de inicio.
Programadores multiinquilino y multihost
Los servicios que procesan solicitudes en nombre de muchos clientes suelen contar ya con límites globales de concurrencia para garantizar la seguridad de los procesos. Sin embargo, siguen necesitando presupuestos por destino para evitar que la lista de URLs de un cliente monopolice un origen frágil. Los límites anidados —globales, por inquilino y por host— se combinan entre sí. Implemente estos límites como capas explícitas en lugar de esperar que una sola semáforo asegure una cola justa.
Cuando los hosts difieren en latencia por órdenes de magnitud, los grupos de trabajo compartido tienden a favorecer al host más rápido. Ese sesgo es beneficioso para la utilización, pero perjudicial para el host lento cuando sus URLs finalmente se pueden ejecutar en grupo. Los límites por host limitan los daños; los pesos opcionales por host pueden reflejar aún más las políticas de cortesía.
Interpretando los resultados del proxy sin pensamientos mágicos
Si la salida directa falla y la salida a través de proxy tiene éxito con configuraciones idénticas del grupo, es probable que el destino aplique controles basados en la identidad de red. Eso constituye un conocimiento operativo útil. No es prueba de que hayan cambiado las reglas relacionadas con la tasa de inicio. Detrás de los proxies aún se deben registrar las conexiones por identidad de salida y por host destino. De lo contrario, simplemente se traslada el punto ciego.
La conformidad y las políticas relacionadas con los robots siguen siendo responsabilidad suya de respetar. Un mayor rendimiento total a través de múltiples salidas aumenta el alcance de un error lógico. La regulación mediante indicadores específicos y límites por host se mantienen incluso cuando están activados los proxies, lo que permite reducir la intensidad sin tener que volver a implementar la infraestructura de identificación.
Cerrar el ciclo desde el experimento hasta los valores predeterminados
Una vez que las mediciones demuestren que la tasa de inicio y los picos por host predicen mejor las prohibiciones que el tamaño total del conjunto de usuarios, se deben codificar esos hallazgos como valores predeterminados en la biblioteca del cliente: requerir un parámetro de RPS (o intervalo mínimo), establecer un límite por host en los modos multi-host y exportar métricas para ambos casos. La documentación debe mostrar tanto el panel de control deficiente (con un aumento en solicitudes completadas por segundo mientras disminuyen las válidas) como el correcto. Enseñar cómo ocurren los fallos evita que el próximo equipo “arregle” la concurrencia causando interrupciones silenciosas en los datos útiles.
Reproducir el experimento de tasa de inicio de manera limpia
Fije las versiones de Pin Node y undici (o su stack HTTP) para que el comportamiento del pooling de conexiones se mantenga comparable. Desactive los middleware de intentos múltiples similares a los del navegador que no estén relacionados durante las pruebas. Limpie los cachés DNS entre ejecuciones directas y a través de proxy si su herramienta resuelve los hosts de forma diferente. Registre el tiempo real total del lote, además de las marcas de inicio y fin por solicitud, para poder graficar las admisiones en los primeros medio segundo: el intervalo en el que los clientes del pooling parecen idénticos, independientemente de la concurrencia en estado estable que cree haber configurado.
Al graficar, siempre muestre el conteo de solicitudes exitosas junto a las solicuciones completadas por segundo. Un gráfico de doble eje que oculte el conteo de solicitudes exitosas es la forma en que las métricas superficiales ganan argumentos. Exporte un archivo CSV desde la herramienta para que otros puedan volver a calcular los resultados sin depender de capturas de pantalla.
Interpretando la rigurosidad al estilo de Arch Wiki
Los servidores de documentación pública varían: algunos limitan el tráfico por IP y ruta, otros por User-Agent, algunos por conexiones simultáneas y otros por la tasa de solicitudes en intervalos temporales. Una respuesta 429 hoy podría convertirse en una ralentización menor mañana debido a cambios en la política del operador. Por eso, las cifras presentadas en el artículo son patrones ilustrativos, no constantes eternas. Lo que realmente importa es la metodología: separar el tamaño del grupo de servidores, la tasa de admisión y los picos por servidor, y luego modificar una variable a la vez.
Si utiliza su propio servidor estricto, mantenga un servidor de control más flexible en la misma ejecución. Si el servidor de control falla, su cliente quedará dañado. Cuando solo falle el servidor estricto, podrá estudiar cómo su política interactúa con su horario de uso.
Detalles de implementación del límite de tasa de inicio
Un margen mínimo derivado del máximo RPS es sencillo y efectivo para clientes de un solo proceso. Los cubos de tokens permiten picos breves al tiempo que imponen promedios a largo plazo, lo cual es útil cuando se desea una recuperación interactiva rápida pero también un rastreo masivo ordenado. Los cubos con fugas suavizan las situaciones más difíciles. Sea cual sea el algoritmo que elija, aplíquelo a los inicios, incluidas las reintentos. Una tormenta de reintentos que evita las restricciones recrea la avalancha en t=0 después de la primera prohibición.
Los rastreadores multiproceso necesitan un mecanismo de bloqueo distribuido (Redis, etcd o un programador central). Los márgenes locales por sí solos no permitirán coordinar a cinco trabajadores que cada uno cree que puede iniciar dos solicitudes por segundo.
Detalles de implementación del límite por host
El patrón habitual es el uso de semáforos anidados identificados por nombre de host: primero se adquiere el semáforo global, luego el del host, a continuación se obtiene la información solicitada y finalmente se libera todo en orden inverso. Decida si www y apex comparten la misma clave. Determine cómo afectan las redirecciones que cambian el número de hosts al límite establecido. Los fragmentos basados en ruta del mismo sitio suelen seguir compartiendo el mismo presupuesto de hosts, a menos que haya evidencia explícita de un CDN que indique lo contrario.
Emita métricas como inflight_global, inflight_per_host{host}, admissions_per_second y http_429_total{host}. Genere alertas cuando aumenten las tasas de 429, y no solo cuando se agoten los presupuestos de errores en respuestas 5xx.
Ordenamiento de colas en los programadores de producción
El orden del mapa del sitio, el BFS desde un punto de partida, las colas de prioridad para URLs “importantes” y las colas de intentos múltiples transforman los picos por host. Una cola de intentos múltiples que coloca al principio las URLs de Arch fallidas puede recrear accidentalmente el orden de los bloques después de una interrupción parcial. Una gestión justa de las colas entre hosts —colas en modo round-robin por host— reduce la concentración accidental incluso antes de que se apliquen límites estrictos. Los límites estrictos siguen siendo necesarios cuando la sola equidad no puede contener los picos ante latencias desiguales.
Proxies sin autoengaño
Las redes residenciales cambian la distribución de las identidades. Pero no anulan las leyes físicas: si cada identidad sigue iniciando cincuenta conexiones al mismo tiempo, los destinos que se basan en el comportamiento y no en la IP pueden seguir bloqueándolas. Registre las admisiones por cada identidad de salida. Rotar de forma educada. Respete a los robots y las condiciones contractuales. Prefiera un ritmo moderado incluso cuando estén activos los proxies, para que ningún error se amplifique y cause un gran impacto.
Los proxies de centros de datos son más económicos y fáciles de identificar; los residenciales son más caros y plantean problemas éticos. Elija con cuidado; no trate “proxy” como sinónimo de solución.
De experimento a valores predeterminados en la biblioteca
Integre dos parámetros obligatorios en los envoltorios del cliente HTTP compartido que utilizan los rastreadores: maxInFlight y maxStartsPerSecond, además de maxInFlightPerHost cuando hay más de un host. Se debe evitar crear un cliente para el modo multi-host sin este límite por host. Ofrezca plantillas de panel de control que muestren el número de solicitudes exitosas por segundo. Imparta formación utilizando dos gráficos: uno que muestre un alto rendimiento aparente con alta concurrencia y otro que destaque la pérdida de páginas útiles.
Lista de verificación ampliada
- Límite en el número de iniciaciones, no solo en las solicitudes en curso.
- Límite por host dentro del conjunto global.
- Mida las solicitudes aceptadas y los picos por host en cada ejecución.
Mientras esas prácticas no se arraiguen, los equipos seguirán “arreglando la concurrencia” para que aparezcan modos de fallo menos evidentes, los cuales siguen devolviendo el código HTTP 429 y siguen agotando los presupuestos de rastreo.