Mars Climate Orbiter : le décalage d’unités qui a fait perdre un vaisseau spatial

Cet article vous a aidé ?

Vous pouvez exploiter une infrastructure de classe mondiale et quand même perdre toute la mission parce qu’un nombre « avait l’air juste ».
Pas d’explosion, pas de NaN, pas en feu. Juste faux en silence — mauvaises unités, mauvaise hypothèse, mauvaise interface — jusqu’à ce que
la physique fasse l’autopsie à votre place.

Mars Climate Orbiter est le cas canonique : un engin spatial a été perdu parce qu’une partie du système produisait
des données en unités impériales tandis qu’une autre attendait le système métrique. Si vous pensez « ça n’arriverait jamais dans mon organisation »,
félicitations — vous avez l’état d’esprit exact qui le rend possible chez vous.

Ce qui s’est réellement passé (et pourquoi ça comptait)

Le Mars Climate Orbiter (MCO) a été lancé en 1998 et devait étudier le climat martien : atmosphère, poussière,
vapeur d’eau, changements saisonniers. Il servait aussi de relais de communication pour son atterrisseur frère. Il n’a jamais
pu faire tout cela. Lors de l’insertion en orbite martienne en septembre 1999, le vaisseau a volé trop bas, a probablement
subi des forces atmosphériques pour lesquelles il n’était pas conçu, et a été perdu. Soit il a brûlé, soit il a été éjecté
sur une orbite solaire ; en opérations, « perdu » est un euphémisme pour « nous n’avons aucune idée d’où il est et ne pouvons
plus communiquer avec lui ».

La cause racine, en termes d’ingénierie simples : l’équipe de navigation a utilisé des données de performance des propulseurs
exprimées en pound-force secondes, tandis que le logiciel de navigation attendait des newton secondes. Même concept physique
(impulsion). Système d’unités différent. Ce décalage a produit un biais constant dans les estimations de trajectoire.
Le vaisseau n’a pas soudainement fait quelque chose de fou. Il a fait quelque chose de systématiquement incorrect assez longtemps
pour que l’erreur devienne fatale.

Et voici la leçon opérationnelle que la plupart des gens manquent : ce n’était pas « une coquille ». C’était une défaillance de la
discipline d’interface. Le système avait plusieurs occasions de le détecter — revues, contrôles, tendances télémétriques, simulations,
réponse aux anomalies. La défaillance n’était pas un bug isolé ; c’était une pile de décisions non-bug qui ont maintenu le bug en vie.

Blague n°1 : Les décalages d’unités sont les seuls bugs qu’on peut corriger à la fois avec un compilateur et un mètre ruban.

Faits rapides et contexte historique

  • Faisait partie du programme Mars Surveyor ’98 : MCO était jumelé au Mars Polar Lander, visant un rythme « plus rapide, meilleur, moins cher ».
  • Lancé sur une fusée Delta II : Un véhicule de travail ; le lancement lui-même n’était pas le problème.
  • Perte lors de l’insertion en orbite : Phase la plus risquée : une fenêtre étroite où la précision de la navigation devient une question de survie.
  • Le décalage concernait des données d’impulsion : Les petites corrections de trajectoire du vaisseau dépendaient d’une modélisation précise des tirs de propulseurs.
  • Impérial vs métrique : Plus précisément, pound-force secondes (lbf·s) vs newton secondes (N·s). Un lbf vaut environ 4.44822 N.
  • La tendance opérationnelle était visible : Les résidus de navigation et les prédictions de trajectoire dérivaient ; le système était « décalé » d’une façon qui aurait dû déclencher une vérification.
  • Le contrôle d’interface importe : L’attente d’unités était documentée ; l’application et la validation ne l’étaient pas.
  • La pression programme était réelle : Contraintes de coût et de calendrier ont réduit la redondance et rendu culturellement acceptable le « on le vérifiera plus tard ».

Anatomie de la défaillance : où le système a menti

Le nombre n’était pas faux ; sa signification l’était

Dans le travail sur la fiabilité, il existe une catégorie spéciale de défaillances : le « non-sens correctement calculé ». Les calculs sont justes.
Le logiciel « fonctionne ». L’instrumentation rapporte quelque chose de cohérent. Et l’opérateur l’interprète selon une mauvaise hypothèse partagée.
Ce n’est pas un bug dans une ligne de code. C’est un bug dans le contrat entre équipes, outils et réalité.

Les décalages d’unités sont particulièrement sournois parce qu’ils sont plausibles. Une impulsion de propulseur « 10 » peut être 10 N·s
ou 10 lbf·s. Rien ne semble absurdes. Et si le système aval reçoit l’une alors qu’il en attend une autre,
l’erreur s’applique linéairement — pas de pics dramatiques, pas de chaos immédiat, juste un biais persistant.

Le biais bat le bruit

Les équipes d’exploitation sont entraînées à chercher les pics : changements soudains, seuils, budgets d’erreur dépassés en quelques minutes.
Le biais est plus lent et plus patient. Le biais peut rester à l’intérieur des tolérances pendant des jours tout en grignotant votre marge.
Il ressemblera à « on est un peu à côté, mais toujours dans la variance attendue », jusqu’à ce que vous sortiez du couloir pendant la seule manœuvre qui compte.

Les interfaces sont l’endroit où la fiabilité meurt

Le code le plus dangereux dans un système est le code qui « ne fait que » transformer des données entre deux sous-systèmes. La couche de mappage.
Le job ETL. L’adaptateur. Le script qui réécrit un champ. L’enveloppe qui « corrige » une API. C’est là que l’information de type disparaît, où la sémantique est écartée,
où la couverture de test devient mince, et où le « tout le monde sait » remplace la vérification.

Dans le MCO, l’interface entre la modélisation de poussée/données opératoires et le logiciel de navigation a effectivement réduit la sémantique à des nombres.
Les humains « savaient » les unités. Le système ne les connaissait pas. Quand les humains ont changé de rôle, ou quand les hypothèses ont dérivé, le système n’avait
aucune garde.

Une citation sur la fiabilité à afficher sur votre écran

Gene Kranz (directeur de vol NASA) a dit : « Failure is not an option. » Si vous l’avez vu imprimé sur des posters,
vous avez aussi vu des gens l’utiliser comme substitut à l’ingénierie. Voici la version adulte : l’échec est toujours une option.
Votre travail est de rendre coûteux le passage d’une petite erreur à une catastrophe.

Transferts, interfaces et la « frontière des unités »

Parlons de ce que signifie vraiment « décalage d’unités » dans une organisation moderne. Ce n’est pas seulement métrique vs impérial. C’est tout
décalage sémantique :

  • millisecondes vs secondes
  • octets vs kibioctets
  • UTC vs heure locale
  • plages inclusives vs exclusives
  • valeurs signées vs non signées
  • taux par seconde vs par minute
  • tailles compressées vs non compressées
  • « customer_id » signifiant compte vs utilisateur vs foyer

Ces défaillances traversent les frontières d’équipes. Cela compte parce que les équipes s’optimisent localement. Une équipe livrant un composant peut
documenter les unités et passer à autre chose. L’équipe réceptrice peut lire ce doc une fois, intégrer, puis compter sur sa mémoire.
La mémoire n’est pas un contrôle opérationnel.

Les ICD ne sont pas de la paperasserie ; ce sont des contraintes exécutables

Les programmes spatiaux utilisent des Interface Control Documents (ICD) pour spécifier comment les systèmes communiquent : formats, timing, unités, tolérances.
Dans les systèmes d’entreprise, nous avons souvent la même chose sous des formes plus faibles : spécifications OpenAPI, schémas protobuf, exemples JSON,
contrats de données, runbooks, « savoir tribal ». Le mode défaillant est le même lorsque ces artefacts ne sont pas appliqués par des tests
et des vérifications à l’exécution.

Si votre spécification d’interface n’est pas testée, ce n’est pas une spécification. C’est une histoire pour s’endormir.

Pourquoi les revues ne détectent pas cela de façon fiable

Les gens aiment dire : « Comment la revue de code a-t-elle pu manquer ça ? » Parce que la revue est un processus d’échantillonnage humain, pas un système de preuve.
Les décalages d’unités sont une erreur sémantique. Le code peut sembler parfaitement raisonnable. Le relecteur peut ne pas avoir le contexte complet,
peut ne pas connaître l’unité en amont, peut ne pas remarquer qu’une variable nommée impulse devrait être impulse_ns.

Les revues aident, mais n’imposent pas. L’application vient de :

  • types d’unités ou conventions de nommage explicites
  • tests de contrat entre producteur et consommateur
  • assertions et validations à l’exécution
  • simulations bout en bout dans des conditions réalistes
  • contrôles de sanity de télémétrie avec alarmes réglées sur le biais, pas seulement les pics

À quoi cela ressemble dans les systèmes de production

Si vous gérez du stockage, des flottes ou des plateformes de données, vous avez déjà vécu une version du MCO. Le décalage d’unités devient :

  • un SLO de latence mesuré en millisecondes, mais le tableau de bord trace en secondes
  • un limiteur de débit configuré en requêtes/minute, mais le client suppose requêtes/seconde
  • une rétention de sauvegarde « 30 » interprétée en jours par un job et en heures par un autre
  • un système de stockage rapportant des « GB » (décimal) tandis que la finance attend des « GiB » (binaire)

La morale n’est pas « utilisez le métrique ». La morale est : rendez les unités explicites et vérifiables à chaque frontière.

Blague n°2 : La façon la plus rapide de réduire la dépense cloud est de traiter accidentellement des millisecondes comme des secondes — jusqu’à ce que le CFO voie votre rapport d’incident.

Guide de diagnostic rapide

Quand vous suspectez une interface/une unité/un décalage sémantique, ne commencez pas par réécrire du code ou ajouter des retries. Les retries
transforment une valeur erronée en valeur erronée plus rapidement. Au lieu de cela, exécutez un diagnostic en trois passes qui priorise « ce qui a changé »
et « quelles hypothèses ne sont pas vérifiées ».

Première étape : prouver le contrat à la frontière

  1. Capturez une charge utile réelle (trame télémétrique, requête/réponse API, enregistrement de fichier) depuis la production.
  2. Comparez-la au spec (schéma, ICD, définition protobuf, attentes d’unités).
  3. Vérifiez les unités et cadres de référence : base temporelle, système de coordonnées, facteurs d’échelle, compression.

Si la charge utile ne s’auto-décrit pas en unités, supposez que vous êtes déjà en danger.

Deuxième étape : cherchez le biais, pas seulement les pics

  1. Tracez les résidus (prédiction vs observation) au fil du temps.
  2. Cherchez une dérive monotone ou un offset constant après un déploiement ou un changement de configuration.
  3. Comparez plusieurs signaux indépendants si possible (par ex., estimation nav vs capteur brut, métrique dérivée API vs compte brut de logs).

Troisième étape : reproduisez avec une entrée « connue bonne »

  1. Trouvez un fixture : une charge utile historique, un jeu de données doré, une séquence de manœuvre enregistrée.
  2. Exécutez le pipeline/composant bout en bout.
  3. Confirmez que la sortie correspond à l’attendu dans les tolérances.

Si vous ne pouvez pas définir « attendu », votre système n’est pas testable, et la fiabilité devient un jeu de hasard.

Tâches pratiques : commandes, sorties, décisions

Ci-dessous se trouvent des tâches pratiques que vous pouvez exécuter sur de vrais systèmes Linux pour détecter des décalages d’unités/contrats, de la dérive, et des « nombres sans sens ».
Chaque tâche inclut une commande, ce que signifie la sortie, et la décision à prendre.

Task 1: Find where units are implied (grep for suspicious fields)

cr0x@server:~$ rg -n "ms|millis|seconds|secs|ns|newton|lbf|pound|unit|scale|multiplier|conversion" /etc /opt/app/config
/opt/app/config/telemetry.yaml:41:  impulse_scale: 1.0
/opt/app/config/telemetry.yaml:42:  impulse_unit: "lbf*s"

Ce que cela signifie : Votre config indique explicitement lbf·s. Bien — au moins c’est écrit.

Décision : Vérifiez que chaque consommateur attend la même unité ; ajoutez une assertion au démarrage qui rejette les chaînes d’unités inconnues.

Task 2: Check service build/version skew across a fleet

cr0x@server:~$ ansible -i inventory all -m shell -a 'myservice --version'
host-a | CHANGED | rc=0 >>
myservice 2.3.1
host-b | CHANGED | rc=0 >>
myservice 2.2.9

Ce que cela signifie : Versions mixtes. Si la gestion des unités a changé entre versions, vous pouvez avoir une sémantique en split-brain.

Décision : Geler les rollouts ; forcer la convergence vers une seule version ; exécuter les tests de contrat contre le nœud le plus ancien encore en service.

Task 3: Inspect OpenAPI/protobuf/schema for explicit unit annotations

cr0x@server:~$ jq '.components.schemas.Telemetry.properties.impulse' openapi.json
{
  "type": "number",
  "description": "Thruster impulse",
  "example": 12.5
}

Ce que cela signifie : « Impulse » sans unités est un piège. L’exemple n’aide pas.

Décision : Mettre à jour les descriptions du schéma pour inclure les unités ; ajouter l’extension x-unit ou renommer les champs (par ex., impulse_ns).

Task 4: Validate a payload against schema (catch missing/extra fields)

cr0x@server:~$ python3 -m jsonschema -i sample_payload.json telemetry_schema.json
sample_payload.json validated successfully

Ce que cela signifie : La forme structurelle est correcte. Cela ne valide pas la sémantique comme les unités.

Décision : Ajouter une validation sémantique : plages acceptables et balises d’unité ; envisager de rejeter les payloads sans métadonnées d’unités.

Task 5: Spot scale-factor drift in time series (quick-and-dirty)

cr0x@server:~$ awk '{sum+=$2; n++} END{print "avg=",sum/n}' impulse_residuals.txt
avg= 4.43

Ce que cela signifie : La moyenne des résidus est biaisée (pas centrée autour de zéro). C’est un indicateur d’un possible décalage d’échelle/unité.

Décision : Comparez l’amplitude du biais aux facteurs de conversion connus (par ex., ~4.448 pour lbf→N). Si ça correspond, cessez de deviner et enquêtez la frontière d’unités.

Task 6: Check NTP/time sync (time base mismatches mimic unit bugs)

cr0x@server:~$ timedatectl
               Local time: Wed 2026-01-22 10:48:11 UTC
           Universal time: Wed 2026-01-22 10:48:11 UTC
                 RTC time: Wed 2026-01-22 10:48:11
                Time zone: UTC (UTC, +0000)
System clock synchronized: yes
              NTP service: active
          RTC in local TZ: no

Ce que cela signifie : L’heure est saine et en UTC. Bien. Les décalages d’unités se cachent souvent derrière la dérive temporelle.

Décision : Si non synchronisé, corrigez d’abord l’heure ; sinon vous risquez de mal diagnostiquer causalité et corrélation.

Task 7: Confirm log timestamps and units in structured logs

cr0x@server:~$ jq -r '.ts,.duration_ms' /var/log/myservice/events.json | head
2026-01-22T10:46:01.120Z
250
2026-01-22T10:46:01.421Z
280

Ce que cela signifie : Les noms de champs de durée incluent « _ms ». C’est un petit miracle.

Décision : Faire respecter les conventions de suffixe (_ms, _bytes, _ns) via du lint et une checklist de revue de code.

Task 8: Trace an API call end-to-end to see unit translation points

cr0x@server:~$ curl -sS -H 'X-Request-ID: diag-123' http://localhost:8080/telemetry | jq .
{
  "impulse": 12.5,
  "impulse_unit": "lbf*s",
  "burn_duration_ms": 250
}

Ce que cela signifie : Le payload auto-décrit les unités pour l’impulsion mais pas forcément pour tout le reste ; la durée est explicite.

Décision : Assurez-vous que tous les champs avec unités sont explicites ; si des consommateurs ignorent impulse_unit, considérez cela comme un bug et refusez la compilation.

Task 9: Confirm which config is live (avoid “fixed in git, broken in prod”)

cr0x@server:~$ systemctl show myservice -p FragmentPath -p Environment
FragmentPath=/etc/systemd/system/myservice.service
Environment=UNIT_SYSTEM=imperial IMPULSE_UNIT=lbf*s

Ce que cela signifie : Le service est explicitement réglé sur impérial via l’environnement. Cela peut écraser les valeurs par défaut du code.

Décision : Suivre la provenance des configs ; si un indicateur de système d’unités existe, verrouillez-le et alertez sur les changements.

Task 10: Identify recent changes (units break at the edges of change)

cr0x@server:~$ git log -n 5 --oneline -- config/telemetry.yaml
a91b2cf set impulse scale
71c0d1a rename burn_duration to burn_duration_ms
9f10bb2 initial telemetry config

Ce que cela signifie : Quelqu’un a touché l’échelle d’impulsion récemment. C’est votre principal suspect.

Décision : Reliez le changement à la timeline de l’incident ; exigez un test prouvant que la nouvelle échelle correspond aux règles physiques/jeu métier.

Task 11: Check metric queries for unit confusion (Prometheus example)

cr0x@server:~$ promtool query instant http://localhost:9090 'rate(myservice_impulse_total[5m])'
myservice_impulse_total{job="myservice"} 0.21

Ce que cela signifie : Un taux est renvoyé, mais l’unité est floue. Est-ce N·s par seconde ? lbf·s par seconde ? Juste des « impulsions » ?

Décision : Encodez les unités dans les noms de métriques et le HELP ; par ex., myservice_impulse_newton_seconds_total et rejetez les métriques ambiguës en revue.

Task 12: Verify dashboard axis units (Grafana JSON check)

cr0x@server:~$ jq -r '.panels[] | select(.title=="Burn duration") | .fieldConfig.defaults.unit' dashboard.json
ms

Ce que cela signifie : Le panneau utilise explicitement millisecondes. Cela empêche un opérateur d’interpréter « 250 » comme « 250 secondes ».

Décision : Standardiser les unités des tableaux de bord ; appliquer via CI sur les exports JSON des tableaux de bord.

Task 13: Detect silent scaling changes in binaries (strings can betray you)

cr0x@server:~$ strings /usr/local/bin/myservice | rg -n "lbf|newton|N\\*s|unit"
10233:IMPULSE_UNIT
10234:lbf*s

Ce que cela signifie : Le binaire contient une chaîne d’unité par défaut. Si la config est absente, ce défaut peut s’appliquer silencieusement.

Décision : Remplacez les valeurs par défaut par un comportement « doit spécifier » pour la config portant des unités ; échouez rapidement au démarrage.

Task 14: Confirm your ingestion pipeline preserves unit fields

cr0x@server:~$ jq -r '.impulse_unit' raw_event.json enriched_event.json
lbf*s
null

Ce que cela signifie : L’enrichissement a supprimé impulse_unit. Maintenant l’analytics en aval supposera une unité.

Décision : Considérez les champs d’unité comme obligatoires ; bloquez les déploiements de pipeline qui les suppriment ; ajoutez des tests d’évolution de schéma.

Trois mini-récits d’entreprise (suffisamment réels pour faire mal)

Mini-récit 1 : L’incident causé par une mauvaise hypothèse

Une plateforme de paiements avait deux services : l’un calculait un « score de risque », l’autre appliquait des règles antifraude.
Le service de scoring émettait un champ appelé score comme entier de 0 à 1000. Le service d’application pensait qu’il s’agissait de 0.0 à 1.0.
Les deux équipes avaient une documentation. Les deux équipes croyaient que l’autre l’avait lue. Aucune n’avait de test de contrat.

L’intégration passait les tests de base parce que les données d’exemple utilisaient de petits scores et que les seuils des règles étaient lâches
lors du déploiement initial. Puis un nouveau modèle a été livré avec une dispersion plus large. Soudain, l’application a commencé à rejeter
une tranche propre du trafic. Pas tout. Juste assez pour déclencher le support client et gâcher une semaine autrement calme.

L’astreinte a d’abord chassé latence et erreurs de base de données parce que le symptôme ressemblait à des retries et timeouts : les clients
soumettaient à nouveau des paiements. La plateforme ne plantait pas ; elle disait juste « non » plus souvent, ce qui est opérationnellement pire
parce que cela ressemble à une décision de politique, pas à une défaillance technique.

La correction fut embarrassante de simplicité : renommer le champ en score_milli (0–1000), ajouter score_unit, et ajouter
un test d’intégration qui échoue si un score > 1.0 arrive à l’API « ratio ». La vraie correction fut culturelle : obliger les deux équipes
à traiter les contrats de données comme du code, pas des pages de wiki.

Mini-récit 2 : L’optimisation qui s’est retournée contre eux

Une équipe de stockage a optimisé un pipeline d’ingestion en passant de JSON à un format binaire compact et a « sauvé des octets » en
supprimant des champs descriptifs. Parmi les champs supprimés : block_size et son unité. Tout le monde « savait » que c’était des octets.
Sauf l’équipe consommatrice qui l’avait historiquement traité comme des kilo-octets parce que leur UI l’étiquetait « KB ».

Le premier signe de problème fut subtil : les tableaux de bord montraient un changement lent et continu de la « taille moyenne de bloc ». Aucune alerte.
La plateforme continuait de fonctionner. Mais la planification de capacité a commencé à dériver. L’organisation a commencé à acheter du matériel trop tôt
dans une région et trop tard dans une autre. Ce décalage est la façon dont on reçoit un appel à 3h du matin de la finance.

L’optimisation a aussi supprimé la capacité de sanity-check des valeurs dans les logs. Les opérateurs ont perdu en observabilité en échange de quelques pourcents
de gain de débit. C’est un mode de défaillance courant : une optimisation locale qui dégrade la capacité globale du système à détecter les erreurs.

Ils ont finalement réintroduit les unités — pas en ajoutant une chaîne à chaque enregistrement (ce qui serait coûteux) — mais en versionnant le schéma
et en intégrant les métadonnées d’unité dans le registre de schéma. Ils ont aussi ajouté un consommateur canari qui valide des propriétés statistiques
(comme les tailles de bloc typiques) et avertit les humains quand la distribution change au-delà des tolérances attendues.

Mini-récit 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise

Une équipe gérant une flotte de sauvegardes de bases de données avait une habitude peu glamour : chaque test de restauration enregistrait non seulement « succès », mais
durée de restauration, octets restaurés et débit avec des unités explicites dans la sortie. Ils stockaient aussi ces résultats dans une petite
base de séries temporelles. C’était ennuyeux. Les gens se plaignaient parfois du « surcoût de processus ».

Puis une mise à jour de fournisseur a changé un paramètre de compression par défaut. Le débit de restauration a chuté et la durée a augmenté, mais le job renvoyait toujours
le code de sortie 0. Personne ne l’aurait remarqué jusqu’au jour où ils auraient eu besoin d’une restauration — moment où il aurait été trop tard pour renégocier le RTO avec la réalité.

Parce que l’équipe disposait de métriques tendance et explicites en unités, ils ont détecté la régression lors d’une revue hebdomadaire. Ils ont rollbacké le changement,
documenté le nouveau défaut, et ajouté une garde : si le débit de restauration descend sous un seuil pendant deux tests consécutifs, le pipeline bloque les rollouts en production.

La « pratique ennuyeuse » n’était pas le logging. C’était la discipline de faire du test de restauration un signal production de première classe.
C’est ainsi qu’on survit : pas par l’héroïsme, mais par des habitudes qui supposent que le système va mentir à moins d’être contraint de le faire.

Erreurs courantes : symptômes → cause racine → fix

1) « Tout est dans les limites » jusqu’à ce que ça ne le soit plus

Symptômes : Dérive lente des résidus, précision qui se dégrade progressivement, incidents intermittents « presque dangereux ».

Cause racine : Biais introduit par un décalage d’échelle/unité ; alerting réglé sur les pics et non sur les offsets.

Fix : Ajouter la détection de biais : alarmes sur moyenne glissante, contrôles de type CUSUM, et validation explicite des conversions à l’ingestion.

2) Tableaux de bord confiants, mauvaise abscisse

Symptômes : Les opérateurs jurent que le système va bien parce que les graphiques semblent normaux ; les incidents sont « surprenants ».

Cause racine : La couche de visualisation suppose des unités ; les panneaux utilisent le format par défaut ; des champs ont été renommés sans mettre à jour les unités.

Fix : Faire respecter les unités des tableaux de bord via CI ; imposer des suffixes d’unité dans les noms de métriques ; ajouter des labels « unité » quand supporté.

3) La documentation d’interface existe, mais la réalité diverge

Symptômes : Les équipes producteur et consommateur citent toutes deux la doc ; l’intégration casse quand même.

Cause racine : Les docs ne sont pas liées à des tests automatisés ; des changements de contrat ont été livrés sans validation consommateur.

Fix : Implémenter des tests de contrat pilotés par le consommateur ; traiter les changements de schéma comme des releases versionnées avec portes de compatibilité.

4) « On convertira plus tard »

Symptômes : Logique de conversion dupliquée dans plusieurs services ; sorties incohérentes ; bugs difficiles à reproduire.

Cause racine : Pas de source de vérité unique pour les conversions d’unités ; code de conversion ad hoc dans chaque client.

Fix : Centraliser la conversion dans une bibliothèque bien testée ; interdire les conversions locales sauf justification et revue.

5) Suppression silencieuse de champs sémantiques dans les pipelines

Symptômes : Rapports aval divergent après une « optimisation de performance » ; les champs d’unité deviennent nuls.

Cause racine : Les jobs ETL/enrichissement traitent les champs d’unité comme optionnels ; l’évolution de schéma n’est pas testée.

Fix : Rendre les champs porteurs d’unités obligatoires ; ajouter des contrôles de pipeline pour détecter les champs perdus ; versionner les schémas et appliquer la compatibilité.

6) Sémantique mixte de versions pendant un déploiement

Symptômes : Seuls certains nœuds se comportent « mal » ; l’incident semble aléatoire ; le canari a l’air correct mais la flotte ne l’est pas.

Cause racine : La gestion des unités a changé dans une nouvelle version ; un déploiement partiel provoque une interprétation incohérente.

Fix : Utiliser une compatibilité stricte en arrière ; inclure des métadonnées d’unité versionnées ; bloquer les rollouts quand une sémantique mixte est détectée.

Listes de contrôle / plan pas à pas

Plan pas à pas : rendre difficile l’expédition des décalages d’unités

  1. Inventoriez les champs porteurs d’unités dans les APIs, télémétrie, logs, métriques et configs. Si c’est un nombre, supposez qu’il a des unités.
  2. Renommez les champs pour inclure les unités quand c’est pratique : duration_ms, size_bytes, temp_c.
  3. Ajoutez des métadonnées d’unité quand renommer n’est pas possible : champs unit ou annotations de schéma.
  4. Implémentez des tests de contrat entre producteurs et consommateurs ; bloquez les merges en cas d’écart.
  5. Échouez rapidement au démarrage quand la config d’unité est manquante ou inconnue ; les valeurs par défaut sont la survie de l’ambiguïté.
  6. Définissez des plages autorisées (min/max) pour les signaux clés ; les décalages d’unités produisent souvent des valeurs hors plausibilité physique/métier.
  7. Alerting conscient du biais : moyenne glissante, détecteurs de dérive, suivi des résidus.
  8. Fixtures dorées : entrées connues avec sorties connues, incluant les conversions (par ex., lbf→N).
  9. Exécutez des simulations bout en bout sur des payloads réalistes ; les chemins synthétiques heureux sont où les bugs vont se cacher.
  10. Revues opérationnelles : chaque passage d’astreinte inclut « quelles hypothèses faisons-nous sur unités/temps/cadres ? »

Checklist d’intégration pré-vol (utilisez-la sérieusement)

  • Tous les champs numériques ont des unités explicites dans le nom, le schéma ou les métadonnées.
  • Toutes les conversions se font dans une bibliothèque/modulaire unique, pas dispersées.
  • Chaque interface a un test de compatibilité dans CI.
  • Les tableaux de bord spécifient les unités d’axe et ne comptent pas sur les valeurs par défaut.
  • Les alertes incluent la détection de dérive/biais, pas seulement des seuils.
  • Le plan de déploiement inclut l’analyse du comportement en version mixte.
  • Le runbook inclut « décalage d’unités » comme hypothèse de première classe.

FAQ

1) Le Mars Climate Orbiter a-t-il été perdu uniquement à cause d’un bug de conversion d’unités ?

Le décalage d’unités fut la cause technique déclenchante, mais la mission a été perdue parce que plusieurs filets de sécurité n’ont pas
fonctionné : application d’interface, validation, réponse opérationnelle aux anomalies et rigueur des revues.

2) Pourquoi ne l’ont-ils pas détecté plus tôt ?

Parce que l’erreur se manifestait comme un biais progressif plutôt que comme une défaillance évidente. La dérive est facile à rationaliser comme du bruit,
surtout sous pression de planning et quand le système « a l’air stable ».

3) Quel est l’équivalent moderne de lbf·s vs N·s dans les systèmes cloud ?

Secondes vs millisecondes, octets vs MiB, UTC vs heure locale, et unités de taux (par seconde vs par minute) sont les principaux. Aussi « pourcentage » vs « fraction »
(0–100 vs 0–1) revient souvent dans les systèmes ML et de scoring.

4) Devrait-on stocker les unités avec chaque point de données ?

Pas nécessairement avec chaque enregistrement. Vous pouvez stocker les unités dans les schémas, registres ou métadonnées versionnées — tant que
les consommateurs ne peuvent pas les ignorer par erreur et continuer.

5) N’est-ce pas moche de nommer les champs avec des suffixes comme _ms ?

Oui. C’est aussi efficace. La fiabilité est pleine de choses moches qui vous maintiennent en vie : coupe-circuits, retries avec jitter,
et suffixes qui empêchent les humains d’imaginer une signification.

6) Quelle est la meilleure façon d’empêcher les bugs d’unités dans le code ?

Utilisez des types sensibles aux unités quand c’est possible (ou des patterns de strong typedef), centralisez les conversions, et écrivez des tests qui comparent
aux constantes de conversion connues et aux plages plausibles. Plus important : appliquez des contrats entre services.

7) Comment détecter un décalage d’unités quand je suis déjà en incident ?

Cherchez des ratios constants dans les erreurs (par ex., ~1000x, ~60x, ~4.448x). Comparez des mesures indépendantes. Validez la charge utile brute à la frontière
et cherchez des champs méta supprimés.

8) Et si deux équipes ne sont pas d’accord sur les unités et que les deux ont une « documentation » ?

La documentation n’est pas une autorité ; la production l’est. Capturez des payloads réels, écrivez un test de contrat qui encode l’attente d’unité correcte,
et versionnez l’interface pour que l’ambiguïté ne revienne pas en douce.

9) Pourquoi ces défaillances continuent-elles d’arriver même dans des organisations matures ?

Parce que les organisations grandissent plus vite que le contexte partagé. Les interfaces se multiplient, et la sémantique se perd dans les transferts.
La seule solution durable est de rendre les hypothèses exécutables : types, tests, vérifications à l’exécution et schémas appliqués.

Conclusion : prochaines étapes à faire cette semaine

Mars Climate Orbiter n’a pas été vaincu par Mars. Il a été vaincu par une interface qui laissait la signification s’évaporer. C’est le véritable péril opérationnel :
des nombres qui voyagent sans leur sémantique, traversant des frontières organisationnelles où « tout le monde sait » devient « personne n’a vérifié ».

Étapes pratiques :

  1. Choisissez vos 10 principaux champs numériques en télémétrie ou API et rendez les unités explicites (nom ou métadonnées).
  2. Ajoutez un test de contrat qui échoue en cas de décalage d’unités — faites-en un blocage de merge.
  3. Ajoutez une alerte de dérive/biais sur une métrique résiduelle clé.
  4. Retirez un réglage d’unité par défaut et forcez une configuration explicite.
  5. Faites un exercice game day où la faute injectée est « unités modifiées en amont ». Observez combien de temps il faut pour trouver.

Si vous ne faites qu’une chose : traitez la sémantique des interfaces comme critique en production. Parce que ça l’est. Le vaisseau spatial se fiche que votre organigramme soit compliqué.

← Précédent
Ubuntu 24.04 « Échec de démarrage … » : le flux de triage systemd le plus rapide (cas n°2)
Suivant →
ZFS secondarycache : quand L2ARC ne doit rien mettre en cache

Laisser un commentaire