- Knight Capital Group, acteur majeur du trading d’actions aux États-Unis, s’est retrouvé au bord de la faillite le 1er août 2012 après un échec de déploiement de SMARS, perdant 460 millions de dollars en 45 minutes
- L’accident est parti de la réutilisation, pour une nouvelle fonctionnalité, du flag qui activait du code Power Peg inutilisé depuis 8 ans, dans le cadre de la prise en charge du Retail Liquidity Program du NYSE
- Le nouveau code n’a été déployé que sur 7 des 8 serveurs, et lorsque le serveur restant a reçu de nouveaux ordres RLP, la fonctionnalité Power Peg, jusque-là morte, s’est réactivée
- Power Peg a continué à router des ordres enfants sans suivre la quantité exécutée de l’ordre parent, et Knight a dû chercher la cause en production, sans kill switch ni procédure de réponse documentée
- Le déploiement est aussi important que l’écriture du code et les tests ; un déploiement reposant sur des procédures manuelles devient un risque opérationnel fatal sans automatisation, répétabilité et validation
La société de trading haute fréquence qui s’est effondrée en 45 minutes
- Knight Capital Group était une société américaine de services financiers active dans la tenue de marché, l’exécution électronique, ainsi que la vente et le trading institutionnels
- En 2012, Knight était le plus gros trader sur les actions américaines, avec environ 17 % de part de marché à la fois sur le NYSE et le NASDAQ
- L’Electronic Trading Group (ETG) de Knight gérait en moyenne plus de 3,3 milliards de transactions par jour, pour plus de 21 milliards de dollars échangés quotidiennement
- Au 31 juillet 2012, Knight détenait environ 365 millions de dollars de trésorerie et équivalents de trésorerie
Mise à jour de SMARS pour prendre en charge RLP
- Le NYSE devait lancer le Retail Liquidity Program le 1er août 2012
- Pour s’y adapter, Knight a mis à jour SMARS, son routeur algorithmique automatique à haute vitesse chargé d’envoyer les ordres sur le marché
- SMARS recevait des « ordres parents » de la plateforme de trading et les divisait en un ou plusieurs « ordres enfants » à exécuter
- Plus l’ordre parent était important, plus il générait d’ordres enfants
- Cette mise à jour visait à remplacer le code Power Peg, inutilisé depuis 8 ans
- Le nouveau code réutilisait le flag existant qui activait Power Peg pour une nouvelle fonctionnalité
- Le code lui-même avait été suffisamment testé et son bon fonctionnement avait été confirmé
Un serveur oublié dans un déploiement manuel
- Du 27 au 31 juillet 2012, Knight a déployé manuellement le nouveau logiciel sur un nombre limité de serveurs par jour, pour un total de 8 serveurs cibles
- Selon des documents de la SEC, un technicien n’a pas copié le nouveau code sur l’un des 8 serveurs SMARS
- Il n’existait pas de procédure imposant la revue de ce déploiement par un second technicien, ni de procédure documentée exigeant une telle vérification
- Résultat : sur le 8e serveur, le code Power Peg n’avait pas été supprimé et le nouveau code RLP n’avait pas été ajouté
Comment du code mort est revenu à la vie
- Le 1er août 2012 à 9 h 30 du matin (heure de l’Est des États-Unis), à l’ouverture du marché, Knight a commencé à traiter des ordres de clients broker-dealers destinés au Retail Liquidity Program
- Les 7 serveurs correctement déployés ont traité les ordres normalement
- Les ordres envoyés au 8e serveur ont réactivé l’ancien code Power Peg via le flag réutilisé
- À l’origine, Power Peg comptait le nombre d’actions achetées ou vendues par rapport à l’ordre parent lorsque des ordres enfants étaient exécutés, puis cessait de router des ordres enfants une fois l’ordre parent rempli
- En 2005, Knight avait déplacé la fonction de suivi cumulatif plus en amont dans l’exécution du code, et le suivi agrégé à l’intérieur de Power Peg avait été supprimé
- Lorsque le flag Power Peg s’est activé sur le 8e serveur, Power Peg a routé les ordres enfants vers les marchés d’exécution, mais sans suivre le nombre d’actions par rapport à l’ordre parent ; il s’est donc comporté de fait comme une boucle sans fin
Les signaux avant l’ouverture et l’emballement après 9 h 30
- Le système de Knight a commencé à envoyer des e-mails automatiques dès 8 h 01 ce matin-là
- Ils étaient déclenchés lorsque SMARS traitait des ordres éligibles au pré-marché
- Les e-mails mentionnaient SMARS et identifiaient l’erreur comme « Power Peg disabled »
- Entre 8 h 01 et 9 h 30, 97 e-mails de ce type ont été envoyés aux employés de Knight
- Ces e-mails n’étaient pas conçus comme des alertes système et n’ont donc pas été vérifiés immédiatement
- Peu après l’ouverture du marché à 9 h 30, plusieurs personnes à Wall Street ont remarqué une anomalie
- À 9 h 31, il était clair que quelque chose de grave se produisait, et à 9 h 32, l’incompréhension grandissait quant au fait que cela ne s’arrêtait pas
- Pendant les 45 premières minutes, les exécutions de Knight ont représenté plus de 50 % du volume sur certaines valeurs et ont fait monter le prix de certaines actions de plus de 10 %
- En réaction aux transactions erronées, d’autres actions ont perdu de la valeur
Absence de kill switch et mauvaise réponse
- Knight ne disposait pas de kill switch permettant d’arrêter immédiatement le système problématique
- Il n’existait pas non plus de procédure de réponse documentée : il fallait diagnostiquer la cause en environnement de production, alors que 8 millions d’actions s’échangeaient chaque minute
- Incapable de trouver la cause, Knight a retiré le nouveau code des serveurs correctement déployés
- Cette action a supprimé le code qui fonctionnait et laissé en place le code problématique
- Ensuite, d’autres ordres parents ont activé le code Power Peg sur tous les serveurs, et non plus sur un seul, aggravant le problème
- Knight n’a pu arrêter le système qu’au bout de 45 minutes
Ampleur des pertes et issue pour l’entreprise
- Pendant les 45 premières minutes après l’ouverture du marché, le code Power Peg a reçu et traité 212 ordres parents
- SMARS a envoyé des millions d’ordres enfants sur le marché, entraînant au final 4 millions de transactions sur 154 valeurs, pour plus de 397 millions d’actions échangées
- Knight s’est retrouvé avec une position nette acheteuse d’environ 3,5 milliards de dollars sur 80 valeurs, et une position nette vendeuse d’environ 3,15 milliards de dollars sur 74 valeurs
- Knight Capital Group a réalisé une perte de 460 millions de dollars en 45 minutes
- Comme la société ne disposait alors que de 365 millions de dollars en trésorerie et équivalents de trésorerie, Knight est passée du statut de plus gros trader d’actions américaines et de grand teneur de marché à celui d’entreprise en faillite
- Pour combler la perte, elle devait lever du capital en 48 heures, et Knight a obtenu un investissement de 400 millions de dollars auprès d’environ six investisseurs
- Knight Capital Group a ensuite été rachetée par Getco LLC en décembre 2012, et l’entité fusionnée est devenue KCG Holdings
Leçons pour DevOps et la Continuous Delivery
- Créer et tester un bon logiciel ne suffit pas
- Pour fournir de la valeur aux clients, le logiciel doit être déployé correctement sur le marché
- La cause de l’accident ne se limite pas à l’ingénieur qui a déployé SMARS ; elle tient aussi au fait que les processus de Knight n’étaient pas capables d’absorber les risques exposés
- Un déploiement qui repose sur des personnes lisant et suivant des instructions comporte intrinsèquement une possibilité d’erreur
- Les erreurs peuvent survenir dans les instructions elles-mêmes, dans leur interprétation ou dans leur exécution
- Les déploiements doivent être autant que possible automatisés et répétables, afin de réduire la probabilité d’erreur humaine
- Si le système de déploiement automatisé avait inclus l’automatisation de la configuration, du déploiement et des tests, l’erreur à l’origine de Knightmare aurait pu être évitée
- Deux principes de la Continuous Delivery s’appliquent à ce cas
- Les releases logicielles doivent être un processus répétable et fiable
- Il faut automatiser autant que possible dans des limites raisonnables
1 commentaires
Avis de Hacker News
Je ne vois pas bien comment le déploiement automatique aurait résolu ce problème. Au contraire, il aurait probablement amplifié l’impact et les répercussions
Remplacer « un développeur a oublié de mettre le code sur un serveur » par « l’agent de déploiement a rencontré une erreur en téléchargeant le nouveau binaire/code sur le serveur, et cette erreur n’a pas été visible à cause d’un bug de l’agent » produit le même mode de défaillance. L’impact se serait propagé plus vite
Ici, la responsabilité incombe au développeur. Parce qu’il a écrit le code d’une manière non rétrocompatible
Le marché comme Knight savaient qu’il y avait un problème grave, mais ils ont tenté plusieurs hotfixes pendant 45 minutes avant d’arrêter le trading. Il n’y avait peut-être pas de kill switch, ou bien personne n’avait l’autorité pour l’actionner parce qu’appuyer dessus au mauvais moment pouvait entraîner un coût d’opportunité d’environ 500 000 dollars
À l’époque, je travaillais chez un concurrent de Knight. Nous aussi, nous déployions souvent en production des bugs terribles, mais lors des post-mortems il était difficile d’imaginer que la même chose puisse nous arriver. Nous avions plusieurs systèmes automatiques qui bloquaient des transactions individuelles, et un trader senior ou un responsable des opérations pouvait, après une conversation de 60 secondes, faire déclencher le kill switch sans avoir à craindre les conséquences
En réalité, nous aurions pu gagner davantage sur la perte de 400 millions de dollars de Knight, mais notre système de risque a jugé que c’était « trop beau pour être vrai » et a continué à désactiver les stratégies, ce qui a réduit nos gains
Il faut revenir à la partie disant qu’« un ingénieur de Knight n’a pas copié le nouveau code sur l’un des 8 serveurs SMARS ». Bien sûr, un pipeline CI/CD peut aussi échouer en cours de route et ne déployer que sur une partie des serveurs, mais je pense que c’est peu probable
Même si cela s’était produit, avec un Ansible Playbook, le processus se serait arrêté au moment de l’échec du transfert, tout le Playbook aurait échoué, et il n’aurait pas atteint l’étape finale, le redémarrage du service
C’était une erreur humaine, et c’est précisément pour cela que l’automatisation existe
De plus, la partie disant qu’« un deuxième ingénieur n’a pas revu le déploiement, et personne ne s’est rendu compte que le code Power Peg n’avait pas été retiré du 8e serveur et que le nouveau code RLP n’y avait pas non plus été ajouté » aurait aussi pu être évitée par le CI/CD. Une « Pull Request » sur le dépôt de code Ansible aurait empêché le premier ingénieur de fusionner dans master/main sans revue. master/main doit être protégé
Je suis convaincu qu’un DevOps fondé sur le CI/CD aurait résolu ce problème à 100 %
Charity Majors a beaucoup parlé de ce sujet à Euruko. Les outils de déploiement ne doivent pas être de simples scripts bash enveloppés dans un manteau ; ils doivent disposer de suffisamment de personnel et de tests, et être automatisés autant que possible de bout en bout
Un processus de déploiement proche d’une architecture immuable, des outils qui surveillent les rollouts en échec/interrompus/inachevés, et la capacité de revenir rapidement au dernier état sain créent des couches de défense et facilitent le chemin à suivre quand les choses tournent mal. Cela n’aurait pas rendu ce problème impossible, mais il aurait été plus difficile qu’il survienne
Une procédure d’exécution manuelle devient, à chaque intervention sur un serveur, un jeu de devinettes du type : « j’ai fait l’étape 12, je crois aussi avoir fait l’étape 13, donc la suivante est l’étape 14 ». Le cerveau humain, lorsqu’une tâche effectuée un million de fois est interrompue au milieu, est souvent incapable de distinguer de façon fiable l’exécution en cours des faux souvenirs issus des exécutions précédentes
S’il n’y a pas d’interverrouillage empêchant de sauter des étapes, c’est un pari à chaque fois. Et l’effort nécessaire pour créer ces interverrouillages représente déjà une grande partie du coût de l’automatisation
« Pourquoi du code mort depuis 8 ans était-il encore présent dans la base de code reste un mystère, mais ce n’est pas le point essentiel », disait-on, mais cela me semble précisément être le point essentiel
On dirait qu’ils ont laissé du code inutilisé pendant 8 ans, puis n’ont essayé de le supprimer qu’au moment où ils ont voulu réutiliser le flag. S’ils avaient fait ce qu’il fallait 8 ans plus tôt et supprimé le code inutilisé, l’histoire aurait été complètement différente. L’ancienne routine n’aurait pas été ressuscitée, et il n’y aurait pas eu de serveur livré à lui-même
Peut-être que Knight Capital n’utilisait pas de gestion de versions et gardait le code « au cas où ». Mais même dans des dépôts entièrement versionnés, j’ai vu des développeurs réticents à supprimer du code, et c’est vraiment surprenant. Si on en a de nouveau besoin, on le restaure depuis l’historique. Si on en a de nouveau besoin mais qu’on a oublié qu’il est là, on n’aurait probablement pas retrouvé non plus le chemin de code mort. Le laisser dans l’arborescence source, c’est de la pure dette
Kevlin Henney a fait à GOTO une excellente présentation sur la fiabilité logicielle, où il prend Knight Capital comme exemple et aborde précisément ce point. Il cite d’ailleurs aussi ce billet de blog https://youtu.be/IiGXq3yY70o?si=hZ9HB2dlfj0vHvNK&t=463
« Il n’existe pas de code vraiment mort. Il suffit d’une petite hypothèse, d’un changement d’hypothèse, et soudain ce n’est plus du code mort, c’est du code zombie. Une apocalypse zombie ressuscitée coûte de l’argent »
git log, et peut-être utilisergit blameSouvent, ils ne savent pas filtrer l’historique git. Ils ne connaissent ni
git pickaxeni les motifs d’exclusion, et ne pensent même pas qu’on puisse rechercherfoodans les logs git en excluant un répertoire spécifique, comme avecgit log -G'int.*foo\(' -- ':(exclude)directory'En revanche, ils savent faire un
grepdans l’arborescence de code actuelle. Ils se disent donc que, tant qu’on ne le supprime pas, on pourra le retrouver avec le bongrep. Une fois supprimé, ils ne sauront peut-être pas le retrouver dans l’historique gitDans une certaine mesure, ça se comprend. Le code dans les logs git est invisible pour beaucoup d’outils. Par exemple, l’éditeur ne le propose pas en autocomplétion, et il n’apparaît pas non plus dans la documentation des bibliothèques
Si l’on croit vraiment que ce code sera réutilisé, on peut défendre dans une certaine mesure le fait de le laisser dans l’arbre pour qu’il suive les refactorings et soit retrouvable au moment voulu. Mais dans un cas comme Knight Capital, où il est évident qu’il ne sera pas réutilisé, c’est difficile à défendre
Même une mise à jour qui se contente de « supprimer du vieux code » peut paraître difficile à quelqu’un qui considère que tout changement crée un risque de panne. Pour être juste, tout changement comporte un risque, mais laisser du vieux code en place en comporte un aussi
Au moins, désormais, on peut citer cet exemple comme illustration claire du risque
Ce que je ne comprends vraiment pas chaque fois que je vois cette histoire, c’est qu’ils aient réutilisé un flag existant au lieu d’en créer un nouveau. Pourquoi faire ça ?
git rebaseJ’espère que la plupart des organisations ont des processus pour empêcher ce genre de chose autour de la branche principale. Mais comme il m’est déjà arrivé, dans une petite organisation, de casser accidentellement une table de base de données de production, je n’irais pas dire qu’un
git rebaseaccidentel est impossibleIl y avait un autre problème avec une base de données qui ne comportait que 256 colonnes. Quand il fallait une nouvelle colonne, on réutilisait simplement une ancienne colonne « non utilisée à l’époque »
Si ma mémoire est bonne, en interne tout le monde reconnaissait globalement que c’était une « mauvaise idée », mais personne ne faisait de la suppression du vieux code ou de la mise en place de meilleures bonnes pratiques une priorité
Aucun système de déploiement continu que j’ai connu n’aurait empêché ce bug précis
Le déploiement était progressif, mais le code contenait un bug logique tel que, si une seule installation échouait pendant cette phase de déploiement progressif, l’entreprise faisait faillite
Pour l’empêcher, il aurait fallu vérifier à l’exécution que les versions du logiciel, par exemple les SHA git, correspondent, et ajouter aussi de l’injection de pannes aux tests qui appellent l’infrastructure de déploiement logiciel
C’était vraiment le Far West. Il est aussi important de noter que, depuis, les systèmes de trading ont beaucoup changé
Quand j’ai commencé à travailler dans ce domaine en 2009, la fiabilité des systèmes des banques, brokers et places de marché était assez catastrophique. Il arrivait souvent de devoir confirmer par téléphone la quantité réellement exécutée
Je me souviens du moment où la Bourse italienne déployait un système. À un moment donné, ils faisaient des « tests » dans un environnement mélangeant production et UAT, et si je me souviens bien, après la clôture du marché, ils se contentaient de changer l’IP de connexion d’envoi des ordres pour tester la release suivante. L’environnement UAT était tellement bogué et la plupart du temps à moitié mort qu’on ne pouvait pas vraiment y tester quoi que ce soit
Ne parlons même pas des feuilles Excel contenant du code VBA qui ferait jurer ChatGPT, utilisées pour pricer des produits avec des volumes de transaction suivis d’une ribambelle de zéros
Aujourd’hui, c’est très différent. En partie grâce à ce genre d’incidents. La plupart des choses sont automatisées, et l’attitude de cow-boy a beaucoup reculé
Il y a des kill switches obligatoires, plusieurs couches de surveillance du risque et de l’activité de trading, de la surveillance côté bourse, et beaucoup de leçons apprises à la dure ont réellement été intégrées aux systèmes. C’est aussi pour cela que les gens sous-estiment naïvement la difficulté de construire un bon système de trading. Les stratégies sont devenues plus intelligentes, certes, mais l’essentiel est généralement de savoir ne pas mourir à cause de quelque chose hors des conditions normales
Littéralement tout le monde dans la finance quantitative connaît Knight Capital. Il existe même l’expression « pulling a knight capital » : prendre des raccourcis, même dans des systèmes mission critical capables de faire couler une entreprise en un instant, puis en payer le prix
Le système de notre équipe joue un rôle crucial dans un chiffre d’affaires de centaines de millions de dollars par jour. S’il reste indisponible suffisamment longtemps, ce chiffre d’affaires disparaît. Ici, « suffisamment longtemps » signifie au minimum quelques heures, et dans ce délai on peut généralement revenir à un état normal sans impact externe majeur.
Nous avons aussi des processus manuels, mais avant de lancer n’importe quel processus manuel, nous documentons la procédure de rollback et surveillons le déploiement. Nous séparons aussi le déploiement du code du déploiement des fonctionnalités, et nous déployons progressivement les fonctionnalités derrière des feature flags.
Toute nouvelle fonctionnalité ou modification de code doit avoir un nouveau feature flag. C’est pénible et lent, mais cela nous a permis d’éviter les situations dangereuses et la panique, et a aussi fortement réduit la charge d’exploitation et d’astreinte.
Pour vraiment échouer gravement, il faut passer à travers plusieurs « filtres de défauts » : qu’une modification de comportement sans feature flag soit manquée à la revue de code, qu’elle soit aussi manquée lors des tests manuels/en environnement de développement, que le déploiement échoue, que le rollback échoue ou soit incorrect, qu’il n’y ait pas de supervision pour alerter que le problème n’est toujours pas corrigé, que l’escalade au niveau supérieur ne se fasse pas à temps, puis que suffisamment de temps s’écoule pour perdre la capacité à respecter le SLA.
Pour les changements manuels plus risqués, on peut aussi faire intervenir deux personnes ensemble. L’une passe en revue, en visioconférence, ce qui est modifié, et l’autre valide.
Si vous gérez un système dont le SLA se compte en minutes et où les changements sont irréversibles, vous devez connaître une méthode concrète pour superviser et rollbacker en quelques minutes. S’il s’agit d’une tâche nouvelle et manuelle, il faut la vérifier quatre fois et demander à quelqu’un d’autre de regarder par-dessus votre épaule. Sinon, ce n’est qu’une question de temps avant que plusieurs problèmes s’enchaînent et qu’arrive un moment où il n’est plus possible de corriger. Aussi talentueuses et intelligentes que soient les personnes, dès qu’un changement doit être fait ou déclenché manuellement, il y aura toujours des erreurs, et la probabilité de ces erreurs doit être intégrée au processus de gestion des changements.
Dans le commerce général ou en B2B, dans beaucoup de cas, le client peut réessayer le même achat un peu plus tard. Ce n’est pas « maintenant ou jamais ».
Moi aussi, j’ai déjà retenté d’acheter quelque chose que je voulais lorsque le vendeur était en panne, que les serveurs avaient explosé à cause d’une nouvelle annonce et d’une forte demande, ou qu’il y avait un problème de maintenance bancaire.
Articles liés :
Knightmare: A DevOps Cautionary Tale (2014) - https://news.ycombinator.com/item?id=22250847 - février 2020, 33 commentaires
Knightmare: A DevOps Cautionary Tale (2014) - https://news.ycombinator.com/item?id=8994701 - février 2015, 85 commentaires
Knightmare: A DevOps Cautionary Tale - https://news.ycombinator.com/item?id=7652036 - avril 2014, 60 commentaires
En plus :
The $440M software error at Knight Capital (2019) - https://news.ycombinator.com/item?id=31239033 - mai 2022, 172 commentaires
Bugs in trading software cost Knight Capital $440M - https://news.ycombinator.com/item?id=4329495 - août 2012, 1 commentaire
Knight Capital Says Trading Glitch Cost It $440 Million - https://news.ycombinator.com/item?id=4329101 - août 2012, 90 commentaires
Y en a-t-il d’autres ?
Nanex ~ 03-Aug-2012 ~ The Knightmare Explaned - https://news.ycombinator.com/item?id=4337359 - aucun commentaire
Le vrai problème, si l’on accepte une formulation façon « vrai Écossais », c’est qu’ils ont utilisé une combinaison non testée de configuration et de release binaire.
La configuration et le binaire peuvent être roll out de façon synchronisée, ce qui évite ce type de problème. Bien sûr, il y a eu d’autres erreurs, mais sans cette condition, ce problème n’aurait pas pu se produire.
Le passage disant que « pourquoi du code mort depuis 8 ans était encore dans la base de code reste un mystère, mais ce n’est pas le point essentiel » n’est peut-être pas la pire erreur de l’histoire, mais on ne peut pas non plus dire que ce n’est pas essentiel.
Si cette fonctionnalité morte avait été supprimée de façon proactive, le logiciel aurait été plus simple et mieux compris, et la probabilité d’un emballement aurait été réduite. Avancer sans cesse sans ce type de maintenance, que ce soit calculé ou non, est un risque.
Je suis vraiment content de ne pas écrire du code qui route automatiquement des millions de dollars sans intervention humaine.
C’est comme écrire le code qui fait voler un gros porteur. Qui voudrait d’une telle responsabilité ?
Je pense que c’est un métier fait pour un certain type de personne, qui aime les processus, les tests, les simulateurs et la redondance. Le code qui fait voler l’avion lui-même ne représente que 1 % de l’ingénierie.
Il suffit de traiter une par une les inquiétudes qui viennent naturellement, et plus on est raisonnablement anxieux, mieux c’est pour l’activité. D’après mon expérience dans la finance, le problème de Knight était à 10 % un problème technique et à 90 % le problème de quelqu’un ressemblant à un CTO qui a pris de l’audace pour de la témérité. Pas seulement ce jour-là ou cette semaine-là, mais de façon générale.
Je pense que cet incident y est sans doute pour quelque chose.