障害モード分類マップ(フェイルストップ〜ビザンチン)
故障を「クラッシュ・オミッション・タイミング・ビザンチン」と検知可能性で整理でき、どこまで対処すべきかの線引きがつかめる。グレー障害や部分故障がなぜ厄介かも腑に落ちる。
- 1.故障は包含関係を持つ階層。クラッシュ ⊂ オミッション ⊂ タイミング ⊂ ビザンチン で、外側ほど検知が難しく対処コストが跳ね上がる。
- 2.現実の分散システムは「きれいに死なない」。グレー障害や部分故障は、観測点によって生死が食い違うため単純なヘルスチェックでは捕まらない。
- 3.クラッシュ系は過半数クォーラム、タイミングは冪等性とフェンシング、ビザンチンは投票多重化(3f+1)で対処。どこまで仮定するかが設計の出発点。
なぜ「故障の種類」を分類するのか
フォールトトレラント設計は、必ず**「どんな壊れ方までを想定するか」という故障モデルの宣言**から始まります。「ノードはクラッシュするだけ」と仮定すれば過半数クォーラムで足りますが、「ノードが嘘の値を返しうる」と仮定するなら、はるかに高価なビザンチン合意が要ります。前提を取り違えると、守れているつもりで穴が空きます。
そこで故障を検知可能性・影響範囲・対処コストという軸で系統立てて並べます。重要なのは、これらが互いに独立した箱ではなく包含関係を持つ階層だという点です。外側の故障モードは内側のすべてを含み、より一般的で、より対処が難しくなります。
故障モデルとは、システムが耐えるべき「最悪の振る舞い」の上限を決めることです。弱い敵(クラッシュのみ)を仮定すれば設計は安く済みますが、現実の敵がそれより強ければ防御は破られます。逆に強い敵(ビザンチン)を常に仮定するとコストが過大になる。どこに線を引くかが設計判断の核心です。
系統図:故障モードの包含階層
古典的な分類(Cristian らの整理)では、故障モードは次の入れ子構造を成します。内側ほど「行儀がよく」、外側ほど「何でもあり」になります。
ビザンチン故障(任意・恣意的な振る舞い)
└─ タイミング故障(速すぎ/遅すぎ)
└─ レスポンス故障(誤った値・誤った状態遷移)
└─ オミッション故障(送るべき応答を出さない)
└─ クラッシュ故障(停止後そのまま停止し続ける)
└─ フェイルストップ(クラッシュ+停止を他者が検知可能)
下から順に見ていきます。
- フェイルストップ(fail-stop): ノードは停止し、かつその停止を他のノードが確実に検知できる理想化された故障。最も扱いやすいが、現実には完全には成立しません(後述の検知不能性のため)。
- クラッシュ(crash / fail-stop の弱版): ノードは応答を止め、二度と正しい動作を再開しない。ただし他者が即座に検知できるとは限らない。
- オミッション(omission): 本来送るべきメッセージや応答を取りこぼす。送信オミッション(出さない)と受信オミッション(届いても処理しない)に分かれる。クラッシュは「すべてのメッセージを永久にオミットする」特殊ケースと見なせる。
- タイミング(timing): 値は正しいが時間制約を破る。遅すぎる応答だけでなく、速すぎる(古い前提で先走る)も含む。同期システムでのみ定義可能で、非同期前提では「遅い」と「停止」を区別できない。
- ビザンチン(Byzantine / 任意故障): 制約なし。誤った値、矛盾する値(相手ごとに別の答え)、結託した攻撃まで含む、最も一般的な故障。バグ・メモリ破壊・悪意のいずれが原因かを問わない。
ビザンチン耐性を持つプロトコルは、定義上クラッシュやオミッションにも耐えます(それらはビザンチンの部分集合だから)。逆は成り立ちません。クラッシュ前提のRaftは、ノードが嘘をつくと容易に破綻します。「より外側を仮定した対策は内側を包含する」——ただしコストも跳ね上がります。
検知可能性という第二の軸
包含階層は「振る舞いの自由度」の軸でした。実務でより痛いのは検知可能性の軸です。FLP不可能性定理が示すとおり、非同期ネットワークでは**「クラッシュしたノード」と「単に遅いノード」を確実に区別できません**。応答が来ないとき、それが停止なのか、長いGCポーズなのか、経路の輻輳なのかを原理的に判定できないのです。
このため、フェイルストップという「停止が即座に分かる」理想は現実には近似でしかなく、障害検知器の理論はこの近似の限界(完全性と正確性のトレードオフ)を扱います。検知できない故障ほど対処が遅れ、被害が広がります。
擬似コード:タイムアウトによる「クラッシュ」判定の危うさ
send heartbeat to node N every T_interval
if no ack from N within T_timeout:
suspect(N) = true # クラッシュと「疑う」だけ。確証ではない
# 問題:N は生きていて遅いだけかもしれない(誤検知)
# T_timeout を伸ばせば誤検知は減るが、本物のクラッシュ検知が遅れる
タイムアウトを短くすれば遅延ノードを誤って「死んだ」と判定し(誤検知)、長くすれば本物のクラッシュ検知が遅れる。この二律背反は消せません。だから現実の検知は常に「疑い(suspicion)」であり、確証ではないのです。
グレー障害と部分故障:階層からはみ出す現実
古典的分類は「ノード単位で1つの故障モード」を暗黙に仮定します。しかし大規模システムで実際に最も厄介なのは、この枠に収まらない2つの現実です。
グレー障害(gray failure) は、ノードが完全には死なず、観測点によって生死の判定が食い違う状態です。ヘルスチェック用の軽量エンドポイントは200を返し続けるのに、実トラフィックではレイテンシが劣化し一部リクエストが失敗する——監視は「健全」、ユーザーは「障害」と認識する**差分観測(differential observability)**が本質です。フェイルストップを暗黙に仮定したフェイルオーバーは、この状態でほぼ機能しません。「死んでいない」と判定され続けるからです。
部分故障(partial failure) は、分散システムを分散システムたらしめる性質そのものです。あるノードへの呼び出しだけが失敗し、他は成功する。送信は届いたが応答だけ失われる(受信側は処理済み、送信側は未達と認識)。この**「一部だけ壊れる」状態は単一マシンには存在せず**(プロセスは生きるか死ぬかのどちらか)、分散環境固有の難しさです。
完全にクラッシュしたノードはクォーラムから外れて影響が局所化します。しかしグレー障害ノードは「生きている」と判定されたままトラフィックを受け続け、遅延や部分失敗を上流へ伝播させます。これがカスケード障害とメタステーブル状態の起点になりがちで、半死のノードを残すより明示的に切り離す(fail-fast)方が系全体では安全なことが多いのです。
分類と対処の対応マップ
各故障モードに対し、検知可能性・影響範囲・標準的な対処と、その対処コストを並べます。外側へ行くほどコストが跳ね上がる構造が見て取れます。
| 故障モード | 検知可能性 | 影響範囲 | 標準的な対処 | 対処コスト |
|---|---|---|---|---|
| フェイルストップ | 高(停止を即検知できる理想) | 停止ノードに局所化 | ホットスタンバイへ切替 | 低 |
| クラッシュ | 中(タイムアウト=疑いのみ) | クォーラムで吸収可能 | 過半数クォーラム・複製 | 低〜中 |
| オミッション | 中(再送で顕在化) | メッセージ単位 | ACK・再送・冪等化 | 中 |
| タイミング | 低(遅延と停止が不可分) | 同期前提が崩れ波及 | フェンシング・期限・冪等性 | 中〜高 |
| ビザンチン | 極低(嘘は嘘と分からない) | 結託で全体に波及 | 投票多重化(3f+1)・署名 | 極高 |
| グレー障害 | 極低(観測点で食い違う) | 上流へ静かに伝播 | 多点観測・SLI・fail-fast | 高 |
ビザンチン耐性の代表的な要件が 3f+1(f台の任意故障に耐えるには最低 3f+1 台必要、つまり故障が全体の3分の1未満)です。クラッシュ耐性の 2f+1(過半数クォーラム)に比べノード数も通信量も大きく増えるため、ビザンチン耐性の仕組みは金融台帳など本当に必要な領域に限って採用されます。多くの社内システムはクラッシュ故障モデルで十分で、ビザンチンを仮定するのは過剰設計になりがちです。
設計判断としての故障モデル選択
分類の実用的な使い方は、自分のシステムがどのモードまでを「起こりうる」と認めるかを明示することです。指針は次のとおりです。
- 信頼できる自社データセンター内・正しく実装されたソフト: クラッシュ+オミッション故障モデルで十分。過半数複製と再送・冪等化で守る。
- ネットワークが不安定・時刻同期が怪しい環境: タイミング故障を直視する。応答の遅延・順序入れ替え・重複を前提に、フェンシングトークンと冪等な操作で守る。
- 相互不信のノードが参加(パブリックチェーン等)・ハードウェア由来のビット化け: ビザンチンを仮定。投票多重化と署名で防ぐが、コストは最大。
- どの環境でも常に: グレー障害と部分故障は必ず起こると考え、単一のヘルスチェックを信用せず、実トラフィックに近い複数視点のSLIで観測する。サーキットブレーカとバルクヘッドで部分故障の伝播を遮断する。
まとめ
- 故障モードはフェイルストップ ⊂ クラッシュ ⊂ オミッション ⊂ タイミング ⊂ ビザンチンという包含階層を成す。外側ほど一般的で、検知が難しく、対処コストが跳ね上がる。
- 第二の軸は検知可能性。非同期環境では「停止」と「遅延」を原理的に区別できず、検知は常に「疑い」にとどまる。フェイルストップという理想は近似でしかない。
- 古典分類からはみ出すのがグレー障害(観測点で生死が食い違う)と部分故障(一部だけ壊れる、分散固有)。単純なヘルスチェックでは捕まらず、半死ノードが残るとカスケードの起点になる。
- 対処はモード別にクォーラム(クラッシュ)/冪等性・フェンシング(タイミング)/3f+1 投票(ビザンチン)/多点観測・fail-fast(グレー)。どこまで仮定するかを最初に宣言することが、過不足ない設計の出発点になる。
DevOps/インフラ Article
障害モード分類マップ(フェイルストップ〜ビザンチン)を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
分散システム
比較で見る軸
難易度: advanced / カテゴリ: DevOps/インフラ / タグ数: 6
導入後に効く点
現実の分散システムは「きれいに死なない」。グレー障害や部分故障は、観測点によって生死が食い違うため単純なヘルスチェックでは捕まらない。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- DevOps/インフラ
- タグ数
- 6
判断チェックリスト
- 自社の用途が「分散システム / 障害モデル」に近いか確認する。
- 強みである「故障は包含関係を持つ階層。クラッシュ ⊂ オミッション ⊂ タイミング ⊂ ビザンチン で、外側ほど検知が難しく対処コストが跳ね上がる。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。