Cómo construir un cliente HTTP robusto con rotación de IP: guía paso a paso para manejar 429, backoff y timeouts
Contenido del artículo
- Introducción: por qué 429 no es un error, sino una señal
- Preparación preliminar
- Conceptos básicos en lenguaje sencillo
- Paso 1: configurar los timeouts correctamente
- Paso 2: construir reintentos con backoff exponencial y jitter
- Paso 3: limitar la concurrencia
- Paso 4: reaccionar específicamente al código 429
- Paso 5: agregar circuit breaker y degradación controlada
- Verificación del resultado: qué métricas medir
- Errores típicos y sus soluciones
- Fragmentos de código listos
- Opciones adicionales y optimización
- Faq: preguntas frecuentes
- Conclusión
Imagina: escribiste un cliente que hace solicitudes a un sitio web y todo funciona. Pero de repente empiezan a llover errores, los workers se cuelgan y el servidor responde con el misterioso código 429. ¿Te suena familiar? Entonces esta guía es para ti. Analizaremos cómo construir un cliente HTTP que no entra en pánico ante el primer problema, sino que se comporta de manera cortés y robusta.
Introducción: por qué 429 no es un error, sino una señal
Muchos desarrolladores ven el código 429 y piensan: se rompió. En realidad, el servidor te está diciendo algo muy concreto: estás enviando demasiadas solicitudes, reduce la velocidad. No es un rechazo ni un bloqueo permanente. Es una petición para que bajes el ritmo. Y si la escuchas correctamente, tu cliente se volverá confiable.
Qué obtendrá el lector al final
Al final de esta guía tendrás un cliente HTTP funcional y listo para usar que sabe hacer varias cosas importantes. Maneja correctamente el código 429 y respeta el encabezado Retry-After. Utiliza backoff exponencial con jitter para no desatar tormentas de reintentos. Limita la concurrencia para no saturar el servidor de destino. Y no se queda colgado gracias a timeouts bien configurados.
Tendrás fragmentos de código listos en tres lenguajes: Python (con la biblioteca httpx y con urllib3 Retry), Node.js y Go. Cada fragmento lo puedes insertar en tu proyecto y adaptarlo a tu tarea.
Para quién es esta guía
La guía está pensada para desarrolladores principiantes que ya saben hacer solicitudes HTTP simples, pero que aún no se han enfrentado a carga de producción. Sin embargo, también incluye bloques para avanzados: circuit breaker, métricas, degradación controlada. Si escribes un scraper, una integración con una API externa o un servicio que consulta recursos externos, este material te ahorrará muchas noches en vela.
Qué necesitas saber de antemano
Basta con que entiendas qué es una solicitud HTTP y una respuesta HTTP. Es recomendable que sepas qué es un código de estado (por ejemplo, 200 es éxito, 404 es página no encontrada). También es útil tener un conocimiento básico de al menos uno de los lenguajes: Python, JavaScript o Go. No se requieren conocimientos profundos de redes; todo se explicará en términos sencillos.
Cuánto tiempo tomará
Leer y comprender la teoría: unos 40 minutos. Armar un cliente básico paso a paso: aproximadamente una hora. La implementación completa con todas las protecciones, métricas y pruebas: alrededor de tres horas. No te apresures: es mejor entender lentamente cada paso que copiar rápidamente un código que no comprendes.
Consejo: Lee la guía con el editor de código abierto. Prueba los ejemplos inmediatamente en un endpoint de prueba, no en un servicio de producción real.
Preparación preliminar
Antes de escribir código, preparemos el entorno de trabajo. Esto te tomará un poco de tiempo, pero te ahorrará confusiones más adelante.
Herramientas necesarias
- Uno de los lenguajes y su entorno: Python 3.11 o superior, o Node.js 20 o superior, o Go 1.22 o superior.
- Un editor de código; cualquier editor sirve, por ejemplo VS Code.
- Una terminal para ejecutar scripts.
- Acceso a Internet a un servicio HTTP de prueba que pueda devolver diferentes códigos de respuesta.
Qué instalar para Python
- Verifica la versión de Python con el comando en la terminal: escribe python --version y presiona Enter.
- Crea un entorno virtual con python -m venv venv.
- Actívalo: en Windows con venv\Scripts\activate; en macOS y Linux con source venv/bin/activate.
- Instala las bibliotecas con pip install httpx urllib3 requests.
Qué instalar para Node.js
- Verifica la versión con node --version.
- Crea una carpeta para el proyecto y entra en ella.
- Inicializa el proyecto con npm init -y.
- A partir de Node.js 20, el fetch incorporado está disponible sin instalación; no se necesitan paquetes adicionales para el cliente básico.
Qué instalar para Go
- Verifica la versión con go version.
- Crea una carpeta e inicializa el módulo con go mod init myclient.
- La biblioteca estándar net/http es suficiente; no se requieren paquetes externos.
Copias de seguridad y seguridad
⚠️ Atención: Nunca pruebes un cliente nuevo directamente en un servicio de producción importante. Primero usa un endpoint de prueba o un servidor local simulado que tú controles. De lo contrario, los reintentos agresivos pueden dañar el servicio de otro y provocar tu bloqueo.
Si estás modificando un proyecto existente, haz una copia del archivo o crea una rama separada en tu sistema de control de versiones. Así siempre podrás revertir los cambios.
✅ Verificación: Instalaste el lenguaje elegido, creaste el proyecto y confirmaste que un script de prueba se ejecuta sin errores. Ahora puedes pasar a la teoría.
Conceptos básicos en lenguaje sencillo
Para construir un cliente con confianza, necesitas entender varios términos clave. Los explicaremos sin palabras complicadas.
Qué significan los códigos 403, 407, 429 y 503
Estos cuatro códigos son fáciles de confundir, pero se comportan de manera diferente y se tratan de forma distinta.
- Código 429 Too Many Requests: el servidor dice que has superado el límite de solicitudes. Es temporal. Debes reducir la velocidad e intentarlo de nuevo más tarde.
- Código 403 Forbidden: el acceso está prohibido. A menudo no se trata de velocidad, sino de permisos: clave incorrecta, falta de autorización, restricción por región. Reintentar sin cambios suele ser inútil.
- Código 503 Service Unavailable: el servidor está temporalmente sobrecargado o en mantenimiento. Al igual que el 429, es temporal y un reintento posterior puede ayudar.
- Código 407 Proxy Authentication Required: y aquí hay un detalle importante. Este código no proviene del sitio de destino, sino del servidor proxy. Significa que el proxy requiere autenticación y tú no la has proporcionado correctamente.
⚠️ Atención: El código 407 no se soluciona con rotación de IP ni con backoff. Es un error de configuración de tu cliente, específicamente credenciales de proxy incorrectas. Verifica el nombre de usuario, la contraseña y el formato de la cadena de conexión. Ningún reintento ayudará hasta que corrijas la autenticación.
Diferencia entre 429 y 403
Recuerda una regla simple. 429 es sobre cantidad: estás haciendo solicitudes con demasiada frecuencia. 403 es sobre derecho: no tienes permiso en absoluto. Con 429, un reintento después de una pausa resuelve el problema. Con 403, un reintento sin cambiar las condiciones no lo resolverá; necesitas cambiar la clave, los encabezados o el enfoque.
Encabezados Retry-After y X-RateLimit
Los servidores educados indican cuándo puedes regresar. El encabezado Retry-After indica cuántos segundos esperar antes de reintentar. A veces contiene un número de segundos, a veces una fecha específica. Tu cliente debe respetar este encabezado: si el servidor dice espera 10 segundos, reintentar en 1 segundo solo empeorará la situación.
El grupo de encabezados X-RateLimit informa los límites: cuántas solicitudes te están permitidas, cuántas te quedan y cuándo se reinicia el contador. Por ejemplo, X-RateLimit-Remaining muestra el saldo restante. Si está cerca de cero, debes reducir la velocidad de antemano, sin esperar al 429.
Cómo funcionan los límites: token bucket y ventana deslizante
Los servidores cuentan tus solicitudes de dos maneras populares.
Token bucket (cubo de fichas) funciona así. Imagina un cubo al que caen fichas constantemente a una velocidad fija. Cada solicitud toma una ficha. Si no hay fichas, la solicitud se rechaza con el código 429. Este esquema permite ráfagas cortas: si has estado en silencio durante un tiempo, el cubo se llena y puedes hacer un lote de solicitudes de una sola vez.
Ventana deslizante (sliding window) cuenta la cantidad de solicitudes en el último período, por ejemplo, el último minuto. En cuanto superas el límite en esa ventana, obtienes un 429. Aquí las ráfagas se castigan con más severidad.
Por qué la concurrencia también es un límite
Muchos olvidan: el límite no solo es sobre la frecuencia, sino también sobre el número de conexiones simultáneas. Si abres 500 solicitudes paralelas, el servidor puede percibirlo como un ataque, incluso si el total por minuto no es alto. La concurrencia debe limitarse tan estrictamente como la frecuencia.
Consejo: Antes de construir tu cliente, averigua los límites del servicio de destino consultando su documentación. Conocer las cifras exactas te ahorrará suposiciones y 429 innecesarios.
✅ Verificación: Entiendes la diferencia entre 429, 403, 407 y 503, conoces el Retry-After y tienes una idea de cómo el servidor cuenta tus solicitudes. Excelente, pasemos a la práctica.
Paso 1: configurar los timeouts correctamente
Objetivo de la etapa: lograr que ninguna solicitud pueda quedar colgada para siempre y bloquear un worker.
Por qué un cliente sin timeout es peligroso
Un cliente sin timeout es una bomba de tiempo. Si el servidor deja de responder, tu solicitud esperará indefinidamente. Una solicitud colgada ocupa un worker. Diez solicitudes colgadas, y todo tu grupo de workers está ocupado, nuevas tareas no se procesan, el servicio prácticamente se detiene. El timeout es tu primera línea de defensa.
Cuatro tipos de timeouts
Un cliente correcto distingue varios timeouts, no solo uno global.
- Timeout de conexión (connect): cuánto esperar para establecer la conexión con el servidor. Si el servidor no está disponible, lo sabrás rápidamente.
- Timeout de lectura (read): cuánto esperar datos después de enviar la solicitud. Protege contra un servidor que aceptó la solicitud pero no responde.
- Timeout de escritura (write): cuánto esperar para enviar el cuerpo de la solicitud. Relevante para subidas grandes.
- Timeout total (total): tiempo máximo para toda la solicitud, incluyendo todas las fases.
Qué valores tomar como punto de partida
No hay cifras universales, pero hay valores iniciales razonables. Para connect, usa 3-5 segundos: la conexión generalmente se establece rápido. Para read, usa 10-30 segundos dependiendo de qué tan rápido el servicio entrega datos. El timeout total debe cubrir la solicitud más larga razonable, por ejemplo 30-60 segundos.
⚠️ Atención: Nunca establezcas timeouts enormes como 300 segundos para todas las solicitudes. Esto oculta problemas y crea una cola de operaciones colgadas. Es mejor fallar rápido y reintentar que esperar mucho en vano.
Configuración paso a paso
- Determina cuánto dura normalmente una solicitud exitosa a tu servicio. Mídela varias veces.
- Establece el timeout de lectura aproximadamente al doble del tiempo de respuesta promedio.
- Establece el timeout de conexión en 3-5 segundos.
- Establece el timeout total como la suma de las fases razonables más un pequeño margen.
- Ejecuta una solicitud de prueba y verifica que finalice, no que se quede colgada.
Consejo: Si tu servicio a veces entrega archivos grandes y otras veces respuestas pequeñas, crea diferentes perfiles de timeout para distintos tipos de solicitudes. Una talla no sirve para todos.
Resultado esperado: al contactar una dirección notoriamente lenta o inaccesible, tu cliente finaliza el intento en el tiempo establecido con un error de timeout claro, no se queda colgado para siempre.
✅ Verificación: Dirige una solicitud a una dirección que no responda (por ejemplo, un puerto inexistente). El cliente debe devolver un error de timeout aproximadamente en el tiempo configurado. Si se cuelga más tiempo, el timeout está mal configurado.
Paso 2: construir reintentos con backoff exponencial y jitter
Objetivo de la etapa: enseñar al cliente a reintentar solicitudes de manera inteligente, sin dañarse a sí mismo ni al servidor.
Qué se puede reintentar: idempotencia
Antes de reintentar una solicitud, pregúntate: ¿es seguro ejecutarla dos veces? Esta propiedad se llama idempotencia. Una solicitud es idempotente si su ejecución repetida da el mismo resultado y no causa efectos secundarios.
- GET, HEAD, PUT, DELETE generalmente son idempotentes. Reintentarlos es seguro.
- POST generalmente no es idempotente. Un reintento puede crear un duplicado del pedido, un segundo cobro, un registro duplicado.
⚠️ Atención: Nunca reintentes solicitudes POST a ciegas. El reenvío de una solicitud no idempotente puede provocar un doble cobro o duplicación de datos. Si necesitas reintentar un POST, usa una clave de idempotencia (Idempotency-Key) que el servidor entienda y no ejecute la operación dos veces.
Cuántas veces reintentar
Los reintentos infinitos son malos. Un límite razonable es de 3 a 5 intentos. Si después de cinco intentos la solicitud no pasa, el problema es más grave que una falla temporal y debe registrarse y manejarse por separado.
Qué es el backoff exponencial
Backoff es la pausa entre reintentos. Exponencial significa que la pausa crece de forma multiplicativa con cada intento. Por ejemplo: primera pausa 1 segundo, segunda 2 segundos, tercera 4, cuarta 8. La fórmula es simple: retraso base multiplicado por dos elevado al número de intento.
¿Por qué así? Si el servidor está sobrecargado, los reintentos cortos y frecuentes solo lo empeoran. Las pausas crecientes le dan tiempo al servidor para recuperarse.
Por qué sin jitter se produce una tormenta de reintentos
Imagina que mil clientes reciben un 429 al mismo tiempo. Todos esperan exactamente 1 segundo, luego exactamente 2, luego exactamente 4. Y todos reintentan en el mismo momento. Se produce una tormenta síncrona: el servidor recibe mil solicitudes a la vez y vuelve a dar 429. El problema no se resuelve, se cicla.
La solución es el jitter, es decir, una adición aleatoria a la pausa. En lugar de exactamente 2 segundos, un cliente espera 1.7, otro 2.3, otro 1.9. Los reintentos se dispersan en el tiempo y el servidor se descarga gradualmente.
Cómo respetar Retry-After
Si el servidor envía el encabezado Retry-After, este tiene prioridad sobre tu fórmula de backoff. La regla es simple: toma el máximo entre tu pausa calculada y el valor de Retry-After. Nunca reintentes antes de lo que pidió el servidor. Es una grave falta de cortesía que provocará nuevos 429.
Implementación paso a paso de la lógica de reintentos
- Verifica si la solicitud es idempotente. Si no lo es y no hay clave de idempotencia, no reintentes.
- Verifica el código de respuesta. Reintenta solo en 429, 503 y errores de red (timeout, desconexión).
- Incrementa el contador de intentos. Si supera el límite, detente y devuelve un error.
- Calcula la pausa base según la fórmula de crecimiento exponencial.
- Añade un jitter aleatorio a la pausa.
- Si llegó un Retry-After, toma el mayor de los dos valores.
- Espera el tiempo calculado y reintenta la solicitud.
Consejo: Limita la pausa máxima superiormente, por ejemplo a 30 o 60 segundos. De lo contrario, en el quinto intento el backoff puede crecer a valores incómodamente grandes y el usuario esperará demasiado.
Resultado esperado: ante un código 429, el cliente hace una pausa, reintenta la solicitud y las pausas entre reintentos crecen y varían ligeramente de una vez a otra.
✅ Verificación: Configura un servidor de prueba que devuelva 429 varias veces seguidas y luego 200. Tu cliente debe obtener con éxito la respuesta final, y en los registros verás pausas crecientes con dispersión.
Paso 3: limitar la concurrencia
Objetivo de la etapa: evitar que el cliente sature al servidor con una avalancha de solicitudes simultáneas.
Qué es un semáforo en palabras sencillas
Un semáforo es un contador de permisos. Imagina un guardarropa con un número limitado de ganchos. Mientras haya un gancho libre, cuelgas tu abrigo. Si todos están ocupados, esperas hasta que alguien libere uno. El semáforo deja pasar un número limitado de tareas al mismo tiempo y mantiene al resto en cola.
Cola de tareas
Todas las solicitudes que deben ejecutarse se colocan en una cola. Los workers toman tareas de la cola a medida que se liberan. Esto te da control total sobre el ritmo: cuantos workers, tantas solicitudes paralelas como máximo.
Límite por host
Un detalle importante: el límite debe mantenerse para cada host por separado. Si trabajas con varios servicios, un límite global para todo no es óptimo. Un host lento no debería bloquear las solicitudes a otro. Establece un límite individual por dominio.
Pool de conexiones y keep-alive
Cada nueva conexión TCP tiene un costo: negociación, establecimiento de canal seguro. Keep-alive permite reutilizar la conexión para varias solicitudes seguidas. Esto ahorra tiempo y recursos del servidor. El pool de conexiones mantiene conexiones abiertas listas. Configura el tamaño del pool de forma coherente con tu límite de concurrencia.
⚠️ Atención: No confundas el tamaño del pool de conexiones con el límite de concurrencia. El pool puede ser un poco más grande que el límite para tener margen, pero si el pool es enorme y el límite pequeño, estás manteniendo conexiones abiertas innecesariamente. Mantenlos en un equilibrio razonable.
Configuración paso a paso del límite
- Determina un número seguro de solicitudes simultáneas por host. Empieza con un valor pequeño, por ejemplo 5-10.
- Crea un semáforo con ese número de permisos.
- Antes de cada solicitud, solicita un permiso al semáforo.
- Después de completar la solicitud, sea exitosa o no, libera el permiso obligatoriamente.
- Configura el pool de conexiones con keep-alive en el mismo orden de valores.
- Aumenta gradualmente el límite observando la proporción de 429. En cuanto crezca, detente.
Consejo: Libera el permiso del semáforo en un bloque finally o su equivalente. De lo contrario, si ocurre un error, el permiso no se devuelve, el contador se fuga y con el tiempo el cliente se detiene por completo.
Resultado esperado: sin importar cuántas tareas pongas en la cola, el número de solicitudes simultáneas al host no supera el límite establecido.
✅ Verificación: Coloca 100 tareas en cola con un límite de 5. En los registros o en el monitor de conexiones, debes ver no más de 5 solicitudes activas en cualquier momento.
Paso 4: reaccionar específicamente al código 429
Objetivo de la etapa: establecer una reacción correcta a la señal de sobrecarga y entender cuándo es apropiado cambiar de IP.
Tres acciones ante el 429
Cuando llega un 429, tienes tres herramientas y deben aplicarse en conjunto.
- Reducir la velocidad: bajar el ritmo general de solicitudes, no solo hacer una pausa para una solicitud específica. Esto es clave: el 429 indica que todo tu ritmo es demasiado alto.
- Cambiar de IP: si trabajas con rotación de direcciones IP, cambiar de dirección puede ayudar cuando el límite está vinculado a una IP específica. Pero no es una panacea.
- Posponer la tarea: devolver la solicitud a la cola con un retraso para ejecutarla más tarde, cuando los límites se hayan restablecido.
⚠️ Atención: Cambiar de IP no anula la cortesía. Si el límite no está en la IP sino en la cuenta o la clave, ninguna rotación ayudará: seguirás encontrando el 429. No conviertas la rotación en una forma de evadir las reglas: respeta los límites del servicio y el Retry-After en cualquier caso.
Matriz de acciones según códigos de respuesta
Ten a mano una tabla simple de decisiones. Esto es lo que debes hacer ante cada código.
- 200-299 Éxito: procesar la respuesta, liberar recursos, tomar la siguiente tarea.
- 429 Too Many Requests: reducir el ritmo, respetar Retry-After, reintentar con backoff, si es necesario posponer la tarea o cambiar de IP.
- 503 Service Unavailable: reintentar con backoff, respetar Retry-After, pero no cambiar de IP: el problema está del lado del servidor.
- 403 Forbidden: no reintentar a ciegas. Verificar autorización, encabezados, permisos. Registrar para analizar.
- 407 Proxy Authentication Required: corregir las credenciales del proxy. No reintentar ni rotar hasta corregir la configuración.
- 400, 404, 422 errores de cliente: no reintentar. Es un error en tu solicitud, reintentar no cambiará nada.
- 500, 502, 504 errores de servidor: reintentar con cuidado con backoff un número pequeño de veces.
- Errores de red y timeouts: reintentar con backoff si la solicitud es idempotente.
Implementación paso a paso de la reacción al 429
- Al recibir un 429, detén inmediatamente el aumento de ritmo.
- Lee el encabezado Retry-After, si existe.
- Calcula la pausa como el máximo entre el backoff y Retry-After.
- Si es probable que el límite esté vinculado a la IP y tienes rotación, cambia de dirección antes de reintentar.
- Si se agotan los intentos, devuelve la tarea a la cola con un retraso grande.
- Reduce el límite general de concurrencia temporalmente para darle un respiro al servidor.
Consejo: Lleva un contador separado de la proporción de 429 en el último minuto. Si aumenta, reduce automáticamente el ritmo incluso antes de que la situación se vuelva crítica. Esto se llama limitación adaptativa.
Resultado esperado: ante una serie de 429, el cliente reduce suavemente el ritmo, respeta Retry-After y finalmente completa las solicitudes con éxito, sin generar una tormenta.
✅ Verificación: Simula un pico de 429 en un servidor de prueba. El cliente debe reducir su actividad, no aumentar los reintentos. La proporción de respuestas exitosas después de la pausa debe recuperarse.
Paso 5: agregar circuit breaker y degradación controlada
Objetivo de la etapa: darle al cliente un fusible que lo proteja a él y al servidor durante problemas prolongados.
Qué es un circuit breaker
Un circuit breaker es un fusible, como en un cuadro eléctrico. Si los errores llegan en cadena, abre el circuito: deja de pasar solicitudes al servicio problemático durante un tiempo. Esto protege al servidor de que lo rematen y a tu cliente de desperdiciar recursos inútilmente.
Tres estados del fusible
- Closed (cerrado): funcionamiento normal, las solicitudes pasan. El cliente cuenta los errores.
- Open (abierto): demasiados errores, las solicitudes se bloquean de inmediato sin ir al servidor. Se mantiene durante un tiempo determinado.
- Half-open (semiabierto): modo de prueba. El cliente deja pasar unas pocas solicitudes para verificar si el servicio se ha recuperado. Si es así, vuelve a closed; si no, vuelve a open.
Degradación controlada en lugar de detención total
Cuando un servicio no está disponible, no es necesario que todo se caiga. La degradación controlada es la capacidad de funcionar peor, pero seguir funcionando. Ejemplos: entregar datos de caché en lugar de datos frescos, mostrar un resultado reducido, posponer tareas no esenciales, devolver un marcador de posición comprensible en lugar de un error.
Consejo: Siempre piensa en qué mostrar al usuario o al sistema cuando el servicio externo está caído. Un marcador de posición con un mensaje significativo es mejor que un cuelgue o un stack trace.
Configuración paso a paso del circuit breaker
- Define un umbral de errores para abrir el fusible, por ejemplo 50 por ciento de fallos en una ventana de 20 solicitudes.
- Define el tiempo durante el cual el circuito permanece abierto, por ejemplo 30 segundos.
- Cuenta los éxitos y fracasos en una ventana deslizante.
- Al superar el umbral, cambia el fusible al estado open.
- Pasado el tiempo, cámbialo a half-open y deja pasar unas pocas solicitudes de prueba.
- Según el resultado de la prueba, vuelve a closed o nuevamente a open.
⚠️ Atención: No confundas el circuit breaker con los reintentos. Los reintentos repiten una sola solicitud, mientras que el fusible controla todo el flujo hacia el servicio. Juntos son poderosos, pero deben configurarse de forma coordinada para que el fusible no se abra demasiado pronto debido a fallos unitarios normales.
Resultado esperado: ante una indisponibilidad prolongada del servicio, el cliente deja de bombardearlo con solicitudes, devuelve rápidamente un marcador de posición y periódicamente verifica la recuperación.
✅ Verificación: Haz que un servidor de prueba deje de estar disponible. El cliente, después de una serie de fallos, debe dejar de enviar solicitudes (open), y tras la recuperación del servidor, debe volver por sí mismo al funcionamiento normal a través de half-open.
Verificación del resultado: qué métricas medir
La robustez no se puede evaluar a simple vista. Se necesitan números. Estas son las métricas clave que mostrarán si el cliente se ha vuelto más confiable.
Indicadores principales
- Proporción de respuestas exitosas (success rate): porcentaje de solicitudes que finalizan con código 2xx. Cuanto más alto, mejor. Apunta a un valor consistentemente alto incluso bajo carga.
- p95 de latencia: el tiempo en el que se completan el 95 por ciento de las solicitudes. Este indicador es más honesto que el promedio porque muestra cómo se siente la mayoría, no solo las solicitudes afortunadas.
- Proporción de 429: porcentaje de respuestas con código 429. Si es alto, estás enviando de forma demasiado agresiva. El objetivo es minimizarlo.
- Número de reintentos por solicitud: muestra lo difícil que es lograr el éxito. Un aumento indica problemas.
- Número de aperturas del circuit breaker: aperturas frecuentes señalan inestabilidad del servicio o configuraciones demasiado agresivas.
Lista de verificación de preparación
- Timeouts configurados en todas las fases, ninguna solicitud se cuelga para siempre.
- Los reintentos solo funcionan para solicitudes idempotentes y códigos seguros.
- El backoff crece exponencialmente y contiene jitter.
- Retry-After siempre se respeta.
- La concurrencia está limitada con un semáforo por host.
- El pool de conexiones con keep-alive está configurado de forma coherente con el límite.
- La reacción al 429 reduce el ritmo, no aumenta los reintentos.
- Se implementó la matriz de acciones según códigos.
- El circuit breaker protege contra fallos prolongados.
- Las métricas se recopilan y están disponibles para su análisis.
Cómo saber si el cliente se ha vuelto más robusto
Compara las métricas antes y después de las mejoras bajo la misma carga. Un cliente robusto muestra una alta proporción de éxito, baja proporción de 429, p95 estable y ausencia de workers colgados. Incluso cuando el servidor se pone caprichoso, tu servicio sigue funcionando sin caídas en cascada.
✅ Verificación: Realiza una prueba de carga en un endpoint de prueba. Si bajo carga la proporción de éxito se mantiene alta y no hay cuelgues, felicidades, el cliente es robusto.
Errores típicos y sus soluciones
Analicemos los problemas comunes con los que casi todos tropiezan.
Error 1: los reintentos aumentan la carga
Problema: el servidor está sobrecargado y tus reintentos agresivos lo terminan de rematar. Causa: reintentos sin backoff y sin reducir el ritmo. Solución: añade backoff exponencial con jitter, limita el número de intentos, reduce la concurrencia general al aumentar los errores.
Error 2: reintentar solicitudes no idempotentes
Problema: pedidos duplicados, cobros repetidos, registros duplicados. Causa: reintento ciego de solicitudes POST. Solución: reintenta solo métodos idempotentes. Para POST, usa una clave de idempotencia que el servidor reconozca y no ejecute la operación dos veces.
Error 3: tratar el 429 con cambio infinito de IP
Problema: cambias de IP una y otra vez, pero el 429 no desaparece. Causa: el límite no está vinculado a la IP sino a la clave o la cuenta, o simplemente estás enviando demasiadas solicitudes en total. Solución: reduce el ritmo y respeta Retry-After. La rotación de IP es solo una herramienta, no un sustituto de la cortesía.
Error 4: tormenta síncrona de reintentos
Problema: todos los clientes reintentan en los mismos momentos, el servidor vuelve a caer. Causa: backoff sin jitter. Solución: añade un componente aleatorio a cada pausa.
Error 5: workers colgados
Problema: el servicio deja de procesar tareas gradualmente. Causa: falta de timeouts, las solicitudes se cuelgan para siempre. Solución: configura timeouts de conexión, lectura y total en todas las solicitudes.
Error 6: fuga de permisos del semáforo
Problema: con el tiempo, el cliente deja de hacer solicitudes. Causa: el permiso del semáforo no se libera cuando ocurre un error. Solución: libera el permiso en un bloque finally para que siempre ocurra.
Error 7: reacción incorrecta al 407
Problema: el cliente reintenta y rota IP infinitamente, pero sigue recibiendo 407. Causa: el código 407 proviene del proxy y significa error de autenticación del proxy, no un problema del servicio. Solución: verifica y corrige las credenciales del proxy. Los reintentos aquí son inútiles.
Fragmentos de código listos
A continuación, descripciones de enfoques en tres stacks. Adáptalos a tu proyecto.
Python con httpx
Crea un cliente httpx con timeouts explícitos a través del objeto Timeout, donde se especifiquen connect y read por separado. Define los límites del pool con httpx Limits, indicando el máximo de conexiones por host. Envuelve la llamada en un bucle de reintentos: en 429 y 503, lee Retry-After, calcula la pausa como el máximo entre el backoff exponencial con jitter y el valor de Retry-After, luego espera con asyncio sleep. Limita la concurrencia con asyncio Semaphore, liberándolo en un bloque finally. Reintenta solo métodos idempotentes, limita el número de intentos a cinco.
Python con urllib3 Retry
La biblioteca urllib3 ofrece un mecanismo listo. Crea un objeto Retry con parámetros: total define el número de intentos, backoff_factor activa las pausas exponenciales, status_forcelist lista los códigos para reintentar, por ejemplo 429, 500, 502, 503, 504. El parámetro respect_retry_after_header activa el respeto por Retry-After. Pasa este Retry a PoolManager o al adaptador de requests a través de HTTPAdapter. Esta es la forma más rápida de obtener robustez básica sin escribir un bucle manualmente.
Node.js
Usa el fetch incorporado con AbortController para el timeout: crea un controlador, establece un setTimeout para abortar, pasa la signal a fetch. Envuelve la llamada en una función con un bucle de reintentos. Verifica response.status: en 429 y 503, lee el encabezado Retry-After con response.headers.get, calcula la pausa con jitter, espera con una promesa y setTimeout. Para limitar la concurrencia, usa un semáforo simple basado en promesas o una biblioteca limitadora popular. Mantén el número de promesas simultáneas bajo control mediante una cola.
Go
En Go, configura un http.Client con el campo Timeout para el timeout general y configura el Transport con los parámetros MaxIdleConnsPerHost e IdleConnTimeout para el pool y keep-alive. Para el timeout de conexión, usa DialContext con net.Dialer. Implementa un bucle de reintentos: en 429 y 503, lee el encabezado Retry-After, calcula la pausa con time.Duration con crecimiento exponencial y jitter aleatorio, espera con time.Sleep o select con context. Limita la concurrencia con un canal con búfer como semáforo: escribe en el canal antes de la solicitud, lee de él en defer después.
Consejo: En cualquier lenguaje, saca la configuración (timeouts, número de intentos, límite de concurrencia) a la configuración, no la codifiques. Así podrás ajustar el comportamiento para cada servicio sin reescribir el código.
Opciones adicionales y optimización
Cuando el cliente básico funcione, puedes hacerlo aún más inteligente.
Limitación adaptativa del ritmo
En lugar de un límite fijo, hazlo flotante. Lee los encabezados X-RateLimit-Remaining y reduce el ritmo de antemano cuando el saldo sea bajo. Así evitas el 429 incluso antes de que aparezca.
Prioridades de tareas
No todas las solicitudes son iguales. Crea una cola con prioridades: las tareas importantes se ejecutan primero, las no esenciales se posponen primero durante la degradación.
Caching
Para solicitudes GET idempotentes, añade una caché con tiempo de vida corto. Esto reduce la carga en el servidor y tu proporción de 429 sin trucos.
Observabilidad
Conecta registros estructurados y métricas. Registra cada reintento, cada apertura del circuit breaker, cada pausa larga. Así encontrarás rápidamente el cuello de botella al analizar incidentes.
Consejo: Empieza con un cliente simple y añade funciones avanzadas según la necesidad real. La complejidad prematura es tan dañina como su ausencia.
FAQ: preguntas frecuentes
¿Siempre hay que respetar Retry-After, incluso si es grande?
Sí. Si Retry-After es demasiado grande para tu escenario, es mejor posponer la tarea o devolver una respuesta degradada que reintentar antes de tiempo. Ignorar Retry-After casi siempre lleva a nuevos 429.
¿Se pueden reintentar solicitudes POST?
Solo con cuidado. Si la operación no es idempotente, el reintento puede crear un duplicado. Usa una clave de idempotencia para que el servidor mismo te proteja de la doble ejecución.
¿Qué número de solicitudes simultáneas tomar como punto de partida?
Empieza con un valor pequeño, por ejemplo 5-10 por host, y ve aumentando mientras observas la proporción de 429 y el p95. En cuanto el 429 crezca, has encontrado el techo.
¿En qué se diferencia el 429 del 503 en la práctica?
429 se trata de tu ritmo: envías con demasiada frecuencia. 503 se trata del servidor: él mismo está sobrecargado o en mantenimiento. Ante el 429, es útil reducir el ritmo y posiblemente cambiar de IP. Ante el 503, cambiar de IP no tiene sentido, solo reintenta más tarde.
¿Por qué mi cliente a veces recibe un 407?
El código 407 proviene del proxy e indica que la autenticación en el proxy falló. Verifica el nombre de usuario y la contraseña del proxy. La rotación de IP y el backoff aquí no ayudan; es un error de configuración.
¿Cuántos intentos de reintento se consideran normales?
Por lo general, de tres a cinco. Más rara vez tiene sentido: si no funcionó en cinco intentos, el problema es más grave que una falla temporal.
¿Para qué sirve el jitter si el backoff ya crece?
Sin jitter, muchos clientes reintentan en los mismos momentos y crean una tormenta síncrona. La dispersión aleatoria distribuye los reintentos en el tiempo y descarga el servidor suavemente.
¿Cuándo abrir el circuit breaker?
Cuando la proporción de errores en una ventana deslizante supera un umbral definido, por ejemplo la mitad de las solicitudes. Esto protege tanto al servidor como a ti de desperdiciar recursos inútilmente.
¿Ayuda cambiar de IP ante un 429?
A veces, si el límite está vinculado a la IP. Pero si el límite está en la clave o la cuenta, cambiar de IP es inútil. Cambiar de IP no reemplaza la reducción del ritmo ni el respeto por Retry-After.
¿Qué mostrar al usuario cuando el servicio está caído?
Un marcador de posición claro, datos de caché o un resultado reducido. Es mejor que un cuelgue o un error técnico en la pantalla.
Conclusión
Has recorrido un largo camino. Recordemos lo que has construido. Configuraste timeouts en todas las fases para que ninguna solicitud se quede colgada para siempre. Añadiste reintentos inteligentes con backoff exponencial y jitter, que solo reintentan solicitudes seguras y respetan Retry-After. Limitaste la concurrencia con un semáforo y configuraste un pool de conexiones con keep-alive. Estableciste una reacción correcta al 429 y creaste una matriz de acciones según los códigos de respuesta. Finalmente, agregaste un circuit breaker y degradación controlada.
La idea principal de toda la guía es simple. El 429 no es un error, es una conversación. El servidor te pide que bajes el ritmo, y un cliente educado escucha. La robustez no nace de la agresividad, sino de la capacidad de reducir la velocidad en el momento adecuado.
Qué hacer a continuación
Recopila métricas bajo carga real y observa las proporciones de éxito y 429. Ajusta gradualmente los límites para cada servicio. Añade limitación adaptativa del ritmo según los encabezados X-RateLimit. Implementa caché para solicitudes idempotentes.
Hacia dónde avanzar
Estudia por separado el tema del pool de direcciones IP y su salud; es un área grande vecina que deliberadamente no tocamos aquí. Profundiza en la observabilidad: trazados, paneles, alertas. Y por supuesto, lee la documentación de los servicios con los que trabajas: los límites exactos siempre son mejores que las suposiciones.
Lo has hecho muy bien. Ahora tienes un cliente que no entra en pánico, sino que se comporta de manera robusta y educada. Es la base sobre la que se construyen integraciones confiables. Buena suerte en tus proyectos.