Arquitectura 8 min de lectura

Chaos Engineering: Rompiendo cosas a propósito

Publicado el 25 de diciembre de 2025

Chaos Engineering: Rompiendo cosas a propósito

Los tests unitarios y de integración te dicen que tu código hace lo que esperas cuando todo va bien. Pero en producción, las cosas fallan: un servidor se cae, la red se corta, un disco se llena, un servicio externo tarda 30 segundos en responder. La pregunta incómoda es: ¿tu sistema se recupera solo o se lleva todo por delante?

Chaos Engineering es la disciplina de experimentar deliberadamente en sistemas (a menudo en producción, de forma controlada) para verificar que la resiliencia que diseñaste en la arquitectura realmente funciona. Netflix lo lleva años haciendo: tras su migración a AWS, necesitaban asegurarse de que un fallo de instancia o de región no tumbara el streaming. Crearon Chaos Monkey (y luego toda la “Simian Army”: Latency Monkey, Conformity Monkey, etc.) para apagar servidores y componentes al azar en producción. La idea: si tu sistema está diseñado para asumir que cualquier cosa puede fallar en cualquier momento, cuando falle de verdad ya no es una sorpresa. Resumido: inyectan caos a propósito, observan cómo responde la plataforma y corrigen lo que no aguanta.

En este artículo, introduciré la filosofía del Chaos Engineering, por qué los tests “normales” no bastan para cubrir estos escenarios, y compartiré una experiencia práctica: un sistema de orquestación de flujos de trabajo en AWS, con microservicios dockerizados, PostgreSQL y Dragonfly como caché, donde decidimos romper cosas a propósito para validar que el sistema aguantaba.

Por qué los tests no lo son todo

Los tests son fundamentales. Cubren lógica de negocio, contratos entre servicios, regresiones. Pero tienen límites claros a la hora de validar resiliencia:

  • No suelen simular fallos reales de infraestructura: Un test puede mockear un timeout, pero no simula que un contenedor desaparezca a mitad de una transacción, que un nodo de Redis caiga, o que una instancia de AWS se reinicie sin aviso.
  • El entorno de test no es producción: En test sueles tener una BD pequeña, un solo nodo, sin red real, sin latencia ni particiones. Los fallos que ves en producción (concurrencia, límites de conexiones, timeouts de red) no siempre se reproducen en CI.
  • No prueban la orquestación de recuperación: Que un servicio tenga reintentos está bien, pero ¿quién detecta el fallo? ¿Quién reenruta el tráfico? ¿Cómo se comportan los demás microservicios cuando uno deja de responder? Eso suele estar en la capa de infraestructura y orquestación, no en un test unitario.

Chaos Engineering no sustituye a los tests; los complementa. Los tests validan “¿hace lo correcto?”. El caos valida “¿cuando algo falla, el sistema sigue cumpliendo o se recupera de forma aceptable?”.

Principios del Chaos Engineering

  • Definir un estado estable: Antes de inyectar caos, defines qué significa “el sistema está bien” (métricas, SLA, comportamiento observable).
  • Formular hipótesis: Por ejemplo: “Si matamos un nodo de Dragonfly, el sistema sigue sirviendo tráfico usando el otro nodo y no perdemos datos críticos.”
  • Experimentar en el mundo real: Los experimentos se hacen en entornos que se parecen a producción (staging) o, con cuidado, en producción. Solo así ves el comportamiento real bajo fallos.
  • Automatar y repetir: Los experimentos se pueden automatizar (scripts, pipelines) y repetir para detectar regresiones de resiliencia.
  • Minimizar el impacto: Se empieza con perturbaciones pequeñas (un pod, un nodo) y se amplía solo si el sistema responde como esperas.

El objetivo no es “romper todo” sino aprender cómo se comporta el sistema cuando algo falla y usar ese aprendizaje para mejorar diseño, configuración y procedimientos.

Experiencia práctica: orquestador de flujos en AWS

En un proyecto de orquestación de flujos de trabajo (pipelines de tareas que se ejecutan en secuencia o en paralelo, con dependencias entre pasos), la arquitectura era algo así:

  • Microservicios dockerizados en AWS (ECS/EKS), varios servicios: API, workers, scheduler.
  • PostgreSQL como fuente de verdad: estado de los flujos, tareas, resultados.
  • Dragonfly como caché y cola: sesiones, resultados intermedios, colas de tareas para workers (compatible con Redis pero con mejor throughput y uso de memoria en nuestro caso).
  • AWS: Load balancers, múltiples instancias, redes privadas.

Todo estaba cubierto por tests: unitarios, integración con BD y con Dragonfly en Docker, y tests de contrato entre servicios. En staging, los flujos se ejecutaban bien. La duda era: cuando algo falla en producción (un contenedor, un nodo de Dragonfly, una instancia), ¿el sistema se recupera o se corrompe el estado?

Qué hicimos

  1. Definimos estado estable

    • Los flujos en ejecución deben completarse o marcarse como fallidos de forma coherente.
    • No pérdida de datos en PostgreSQL.
    • Latencia y errores por debajo de umbrales definidos.
  2. Escenarios de caos (en staging primero)

    • Matar un contenedor de un microservicio (worker o API) al azar durante carga.
    • Apagar un nodo de Dragonfly (teníamos dos en modo alta disponibilidad).
    • Reiniciar una instancia que hospedaba parte de los servicios.
  3. Herramientas

    • Scripts que, vía AWS CLI y APIs de orquestación, mataban tareas o instancias en ventanas de tiempo acotadas.
    • Para Dragonfly, scripts que forzaban la “caída” de un nodo y comprobaban que el otro asumía el tráfico.
  4. Qué aprendimos

    • Sin caos: Los tests pasaban, pero en el primer experimento vimos que al caer un worker, algunas tareas quedaban “colgadas” en la BD porque el consumidor de la cola (en Dragonfly) desaparecía y no habíamos definido bien timeouts y reintentos en la cola.
    • Dragonfly: Al tirar un nodo, el otro asumía, pero hubo un pico de latencia y algunos reintentos que no habíamos contemplado en el cliente; ajustamos timeouts y lógica de reintento.
    • PostgreSQL: No perdimos datos; las transacciones bien acotadas y el hecho de que la BD fuera la fuente de verdad nos salvó. Lo que sí fallaba era la coordinación: workers que asumían tareas “huérfanas” solo después de un timeout que habíamos puesto demasiado alto.
  5. Cómo solucionamos los problemas (en prod)

    • Tareas colgadas: Definimos un timeout de visibilidad en la cola (Dragonfly): si un worker tomaba una tarea y no la confirmaba/completaba en X segundos, la tarea volvía a la cola para que otro worker la tomara. Así ningún mensaje quedaba “pegado” a un consumidor muerto.
    • Dragonfly (pico de latencia): En el cliente que habla con Dragonfly añadimos reintentos con backoff y un timeout de conexión mayor durante failover; si un nodo caía, el cliente no fallaba a la primera, esperaba y reconectaba al nodo que seguía vivo.
    • Tareas huérfanas y timeout alto: Bajamos el tiempo tras el cual un worker considera que una tarea está “abandonada” y la puede reclamar otro. Además, un job periódico (cron/scheduler) revisa la BD en busca de tareas en estado “en progreso” cuyo worker lleva más de N minutos sin dar señales de vida y las marca como fallidas o las reencola, según la política de negocio.
    • Marcado coherente de fallos: Cuando un worker muere, las tareas que tenía asignadas no quedan en limbo: o bien vuelven a la cola (idempotencia permitida) o se marcan como fallidas en PostgreSQL y se notifica. Así el estado en BD y en cola siguen alineados y no hay flujos “zombie”.

Con esas soluciones desplegadas, repetimos los experimentos de caos en staging y luego en producción en ventanas controladas. Los tests “normales” no nos habían mostrado estos fallos; el caos sí, y las correcciones nos dieron una recuperación real cuando algo fallaba de verdad.

Por qué Dragonfly, PostgreSQL, AWS y Docker

  • Dragonfly: Necesitábamos alta capacidad de throughput y buen uso de memoria; Dragonfly nos daba compatibilidad con Redis con menos recursos. Que un nodo cayera era un escenario real que queríamos validar.
  • PostgreSQL: Era la fuente de verdad. El caos nos permitió confirmar que, ante fallos de otros componentes, no corrompíamos datos y que la recuperación se basaba en el estado guardado ahí.
  • AWS y microservicios dockerizados: Los fallos que nos preocupaban eran de infraestructura real: instancias, contenedores, red. Eso no se modela bien solo con mocks en tests; hace falta tocar el sistema real o algo muy parecido.

Cómo empezar con Chaos Engineering

  • Empezar en staging, no en producción.
  • Definir métricas y umbrales antes de inyectar caos.
  • Un solo tipo de fallo cada vez (un servicio, un nodo, un disco).
  • Automatizar los experimentos para poder repetirlos (por ejemplo en cada release).
  • Documentar qué rompiste, qué observaste y qué cambiaste (config, código, runbooks).

Herramientas que puedes explorar: Chaos Monkey (Netflix), Litmus Chaos, Gremlin, o scripts propios sobre tu orquestador (Kubernetes, ECS, etc.) y sobre tus servicios (Dragonfly, PostgreSQL, APIs).

Mi perspectiva personal

Los tests te dan confianza en que el código hace lo que debe cuando el mundo se comporta. El Chaos Engineering te da confianza en que, cuando el mundo se desvía (un servidor menos, un nodo de caché caído, una red inestable), el sistema que diseñaste en la Serie II (redundancia, reintentos, timeouts, fuentes de verdad) realmente responde como esperabas.

En el proyecto del orquestador, los tests no lo cubrían todo: fallos de infraestructura, cortes de un nodo de Dragonfly, contenedores muriendo en medio de un flujo. Hasta que no rompimos cosas a propósito, no vimos los puntos flacos en timeouts, reintentos y coordinación entre workers y BD.

Recomiendo tratar el caos como parte del diseño de sistemas resilientes: definir qué puede fallar, cómo debería comportarse el sistema cuando falla, y luego validarlo con experimentos controlados. Así, cuando algo falle de verdad en producción, no será la primera vez que tu sistema se enfrenta a ese escenario.