Simulador de Fallos

Simula fallos en producción — caídas de servicios, timeouts, errores de red — y practica estrategias de resiliencia.

Objetivo

En este laboratorio vas a simular fallos comunes en sistemas distribuidos y practicar las estrategias de resiliencia que los mitigan. Cada escenario presenta un fallo, te pide que identifiques el impacto y propongas una solución.

En producción, los fallos no avisan. Aquí puedes practicar sin consecuencias.

Escenario 1: Timeout en cascada

El fallo

El servicio A llama al servicio B, que llama al servicio C. El servicio C empieza a responder lento (3 segundos en vez de 200ms). No está caído — solo lento.

[Servicio A] ──timeout 5s──► [Servicio B] ──timeout 5s──► [Servicio C: LENTO 3s]

¿Qué pasa?

  1. C responde en 3s → B espera 3s + su propio procesamiento (500ms) = 3.5s
  2. A espera 3.5s por B → los usuarios ven latencia de 4s+
  3. Los threads de A y B están bloqueados esperando → el pool de conexiones se agota
  4. Nuevos requests a A empiezan a fallar por timeout → cascada de fallos

Ejercicio: Diseña la mitigación

Responde estas preguntas:

  1. ¿Qué timeout pondrías en cada servicio?

    • Pista: los timeouts deben ser decrecientes en la cadena (A > B > C)
  2. ¿Dónde pondrías un circuit breaker?

    • Pista: en el punto donde el fallo se origina (B → C)
  3. ¿Qué respuesta daría B cuando el circuit breaker está abierto?

    • Pista: respuesta degradada, caché, o error rápido
  4. ¿Cómo detectarías este problema antes de que afecte a los usuarios?

    • Pista: métricas de latencia p99, alertas por percentil

Solución recomendada

Ver solución
[Servicio A]          [Servicio B]              [Servicio C]
  timeout: 2s           timeout: 1s               ---
                        circuit breaker: ON
                        fallback: caché local
                        bulkhead: pool separado
  • Timeouts decrecientes: A=2s, B=1s. Si C tarda más de 1s, B corta inmediatamente.
  • Circuit breaker en B→C: Después de 5 fallos consecutivos, B deja de llamar a C por 30 segundos.
  • Fallback: Cuando el circuit breaker está abierto, B responde con datos de caché (aunque sean stale).
  • Bulkhead: B usa un pool de conexiones separado para C, así los fallos de C no agotan las conexiones para otros servicios.

Escenario 2: Base de datos saturada

El fallo

Es Black Friday. El tráfico se multiplicó por 5x. La base de datos PostgreSQL del servicio de catálogo está al 95% de CPU. Las queries que normalmente tardan 50ms ahora tardan 2 segundos. El connection pool está lleno.

Síntomas observados

  • Latencia de API: p50 = 500ms (normal: 100ms), p99 = 8s (normal: 300ms)
  • Connection pool: 100/100 conexiones en uso
  • CPU de PostgreSQL: 95%
  • Errores 503 en el frontend: 15% de los requests

Ejercicio: Respuesta inmediata

Estás de guardia. ¿Qué haces en los próximos 30 minutos?

  1. Acción inmediata (primeros 5 minutos):

    • ¿Qué puedes hacer sin cambiar código?
  2. Mitigación a corto plazo (siguientes 25 minutos):

    • ¿Qué cambios de configuración ayudarían?
  3. Prevención futura:

    • ¿Qué cambios arquitectónicos evitarían este problema?

Solución recomendada

Ver solución

Inmediato (5 min):

  • Activar modo de solo lectura para operaciones no críticas
  • Aumentar el tamaño del connection pool si el servidor lo permite
  • Habilitar la read replica si existe pero no está en uso

Corto plazo (25 min):

  • Activar caché de Redis para las queries más frecuentes del catálogo
  • Reducir el timeout de queries lentas para liberar conexiones
  • Escalar verticalmente la instancia de PostgreSQL (más CPU/RAM)

Prevención futura:

  • Implementar caché de lectura (Redis) como capa permanente
  • Agregar read replicas para distribuir la carga de lectura
  • Implementar rate limiting para proteger la base de datos
  • Configurar auto-scaling basado en métricas de CPU/conexiones
  • Revisar y optimizar las queries más costosas (EXPLAIN ANALYZE)

Escenario 3: Mensaje perdido en la cola

El fallo

El servicio de órdenes publica un evento OrderCreated en RabbitMQ. El servicio de pagos consume ese evento y procesa el pago. Pero un día descubres que 50 órdenes de las últimas 24 horas nunca se procesaron — el evento se publicó pero el servicio de pagos nunca lo recibió.

Investigación

¿Qué pudo haber pasado? Analiza cada posibilidad:

  1. El mensaje nunca llegó a RabbitMQ

    • ¿El publisher usa confirmaciones (publisher confirms)?
    • ¿Qué pasa si RabbitMQ no responde al publish?
  2. El mensaje llegó pero se perdió en RabbitMQ

    • ¿La cola es durable?
    • ¿Los mensajes son persistentes?
    • ¿RabbitMQ se reinició y perdió mensajes en memoria?
  3. El consumer recibió el mensaje pero falló al procesarlo

    • ¿El consumer hace ACK antes o después de procesar?
    • ¿Hay dead letter queue para mensajes que fallan?
  4. El consumer estaba caído cuando llegó el mensaje

    • ¿La cola retiene mensajes cuando no hay consumers?
    • ¿Hay un TTL que expire mensajes no consumidos?

Ejercicio: Diseña un sistema de mensajería resiliente

[Servicio de Órdenes]

    ▼ (publisher confirms)
[RabbitMQ]
    │ (cola durable, mensajes persistentes)

[Servicio de Pagos]
    │ (ACK después de procesar)

    ├── éxito → ACK
    └── fallo → NACK → Dead Letter Queue → alerta

Checklist de resiliencia para mensajería

Ver checklist
  • Publisher confirms: El publisher espera confirmación de RabbitMQ antes de considerar el mensaje enviado
  • Cola durable: La cola sobrevive reinicios de RabbitMQ
  • Mensajes persistentes: Los mensajes se escriben a disco, no solo a memoria
  • ACK manual: El consumer hace ACK solo después de procesar exitosamente
  • Dead letter queue: Los mensajes que fallan N veces van a una cola separada para investigación
  • Idempotencia: El consumer puede procesar el mismo mensaje dos veces sin efectos duplicados
  • Monitoreo: Alertas cuando la cola crece más de lo normal o hay mensajes en la DLQ
  • Retry con backoff: Los mensajes fallidos se reintentan con delays crecientes

Escenario 4: Split brain en caché

El fallo

Tienes un cluster de Redis con 3 nodos (1 master + 2 replicas). Durante una partición de red, una replica se promueve a master. Ahora tienes 2 masters aceptando escrituras con datos diferentes.

Impacto

  • Algunos usuarios ven el precio viejo de un producto ($100)
  • Otros usuarios ven el precio nuevo ($80)
  • Cuando la partición se resuelve, ¿qué datos ganan?

Ejercicio

  1. ¿Cómo detectarías un split brain en producción?
  2. ¿Qué estrategia de resolución usarías? (last-write-wins, merge, manual)
  3. ¿Cómo prevendrías este escenario?
Ver solución

Detección:

  • Monitorear el número de masters en el cluster (alerta si > 1)
  • Sentinel o Redis Cluster detectan automáticamente y resuelven

Resolución:

  • Redis usa last-write-wins por defecto al reconectar
  • Los datos del master “perdedor” se descartan
  • Para datos críticos (precios, inventario), la fuente de verdad debe ser la base de datos, no el caché

Prevención:

  • Configurar min-replicas-to-write para que el master rechace escrituras si no tiene quórum
  • Usar Redis como caché (no como fuente de verdad) — siempre poder reconstruir desde la DB
  • Implementar health checks que detecten particiones de red

Reflexión

Después de completar los escenarios:

  1. ¿Cuántos de estos fallos has visto en producción?
  2. ¿Tu sistema actual tiene protecciones contra estos escenarios?
  3. ¿Qué fallo te parece más probable en tu contexto?
  4. ¿Qué acción concreta puedes tomar mañana para mejorar la resiliencia de tu sistema?

Los sistemas no fallan de formas nuevas — fallan de las mismas formas una y otra vez. Conocer los patrones de fallo es la mejor preparación.