ソフトウェア システムの障害は避けられません。これらの障害への対処方法は、システムのパフォーマンス、信頼性、およびビジネスの収益に大きく影響します。この記事では、障害の利点について説明します。なぜ失敗を追求すべきか、なぜ失敗は良いことか、そしてなぜ失敗を避けるとアプリケーションの信頼性が低下する可能性があるかについて説明します。まず、フェイルファーストとフェイルセーフについて説明し、次に一般的な障害について 2 つ目の説明に移ります。
ちなみに、この投稿やこのシリーズの他の投稿の内容が気に入ったら、私の
フェイルファースト システムは、予期しない状況が発生するとすぐに機能を停止するように設計されています。この即時の障害により、エラーを早期に検出し、デバッグを簡単に行うことができます。
フェイルファスト アプローチにより、エラーがすぐに検出されます。たとえば、プログラミング言語の世界では、Java はnull
値に遭遇するとすぐにNullPointerException
を生成し、システムを停止してエラーを明らかにすることでこのアプローチを具体化しています。この即時の応答により、開発者は問題を迅速に特定して対処し、問題が深刻化することを防ぐことができます。
フェイルファスト システムは、エラーを早期に検出して停止することで、1 つのエラーが他のエラーにつながる連鎖的な障害のリスクを軽減します。これにより、問題がシステム全体に広がる前に問題を封じ込めて解決しやすくなり、全体的な安定性が維持されます。
フェイルファースト システムでは、単体テストと統合テストを簡単に記述できます。この利点は、テストの失敗を理解する必要があるときにさらに顕著になります。フェイルファースト システムは通常、エラー スタック トレースで問題を直接指摘します。
ただし、フェイルファスト システムには、特に実稼働環境では独自のリスクが伴います。
フェイルセーフ システムは、予期しない状況に直面しても回復して継続することを目指した異なるアプローチを採用しています。このため、フェイルセーフ システムは不確実性や不安定な環境に特に適しています。
マイクロサービスは、アーキテクチャを通じて回復力を取り入れたフェイルセーフ システムの代表的な例です。物理ベースとソフトウェア ベースの両方の回路ブレーカーは、障害が発生した機能を切断して連鎖的な障害を防ぎ、システムの継続的な動作を支援します。
フェイルセーフ システムにより、システムは過酷な生産環境でも耐えることができ、壊滅的な障害のリスクが軽減されます。そのため、エラーからのスムーズな回復が重要なハードウェア デバイスや航空宇宙システムなどのミッション クリティカルなアプリケーションに特に適しています。
ただし、フェイルセーフ システムには欠点もあります。
どちらのアプローチが優れているかを判断するのは困難です。どちらにもメリットがあるからです。フェイルファースト システムは、即時デバッグ、連鎖障害のリスク低減、バグの検出と解決の迅速化を実現します。これにより、問題を早期に発見して修正し、問題の拡大を防ぐことができます。
フェイルセーフ システムはエラーを適切に処理するため、壊滅的な障害が壊滅的な被害をもたらす可能性があるミッション クリティカルなシステムや不安定な環境に適しています。
それぞれのアプローチの長所を活かすには、バランスの取れた戦略が効果的です。
バランスの取れたアプローチには、コーディング、レビュー、ツール、テストの各プロセス全体にわたって明確で一貫した実装も必要であり、シームレスに統合されることが保証されます。フェイルファーストは、オーケストレーションや可観測性とうまく統合できます。実質的に、これによりフェイルセーフの側面が開発者層ではなく OPS の別の層に移動します。
ここからが面白くなります。フェイルセーフとフェイルファストのどちらかを選ぶということではありません。適切なレイヤーを選ぶということです。たとえば、フェイルセーフ アプローチを使用して深いレイヤーでエラーが処理された場合、エラーは気付かれません。これで問題ないかもしれませんが、そのエラーが悪影響 (パフォーマンス、ガベージ データ、破損、セキュリティなど) をもたらす場合、後で問題が発生しても原因がわかりません。
適切な解決策は、すべてのエラーを単一のレイヤーで処理することです。最新のシステムでは、最上位レイヤーは OPS レイヤーであり、これが最も理にかなっています。エラーに対処するのに最も適したエンジニアにエラーを報告できます。また、サービスの再起動、追加リソースの割り当て、バージョンの復元など、即時の緩和策を提供することもできます。
最近、講演会に参加したのですが、講演者が最新のクラウド アーキテクチャを紹介していました。講演者は、障害が発生した場合に再試行できるフレームワークを使用して、マイクロサービスへの近道を取ることを選択しました。残念ながら、障害は私たちが望むようには動作しません。テストだけでは障害を完全に排除することはできません。再試行はフェイルセーフではありません。実際、大惨事を意味することもあります。
彼らはシステムをテストし、実稼働環境でも「動作する」ことを確認しました。しかし、壊滅的な状況が発生した場合、再試行メカニズムは自社のサーバーに対するサービス拒否攻撃として動作する可能性があります。このようなアドホック アーキテクチャが失敗する可能性のある方法は、驚くほど多くあります。
これは、失敗を再定義する際に特に重要になります。
ソフトウェア システムの障害はクラッシュだけではありません。クラッシュは単純で即時の障害とみなすことができますが、考慮すべきより複雑な問題があります。実際、コンテナ時代のクラッシュはおそらく最高の障害です。システムはほとんど中断することなくシームレスに再起動します。
データの破損はクラッシュよりもはるかに深刻で、悪質です。長期的な影響を伴います。破損したデータは、セキュリティと信頼性の問題を引き起こす可能性があり、修復が困難で、大規模なやり直しが必要になり、データが回復不能になる可能性があります。
クラウド コンピューティングは、サーキット ブレーカーや再試行などの防御プログラミング手法を生み出し、障害を適切に捕捉して処理するための包括的なテストとログ記録を重視しています。ある意味で、この環境は品質の面で私たちを後退させました。
データ レベルのフェイル ファスト システムにより、このような事態を防止できます。バグへの対処は、単純な修正では不十分です。バグの根本原因を理解し、再発を防止し、包括的なログ記録、テスト、プロセス改善まで行う必要があります。これにより、バグが完全に対処され、再発の可能性が減ります。
それが本番環境のバグである場合、本番環境を即座に元に戻せない場合は、元に戻したほうがよいでしょう。これは常に可能であるべきであり、それができない場合は、取り組むべきことです。
修正を行う前に、障害を完全に理解する必要があります。私の会社では、プレッシャーのためにそのステップを省略することがよくありましたが、小規模なスタートアップではそれは許容範囲です。大企業では、根本原因を理解する必要があります。バグや生産上の問題について報告する文化が不可欠です。修正には、同様の問題が生産に及ばないようにするプロセス緩和策も含める必要があります。
フェイルファスト システムはデバッグがはるかに簡単です。本質的にシンプルなアーキテクチャを備えているため、特定の領域の問題を正確に特定しやすくなります。軽微な違反 (検証など) でも例外をスローすることが重要です。これにより、ルーズ システムで蔓延する連鎖的なバグを防止できます。
これは、定義した制限を検証し、適切な例外がスローされることを確認する単体テストによってさらに強化する必要があります。再試行はデバッグを非常に困難にするため、コード内では避けるべきであり、OPS レイヤー内に配置する必要があります。これをさらに容易にするために、タイムアウトはデフォルトで短くする必要があります。
障害は回避したり、予測したり、完全にテストしたりできるものではありません。私たちにできるのは、障害が発生したときにその衝撃を和らげることだけです。多くの場合、この「緩和」は、アプリケーションの弱点を見つけることを目的に、可能な限り極端な条件を再現することを目的とした長期実行テストを使用することで実現されます。これだけでは十分ではありません。堅牢なシステムでは、実際の運用障害に基づいてこれらのテストを修正する必要があることがよくあります。
フェイルセーフの優れた例としては、サービスがダウンしているときでも作業を継続できるようにする REST 応答のキャッシュが挙げられます。残念ながら、これはキャッシュ ポイズニングや、禁止されたユーザーがキャッシュのせいで引き続きアクセスできる状況など、複雑でニッチな問題につながる可能性があります。
フェイルセーフは、本番環境/ステージングおよび OPS レイヤーにのみ適用するのが最適です。これにより、本番環境と開発環境間の変更の量が減り、それらを可能な限り同じにすることが望まれますが、それでも本番環境に悪影響を与える可能性のある変更です。ただし、可観測性によってシステム障害を明確に把握できるため、メリットは計り知れません。
ここでの議論は、観測可能なクラウド アーキテクチャを構築した最近の経験に少し影響を受けています。ただし、組み込みかクラウドかに関係なく、同じ原則があらゆる種類のソフトウェアに適用されます。このような場合、コードにフェイルセーフを実装することを選択することがよくありますが、この場合は、特定のレイヤーで一貫して意識的に実装することをお勧めします。
また、このような状況では、一貫性がなく、ドキュメント化が不十分な動作を提供するライブラリ/フレームワークという特殊なケースもあります。私自身、自分の仕事の一部でそのような一貫性のなさを犯したことがあります。これは簡単に犯してしまう間違いです。
これは、デバッグに関する私の本/コースの一部であるデバッグ理論シリーズの最後の投稿です。デバッグは、何かが失敗したときに行うアクションであると考えることがよくあります。そうではありません。デバッグは、コードの最初の行を書いた瞬間から始まります。コーディング中にデバッグ プロセスに影響を与える決定を下しますが、多くの場合、失敗が発生するまでこれらの決定に気付きません。
この投稿とシリーズが、未知の状況に備えたコードの作成に役立つことを願っています。デバッグは、その性質上、予期しない状況に対処するものです。テストは役に立ちません。しかし、以前の投稿で説明したように、準備を容易にするために実行できるシンプルなプラクティスが数多くあります。これは 1 回限りのプロセスではなく、障害に遭遇するたびに下した決定を再評価する必要がある反復的なプロセスです。