Timeout Pattern

Cómo proteger tus servicios de esperas indefinidas estableciendo límites de tiempo en las llamadas a dependencias externas.

Qué problema resuelve

Cuando un servicio llama a otro servicio o recurso externo, la llamada puede quedarse esperando indefinidamente si el receptor está lento o no responde. Sin un timeout configurado, el servicio que llama mantiene la conexión abierta, consume un thread del pool y eventualmente agota todos sus recursos.

Sin Timeout:
  Servicio A ──► Servicio B (lento, no responde)
                 ⏳ Esperando... 10s
                 ⏳ Esperando... 30s
                 ⏳ Esperando... 60s
                 ⏳ Esperando... 120s
                 
  Thread de Servicio A bloqueado indefinidamente
  Más requests llegan → más threads bloqueados
  Thread pool agotado → Servicio A deja de responder

Este es uno de los problemas más comunes y peligrosos en sistemas distribuidos. Un servicio lento puede ser peor que un servicio caído, porque un servicio caído falla rápido, mientras que uno lento consume recursos durante un tiempo prolongado.

Cómo funciona

El patrón Timeout establece un límite de tiempo máximo para cada llamada a una dependencia. Si la respuesta no llega dentro del tiempo configurado, la llamada se cancela y se devuelve un error o se ejecuta un fallback.

Con Timeout (5 segundos):
  Servicio A ──► Servicio B (lento)
                 ⏳ Esperando... 1s
                 ⏳ Esperando... 3s
                 ⏳ Esperando... 5s
                 ⏠ TIMEOUT → Error o fallback
  
  Thread liberado después de 5 segundos
  Servicio A puede atender otras solicitudes

Tipos de timeout

TipoDescripciónEjemplo
Connection timeoutTiempo máximo para establecer la conexión TCP2-5 segundos
Read/Response timeoutTiempo máximo para recibir la respuesta completa5-30 segundos
Request timeoutTiempo total de la operación (conexión + lectura)10-60 segundos
Idle timeoutTiempo máximo de inactividad en una conexión abierta30-120 segundos

Cómo elegir el valor del timeout

No existe un valor universal. El timeout debe basarse en datos reales:

Paso 1: Medir la latencia normal del servicio
  P50 (mediana): 100ms
  P95: 500ms
  P99: 2s

Paso 2: Establecer timeout con margen
  Timeout = P99 × 2 = 4 segundos
  
  Esto permite que el 99% de las llamadas normales
  completen, pero corta las que tardan anormalmente.
CriterioTimeout sugeridoRazonamiento
API interna rápida1-3 segundosLatencia esperada < 200ms
API interna con DB5-10 segundosQueries complejas pueden tardar
API externa (tercero)10-30 segundosMenos control sobre la latencia
Operación batch60-300 segundosProcesamiento pesado esperado
Health check2-5 segundosDebe responder rápido o está mal

Timeout en cadena

En una cadena de llamadas, los timeouts deben ser decrecientes para evitar que un servicio intermedio espere más que el servicio que lo llamó:

Cliente (timeout: 30s)
  └──► API Gateway (timeout: 25s)
         └──► Servicio A (timeout: 10s)
                └──► Servicio B (timeout: 5s)

Si Servicio B tarda 6s:
  Servicio B: timeout a los 5s → error
  Servicio A: recibe error, puede hacer fallback
  API Gateway: recibe respuesta de A (con fallback)
  Cliente: recibe respuesta dentro de sus 30s

Si los timeouts no son decrecientes, puede ocurrir que el cliente ya haya abandonado la solicitud cuando el servicio finalmente responde, desperdiciando recursos.

Estrategias ante timeout

EstrategiaDescripciónCuándo usar
Error inmediatoDevolver HTTP 504 Gateway TimeoutCuando no hay alternativa
FallbackDevolver datos cacheados o por defectoCuando hay datos alternativos
RetryReintentar la operaciónCuando el fallo puede ser transitorio
DegradaciónOfrecer funcionalidad reducidaCuando parte del dato es suficiente

Ventajas

  • Liberación de recursos: Los threads no se bloquean indefinidamente
  • Fail fast: El usuario recibe una respuesta (aunque sea de error) en tiempo predecible
  • Prevención de cascadas: Un servicio lento no arrastra a los que dependen de él
  • Implementación simple: La mayoría de HTTP clients y frameworks soportan timeouts nativamente
  • Predecibilidad: El tiempo máximo de respuesta del sistema es conocido y acotado
  • Base para otros patrones: El timeout alimenta las métricas del Circuit Breaker y los reintentos del Retry

Trade-offs / Desventajas

  • Falsos positivos: Un timeout demasiado agresivo puede cortar operaciones legítimas que tardan más de lo normal
  • Operaciones huérfanas: El servicio downstream puede completar la operación después del timeout, generando inconsistencias
  • Configuración delicada: Valores incorrectos causan más problemas que soluciones
  • No resuelve la causa raíz: El timeout protege al llamador, pero el servicio lento sigue necesitando arreglo
  • Complejidad en cadenas: Coordinar timeouts en cadenas de servicios requiere diseño cuidadoso

Cuándo usar

  • Toda llamada a un servicio externo o dependencia de red (es una práctica obligatoria)
  • Consultas a bases de datos que pueden bloquearse por locks o queries lentas
  • Llamadas a APIs de terceros con latencia variable
  • Cualquier operación de I/O que pueda bloquearse indefinidamente
  • Como complemento del Circuit Breaker (el timeout define cuándo una llamada se considera fallida)

Cuándo evitar

  • Operaciones locales en memoria donde el timeout no tiene sentido
  • Procesos batch de larga duración donde el timeout debe ser muy alto o gestionarse de otra forma (progress tracking)
  • Streams de datos continuos donde la conexión debe permanecer abierta (usar heartbeats en su lugar)

Tecnologías e implementaciones comunes

CategoríaOpciones
HTTP ClientsAxios (Node.js), HttpClient (.NET), OkHttp (Java), requests (Python)
FrameworksSpring Boot (configuración de timeouts), ASP.NET Core, Express.js
Bases de datosConnection pool timeouts, query timeouts, statement timeouts
Service MeshIstio, Linkerd, Envoy (timeouts a nivel de infraestructura)
CloudAWS ALB/NLB timeouts, Azure Application Gateway, Cloud Load Balancing

Relación con otros patrones

Solicitud


┌──────────┐
│ Timeout  │ ← Define cuánto esperar
└────┬─────┘

┌──────────┐
│  Retry   │ ← Reintenta si hay timeout
└────┬─────┘

┌──────────┐
│ Circuit  │ ← Se abre si hay muchos timeouts
│ Breaker  │
└──────────┘
  • Retry: Cuando ocurre un timeout, el Retry puede reintentar la operación
  • Circuit Breaker: Los timeouts cuentan como fallos; muchos timeouts abren el circuito
  • Bulkhead: Aísla los recursos para que los timeouts de un servicio no afecten a otros
  • Fallback: Estrategia de respuesta cuando ocurre un timeout

Próximos pasos

El Timeout es la base de toda estrategia de resiliencia. Para manejar reintentos después de un timeout, explora el patrón Retry. Para entender cómo los timeouts alimentan al Circuit Breaker, revisa el artículo de Circuit Breaker.