Error y resiliencia

Patrones de resiliencia en acción: circuit breaker, retry, timeout y fallback aplicados a flujos reales entre servicios.

Por qué la resiliencia es parte del flujo

En un sistema distribuido, los fallos no son excepciones: son parte normal de la operación. Servicios que se caen, redes que fallan, bases de datos que se saturan. La pregunta no es si va a fallar, sino cómo se comporta el sistema cuando falla.

Los patrones de resiliencia no son conceptos teóricos aislados. Se aplican en los flujos reales entre servicios para que el sistema siga funcionando de forma degradada en lugar de colapsar completamente.

Los cuatro patrones principales

Timeout

Establece un tiempo máximo de espera para una operación. Si no hay respuesta en ese tiempo, se cancela la petición y se maneja el error.

Retry

Reintenta una operación que falló, asumiendo que el fallo puede ser transitorio (un pico de carga, un error de red momentáneo).

Circuit Breaker

Detecta cuando un servicio está fallando repetidamente y deja de enviarle peticiones por un tiempo, permitiéndole recuperarse.

Fallback

Proporciona una respuesta alternativa cuando la operación principal falla.

Timeout en acción

sequenceDiagram
    participant BFF as BFF
    participant MS as ms-catalog
    participant DB as Database

    BFF->>MS: GET /products (timeout: 3s)
    MS->>DB: SELECT * FROM products
    Note over DB: La DB está lenta (5s)
    
    BFF--xMS: Timeout después de 3s
    BFF->>BFF: Manejar timeout
    BFF->>BFF: Devolver respuesta de caché o error

Cómo configurar timeouts

Cada llamada entre servicios debe tener un timeout explícito. Sin timeout, una petición puede quedar colgada indefinidamente, consumiendo recursos.

Reglas prácticas:

  • El timeout debe ser mayor que el tiempo de respuesta normal del servicio
  • Pero menor que el tiempo que el usuario está dispuesto a esperar
  • Diferentes operaciones pueden tener diferentes timeouts
OperaciónTimeout sugerido
Consulta de catálogo2–3 segundos
Creación de orden5 segundos
Pago externo10–30 segundos
Envío de notificación5 segundos

Qué hacer cuando hay timeout

  1. Registrar el timeout en los logs con contexto (servicio, operación, duración)
  2. Devolver una respuesta degradada si es posible (datos de caché, valores por defecto)
  3. No asumir que la operación falló: puede haberse completado pero la respuesta se perdió

Retry en acción

sequenceDiagram
    participant BFF as BFF
    participant MS as ms-orders

    BFF->>MS: POST /orders
    MS-->>BFF: 503 Service Unavailable

    Note over BFF: Esperar 1s (intento 2)
    BFF->>MS: POST /orders
    MS-->>BFF: 503 Service Unavailable

    Note over BFF: Esperar 2s (intento 3)
    BFF->>MS: POST /orders
    MS-->>BFF: 201 Created

Estrategias de retry

Retry inmediato: reintentar sin espera. Solo útil para errores muy transitorios.

Retry con backoff fijo: esperar un tiempo fijo entre reintentos (1s, 1s, 1s).

Retry con backoff exponencial: duplicar el tiempo de espera en cada intento (1s, 2s, 4s, 8s). Es la estrategia más recomendada porque reduce la presión sobre el servicio que está fallando.

Retry con jitter: añadir un componente aleatorio al backoff para evitar que múltiples clientes reintenten al mismo tiempo (thundering herd).

Cuándo reintentar y cuándo no

Reintentar:

  • Errores 503 (Service Unavailable)
  • Errores 429 (Too Many Requests)
  • Timeouts de red
  • Errores de conexión

No reintentar:

  • Errores 400 (Bad Request) — el request es inválido
  • Errores 401/403 — problema de autenticación/autorización
  • Errores 404 — el recurso no existe
  • Errores 409 (Conflict) — conflicto de estado

Idempotencia y retry

Si se reintenta una operación de escritura (crear orden, procesar pago), el servicio receptor debe ser idempotente. De lo contrario, el retry puede crear duplicados.

Circuit Breaker en acción

stateDiagram-v2
    [*] --> Closed
    Closed --> Open: N fallos consecutivos
    Open --> HalfOpen: Después del timeout
    HalfOpen --> Closed: Request exitoso
    HalfOpen --> Open: Request falla

Los tres estados

Closed (cerrado): el circuito funciona normalmente. Las peticiones pasan al servicio. Si se acumulan fallos, el circuito se abre.

Open (abierto): el circuito está cortado. Las peticiones no llegan al servicio; se devuelve un error o un fallback inmediatamente. Esto protege al servicio que está fallando y evita que el llamador desperdicie recursos esperando.

Half-Open (semi-abierto): después de un tiempo, el circuito permite pasar una petición de prueba. Si tiene éxito, el circuito se cierra. Si falla, vuelve a abrirse.

Ejemplo concreto

El BFF llama al microservicio de catálogo. Si 5 de las últimas 10 peticiones fallaron:

  1. El circuit breaker se abre
  2. Durante 30 segundos, todas las peticiones al catálogo reciben un fallback (datos de caché)
  3. Después de 30 segundos, se permite una petición de prueba
  4. Si funciona, el circuito se cierra y el tráfico normal se reanuda

Configuración típica

ParámetroValor típico
Umbral de fallos50% en ventana de 10 requests
Tiempo en estado Open30 segundos
Requests de prueba en Half-Open1

Fallback en acción

Cuando una operación falla (por timeout, circuit breaker abierto o error), el fallback proporciona una alternativa:

ServicioFallback
CatálogoDevolver datos de caché (aunque estén obsoletos)
RecomendacionesDevolver productos populares genéricos
Perfil de usuarioDevolver datos básicos del token JWT
NotificacionesEncolar para envío posterior
AnalyticsDescartar el evento (no es crítico)

Niveles de degradación

No todos los fallos requieren la misma respuesta. Se pueden definir niveles:

  1. Degradación mínima: datos ligeramente obsoletos (caché de 5 minutos)
  2. Degradación moderada: funcionalidad reducida (catálogo sin filtros avanzados)
  3. Degradación severa: funcionalidad mínima (solo lectura, sin operaciones de escritura)
  4. Indisponibilidad: página de mantenimiento

Combinando patrones

En la práctica, estos patrones se combinan:

BFF → [Timeout: 3s] → [Retry: 3 intentos con backoff] → [Circuit Breaker] → ms-catalog
                                                                    ↓ (si abierto)
                                                              [Fallback: caché]
  1. El BFF hace una petición con timeout de 3 segundos
  2. Si hay timeout, reintenta hasta 3 veces con backoff exponencial
  3. Si los reintentos fallan, el circuit breaker se abre
  4. Con el circuito abierto, las siguientes peticiones reciben el fallback directamente

Observabilidad de la resiliencia

Cada patrón debe generar métricas y logs:

  • Timeouts: cuántos, en qué servicio, duración
  • Retries: cuántos reintentos, tasa de éxito en cada intento
  • Circuit Breaker: transiciones de estado, tiempo en cada estado
  • Fallbacks: cuántas veces se activó, qué tipo de fallback

Estas métricas permiten detectar problemas antes de que se conviertan en incidentes graves.

Resumen

La resiliencia no es un componente que se añade al final. Es una propiedad del sistema que se diseña en cada flujo. Timeout evita esperas infinitas, retry maneja fallos transitorios, circuit breaker protege servicios saturados y fallback mantiene la funcionalidad degradada. Combinados, permiten que el sistema siga funcionando incluso cuando partes de él fallan.