En las facultades de ingeniería y en los manuales de arquitectura de software se habla constantemente de conceptos como acoplamiento, puntos únicos de fallo (Single Points of Failure), validación de entradas, redundancia o tolerancia a fallos. Para muchos devs como yo en mis inicios, estos principios terminan convirtiéndose en simples casillas que marcar durante una revisión de código o una auditoría técnica.
Sin embargo, en el mundo de los sistemas críticos, estos principios pueden marcar literalmente la diferencia entre un incidente menor y una tragedia.
Los accidentes del Boeing 737 MAX ocurridos en 2018 y 2019 constituyen uno de los ejemplos más estudiados de la ingeniería moderna sobre cómo una cadena de decisiones de diseño puede desembocar en un fallo sistémico.
No fue un problema de un bucle infinito, una excepción no controlada o un desbordamiento de memoria.
Fue un problema de arquitectura de sistemas.
Un software que, bajo determinadas condiciones, emitía repetidamente órdenes opuestas a las acciones correctivas de los pilotos.
El problema comenzó mucho antes de escribir una línea de código
Para entender el software, primero hay que entender el contexto.
A principios de la década de 2010, Boeing necesitaba responder rápidamente al éxito del Airbus A320neo, un modelo que ofrecía un consumo de combustible considerablemente menor gracias a una nueva generación de motores más eficientes.
Diseñar una aeronave completamente nueva habría supuesto años de desarrollo, miles de millones de dólares y un largo proceso de certificación.
En su lugar, Boeing optó por evolucionar el exitoso 737, cuyo diseño original se remontaba a finales de los años sesenta.
Los nuevos motores CFM LEAP-1B eran considerablemente más grandes que los utilizados en generaciones anteriores. Debido a la baja altura del tren de aterrizaje del 737, fue necesario instalarlos en una posición más adelantada y ligeramente más elevada bajo las alas.
Ese cambio modificó el comportamiento aerodinámico de la aeronave.
En determinadas maniobras manuales y con elevados ángulos de ataque, la nueva ubicación de los motores generaba una tendencia adicional a elevar la nariz del avión, aumentando el riesgo de aproximarse a una entrada en pérdida (stall).
La respuesta fue una solución de software
Para compensar ese nuevo comportamiento, Boeing desarrolló el MCAS (Maneuvering Characteristics Augmentation System).
Su objetivo era relativamente sencillo, digo, relativamente, ya que en este tipo de software nada es sencillo, jaja.
Cuando determinadas condiciones indicaban que el avión estaba alcanzando un ángulo de ataque elevado, el sistema ajustaba automáticamente el estabilizador horizontal para empujar suavemente la nariz hacia abajo y ayudar a mantener un comportamiento de vuelo similar al de generaciones anteriores del 737.
Uno de los objetivos del programa era que la transición desde el 737 NG requiriera el menor entrenamiento adicional posible para pilotos y aerolíneas.
Sin embargo, el diseño original del MCAS presentaba varios problemas que hoy se estudian como ejemplos clásicos de arquitectura de sistemas críticos.
1. Un punto único de fallo
El MCAS necesitaba conocer el ángulo de ataque (Angle of Attack o AoA) para decidir cuándo intervenir.
El Boeing 737 MAX disponía de dos sensores físicos de AoA, uno a cada lado del fuselaje.
Sin embargo, la versión original del MCAS utilizaba únicamente uno de ellos durante cada vuelo.
Si ese sensor comenzaba a enviar datos incorrectos debido a una avería, el sistema asumía que la información era válida y actuaba en consecuencia.
No existía una validación cruzada con el segundo sensor antes de tomar una decisión crítica.
Desde el punto de vista de la arquitectura de software, el sistema había convertido una infraestructura redundante en un punto único de fallo.
2. Una automatización que insistía una y otra vez
Cuando el MCAS detectaba un ángulo de ataque elevado, ordenaba mover el estabilizador para bajar la nariz del avión.
Los pilotos podían compensar manualmente esa acción utilizando los mandos de control.
Sin embargo, si el sensor seguía enviando la misma lectura errónea, el MCAS volvía a activarse pocos segundos después y aplicaba nuevamente otra corrección.
Cada intervención individual era limitada, pero el efecto acumulado de múltiples activaciones sucesivas terminaba haciendo cada vez más difícil recuperar el control de la aeronave.
En cualquier sistema distribuido reconoceríamos este patrón.
No era un bucle infinito de programación, sino una lógica de reactivación que seguía ejecutándose mientras la condición de entrada permaneciera aparentemente válida... como te darás cuenta, en la práctica esto generaba una situación incontrolable.
3. Confiar ciegamente en la entrada de datos
Uno de los principios más importantes de cualquier sistema crítico es que ninguna entrada debería asumirse como correcta únicamente porque proviene de un sensor.
El diseño original del MCAS confiaba plenamente en la lectura recibida.
No existían mecanismos suficientes para contrastar esa información con otras fuentes disponibles ni para detectar determinadas inconsistencias antes de ejecutar una acción automática.
En otras palabras, el sistema tomó una lectura errónea del mundo físico y la trató como una verdad absoluta.
En ingeniería de software hacemos exactamente el mismo tipo de error cuando asumimos que una API externa, un mensaje de una cola o un dispositivo siempre enviarán información válida.
Lecciones de arquitectura para cualquier sistema backend
Aunque no estés desarrollando el software de un avión comercial, los principios que aparecen en este caso son sorprendentemente aplicables al software empresarial; de manera similar al artículo anterior, podemos sacar valiosas lecciones.
La redundancia solo sirve si el software sabe utilizarla
Disponer de dos bases de datos, múltiples réplicas o varios proveedores externos aporta muy poco si la aplicación siempre consulta únicamente una fuente y nunca compara resultados ni realiza mecanismos de failover.
La redundancia física debe ir acompañada de una arquitectura capaz de aprovecharla.
Nunca confíes ciegamente en una entrada
Los datos externos siempre pueden estar incompletos, corruptos o ser simplemente incorrectos.
Validar entradas, detectar valores imposibles, comparar múltiples fuentes y aplicar reglas de consistencia son prácticas habituales en sistemas críticos precisamente porque el hardware también falla.
Toda automatización necesita un mecanismo de escape
Las automatizaciones son excelentes... hasta que dejan de serlo.
Todo proceso capaz de modificar datos, ejecutar acciones o tomar decisiones importantes debería disponer de una forma rápida, documentada y segura de ser desactivado cuando el comportamiento observado deja de ser el esperado.
En ingeniería este principio suele conocerse como fail safely: cuando el sistema no puede garantizar que está actuando correctamente, debe priorizar un estado seguro antes que continuar ejecutando acciones potencialmente peligrosas.
Cuestiona siempre tus supuestos
Quizá la lección más importante del MCAS no sea que un sensor haya fallado.
Los sensores fallan constantemente en cualquier sistema complejo.
La verdadera pregunta es:
¿Qué hace tu software cuando una de las premisas sobre las que fue diseñado deja de cumplirse?
Los mejores sistemas no son aquellos que nunca encuentran errores.
Son aquellos que fueron diseñados esperando que esos errores ocurran.
En conclusión dev...
Como ya sabes, siempre me gusta añadir conclusiones y este artículo tiene varias.
Los accidentes del Boeing 737 MAX cambiaron profundamente la forma en que la industria aeronáutica y las autoridades certificadoras analizan los sistemas críticos, reforzando el escrutinio sobre los análisis de seguridad, la validación de requisitos y los procesos de certificación.
El MCAS no falló por un error de sintaxis.
Tampoco por utilizar el lenguaje de programación equivocado, que por cierto era C, por si te lo estás preguntando crack.
Falló porque una cadena de decisiones arquitectónicas permitió que un conjunto de supuestos incorrectos permaneciera sin detectar hasta enfrentarse a la realidad.
Como ingenieros de software, nuestra responsabilidad no termina cuando el código compila o las pruebas unitarias pasan correctamente, nah!
También consiste en cuestionar nuestras hipótesis, identificar los puntos únicos de fallo y diseñar sistemas capaces de responder de forma segura cuando el mundo deja de comportarse como esperábamos.
Porque, al final estimado dev, la calidad de un sistema no se mide por cómo funciona cuando todo sale bien, sino por cómo responde cuando inevitablemente algo sale mal, eso te convierte en un ingeniero de software que se respeta.
Este artículo merece que lo compartas con todito tu equipo de ingeniería eh
