Recurso técnico

Cómo consumir servicios externos sin que una caída ajena detenga tu operación

Tu sistema depende de servicios que no controlas: pasarelas, facturación, mensajería, mapas. Cuando uno de ellos falla, la diferencia entre un inconveniente y una caída total está en cómo diseñaste la integración.

Un escenario común

Una tienda online consulta a un servicio externo para calcular el costo de envío durante la compra. Un día, ese servicio no se cae: simplemente empieza a tardar 60 segundos en responder. Cada cliente que llega al checkout ocupa una conexión del servidor esperando. En pocos minutos, todas las conexiones están ocupadas y la tienda deja de responder, incluso para clientes que solo querían ver el catálogo.

El servicio de envíos tuvo un problema; la tienda sufrió una caída completa. Esto se conoce como falla en cascada, y casi siempre se origina en llamadas externas sin límites.

1. Tiempos de espera: la primera defensa

Muchas librerías de HTTP esperan indefinidamente o durante minutos si no se configura un límite. Toda llamada externa debe tener tiempos de espera explícitos:

  • Tiempo de conexión: cuánto esperar para establecer la conexión. Suele ser corto, porque si el servidor no acepta la conexión rápido, es poco probable que lo haga después.
  • Tiempo de respuesta: cuánto esperar la respuesta una vez conectados. Se define según el comportamiento normal del servicio y lo que el usuario puede tolerar.
  • Plazo total de la operación: si hay reintentos, la suma de todos los intentos también necesita un límite.

Una forma práctica de elegir el valor es observar cuánto tarda normalmente el servicio en los casos lentos (por ejemplo, el percentil 99) y dejar un margen razonable, sin superar lo que la experiencia del usuario permite.

2. Reintentos: útiles, pero peligrosos

Muchas fallas son transitorias y un segundo intento funciona. Pero los reintentos mal diseñados multiplican la carga sobre un servicio que ya tiene problemas y pueden empeorar la situación.

Qué hacer según el tipo de error
Situación¿Reintentar?Comentario
Tiempo de espera agotadoSolo si la operación es idempotenteNo se sabe si la operación se ejecutó.
Error de conexiónSí, con esperaProbablemente la solicitud no llegó.
Error 5xx del servidorSí, con espera y límitePuede ser transitorio.
429 (demasiadas solicitudes)Sí, respetando el tiempo indicadoSi el servicio indica cuánto esperar, hay que respetarlo.
Error 4xx de validaciónNoReintentar la misma solicitud dará el mismo error.
401 / 403NoProblema de credenciales o permisos.

Backoff exponencial con jitter

Entre intentos hay que esperar, y cada espera debe ser mayor que la anterior (backoff exponencial). Además, conviene agregar un componente aleatorio (jitter) para que miles de clientes no reintenten exactamente al mismo tiempo.

llamarConReintentos(operacion, maxIntentos = 3, base = 200ms, tope = 5s):
    para intento en 1..maxIntentos:
        resultado = operacion(timeout = 2s)
        si resultado.exitoso o no esReintentable(resultado):
            devolver resultado
        si intento == maxIntentos:
            devolver resultado
        espera = aleatorio(0, min(tope, base * 2^intento))   // "full jitter"
        dormir(espera)
Los reintentos se multiplican

Si una solicitud atraviesa tres capas y cada una reintenta tres veces, un solo error en el fondo puede generar hasta 27 llamadas. Reintenta en una sola capa, preferiblemente la más cercana al servicio que falla.

Reintentar solo lo que es seguro

Una consulta se puede repetir sin consecuencias. Crear un pedido, cobrar o enviar un mensaje, no. Para reintentar operaciones que modifican datos, el servicio debe admitir una clave de idempotencia, o tu sistema debe verificar el resultado antes de repetir. Lo explicamos aplicado a pagos en webhooks de pago e idempotencia.

3. Circuit breaker: dejar de insistir

Si un servicio está claramente caído, seguir llamándolo solo consume recursos y retrasa la respuesta al usuario. Un circuit breaker (interruptor) observa los resultados y, cuando los errores superan un umbral, deja de llamar durante un tiempo y falla de inmediato.

Estados de un circuit breaker
EstadoComportamientoTransición
CerradoLas llamadas pasan normalmente y se cuentan los errores.Si los errores superan el umbral, pasa a abierto.
AbiertoLas llamadas fallan de inmediato sin contactar al servicio.Tras un tiempo de espera, pasa a semiabierto.
SemiabiertoSe permiten unas pocas llamadas de prueba.Si funcionan, vuelve a cerrado; si fallan, vuelve a abierto.
Estado Cerrado (Normal)Tráfico pasa → Contando fallos
Estado Abierto (Corte activo)Falla rápido inmediato (0 llamadas a la API externa)
Estado Semiabierto (Sondeo)Pocas llamadas de prueba permitidas
Máquina de estados del Circuit Breaker: protege tus servidores de colapsar intentando hablar con un servicio no disponible.

Fallar rápido parece contradictorio, pero protege al resto del sistema y permite mostrar al usuario un mensaje claro en lugar de una pantalla que carga indefinidamente.

4. Aislamiento: que un problema no consuma todo

Si todas las llamadas externas comparten el mismo grupo de conexiones o hilos, un único servicio lento puede agotarlo. El patrón de compartimentos asigna recursos separados a cada dependencia: si el servicio de envíos se vuelve lento, solo se agotan sus conexiones, y el catálogo y los pagos siguen funcionando.

5. Degradación controlada

La pregunta clave de diseño es: ¿qué debe hacer el sistema si este servicio no está disponible? Algunas respuestas habituales:

  • Usar el último valor conocido desde una caché, cuando un dato ligeramente desactualizado es aceptable (tipos de cambio, catálogos).
  • Diferir la operación: guardar la tarea en una cola y completarla cuando el servicio se recupere (envío de correos, registro en un ERP).
  • Desactivar una función secundaria y mantener el resto: por ejemplo, ocultar recomendaciones si su servicio falla.
  • Informar al usuario cuando no hay alternativa, con un mensaje claro y sin perder lo que ya ingresó.
Lo que nunca debe hacerse

Inventar un resultado favorable cuando el servicio no responde. Un pago, una validación de identidad o un control de stock nunca deben asumirse exitosos por defecto.

6. Pasar a asíncrono cuando sea posible

Muchas llamadas externas no necesitan completarse mientras el usuario espera. Registrar una venta en el ERP o enviar una notificación pueden hacerse en segundo plano. Una técnica útil es guardar en la misma transacción de base de datos el cambio de negocio y un registro de «mensaje pendiente» (patrón outbox), que luego un proceso envía con reintentos. Así no se pierde el mensaje si el servicio externo está caído en ese momento.

7. Medir cada dependencia

Ninguno de estos mecanismos sirve si no sabes cuándo se activan. Para cada servicio externo conviene medir cantidad de llamadas, porcentaje de errores, tiempos de respuesta, reintentos y aperturas del circuit breaker. Profundizamos en esto en observabilidad en integraciones.

Errores frecuentes

  • Usar los tiempos de espera por defecto de la librería sin revisarlos.
  • Reintentar errores de validación o de autenticación.
  • Reintentar sin espera o con esperas fijas idénticas para todos los clientes.
  • Reintentar operaciones de cobro sin idempotencia.
  • Configurar reintentos en varias capas a la vez.
  • No definir qué hace el sistema cuando una dependencia no está disponible.

Lista de verificación

  • Cada llamada externa tiene tiempo de conexión, de respuesta y plazo total.
  • Los reintentos son limitados, con backoff y jitter, y solo para errores reintentables.
  • Las operaciones que modifican datos se reintentan solo con idempotencia.
  • Las dependencias críticas tienen circuit breaker y recursos aislados.
  • Está definido el comportamiento degradado de cada dependencia.
  • Cada dependencia tiene métricas y alertas propias.

Preguntas frecuentes

¿Cuántos reintentos son adecuados?

Para llamadas que ocurren mientras el usuario espera, pocos (por ejemplo, dos o tres) y con un plazo total corto. Para procesos en segundo plano se puede reintentar más veces y durante más tiempo, siempre con límite y con un destino para los mensajes que agotan los intentos.

¿Necesito una librería especial para el circuit breaker?

No necesariamente, pero la mayoría de lenguajes tienen librerías probadas para reintentos y circuit breakers. Suele ser preferible usarlas antes que implementar la lógica desde cero.

¿El proveedor no debería encargarse de su disponibilidad?

Sí, pero ningún servicio está disponible el 100 % del tiempo. La resiliencia de tu sistema frente a sus fallas es responsabilidad de tu diseño.

¿Quieres revisar tu caso con nosotros?

Si este tema afecta a un sistema que operas o estás construyendo, escríbenos con el contexto y lo conversamos.