知るべき3つの障害概念
歓迎
分散システムはパターンに従って障害を起こします。パターンを学ぶことで、すべてのポストモーテムが謎解きではなく、パターン認識の演習になります。
本番環境の障害分析で重要となることの大部分をカバーする3つの概念があります:
単一障害点(SPOF):その障害によりより大きなシステムが停止するコンポーネント。しばしば隠れています:皆が依存しているDNSサーバー、すべてが更新に使用する証明書、単一のデータベースマスター。
カスケード障害: 1つのコンポーネントの故障が別のコンポーネントの故障を引き起こし、それがさらに別の故障を引き起こす連鎖現象です。遅延したデータベースがAPI層でタイムアウトを引き起こし、それがリトライを発生させ、リトライがさらにデータベースに負荷をかけ、結果としてより多くのタイムアウトが発生します。爆発が広がっていきます。
ブラスト半径: システムの一部が故障した際に、システム全体の中でどの範囲がダウンするかを示す指標です。アーキテクチャ上の選択は、この半径を制限するか、無制限にするかのどちらかになります。SPOF(単一障害点)はブラスト半径が無制限です。バルクヘッド化されたサービスは、ブラスト半径が制限されています。
このレッスンの終了時には、以下のことが可能になります:
- アーキテクチャを視覚的に確認してSPOFを特定する
- カスケード障害のパターン(サンダリング・ハード、リトライ・ストーム、死のキュー)を認識する
- 実際のタイムラインを読み解き、トリガーと、そのトリガーによって表面化した潜在的要因を区別する
- 個人ではなくシステムを対象とした、予防 / 検知 / 回復をカバーする非責任意向のアクションアイテムを作成する
- バルクヘッドとサーキットブレーカーを、ブラスト半径を制限するためのツールとして論理的に検討する
単一障害点(SPOF)を発見する
階層アーキテクチャの検査
小さなWebアーキテクチャを考えてみましょう:
- DNS: api.example.com -> 単一のネームサーバーIP 203.0.113.10(1つのDNSプロバイダーがホスト)
- CDN: api.example.com の前に単一のCDNベンダー
- イングレス: ロードバランサーの背後にある2台のリバースプロキシマシン
- バックエンド: 2つのアベイラビリティゾーン(ゾーンごとに3つ)に配置された6つのAPIレプリカ
- データベース: 1つのプライマリ + 1つのリードレプリカ(同じアベイラビリティゾーン内)
- キャッシュ: Redis クラスタ、同じ2つのアベイラビリティゾーンに分散された3ノード
質問: どのコンポーネントがSPOF(単一障害点)ですか? ヒント: SPOFは必ずしも明白な「単一マシン」型とは限りません。1つのアベイラビリティゾーンに配置された3台のマシンのクラスタは、そのゾーンの障害に対してSPOFです。
3つの典型的なカスケードパターン
障害は依存関係を通じて伝播する
パターン1: サンダーリング・ハーッド(雷鳴する群れ)。 共有リソース(キャッシュ、ロック、データベース)が障害を起こしたり再起動したりします。それらに依存していたすべてのクライアントが同時にリトライを実行します。このリトライの洪水が復旧したシステムを圧倒し、リトライの蓄積速度が復旧の吸収速度を上回るため、復旧が完了することはありません。
パターン2: リトライ・ストーム(リトライの嵐)。 下流のサービスが遅延します。上流の呼び出し元は、失敗する代わりにリトライを実行します。リトライにより元の負荷が増幅されます。遅延しているサービスはさらに遅くなり、さらなるリトライを引き起こします。最終的に、負荷は正常な状態のサービスでも処理できるレベルを超えてしまいます。
パターン3: 死のキュー(Queue of death)。 バックプレッシャー(背圧)のない処理キューが、処理速度よりも速くデータを受信します。キューは無制限に増加します。メモリが枯渇し、コンシューマーがクラッシュし、再起動しても依然として巨大なキューを発見し、再びクラッシュします。
共通点: 小さな初期の擾乱(変化)が正のフィードバックループを引き起こします。システム自身の反応が、障害を減衰させるのではなく増幅してしまいます。
減衰メカニズム
ジッター付き指数バックオフ。 リトライを行うクライアントは、毎回より長い時間待機し、ランダムなオフセットを加えます。これにより、同期されたリトライの波(リトライストーム)を防ぎます。
サーキットブレーカー。 呼び出し元が下流の障害率を追跡します。閾値を超えると、呼び出し元はクールダウン期間中、下流への呼び出しを停止し、代わりに自身のリクエストを即座に失敗させます。無駄な処理を防ぎ、下流システムの回復を促します。
バルクヘッド。 依存先ごとにリソースを分離します。データベースには接続プールA、キャッシュには別の接続プールBを使用します。これにより、遅延したデータベースがすべての接続を枯渇させることはなく、キャッシュへの呼び出しは継続できます。
ロードシェディング。 過負荷状態では、リクエストを受け入れてゆっくりと失敗させるのではなく、エッジ(入口)でリクエストを破棄します。1ミリ秒で429を返す方が、30秒かけて500を返すよりも優れています。
バックプレッシャー。 消費者(コンシューマ)が処理しきれない場合、生産者(プロデューサ)を減速させます。キューには上限が設けられ、送信側はブロックされ、元の作業ソースがその摩擦(遅延)を実感します。
カスケード障害の診断
あるチームのAPIティアが、定例のデータベースフェイルオーバー中に機能停止しました。タイムライン:
- 14:00:00 — 運用担当者がスタンバイデータベースを昇格させました。想定される利用不可時間: 約10秒。
- 14:00:08 — プライマリが利用不可に。APIティアのリクエストがデータベース接続エラーで失敗し始めました。
- 14:00:08 — APIティアがリトライを実行(デフォルト設定: 5回リトライ、バックオフなし、100ms間隔)。
- 14:00:11 — スタンバイが昇格し、新しい接続を受け入れ始めました。
- 14:00:11 — APIティアが同時に数千本の新しいデータベース接続を開こうとしました(すべてのレプリカ × すべての並行リクエスト × すべてのリトライ)。
- 14:00:13 — 新しいプライマリの接続プールが枯渇し、新しい接続が拒否されました。
- 14:00:13-14:05:00 — APIティアのレプリカが接続プールを枯渇させ、例外をスローし、クラッシュし、再起動し、繰り返す。
- 14:05:00 — 運用担当者が手動でAPIティアへのトラフィックを停止。データベースが安定しました。
- 14:10:00 — 段階的なトラフィック復旧が完了。総停止時間:約10分(予想されていた約10秒と比較して)。
DNS SERVFAIL: 2つの複合的な欠陥
実在の形状をしたポストモーテム
以下は、実際のインシデントの匿名化されたバージョンです。ベンダー名は変更され、IPアドレスは匿名化されていますが、その形状、タイムライン、そして教訓は実物そのものです。
概要
サイト example.com は、約3〜4時間にわたり、すべてのパブリックDNSリゾルバーから SERVFAIL を返していました。同じDNSマスター上の他の46ゾーンは影響を受けませんでした。根本原因:2つの複合的な欠陥。
1. ベンダーA(セカンダリDNSプロバイダー)は、プライマリの allow-axfr-ips 許可リストに含まれていない新しい内部同期IPを追加しました。
2. example.com ゾーンには、何年も前から存在していたRFC違反のCNAME競合(demo.example.com が同じラベルでCNAMEとMX/TXTレコードの両方を持っていた)があり、これがベンダーAが新しいAXFRでゾーンを拒否する原因となっていました。
タイムライン(UTC)
- 15:00頃 — ベンダーAがインフラストラクチャに新しい同期IP 198.51.100.42 を追加
- 15:02 — 198.51.100.42 に対する最初の AXFR-out denied がプライマリDNSログに記録される(このシグナルに対するアラートは設定されていなかった)
- 18:00頃 — SOAのexpireウィンドウに到達;ベンダーAが example.com ゾーンをキャッシュから破棄
- 18:30頃 — 外部からSERVFAILが検出される
- 19:45頃 — 根本原因が特定される
- 20:00 — 198.51.100.42 が allow-axfr-ips に追加され、プライマリが再起動される
- 20:05 — NOTIFYが送信され、AXFRが開始されるが、ゾーンは依然としてSERVFAIL状態(CNAME競合)
- 20:07 — check-zone により1つのエラーが判明:demo.example.com におけるCNAME競合
- 20:09 — CNAMEレコードをAレコードに置換。ゾーンチェックがクリーン(エラー0件)
- 20:10 — NOTIFYを送信。AXFRが完了。ベンダーAがゾーンの配信を開始
- 20:11 — dig @8.8.8.8 example.com A が正しいIPを返却 — 解決済み
なぜexample.comのみか?
全47のゾーンは同じDNSプライマリを共有している。AXFRのIPブロックは全ゾーンに影響を与えた。しかし、CNAMEの競合が存在したのはexample.comのみであり、拒否が適用された時点で新しいAXFRが必要だったのもexample.comのみだった。他のゾーンは、拒否が適用される前にすでにリフレッシュ済みだったか、まだリフレッシュの必要がなかった。
潜在的不具合
demo.example.comにおけるCNAMEの競合は、何年も前から存在していた。プライマリがデータベースからゾーンを配信していたため(RFC違反に対して寛容だった)、かつベンダーAが違反が導入される前の古いキャッシュデータから配信していたため、問題なく動作していた。ベンダーAがキャッシュを破棄し、新しいデータが必要になった時点で、その違反が表面化した。
トリガー
ベンダーAは、新しい同期用IPアドレスを黙って追加しました。プライマリノードの許可リスト(allowlist)には、そのIPが含まれていませんでした。AXFRリクエストは拒否されました。3時間後(SOAのexpire時間)、ベンダーAはゾーンを削除しました。システムが復旧を試みた際、潜在していた欠陥が表面化しました。
非難を伴わない(Blameless)アクションアイテムを作成する
非難を伴わない(Blameless):個人ではなくシステムを標的とする
非難を伴わないアクションアイテムは、個人が異なる行動を取るべきことを指すのではなく、システムが異なる動作をするべきことを特定するものです。「オペレーターを教育する」は非難を伴うものです。「デプロイ前にこれを検出する自動チェックを追加する」は非難を伴わないものです。
良い非難を伴わないアクションアイテムは、主に3つの次元に分類されます:
- 予防(Prevention):悪いことが起こることを困難にする、または不可能にする
- 検知(Detection):もし起こった場合、より早く気づく
- 回復(Recovery):もし起こった場合、被害を最小限に抑える
各項目には、(1) 具体的なシステム変更、(2) 責任チーム、(3) 対応する次元(ディメンション)を明記する必要があります。
船が沈まずに沈没する区画
船舶工学からの借用
船には防水隔壁(バルクヘッド)があります。これは船体を区画に分割する垂直の壁です。ある区画が浸水しても船は沈まず、別の区画が故障しても他の部分には影響しません。
分散システムも同じ言葉と、同じアイデアを借用しています。
バルクヘッドパターン: 依存先ごとにリソースを分離します。3つの下流APIを呼び出すサービスは、3つの独立した接続プール、3つの独立したスレッドプール、3つの独立したリトライ予算を使用します。遅延や障害のある下流は、他の2つに割り当てられたリソースを消費することができません。
バルクヘッドなしの場合: 1つの遅い依存先が共有スレッドプールを使い果たします。他の依存先への呼び出しはスレッドを待ってブロックされ、サービス全体が応答不能になります。
バルクヘッドありの場合: 1つの遅い依存先が自身のプールを使い果たします。それへの呼び出しは素早く失敗し、他の依存先への呼び出しは正常に続行されます。影響範囲(ブラストレディウス)は障害のある依存先に限定されます。
サーキットブレーカー
サーキットブレーカーパターン: 下流の依存性をラップし、失敗率を追跡する状態付きのラッパーです。3つの状態があります:
- クローズ(正常): 呼び出しが通過します。失敗がカウントされます。
- オープン(トリップ): 失敗閾値(例: 直近30秒間で50%の失敗)を超えると、ブレーカーが開きます。呼び出しは依存性を試さずに即座に失敗します。呼び出し元が無駄な処理を行わないようにし、不健康な状態の依存性に負荷をかけないようにします。
- ハーフオープン(テスト): クールダウン期間の後、ブレーカーは呼び出しの一小部を通過させます。成功すれば、正常な状態(クローズ)に戻ります。失敗すれば、もう一度クールダウンのために開きます。
重要な洞察: サーキットブレーカーは、既知の不健康な期間中の無駄な努力を防ぎ、継続的な負荷なしで下流が回復する機会を与えます。
バルクヘッドは爆発半径を制限します。サーキットブレーカーは爆発が自己持続することを防ぎます。
爆発半径を制限する
あなたのAPIサービスは4つの下流サービス(ユーザーサービス、レコメンドサービス、通知サービス、サードパーティの支払いAPI)を呼び出しています。チームは「レコメンドサービスが少し不安定だ」と聞いており、それが失敗した際にシステムの残りが健全な状態を保つことを確認したいと考えています。
現在、このサービスは200スレッドの単一の共有スレッドプールと、単一の共有HTTP接続プールを使用しています。4つの下流サービスがこれらのリソースを競合しています。サーキットブレーカーは実装されていません。
障害モードレビューの設計
統合
あなたは、視覚的な検査によって単一障害点(SPOF)を特定し、カスケード障害のパターンを認識し、ポストモーテム(事後分析)を読む際にトリガーと潜在的要因を区別し、予防 / 検知 / 回復の観点から非責任意向のアクションアイテムを作成し、バルクヘッド + サーキットブレーカー + グレースフルデグラデーションによって影響範囲(ブラスト半径)を制限する方法を学びました。
これら5つのすべてを適用してください。
あなたのチームは、3つの下流サービスに依存する新しいサービス search.example.com をリリースしようとしています。これらは、プライマリ検索インデックス(index.example.com)、アナリティクスサービス(analytics.example.com)、およびレコメンドサービス(recs.example.com)です。チームは、リリース前に「障害モードレビュー」を主導するようあなたに依頼しています。
このコースの次のステップ
このコースの次のステップ
これにより、SPOFを特定し、カスケード障害を認識し、ポストモーテムを効果的に読み解き、非責任意向きのアクションアイテムを作成し、設計段階でブラスト半径を制限できるようになります。
このコースの最終レッスン(cs_distsys_observability_and_capacity)では、ユーザーが気づく前に問題が発生していることを把握するために何を測定すべきかを学びます。ヘルスチェック、バージョンエンドポイント、プロキシ層における4つのゴールデンシグナル、そしてサージキャパシティの決定が観測データとどのように結びつくかについてです。
関連レッスン: geometry_of_failure_modes_and_blast_radius では、介在中心性(どのグラフノードがボトルネックであるか)と最小カット(ブラスト半径の上限)を導出します。
お疲れさまでした。次へ進みましょう。