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?
- C responde en 3s → B espera 3s + su propio procesamiento (500ms) = 3.5s
- A espera 3.5s por B → los usuarios ven latencia de 4s+
- Los threads de A y B están bloqueados esperando → el pool de conexiones se agota
- Nuevos requests a A empiezan a fallar por timeout → cascada de fallos
Ejercicio: Diseña la mitigación
Responde estas preguntas:
-
¿Qué timeout pondrías en cada servicio?
- Pista: los timeouts deben ser decrecientes en la cadena (A > B > C)
-
¿Dónde pondrías un circuit breaker?
- Pista: en el punto donde el fallo se origina (B → C)
-
¿Qué respuesta daría B cuando el circuit breaker está abierto?
- Pista: respuesta degradada, caché, o error rápido
-
¿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?
-
Acción inmediata (primeros 5 minutos):
- ¿Qué puedes hacer sin cambiar código?
-
Mitigación a corto plazo (siguientes 25 minutos):
- ¿Qué cambios de configuración ayudarían?
-
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:
-
El mensaje nunca llegó a RabbitMQ
- ¿El publisher usa confirmaciones (publisher confirms)?
- ¿Qué pasa si RabbitMQ no responde al publish?
-
El mensaje llegó pero se perdió en RabbitMQ
- ¿La cola es durable?
- ¿Los mensajes son persistentes?
- ¿RabbitMQ se reinició y perdió mensajes en memoria?
-
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?
-
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
- ¿Cómo detectarías un split brain en producción?
- ¿Qué estrategia de resolución usarías? (last-write-wins, merge, manual)
- ¿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-writepara 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:
- ¿Cuántos de estos fallos has visto en producción?
- ¿Tu sistema actual tiene protecciones contra estos escenarios?
- ¿Qué fallo te parece más probable en tu contexto?
- ¿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.