1 ポイント 投稿者 GN⁺ 2023-09-11 | 1件のコメント | WhatsAppで共有
  • 米国株取引の大きな一角だったKnight Capital Groupは、2012年8月1日にSMARSのデプロイ失敗で45分のうちに4億6,000万ドルの損失を出し、破綻の危機に追い込まれた
  • NYSEのRetail Liquidity Programへの対応過程で、8年間使われていなかったPower Pegコードのフラグを新機能に再利用したことが事故の出発点だった
  • 新コードは8台のサーバーのうち7台にしかデプロイされず、残る1台のサーバーが新しいRLP注文を受け取ると、停止していたPower Peg機能が再び実行された
  • Power Pegは親注文の約定量を追跡しないまま子注文を送り続け、Knightはキルスイッチと文書化された対応手順なしに本番環境で原因を特定しなければならなかった
  • デプロイはコード作成やテストと同じくらい重要であり、手動手順に依存するデプロイは自動化・反復可能性・検証なしでは致命的な運用リスクになる

45分で崩壊した高速取引会社

  • Knight Capital Groupは、マーケットメイク、電子執行、機関営業・トレーディングを手がけていた米国の金融サービス企業だった
  • 2012年のKnightは、米国株式市場でNYSEとNASDAQそれぞれ約17%の市場シェアを持つ最大級のトレーダーだった
  • KnightのElectronic Trading Group(ETG)は、1日平均33億件以上の出来高を扱い、1日210億ドル以上を取引していた
  • 2012年7月31日時点でKnightは約3億6,500万ドルの現金および現金同等物を保有していた

RLP対応のためのSMARSアップデート

  • NYSEは2012年8月1日にRetail Liquidity Programを開始する予定だった
  • Knightはこれに合わせて、注文を市場へ送る自動高速アルゴリズムルーターSMARSを更新した
  • SMARSは取引プラットフォームの「親注文」を受け取り、1つ以上の「子注文」に分けて執行するシステムだった
    • 親注文が大きいほど、より多くの子注文が生成された
  • 今回のアップデートは、8年間使われていなかったPower Pegコードを置き換える作業だった
  • 新コードはPower Pegを有効化していた既存フラグを新機能に再利用した
  • コード自体は十分にテストされ、正常動作が確認されていた

手動デプロイで抜け落ちた1台のサーバー

  • Knightは2012年7月27日から7月31日まで、新しいソフトウェアを1日ごとに限られた台数のサーバーへ手動デプロイしており、対象全体は8台だった
  • SEC文書によると、ある技術者が8台のSMARSサーバーのうち1台に新コードをコピーしていなかった
  • このデプロイを別の技術者が確認する手順はなく、そのような確認を求める文書化された手順もなかった
  • その結果、8台目のサーバーではPower Pegコードが削除されず、新しいRLPコードも追加されなかった

死んだコードが再び動き出した仕組み

  • 2012年8月1日午前9時30分(米東部時間)に市場が開くと、Knightはブローカー・ディーラー顧客の注文をRetail Liquidity Program向けに処理し始めた
  • 正しくデプロイされた7台のサーバーは注文を正常に処理した
  • 8台目のサーバーに送られた注文は、再利用されたフラグによって旧Power Pegコードを再び実行させた
  • Power Pegは本来、子注文が執行されるたびに親注文に対して売買された株数を数え、親注文が満たされると子注文のルーティングを止める機能だった
  • Knightは2005年に累積追跡機能をコード実行のさらに前段へ移しており、Power Peg内の集計追跡は削除された状態だった
  • 8台目のサーバーでPower Pegフラグが有効になると、Power Pegは子注文を執行市場へルーティングしたが、親注文に対する株数を追跡しないため、実質的に終わりのない反復のように動作した

寄り付き前の兆候と9時30分以降の暴走

  • Knightのシステムは当日午前8時1分から自動メールを送り始めた
    • SMARSが取引開始前の取引対象注文を処理したときに発生した
    • メールはSMARSに言及し、エラーを「Power Peg disabled」と識別していた
    • 8時1分から9時30分までの間に、Knightの従業員へこの種のメールが97件送信された
  • このメールはシステム警報として設計されていなかったため、直ちには確認されなかった
  • 9時30分の市場開始直後、ウォール街の複数の関係者が異常事態に気づいた
  • 9時31分には深刻な事態が起きていることが明らかになり、9時32分にはなぜ止まらないのかという疑問が大きくなった
  • 最初の45分間で、Knightの執行は一部銘柄で出来高の50%以上を占め、特定銘柄の株価を10%以上押し上げた
  • 誤った取引への反応で、別の銘柄は価値が下落した

キルスイッチ不在と誤った対応

  • Knightには問題のシステムを即座に停止するキルスイッチがなかった
  • 文書化された対応手順もなく、毎分800万株が取引される本番環境で原因を診断しなければならなかった
  • 原因を特定できなかったKnightは、正常にデプロイされていたサーバーから新コードを削除した
  • この措置は、動いていたコードを取り除き、問題のあるコードを残す結果になった
  • その後、追加の親注文が1台のサーバーではなく全サーバーでPower Pegコードを有効化し、問題はさらに拡大した
  • Knightは45分が経過してようやくシステムを停止できた

損失規模と会社の結末

  • 市場開始後の最初の45分間で、Power Pegコードは212件の親注文を受けて処理した
  • SMARSは数百万件の子注文を市場に送り、結果として154銘柄で400万件の取引と3億9,700万株超の売買が発生した
  • Knightは80銘柄で約35億ドルの純買いポジション、74銘柄で約31億5,000万ドルの純売りポジションを抱え込んだ
  • Knight Capital Groupは45分で4億6,000万ドルの損失を確定させた
  • 当時保有していた現金および現金同等物は3億6,500万ドルだったため、Knightは米国株最大級のトレーダーかつ主要マーケットメイカーから、破綻状態へ転落した
  • 損失を埋めるには48時間以内に資本を調達する必要があり、Knightは約6社の投資家から4億ドルの出資を取り付けた
  • その後Knight Capital Groupは2012年12月にGetco LLCに買収され、統合後の会社はKCG Holdingsとなった

DevOpsとContinuous Deliveryの教訓

  • 良いソフトウェアを作り、テストするだけでは十分ではない
  • 顧客に価値を届けるには、ソフトウェアが市場へ正確にデプロイされなければならない
  • 事故の原因はSMARSをデプロイした1人のエンジニアだけにあったのではなく、Knightのプロセスが露出したリスクに耐えられなかったことにあった
  • 人が手順書を読んで従う方式に依存するデプロイは、誤りの可能性を内包する
    • ミスは手順書そのもの、手順書の解釈、手順の実行過程で起こりうる
  • デプロイは可能な限り自動化され、反復可能であるべきで、人為的ミスの可能性を減らす必要がある
  • 自動化されたデプロイシステムに構成管理、デプロイ、テスト自動化が含まれていれば、Knightmareを引き起こした誤りは回避できたはずだ
  • Continuous Deliveryの原則のうち、この事例に当てはまるものは2つある
    • ソフトウェアリリースは反復可能で信頼できるプロセスであるべき
    • 合理的な範囲で可能な限り多くを自動化すべき

1件のコメント

 
GN⁺ 2023-09-11
Hacker News の意見
  • 自動デプロイがこの問題をどう解決したのかはよく分からない。むしろ問題の影響と余波をさらに大きくした可能性が高い
    「開発者がサーバー1台にコードを載せるのを忘れた」を、「デプロイエージェントがサーバーに新しいバイナリ/コードをダウンロードする途中でエラーが発生し、エージェントのバグのせいでそのエラーが表面化しなかった」に置き換えても、同じ失敗モードになる。影響はより速く広がったはずだ
    ここでの責任は開発者にある。コードを後方互換性のない方法で書いたからだ

    • 責任は完全にリスク管理チームにある
      市場もKnightも深刻な問題があることを認識していたのに、取引を止めるまで45分間にわたって複数のホットフィックスを試していた。キルスイッチがなかったか、間違ったタイミングで押すと機会費用が50万ドルほど発生するという理由で、それを押す権限を持つ人がいなかった可能性が高い
      当時Knightの競合で働いていたが、私たちもひどいバグを本番環境によくデプロイしていたものの、事後分析のときに同じことが自分たちに起こり得るとは想像しにくかった。個別の取引を止める自動システムが複数あり、シニアトレーダーや運用担当者は60秒の会話だけでキルスイッチを引かせることができ、その余波を恐れる必要もなかった
      実際にはKnightの4億ドルの損失からもっと稼げたかもしれないが、私たちのリスクシステムが「良すぎて信じがたい」と判断して戦略を止め続けたため、利益は減った
    • CI/CDならこの問題を100%解決していたはずだ
      「Knightの技術者の1人が、8台のSMARSサーバーのうち1台に新しいコードをコピーしなかった」という部分をもう一度見るべきだ。もちろんCI/CDパイプラインも途中で失敗して一部のサーバーにだけデプロイされることはあり得るが、その可能性は低いと思う
      たとえそうだったとしても、Ansible Playbookならその転送失敗の瞬間に停止し、Playbook全体が失敗していたはずで、最後の段階であるサービス再起動までは到達しなかったはずだ
      これは人為的ミスであり、まさにそのために自動化が存在する
      さらに「2人目の技術者がデプロイをレビューせず、8台目のサーバーでPower Pegコードが削除されておらず、新しいRLPコードも追加されていないことに誰も気づかなかった」という部分も、CI/CDなら防げたはずだ。Ansibleコードリポジトリへの「Pull Request」は、1人目の技術者がレビューなしにmaster/mainへマージすることを防いだはずだ。master/mainは保護されているべきだからだ
      CI/CDを基盤にしたDevOpsなら、この問題を100%解決していたと確信している
    • 既存のフラグを再利用しようと決めた人に責任があるのかもしれない。ソフトウェア開発をしたことがある人なら分かるが、そうした決定は必ずしも、あるいはたいていの場合、開発者が下すものではない
    • これはデプロイプロセスに十分投資しなかった問題だと見ている。ちなみに私はオープンソースのデプロイツールを保守して生計を立てている
      Charity MajorsがEurukoでこれに関連する話を多くしていた。デプロイツールはbashスクリプトをコートで包んだ程度であってはならず、十分な人員とテストを備え、可能な限り最後まで自動化されているべきだ
      イミュータブルアーキテクチャに近いデプロイプロセス、失敗/停止/未完了のロールアウトを監視するツール、以前の正常な状態へ素早く戻す能力があれば防御層が生まれ、物事がうまくいかなくなったときの行動経路も簡単になる。この問題が不可能になったわけではないだろうが、発生しにくくはなっていたはずだ
    • 自動化の目的は、時間が経つにつれて特定されていないエッジケースを減らすことにある
      手動の手順書は、サーバー作業をするたびに「12番目の手順はやったし、13番目もやった気がするから次は14番目だ」と推測するゲームになる。人間の脳は、100万回やった作業が途中で中断されると、今回の実行と前回の記憶から生まれた偽の記憶を安定して区別できないことが多い
      手順を飛ばせないようにするインターロックがなければ、毎回がギャンブルだ。そしてインターロックを作る労力は、自動化コストのかなりの部分をすでに占めている
  • 「8年間も死んでいたコードがなぜコードベースに残っていたのかは謎だが、そこが本質ではない」と言っているけれど、まさにそこが本質に見える。
    8年間使っていないコードをそのままにしておいて、フラグを再利用しようとして初めて削除しようとしたように見える。8年前に正しく対処して、使っていないコードを消していれば、話はまったく違っていたはず。古いルーチンが蘇ることも、勝手に動くサーバーもなかったはず。
    Knight Capital はバージョン管理を使っていなかったので、「念のため」にコードを抱え込んでいたのかもしれない。だが、完全にバージョン管理されたリポジトリでも、コードを消したがらない開発者を見たことがあり、本当に驚く。再び必要になったらバージョン管理から復元すればいい。再び必要になったのにそこにあることを忘れていたのなら、死んだコードパスだって同じように見つけられなかったはず。ソースツリーに残しておくのは純粋な負債だ。
    Kevlin Henney は GOTO でソフトウェアの信頼性に関する素晴らしい講演をしていて、Knight Capital を例にこの点を扱っている。実際、このブログ記事も引用している https://youtu.be/IiGXq3yY70o?si=hZ9HB2dlfj0vHvNK&t=463
    「本当に死んだコードなど存在しない。小さな前提が一つ、その前提の変化が一つあるだけで、突然それは死んだコードではなくゾンビコードになる。蘇ったゾンビ・アポカリプスには金がかかる」

    • 多くの開発者は git の基本しか知らないように思う。変更をコミットし、git logで履歴を見て、場合によってはgit blameを使える程度だ。
      git の履歴をフィルタリングする方法を知らないことが多い。git pickaxeや除外パターンも知らず、git log -G'int.*foo\(' -- ':(exclude)directory'のように特定のディレクトリを除外して git ログからfooを検索できるかどうかさえ考えない。
      その代わり、現在のコードツリー内でgrepする方法は知っている。だから削除しなければ、適切なgrepでまた見つけられると思っている。削除されると、git 履歴から探す方法が分からないかもしれない。
      ある程度は理解できる。git ログ内のコードは多くのツールから見えない。たとえばエディタが補完候補として提示することもないし、ライブラリ文書にも現れない。
      そのコードが本当にまた使われると信じているなら、リファクタリングに追随し、必要になったとき発見されるようツリーに残しておくことも、ある程度は擁護できる。だが Knight Capital のように、明らかに二度と使うことがない場合には擁護しにくい。
    • 「動いているなら触るな」という言葉はよく聞いてきたし、特に自分が何を言っているのか分かっていない管理者がよく言う。
      単に「古いコードを削除する」更新であっても、どんな変更でも故障のリスクを生むと考える人には難しく感じられることがある。公平に言えば、あらゆる変更にはリスクがあるが、古いコードを残すことにもリスクはある。
      少なくとも今では、この事例を明確なリスクの例として指し示せる。
    • 死んだコードを残していたこと自体はそれほど驚かない。私が働いたほぼすべての会社でそういうことがあった。
      この話を見るたびに本当に理解できないのは、新しいフラグを作らずに既存のフラグを再利用した点だ。なぜそんなことをしたのだろう。
    • バージョン管理も完全無欠ではない。コード履歴全体はgit rebase一発で永遠に消えうる。
      ほとんどの組織では、メインブランチ周辺でそういうことが起きないようなプロセスを持っていることを願う。だが私自身、小さな組織で本番データベースのテーブルをうっかり壊したことがあるので、偶発的なgit rebaseもあり得ないことだとは見なせない。
    • 障害直後に Knight で働いた経験は、別の兄弟スレッドに書いた。
      256個のカラムしかないデータベースを使っていた、別の問題もあった。新しいカラムが必要になると、「当時使っていなかった」古いカラムをそのまま再利用していた。
      記憶が正しければ、社内でもおおむね「悪い考え」だとは認められていたが、古いコードを整理したり、よりよいベストプラクティスを整えたりすることを、誰も優先順位に上げなかった。
  • 私が経験したどの継続的デプロイシステムでも、この特定のバグは防げなかったはずだ。
    段階的にロールアウトしている状況だったが、その段階的ロールアウトの途中でインストールが一つ失敗すると会社が破産する、という論理バグがコードにあった。
    これを防ぐには、実行時にソフトウェアのバージョン、たとえば git SHA が一致しているかを検査し、ソフトウェアのロールアウト基盤を呼び出すテストに障害注入も追加していただろう。

    • まともな継続的デプロイシステムなら、設定とコードが食い違うことを許さない。以前は Power Peg を有効にしていたが、今は別のものを有効にするフラグに対する設定変更があり、そのフラグを違う意味で解釈するコード変更もあった。
  • 完全な西部開拓時代だった。その後、取引システムは大きく変わったという点も重要だ。
    私がこの分野で働き始めた2009年には、銀行、ブローカー、取引所のいずれもシステム信頼性はかなりひどかった。約定数量がいくらだったのかを電話で確認しなければならないことがよくあった。
    イタリア取引所がシステムをロールアウトしていたときのことを覚えている。ある時点では本番環境と UAT が混ざった場所で「テスト」をしていて、記憶が正しければ、取引終了後に次のリリースを試すため、注文送信接続の IP だけを変えていた。UAT 環境はバグが多すぎ、大半が半分死んでいたので、そこでテストできなかった。
    0が大量に並ぶ取引量の商品に価格を付けていた、ChatGPT でも悪態をつきそうな VBA コード入りの Excel スプレッドシートの話はやめておこう。
    今はかなり違う。こうした事件のおかげでもある。ほとんどが自動化され、カウボーイ的な態度もずっと減った。
    義務化されたキルスイッチ、何重ものリスク/取引活動の監視、取引所側の監視があり、苦労して学んだ教訓が本当に多くシステムに反映されている。だからこそ、人々がよい取引システムを作る難しさを甘く見てしまう理由にもなっている。戦略が賢くなったこともあるが、核心はたいてい、通常条件の外にある何かで死なない方法だ。

  • クオンツ金融にいる人なら、文字どおり全員がKnight Capitalを知っている。「pulling a knight capital」という表現もある。つまり、会社を一瞬で破産させ得るミッションクリティカルなシステムでさえ近道をして、その代償を払うという意味だ。

    • 実際、うちの会社のオンボーディング資料にも使われている
  • 私たちのチームのシステムは、1日あたり数億ドルの売上に重要な役割を果たしている。システムが十分長く落ちていれば、その売上は消える。ここでいう十分長くとは少なくとも数時間のことで、その時間内ならたいてい外部への影響を大きくせずに正常状態へ戻せる。
    私たちにも手動プロセスはあるが、どんな手動プロセスでも開始前にロールバック手順を文書化し、デプロイを監視する。コードのデプロイと機能のデプロイも分離し、機能のデプロイはフィーチャーフラグの後ろで段階的に行う。
    新機能やコード変更には、新しいフィーチャーフラグを求める。つらくて遅いが、危険な状況やパニックを避けられるようになり、運用とオンコールの負担も大きく減った。
    本当に深刻に失敗するには、複数の「欠陥フィルター」を通過しなければならない。コードレビューでフィーチャーフラグなしの挙動変更を見逃し、手動/開発環境でのテストも見逃し、デプロイが失敗し、ロールバックが失敗するか誤っていて、問題がまだ直っていないことを知らせる監視がなく、上位レベルへ適時にエスカレーションできず、十分な時間が経ってSLAを満たす能力を失う、といった具合だ。
    より危険な手動変更では、2人で一緒に変更させることもできる。1人がビデオ通話で何を変更するのかを確認し、もう1人が検証する。
    SLAが分単位で、変更を元に戻せないシステムを扱うなら、数分以内に監視してロールバックする実際的な方法を知っていなければならない。新しく手動の作業なら4回確認し、別の人に横で見てもらうべきだ。そうでなければ、複数の問題が連続して重なり、修復できない瞬間が来るのは時間の問題だ。どれほど優秀で賢くても、人が手動で変更したり変更を開始したりする必要があるならミスは常に起きるし、そのミスの確率は変更管理プロセスに組み込まれていなければならない。

    • 本当にその売上は消えるのだろうか。それとも後で発生するだけなのだろうか。
      一般的な商取引やB2Bでは、多くの場合、顧客は同じ購入を少し後に再試行できる。「今でなければ永遠に無理」というわけではない。
      私も、販売者がダウンしていたり、新発表と大きな需要でサーバーが落ちていたり、銀行のメンテナンス問題があったりしたときに、買いたかったものを再試行したことがある。
  • 関連記事:
    Knightmare: A DevOps Cautionary Tale (2014) - https://news.ycombinator.com/item?id=22250847 - 2020年2月、コメント33件
    Knightmare: A DevOps Cautionary Tale (2014) - https://news.ycombinator.com/item?id=8994701 - 2015年2月、コメント85件
    Knightmare: A DevOps Cautionary Tale - https://news.ycombinator.com/item?id=7652036 - 2014年4月、コメント60件
    追加で:
    The $440M software error at Knight Capital (2019) - https://news.ycombinator.com/item?id=31239033 - 2022年5月、コメント172件
    Bugs in trading software cost Knight Capital $440M - https://news.ycombinator.com/item?id=4329495 - 2012年8月、コメント1件
    Knight Capital Says Trading Glitch Cost It $440 Million - https://news.ycombinator.com/item?id=4329101 - 2012年8月、コメント90件
    ほかにもあるだろうか。

  • 本当の問題は、「真のスコットランド人」式の表現をあえて受け入れるなら、テストされていない設定とバイナリリリースの組み合わせを使ったことだ。
    設定とバイナリは同時に揃えてロールアウトできるし、そうすればこの種の問題は防げる。もちろん他のミスもあったが、この条件がなければこの問題は発生し得なかった。

  • 「8年間死んでいたコードがなぜコードベースに残っていたのかは謎だが、それが核心ではない」という部分は、この話の中で最悪のミスではないものの、核心ではないとも言えない。
    死んだ機能を先回りして切り捨てていれば、より単純で、よりよく理解されたソフトウェアになっていただろうし、暴走する可能性も下がっていただろう。こうしたメンテナンスなしに絶えず前へ進むだけなのは、計算されたものかどうかにかかわらずリスクだ。

  • 人間の介入なしに数百万ドルを自動でルーティングするコードを書いていなくて本当に良かった。
    ジャンボジェットを飛ばすコードを書くようなものだ。誰がそんな責任を負いたいだろうか。

    • そういう責任自体は構わないが、実際に自分の責任でなければならない。つまり、上司が明日リリースしたがっていても、XYZを作るのにさらに2年かかるとしても、「XYZが直るまでリリースしない」と言う権限がなければならない。
    • きちんとやれば怖くない。そして、きちんとやる仕事は途方もなく退屈な仕事に見えることがある。
      プロセス、テスト、シミュレーター、冗長性を愛する特定タイプの人に向いた仕事だと思う。飛行機を飛ばすコードそのものは、エンジニアリングの1%にすぎない領域だ。
    • 最初は不安を引き起こすが、良い制御と監視があれば日常になる。
      自然に浮かぶ心配を一つずつ解決していけばよく、合理的に不安がるほど事業には良い。金融業界での経験から見ると、Knightの問題は技術問題が10%、CTOのような人物が大胆さを勘違いした問題が90%だったと思う。その日やその週だけでなく、全般的にだ。
    • すべての会社がそうかは分からないが、通常、ソフトウェアが取引所に注文を出すときは、多くの人が何が起きているかを綿密に監視している。
      この事件のおかげも、ある程度あるのだと思う。
    • 金融業界で働いて人生を無駄にしなくて本当に良かった。