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
| Tipo | Descripción | Ejemplo |
|---|---|---|
| Connection timeout | Tiempo máximo para establecer la conexión TCP | 2-5 segundos |
| Read/Response timeout | Tiempo máximo para recibir la respuesta completa | 5-30 segundos |
| Request timeout | Tiempo total de la operación (conexión + lectura) | 10-60 segundos |
| Idle timeout | Tiempo máximo de inactividad en una conexión abierta | 30-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.
| Criterio | Timeout sugerido | Razonamiento |
|---|---|---|
| API interna rápida | 1-3 segundos | Latencia esperada < 200ms |
| API interna con DB | 5-10 segundos | Queries complejas pueden tardar |
| API externa (tercero) | 10-30 segundos | Menos control sobre la latencia |
| Operación batch | 60-300 segundos | Procesamiento pesado esperado |
| Health check | 2-5 segundos | Debe 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
| Estrategia | Descripción | Cuándo usar |
|---|---|---|
| Error inmediato | Devolver HTTP 504 Gateway Timeout | Cuando no hay alternativa |
| Fallback | Devolver datos cacheados o por defecto | Cuando hay datos alternativos |
| Retry | Reintentar la operación | Cuando el fallo puede ser transitorio |
| Degradación | Ofrecer funcionalidad reducida | Cuando 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ía | Opciones |
|---|---|
| HTTP Clients | Axios (Node.js), HttpClient (.NET), OkHttp (Java), requests (Python) |
| Frameworks | Spring Boot (configuración de timeouts), ASP.NET Core, Express.js |
| Bases de datos | Connection pool timeouts, query timeouts, statement timeouts |
| Service Mesh | Istio, Linkerd, Envoy (timeouts a nivel de infraestructura) |
| Cloud | AWS 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.