Ariane 5 y la conversión numérica que destruyó un cohete

¿Te fue útil?

Si alguna vez has visto un despliegue limpio convertirse en un incidente en cadena, ya entiendes la física emocional de Ariane 5. Todo parece nominal hasta que deja de serlo. Entonces recibes telemetría que parece arte abstracto, alarmas que se encadenan y un panel que insiste en que el cielo se está cayendo porque, en un sentido muy literal, así es.

Esto no fue una falla misteriosa de “la ciencia de cohetes es difícil”. Fue una falla de ingeniería de software con un olor familiar: código reutilizado, suposiciones desajustadas, una excepción no manejada y una “redundancia” que duplicó el mismo bug dos veces. En términos de SRE: una falla por modo común con excelente tiempo de actividad hasta el segundo exacto en que dejó de serlo.

Qué ocurrió en los primeros 40 segundos

El 4 de junio de 1996, el primer vuelo de Ariane 5 despegó desde Kourou. Ariane 5 era un lanzador nuevo, diseñado para llevar cargas más pesadas que Ariane 4. Vehículo nuevo, perfil de vuelo nuevo, dinámica nueva. Pero partes del software —específicamente dentro del sistema de referencia inercial (IRS)— se reutilizaron de Ariane 4.

Unos 37 segundos después del despegue, el software del IRS sufrió un desbordamiento numérico durante una conversión de un valor en coma flotante a un tipo entero que no podía representar el número mayor. Ese desbordamiento generó una excepción. La excepción no fue manejada como debía ser para un vehículo en vuelo. El ordenador del IRS se apagó. Casi inmediatamente, el IRS redundante también falló de la misma manera, porque ejecutaba el mismo código con las mismas suposiciones. La redundancia hizo lo que estaba diseñada para hacer: conmutó exactamente al mismo fallo.

El sistema de guiado, al quedar sin datos válidos de actitud, interpretó datos de diagnóstico como si fueran datos reales de vuelo. El cohete se desvió bruscamente de su trayectoria prevista. Las cargas estructurales se dispararon. La seguridad de rango —al ver un vehículo fuera de control— activó la autodestrucción. Esta parte no es melodrama; es una limitación de seguridad. Cuando lanzas sobre un planeta habitado, no puedes “esperar y ver”.

Los cohetes no explotan porque estén enfadados. Explotan porque nosotros se lo ordenamos, a propósito, para mantener el radio de explosión fuera de lugares que no deberían recibirlo.

Hechos interesantes y contexto histórico

  • El vuelo 501 fue el vuelo inaugural de Ariane 5, lo que significa que no había una larga historia operativa detrás en la que ocultarse —solo simulaciones y suposiciones.
  • Ariane 5 usaba dos sistemas de referencia inercial destinados a redundancia, pero ambos ejecutaban software idéntico y estaban expuestos a las mismas condiciones de entrada.
  • La variable que falló representaba un valor relacionado con la velocidad horizontal proveniente del procesamiento de alineamiento/actitud —válido para el envolvente de vuelo de Ariane 4, no para la trayectoria temprana de Ariane 5.
  • La excepción ocurrió durante una conversión de float a entero donde el tipo entero era demasiado pequeño para representar el valor, provocando un desbordamiento.
  • Algunas funciones del IRS estaban ejecutándose después del despegue aunque no eran necesarias para el vuelo, porque desactivarlas se consideró arriesgado o no merecía el cambio.
  • El ordenador de guiado interpretó ciertas palabras de diagnóstico como datos después de que el IRS fallara, un clásico escenario de “entrada basura, salida peligrosa”.
  • La destrucción por seguridad de rango es una función diseñada, no un accidente: cuando se pierde el guiado y la trayectoria es insegura, la terminación protege a las personas en tierra.
  • El incidente se convirtió en un estudio de caso emblemático de ingeniería de software porque la falla fue determinista, bien documentada y dolorosamente evitable.

La falla técnica: una conversión, un desbordamiento, dos ordenadores

Aquí está el núcleo, despojado de mitología: el software del IRS intentó convertir un número de punto flotante a un entero con signo de 16 bits (o un tipo entero similarmente restringido en la implementación). El valor float excedió el rango máximo representable por el entero. El runtime generó una excepción por desbordamiento. Esa excepción provocó el apagado del ordenador IRS. Una vez apagado, no proporcionaba referencias de actitud ni velocidad. El guiado ahora volaba a ciegas.

Por qué existía la conversión

En muchos sistemas de guiado embebidos, verás representaciones numéricas mixtas. El punto flotante se usa para cálculos intermedios; los enteros se usan para mensajes de formato fijo, almacenamiento o rendimiento. Cuando los sistemas se diseñaron hace décadas, la memoria y los ciclos de CPU eran escasos. Incluso ahora, la determinismo importa: campos enteros de tamaño fijo en mensajes de bus o tramas de telemetría son estables, comprobables y fáciles de parsear.

Así que la conversión en sí no es sospechosa. Lo sospechoso es la suposición incrustada en ella: “este valor siempre cabrá”. Eso era cierto bajo las condiciones de vuelo de Ariane 4. No lo fue para Ariane 5. El vuelo temprano de Ariane 5 produjo una componente de velocidad horizontal mayor de lo que Ariane 4 había tenido nunca en el mismo instante. Mismo código, nueva física.

Por qué el manejo de excepciones fue fatal

En sistemas críticos para la seguridad, las excepciones no son “bugs”, son señales. Un desbordamiento es el equivalente software de una válvula de alivio gritando. O lo manejas de un modo que preserve un comportamiento seguro, o permites que el componente se caiga y rezas para que la redundancia te salve.

Manejaron algunas conversiones con protección, porque ya sabían que podían desbordarse. Pero esta conversión no estaba protegida —porque no se esperaba que se desbordara. Esa es la trampa: las fallas más peligrosas son las que “probaste” que no pueden ocurrir basándote en el sistema del año pasado.

Qué debería haber pasado en su lugar

En un mundo perfecto, la conversión habría estado protegida y saturante: limitar el valor a min/max y establecer una bandera de estado, o usar un tipo entero mayor, o mantenerlo como float, o desactivar ese cálculo después del despegue si no es necesario. Elige uno. Cualquiera de ellos, implementado correctamente, cuesta mucho menos que reconstruir un cohete y explicar el cráter a tus stakeholders.

Aquí va la verdad seca y divertida: las computadoras son empleados muy literales. No “más o menos” se desbordan; lo hacen con precisión y luego se van sin avisar.

Una cita para mantener la honestidad: “La esperanza no es una estrategia.” — General Gordon R. Sullivan

Por qué falló la redundancia: la trampa del modo común

La redundancia no es magia; es matemática. Dos sistemas solo mejoran la fiabilidad si sus modos de fallo son suficientemente independientes. Si ambos sistemas ejecutan el mismo software, misma configuración, mismos tipos numéricos y ven las mismas entradas al mismo tiempo, no has construido redundancia —has construido una falla sincronizada.

Esto se llama falla por modo común. Aparece en todas partes: alimentaciones duales desde el mismo panel de interruptores; dos clusters de Kubernetes que comparten el mismo proveedor de DNS; bases de datos “active-active” que ambas se bloquean bajo el mismo patrón de consultas.

Ariane 5 tenía dos unidades IRS. Ambas experimentaron el mismo desbordamiento casi al mismo tiempo. No hubo degradación gradual, ni “una unidad pasa a modo de función reducida”, ni implementación independiente, ni representación numérica heterogénea, ni diversidad que importara.

También: el tiempo de conmutación importa. Si la unidad redundante muere milisegundos después de la primaria, no conmutaste: solo añadiste un retardo tan corto que parece un fallo en los logs.

La redundancia sin diversidad es una manta de confort. Se siente cálida hasta que el fuego la alcanza.

Una visión de sistemas: requisitos, validación y código “no esencial”

La falla no fue “un mal cast”. Fue una cadena de decisiones que hizo que el cast fuera fatal. Diagnostiquemos los puntos de decisión.

1) Reutilizar sin revalidar

La reutilización de software es buena ingeniería. La reutilización a ciegas es apostar. El software de Ariane 4 se reutilizó en Ariane 5, pero la validación no cubrió completamente el nuevo envolvente de vuelo. Ese es el tipo de atajo que parece responsable en un plan de proyecto: menos cambios, menos regresiones, menos riesgo.

Realidad: menos cambios pueden significar menos atención. El código se vuelve “confiable”. El código confiable es donde los bugs se jubilan y luego regresan como fantasmas.

2) Funciones no esenciales ejecutándose en vuelo

Parte del cálculo que falló pertenecía a la lógica de alineamiento que no se necesitaba después del despegue. Sin embargo, siguió ejecutándose. ¿Por qué? Porque apagarla requería cambios y re-pruebas, y dejarla encendida parecía seguro porque “funcionó antes”.

Esto es un anti-patrón operativo: permitir que trabajos de fondo no críticos se ejecuten en una ventana crítica porque desactivarlos parece arriesgado. En sistemas de producción lo llamamos “cron jobs inofensivos” que se convierten en los consumidores más ruidosos durante un corte. En cohetes, no puedes hacer SSH y matar el proceso.

3) Política de manejo de excepciones que priorizaba el apagado

Hay casos donde apagar es más seguro que continuar. Pero el apagado debe diseñarse como un estado seguro. Para un sistema de referencia inercial que alimenta al guiado, apagar no es seguro a menos que el guiado tenga una fuente alternativa verificada y el protocolo evite la interpretación errónea de diagnósticos como verdad.

En operaciones decimos: fallar rápido es genial cuando puedes reintentar. En un cohete a T+37 segundos, no puedes reintentar. Tu botón de “reiniciar” es la póliza de seguros.

4) Verificación que no cubrió el verdadero envolvente operativo

Esta es la parte que a los ingenieros no les gusta porque no es un solo bug. Es una desalineación entre lo que se probó y lo que importaba. Puedes tener miles de casos de prueba y aún así perder el único límite que define la realidad: “¿Puede esta variable exceder 32767?”

Y sí, es aburrido. Por eso mata sistemas.

Tres mini-historias corporativas que reconocerás

Mini-historia 1: El incidente causado por una suposición equivocada

Una empresa de pagos ejecutaba una flota de servicios que calculaban puntuaciones de riesgo de fraude. La puntuación se almacenaba en un entero con signo de 16 bits porque “solo necesitaba representar 0–10,000” cuando se lanzó el sistema años antes. El modelo original limitaba la puntuación. Todos olvidaron que el límite era un techo, no una ley de la naturaleza.

Se desplegó un nuevo modelo. Era mejor—mayor recall, menos falsos negativos. También emitía puntuaciones por encima del antiguo techo durante ciertos picos navideños. Una conversión profunda en una librería compartida truncó o desbordó valores, que luego se mapeaban a “riesgo ultra-bajo” debido a un wraparound. El sistema de fraude no se volvió más ruidoso. Se volvió más silencioso. Ese es el peor tipo de fallo: el que parece un éxito.

El incidente duró horas porque los paneles de monitorización siguieron métricas de puntuación promedio y conteo de pagos bloqueados. Los promedios no se movieron mucho. La cola explotó. Hizo falta que alguien mirara un histograma bruto para ver que un grupo de transacciones tenía puntuaciones negativas sin sentido.

La solución fue simple: almacenar el riesgo como 32 bits, añadir comprobaciones explícitas de límites y validar los rangos de salida del modelo como parte del despliegue. La corrección cultural fue más difícil: dejar de tratar “nunca hemos visto ese valor” como una garantía. En un sistema vivo, el futuro es donde van a morir tus suposiciones.

Mini-historia 2: La optimización que salió mal

Un equipo de almacenamiento “optimizó” una canalización de ingestión cambiando timestamps de 64 bits a 32 bits segundos-desde-epoch dentro de un índice, porque reducía memoria y mejoraba la tasa de aciertos de caché. Los benchmarks parecían geniales. Las gráficas subían y subían. Hubo promociones.

Luego se expandieron a una región donde algunos dispositivos emitían timestamps muy en el futuro debido a un bug del reloj del firmware. Esos timestamps desbordaron la representación de 32 bits. Los registros se indexaron en buckets de tiempo sin sentido. Las consultas de “últimos 15 minutos” ocasionalmente incluían datos futuros, que la aplicación trataba como los más recientes y por tanto “más autorizados”. Los usuarios vieron datos teletransportándose en el tiempo.

Tomó días deshacerlo porque los datos no estaban corruptos en almacenamiento; estaban corruptos en el índice. Reconstruir el índice requirió rellenar miles de millones de filas. Eso significó limitar la ingestión, lo que significó quedarse atrás, lo que significó ejecutivos mirando un SLA de “frescura de datos” que de repente importaba.

Revirtieron a 64 bits, añadieron validación de entrada e implementaron una vía de cuarentena: los registros con timestamps fuera de rango aún llegaban, pero se marcaban, aislaban y excluían de las consultas por defecto. La lección fue directa: la optimización que cambia límites numéricos no es optimización; es rediseño.

Mini-historia 3: La práctica aburrida pero correcta que salvó el día

Un proveedor de SaaS de tamaño medio ejecutaba una base de datos multi-inquilino con trabajos por lotes intensos. Nada emocionante—hasta que una actualización de una librería upstream cambió un formato de serialización. La mayoría de los equipos lo descubrió en producción con estruendo.

Este equipo no. Tenían una práctica dolorosamente aburrida: cada actualización de dependencia disparaba una prueba de reproducción contra una muestra de tráfico de producción capturada, y almacenaban artefactos “golden” para chequear compatibilidad. El entorno de reproducción no era perfecto, pero era lo suficientemente fiel para detectar desajustes de límite.

La prueba señaló un problema sutil: un campo numérico previamente serializado como entero de 64 bits ahora se decodificaba como 32 bits en un consumidor debido a una ambigüedad de esquema. Bajo rangos normales funcionaba. Bajo valores raros, se desbordaba y disparaba un bucle de reintentos. Ese bucle de reintentos se habría vuelto una avalancha contra la base de datos.

Arreglaron el esquema, fijaron versiones y desplegaron. Nadie fuera del equipo lo notó. Ese es el punto. El trabajo de fiabilidad suele ser invisible. Si tu práctica de fiabilidad es glamurosa, probablemente estás haciendo respuesta a incidentes, no ingeniería.

Tareas prácticas: comandos, salidas y decisiones

La falla de Ariane 5 es un problema de límite numérico envuelto en un problema de verificación y disciplina operativa. Aquí tienes tareas concretas que puedes ejecutar en sistemas reales para prevenir la misma clase de fallo. Cada tarea incluye: un comando, salida de ejemplo, qué significa y la decisión que tomas.

1) Encontrar conversiones de estrechamiento entero en compilaciones C/C++ (warnings del compilador)

cr0x@server:~$ make CFLAGS="-O2 -Wall -Wextra -Wconversion -Wsign-conversion" 2>&1 | head -n 8
src/nav.c:214:23: warning: conversion from ‘double’ to ‘int16_t’ may change value [-Wfloat-conversion]
src/nav.c:215:18: warning: conversion to ‘int16_t’ from ‘int’ may change the sign of the result [-Wsign-conversion]
...

Significado de la salida: El compilador te está indicando exactamente dónde podrías desbordarte, truncar o cambiar de signo.

Decisión: Trata estas advertencias como bloqueantes de lanzamiento para código crítico de seguridad o monetización. Añade comprobaciones explícitas de rango o amplía los tipos. Si debes estrechar, documenta el límite y haz que se cumpla.

2) Buscar casts riesgosos en un repositorio (grep rápido)

cr0x@server:~$ rg -n "(\(int16_t\)|\(short\)|\(int\))\s*\(" src include
src/irs/align.c:88:(int16_t)(h_velocity)
src/irs/align.c:131:(short)(bias_estimate)

Significado de la salida: Los casts explícitos son donde la intención y el peligro se encuentran.

Decisión: Revisa cada cast: qué rango se espera, qué ocurre cuando se excede y si se ejecuta en una ventana crítica.

3) Identificar excepciones/crashes no manejados vía logs de systemd

cr0x@server:~$ journalctl -u navd --since "1 hour ago" | tail -n 12
Jan 22 10:11:04 stage navd[1827]: converting float to int16: value=40211.7
Jan 22 10:11:04 stage navd[1827]: FATAL: SIGABRT after overflow trap
Jan 22 10:11:04 stage systemd[1]: navd.service: Main process exited, code=killed, status=6/ABRT
Jan 22 10:11:04 stage systemd[1]: navd.service: Failed with result 'signal'.

Significado de la salida: Una conversión numérica disparó una trampa que tumbó el servicio.

Decisión: Decide si el servicio debe fallar y detenerse o degradarse. Para bucles de control críticos, diseña un modo seguro; para servicios sin estado, implementa reintentos y circuit breakers.

4) Verificar trampas del kernel/CPU por crashes tipo overflow (core dump habilitado)

cr0x@server:~$ coredumpctl list navd | tail -n 3
TIME                            PID   UID   GID SIG COREFILE  EXE
Jan 22 10:11:04                 1827  1001  1001  6 present   /usr/local/bin/navd

Significado de la salida: Hay un volcado de memoria que puedes inspeccionar en vez de adivinar.

Decisión: Saca el core, identifica la conversión exacta y el rango de entrada, y añade una prueba de regresión para ese límite.

5) Medir si tus servicios “redundantes” fallan juntos (falla correlacionada)

cr0x@server:~$ awk '$3=="ERROR" {print $1,$2,$6}' /var/log/irs-a.log | tail -n 5
2026-01-22 10:11:04 overflow value=40211.7
2026-01-22 10:11:04 shutdown reason=exception
cr0x@server:~$ awk '$3=="ERROR" {print $1,$2,$6}' /var/log/irs-b.log | tail -n 5
2026-01-22 10:11:04 overflow value=40212.1
2026-01-22 10:11:04 shutdown reason=exception

Significado de la salida: Mismo timestamp, misma razón: falla por modo común.

Decisión: Introduce diversidad: implementaciones diferentes, validación diferente, umbrales distintos o al menos comportamientos escalonados (uno limita, otro alerta).

6) Validar rangos numéricos en el límite (guardas en tiempo de ejecución)

cr0x@server:~$ python3 - <<'PY'
import math
MAX_I16=32767
vals=[120.0, 32766.9, 40000.1]
for v in vals:
    ok = -MAX_I16-1 <= v <= MAX_I16
    print(f"value={v} fits_int16={ok}")
PY
value=120.0 fits_int16=True
value=32766.9 fits_int16=True
value=40000.1 fits_int16=False

Significado de la salida: Puedes detectar condiciones de desbordamiento antes de convertir.

Decisión: Aplica guardas en las interfaces: si está fuera de rango, limita, descarta o enruta a una vía de modo seguro con alarmas.

7) Confirmar que tu telemetría no es “basura interpretada como verdad” (chequeos de esquema)

cr0x@server:~$ jq -r '.frame_type, .attitude.status, .attitude.roll' telemetry/latest.json
DIAGNOSTIC
FAIL
-1.7976931348623157e+308

Significado de la salida: Se está parseando un frame de diagnóstico como un frame de actitud, y un valor centinela está filtrando.

Decisión: Haz que los tipos de mensaje sean explícitos y validados. Rechaza consumir tramas que no coincidan con el esquema y el estado.

8) Detectar eventos de saturación/clamping (quieres verlos)

cr0x@server:~$ grep -R "SATURAT" -n /var/log/navd.log | tail -n 5
41298:WARN SATURATION h_velocity=40211.7 clamped_to=32767
41302:WARN SATURATION h_velocity=39880.2 clamped_to=32767

Significado de la salida: El sistema encontró valores fuera del rango esperado pero se mantuvo vivo.

Decisión: Investiga por qué se excede el rango. Decide si el clamp es aceptable o está ocultando un cambio real en el modelado/física.

9) Validar que trabajos “no esenciales” no corran en la ventana crítica

cr0x@server:~$ systemctl list-timers --all | head -n 12
NEXT                         LEFT     LAST                         PASSED    UNIT                         ACTIVATES
Wed 2026-01-22 10:12:00 UTC  32s      Wed 2026-01-22 10:07:00 UTC  4min ago  rotate-alignment.timer       rotate-alignment.service
Wed 2026-01-22 10:15:00 UTC  3min 32s Wed 2026-01-22 10:00:00 UTC  11min ago logrotate.timer             logrotate.service

Significado de la salida: Timers están disparándose durante tu periodo crítico.

Decisión: Desactiva o reprograma tareas no críticas durante lanzamientos/ventanas pico. El trabajo “de fondo” solo es de fondo hasta que deja de serlo.

10) Confirmar que la conmutación por fallo funciona bajo carga (health y readiness)

cr0x@server:~$ kubectl get pods -n guidance -o wide
NAME                     READY   STATUS    RESTARTS   AGE   IP           NODE
irs-a-7c6b9f9c7b-2m8qk   1/1     Running   0          3d    10.42.1.12   node-1
irs-b-7c6b9f9c7b-q9d2p   1/1     Running   0          3d    10.42.2.19   node-2
cr0x@server:~$ kubectl describe svc irs -n guidance | sed -n '1,30p'
Name:              irs
Namespace:         guidance
Selector:          app=irs
Type:              ClusterIP
IP Family Policy:  SingleStack
IP Families:       IPv4
IP:                10.43.128.10
Endpoints:         10.42.1.12:9000,10.42.2.19:9000

Significado de la salida: Tienes dos endpoints, pero esto no prueba que fallen de forma independiente.

Decisión: Ejecuta pruebas de caos que inyecten el fallo específico (ruta de overflow) y verifica que el consumidor rechace datos inválidos y continúe de forma segura.

11) Detectar reinicios correlacionados (señal de modo común)

cr0x@server:~$ kubectl get events -n guidance --sort-by=.lastTimestamp | tail -n 10
10m   Warning   BackOff     pod/irs-a-7c6b9f9c7b-2m8qk   Back-off restarting failed container
10m   Warning   BackOff     pod/irs-b-7c6b9f9c7b-q9d2p   Back-off restarting failed container
10m   Normal    Pulled      pod/irs-a-7c6b9f9c7b-2m8qk   Container image pulled
10m   Normal    Pulled      pod/irs-b-7c6b9f9c7b-q9d2p   Container image pulled

Significado de la salida: Ambas réplicas están fallando por la misma razón al mismo tiempo.

Decisión: Deja de asumir que réplicas equivalen a resiliencia. Introduce versiones independientes, feature flags o despliegues escalonados con canarios.

12) Inspeccionar límites numéricos en esquemas de mensajes (ejemplo protobuf)

cr0x@server:~$ rg -n "int32|int64|sint32|sint64|fixed32|fixed64" schemas/attitude.proto | head -n 20
12:  int32 roll_millirad = 1;
13:  int32 pitch_millirad = 2;
14:  int32 yaw_millirad = 3;
18:  int32 h_velocity_cm_s = 7;

Significado de la salida: Estás codificando velocidad en int32 centímetros/segundo. Bien. Pero debes confirmar los valores máximos con margen.

Decisión: Escribe el valor máximo físicamente posible (más factor de seguridad) y confirma que el tipo puede representarlo en todos los modos de misión.

13) Exigir pruebas basadas en propiedades para rangos numéricos (fuzzing de límites)

cr0x@server:~$ pytest -q tests/test_numeric_bounds.py -k "int16_guard"
1 passed, 0 failed, 0 skipped

Significado de la salida: Tienes cobertura automatizada que prueba valores cerca y más allá de los límites.

Decisión: Requiere estas pruebas para cualquier código que convierta tipos o serialice palabras de telemetría/control.

14) Verificar que el comportamiento de modo seguro sea alcanzable (feature flag / conmutador de modo)

cr0x@server:~$ curl -s localhost:9000/status | jq
{
  "mode": "SAFE_DEGRADED",
  "reason": "overflow_guard_triggered",
  "attitude_valid": true,
  "velocity_valid": false
}

Significado de la salida: El componente no se cayó; entró en un modo degradado y marcó claramente qué es válido.

Decisión: Prefiere degradación explícita sobre fallo implícito. Haz que los consumidores downstream respeten las banderas de validez o rechacen campos inválidos.

Segundo chiste corto (y último): Si tus comprobaciones de rango son “para añadir más tarde”, felicitaciones: has inventado una lista de TODO propulsada por cohete.

Manual de diagnóstico rápido

Cuando un sistema de repente deriva hacia un comportamiento sin sentido —giros bruscos en bucles de control, telemetría basura, reinicios en cascada— no empieces reescribiendo código. Empieza encontrando el cuello de botella y el límite. Aquí tienes un orden práctico de triage que funciona para cohetes y servicios web.

1) Confirma el modo de fallo: crash, salida incorrecta o interpretación errónea

  • Crash: procesos salen, pods reinician, systemd reporta señales. Busca traps, abortos, excepciones.
  • Salida incorrecta: servicio sigue activo pero emite valores imposibles (negativos donde no corresponden, NaNs, floats extremos).
  • Interpretación errónea: consumidor parsea mal tramas o trata diagnósticos como datos.

2) Comprueba fallas por modo común entre redundancias

Si primaria y respaldo fallan en segundos, asume causa compartida: binario compartido, configuración compartida, dependencia compartida, patrón de entrada compartido. La redundancia no es tu causa raíz; es una pista.

3) Identifica el límite que cambió

Pregunta: ¿qué valor se hizo más grande, más pequeño o cambió de unidades? Nueva trayectoria, nueva carga, nuevo tier de cliente, nuevo modelo, nueva localidad, hardware nuevo. Aquí vivió Ariane 5: un nuevo envolvente de vuelo alimentando suposiciones numéricas antiguas.

4) Valida el contrato de datos en las interfaces

La mayoría de fallas catastróficas ocurren entre componentes: serialización, deriva de esquemas, desajuste de unidades, endian, o truncamiento silencioso. Confirma el tipo de mensaje y los rangos numéricos en el límite.

5) Solo entonces optimiza o refactoriza

La optimización tiende a eliminar margen de seguridad (tipos más pequeños, menos comprobaciones, caminos más rápidos). Durante el diagnóstico quieres más observabilidad y más comprobaciones, no menos. Primero corrige; segundo hazlo rápido; tercero hazlo barato.

Errores comunes: síntoma → causa raíz → solución

Síntoma: “Funcionó en simulación, falla en producción”

Causa raíz: El envolvente simulado no incluía los rangos de peor caso (o usó valores tipo Ariane 4).

Solución: Amplía los vectores de prueba para incluir valores extremos pero físicamente posibles; añade pruebas basadas en propiedades; exige pruebas de rango para cualquier conversión de estrechamiento.

Síntoma: Primario y respaldo fallan casi simultáneamente

Causa raíz: Falla por modo común: software/config idéntico viendo entradas idénticas.

Solución: Añade diversidad (implementación o versión distinta), comportamientos escalonados (uno limita, otro se apaga) y valida la conmutación bajo la inyección específica del fallo.

Síntoma: El downstream toma decisiones erráticas tras fallar un componente upstream

Causa raíz: Datos de diagnóstico o inválidos son interpretados como válidos debido a contratos débiles.

Solución: Añade tipos de trama explícitos y banderas de validez; rechaza datos inválidos; falla cerrado para decisiones de control; evita parseos de “mejor esfuerzo” en rutas de seguridad.

Síntoma: Tarea de fondo “no esencial” dispara incidentes en ventanas críticas

Causa raíz: Cómputo innecesario sigue ejecutándose en la fase crítica, consumiendo CPU o activando rutas de código raras.

Solución: Desactiva o blinda tareas no esenciales tras transiciones de estado; usa programación consciente del modo; demuestra que los caminos de código post-despegue (o pico) son mínimos.

Síntoma: Un pequeño cambio de código causa inestabilidad masiva

Causa raíz: Cambiaste representación numérica, unidades o comportamiento de saturación; el sistema dependía de suposiciones no documentadas.

Solución: Trata cambios de tipo numérico como cambios de interfaz. Versiona el contrato. Añade pruebas de compatibilidad y replays de tráfico.

Síntoma: La monitorización muestra “promedios normales” mientras la realidad está rota

Causa raíz: Fallo en la cola (overflow raro) oculto por promedios; faltan histogramas/percentiles.

Solución: Monitoriza distribuciones, no solo medias. Rastrea min/max, percentiles y conteo de valores clampados/invalidos.

Listas de verificación / plan paso a paso

Checklist: Prevenir fallos por desbordamiento numérico en código crítico de seguridad o monetización

  1. Inventario de conversiones numéricas (float-a-int, int-a-enteros más pequeños, conversiones de unidades). Si no puedes listarlas, no puedes controlarlas.
  2. Define rangos esperados con margen para cada valor, incluyendo “nuevos modos de misión” (vehículo nuevo, región nueva, modelo nuevo, nueva clase de cliente).
  3. Haz las conversiones explícitas y protegidas: comprobación de rango, clamp/saturate y registro de evento estructurado.
  4. Estandariza unidades en las interfaces. Si debes convertir, hazlo una vez en el borde y registra la unidad en el esquema.
  5. Decide la política de fallo: detenerse, degradarse, limitar o rechazar. Para sistemas de control, prefiere degradación segura con banderas de validez claras.
  6. Prueba más allá del envolvente: no solo “máximo esperado”, sino “máximo plausible”, más valores adversariales (NaN, infinito, negativo, enorme).
  7. Verifica independencia de la redundancia: versión diferente, flags del compilador distintos, camino de código distinto, o al menos comportamiento diferente ante overflow.
  8. Exige pruebas de contrato entre productor y consumidor, incluyendo esquema, unidades y límites de valor.
  9. Observa el clamping: dashboards para “eventos de saturación” y “datos inválidos rechazados”. Deben ser casi cero y ser investigados.
  10. Realiza un game day que inyecte el escenario exacto de overflow y pruebe que el sistema permanece seguro (no solo “activo”).

Checklist: Reutilizar código sin importar suposiciones antiguas

  1. Lista cada módulo reutilizado y su envolvente operativo original.
  2. Para cada módulo, anota qué cambió en el nuevo sistema (entradas, rangos, tiempos, unidades, rendimiento).
  3. Vuelve a ejecutar la verificación contra los nuevos envolventes; no aceptes “ya estaba cualificado” como argumento.
  4. Elimina o desactiva lógica que no se necesita en fases críticas. Menos código en ejecución significa menos sorpresas.
  5. Documenta la razón de cualquier conversión no protegida: por qué no puede desbordarse y qué lo garantiza.

Preguntas frecuentes

1) ¿Fue la falla de Ariane 5 “solo un desbordamiento entero”?

No. El desbordamiento fue el desencadenante. La falla fue sistémica: reutilización sin revalidación, código no esencial ejecutándose en vuelo, manejo de excepciones que apagó un componente crítico y consumidores que interpretaron salida inválida.

2) ¿Por qué el sistema de referencia inercial de respaldo no salvó el cohete?

Porque falló de la misma forma, por la misma razón, al mismo tiempo. Eso es una falla por modo común. La redundancia solo ayuda cuando los fallos son suficientemente independientes.

3) ¿Por qué se usó una conversión de float a int en primer lugar?

Los campos enteros de tamaño fijo son comunes para mensajería, formatos de telemetría y rendimiento determinista. La conversión es normal. Lo peligroso fue la ausencia de una protección para valores fuera de rango.

4) ¿No podrían simplemente “capturar la excepción” y continuar?

Podrían haberla manejado, pero “continuar” debe significar “continuar de forma segura”. Opciones incluyen limitar con una bandera de validez, cambiar a un modo degradado o desactivar ese cálculo después del despegue.

5) ¿Por qué el guiado respondió tan violentamente tras la falla del IRS?

El guiado necesita información de actitud y tasas. Cuando recibió datos inválidos (o diagnósticos tratados como datos), calculó comandos de control incorrectos. En control en lazo cerrado, entradas malas pueden producir salidas agresivas rápidamente.

6) ¿Cuál es la lección de SRE aquí?

Las suposiciones son dependencias de producción. Si reutilizas componentes, debes revalidar suposiciones bajo nuevos patrones de carga. Además: monitoriza eventos de límite, no solo tiempo de actividad.

7) ¿Es “fallar rápido” algo malo?

Fallar rápido es excelente cuando puedes reintentar o enrutar alrededor del fallo. En sistemas donde no puedes reintentar (sistemas de control, sistemas de seguridad, acciones irreversibles), necesitas degradación segura y reglas estrictas de validez de datos.

8) ¿Cómo evito que “diagnósticos se interpreten como datos” en sistemas distribuidos?

Usa tipos de mensaje explícitos, validación de esquema, contratos versionados y consumidores defensivos que se nieguen a actuar sobre tramas inválidas o inesperadas. Trata errores de parseo como eventos de seguridad.

9) ¿Agregar más pruebas arregla esta clase de problemas?

Sólo si las pruebas incluyen los límites correctos. Diez mil pruebas de camino feliz no atraparán una guardia de overflow faltante. Enfócate en pruebas de envolvente, fuzzing cerca de límites numéricos y pruebas de contrato entre interfaces.

10) ¿Cuál es el tipo correcto de redundancia?

Redundancia con independencia: implementaciones diferentes, versiones distintas, compiladores distintos, modalidades de sensor diferentes, o al menos comportamiento de fallo diferente. Si ambos lados comparten el mismo bug, compraste dos billetes para un solo accidente.

Próximos pasos que deberías tomar

La lección de Ariane 5 no es “no cometer errores”. Es “deja de confiar en suposiciones que no están reforzadas”. Un cohete es solo un despliegue de producción muy caro con menos opciones de rollback.

  1. Haz un inventario de conversiones numéricas en tus sistemas críticos esta semana. Si no sabes dónde están, no puedes defenderlas.
  2. Activa warnings estrictos del compilador y convierte las conversiones de estrechamiento en una puerta de revisión.
  3. Añade guardas en tiempo de ejecución en interfaces: comprobaciones de rango, clamp con banderas o rechazo con alarma.
  4. Demuestra la independencia de la redundancia con inyección de fallos. Si ambas réplicas mueren juntas, llama a las cosas por su nombre: un sistema duplicado.
  5. Elimina trabajo no esencial en ventanas críticas. Si no se necesita después del despegue, no lo ejecutes después del despegue.
  6. Monitoriza distribuciones y eventos límite (conteos de saturación, tramas inválidas rechazadas), no solo promedios y tiempo de actividad.

Haz eso, y no solo evitarás un momento Ariane 5. También lanzarás más rápido, dormirás mejor y pasarás menos mañanas explicando a la dirección por qué las gráficas parecían bien mientras la realidad ardía.

← Anterior
Cifrado de respaldos Docker: protege secretos sin romper restauraciones
Siguiente →
Debian/Ubuntu «Conectado, pero sin internet»: soluciones de enrutamiento, puerta de enlace y enrutamiento por políticas

Deja un comentario