サーキットブレーカとバルクヘッドの状態機構
落ちた依存先を叩き続けて自分まで道連れにする——その連鎖を断つのがサーキットブレーカとバルクヘッドです。状態遷移としきい値、隔離と負荷制限の原理を押さえれば、カスケード障害を設計で止められます。
- 1.サーキットブレーカは Closed/Open/Half-Open の3状態を持つ。失敗率がしきい値を超えると Open に落ちて即座に失敗を返し、弱った依存先への呼び出しを止めて回復時間を与える。
- 2.Open から一定時間後に Half-Open へ移り、少数の試験リクエストだけを通す。成功すれば Closed に戻し、失敗すれば再び Open に落とす。これで自動復旧と再障害の抑止を両立する。
- 3.バルクヘッドはリソースプール(スレッド・接続・並行数)を依存先ごとに区切り、1つの依存先の遅延が全リソースを食い尽くすのを防ぐ。load shedding は容量超過分を早期に捨てて系の崩壊を避ける。
なぜ「呼び出しを止める」制御が要るのか
分散システム(/devops/microservices/)では、サービス A がサービス B を、B が C を呼ぶ依存の連鎖が生まれます。ここで C が遅くなると何が起きるか。B が C への応答を待つ間、B のスレッドや接続はブロックされたまま占有され続けます。C の遅延が長引くほど B のリソースは待ち行列に消費され、やがて B 自身が新規リクエストを捌けなくなる。すると B を呼ぶ A も同じ経路で詰まる——1つの末端の遅延が、依存をさかのぼって系全体を停止させる。これが カスケード障害(cascading failure) です。
増幅の本質は、再試行(/devops/retry-backoff-jitter/)と同じく 遅い相手を待つこと自体がコスト だという点にあります。タイムアウトを長く取るほど、失敗が確定するまでリソースが拘束される。素朴な「待って再送する」だけの設計では、最も負荷を捌けない瞬間に呼び出しが積み上がる。
即座に返るエラーは、呼び出し側のリソースをすぐ解放します。本当に怖いのは 遅い成功・遅い失敗 です。タイムアウト直前まで待たされた呼び出しが大量に滞留すると、待ち行列の原理(/devops/queueing-theory-tail-latency/)どおり、稼働率が飽和に近づくと待ち時間が急激に伸び、系のリソースが一気に枯渇します。
サーキットブレーカ:3状態の状態機構
サーキットブレーカ は、電気回路のブレーカと同じく「異常を検知したら回路を開いて遮断する」装置です。依存先への呼び出しを監視し、失敗が一定水準を超えたら呼び出しを 止めて即座に失敗を返す(fail fast)。弱った相手に回復の時間を与え、こちら側のリソース拘束も防ぎます。
中核は3つの状態とその遷移です。
| 状態 | 呼び出しの扱い | 意味 | 次の遷移 |
|---|---|---|---|
| Closed(閉) | 通常どおり相手へ転送する | 正常。失敗を計数しつつ通す | 失敗率がしきい値超で Open へ |
| Open(開) | 相手を呼ばず即座に失敗を返す | 遮断中。fail fast で相手を休ませる | 待機時間の経過で Half-Open へ |
| Half-Open(半開) | 少数の試験リクエストだけ通す | 回復したか様子見 | 成功で Closed、失敗で Open へ戻る |
「閉(Closed)=回路がつながり電流が流れる=呼び出しが通る」という対応です。ブレーカが落ちる(trip)とは Closed から Open への遷移を指します。
状態遷移(簡略)
Closed ──失敗率がしきい値超──▶ Open
Open ──待機タイマ経過──▶ Half-Open
Half-Open ──試験リクエスト成功──▶ Closed
Half-Open ──試験リクエスト失敗──▶ Open(タイマ再開)
Half-Open が要石です。Open のまま放置すれば永久に遮断したままですし、待機時間後にいきなり全トラフィックを戻せば、まだ回復していない相手を再び殴って即 Open に逆戻りします。そこで 少数の試験リクエストだけを通し、その結果で復旧可否を判定する。これにより自動復旧と、回復前の再障害(thundering herd 的な再殺到)の抑止を両立します。
しきい値設計:いつ落とし、いつ戻すか
ブレーカの挙動はパラメータで決まります。設計の要点を整理します。
- 失敗の判定方式:単純な「連続 N 回失敗」より、直近ウィンドウの失敗率(例:直近100件のうち50%超が失敗)で判定するのが堅牢です。Resilience4j などは件数ベース/時間ベースのスライディングウィンドウを持ちます。低トラフィック時に少数の失敗で誤発動しないよう、最小呼び出し数(ウィンドウ内に最低 M 件たまるまで判定しない)を併用します。
- 何を失敗と数えるか:相手の例外やタイムアウトは失敗。一方、
404のような 呼び出し側起因の応答は失敗に含めない——遮断しても回復しないからです。再試行と同様、一時的失敗とビジネス上の正常応答を区別します。 - 待機時間(Open の保持時間):相手が回復に要する見積もり時間。短すぎれば Half-Open で何度も叩いて回復を妨げ、長すぎれば正常化後も無駄に遮断が続きます。失敗が続くほど待機時間を指数的に伸ばす実装もあります。
- Half-Open の試験本数と復帰条件:試験リクエストを何件通し、何件成功したら Closed に戻すか。1件で判定すると偶発成功で早すぎる復帰を招くため、複数件の成功を要求するのが安全です。
Open のとき即失敗を返すだけでは、ユーザー体験は「エラー」のままです。ブレーカが開いている間の 代替応答(フォールバック) ——キャッシュ値、デフォルト値、機能の縮退(graceful degradation)——を用意して初めて、遮断が「保護」として機能します。fail fast は手段であって、目的は系全体を生かしたまま部分的に劣化させることです。
バルクヘッド:リソースを隔壁で区切る
サーキットブレーカが「時間軸の遮断」なら、バルクヘッド(bulkhead) は「空間軸の隔離」です。語源は船の防水隔壁で、1区画に浸水しても他区画へ広がらず船全体は沈まない、という発想を借りています。
ソフトウェアでは、依存先ごとにリソースプールを分割 します。サービスが依存先 X, Y, Z を呼ぶとき、全体で1つの共有スレッドプール(例:200スレッド)を使うと、X が遅延して全スレッドを待ち行列で食い尽くした瞬間、健全な Y, Z への呼び出しに使えるスレッドが枯渇し、巻き添えで全機能が止まります。
共有プール(危険) バルクヘッド(隔離)
[ 200スレッド共有 ] X専用[ 50 ] Y専用[ 50 ] Z専用[ 50 ]
↓ X が遅延 ↓ X が遅延
全200を食い尽くす X用50だけ枯渇。Y/Zは無事
→ Y/Z も道連れ → 障害が X に封じ込まれる
依存先ごとに上限を区切れば、1つの依存先の障害はその区画の枯渇で止まり、他の機能は生き残ります。実装は2方式あります。
| 方式 | 隔離の単位 | 並行実行 | コスト | 向く場面 |
|---|---|---|---|---|
| スレッドプール隔離 | 依存先ごとの専用スレッドプール | 呼び出し元と別スレッドで実行 | コンテキストスイッチのオーバーヘッド | 外部呼び出しの遅延を呼び出しスレッドから切り離したい |
| セマフォ隔離 | 依存先ごとの並行数カウンタ | 同一スレッドで実行 | 軽量だが遅延を別スレッドへ逃がせない | 高速・低遅延な内部呼び出しの並行数制限 |
スレッドプール隔離は別スレッドで実行するため、相手が無限に遅くてもタイムアウトで呼び出し元スレッドを解放でき、遅延の伝播そのものを断てます。セマフォ隔離は「同時に通せる本数」を数えるだけで軽量ですが、実行は同一スレッドなので遅延の切り離しはできません。
load shedding:捨てることで全体を守る
バルクヘッドで区画を切っても、その区画自身の容量を超える負荷が来れば区画内で待ち行列が膨らみます。ここで効くのが 負荷制限(load shedding) ——処理しきれない分を早期に拒否して捨てる 制御です。
直感に反しますが、過負荷時は 全リクエストを受け付けようとする方が危険 です。待ち行列が無限に伸びると、リトルの法則(待ち人数 = 到着率 × 滞在時間、/devops/queueing-theory-tail-latency/)どおり滞在時間が発散し、結局どの応答もタイムアウトして 誰一人成功しない(goodput がゼロに崩落する) 状態に陥ります。容量を超えた分を即座に 503 で返して捨てれば、受け入れた分は確実に捌け、系のスループットを最大点付近で安定させられます。
バルクヘッドのプールや受信キューに 上限を設けない のは典型的な事故です。上限がなければメモリが尽きるまでリクエストが溜まり、最終的にプロセスごと落ちます。「キューが満杯なら即拒否」という有界キュー(bounded queue)こそが load shedding の実体です。区画を区切る目的は、捨てる判断を 依存先ごとに独立して下せる ようにすることでもあります。
優先度付きの load shedding——重要なリクエスト(決済など)は通し、低優先のもの(推薦表示など)を先に捨てる——を組み合わせると、限られた容量を価値の高い処理に振り向けられます。
3つの制御の組み合わせ
これらは排他ではなく層をなします。バルクヘッドで爆発を区画に封じ込め、サーキットブレーカで弱った依存先への呼び出しを時間的に遮断し、load shedding で容量超過分を捨てる。さらに再試行(/devops/retry-backoff-jitter/)の予算制御を重ね、サービスメッシュ(/devops/service-mesh/)のサイドカーでアプリ非改変に適用するのが実務の定石です。
「タイムアウトを設定した」だけでは耐障害性として不十分です。問われるのは——(1) サーキットブレーカで遅い依存先を fail fast し、(2) バルクヘッドでリソースを依存先ごとに隔離し、(3) load shedding と有界キューで過負荷を捨て、(4) フォールバックで縮退応答を返す。そして効果は推測でなく観測で確かめる(/devops/observability/)。ブレーカの開閉回数・区画の枯渇回数・拒否率をメトリクス化するのが上級者の作法です。
まとめ
- カスケード障害は、遅い依存先を待つことでリソースが拘束され、依存をさかのぼって系全体が止まる現象。鍵は 遅延の伝播を断つ こと。
- サーキットブレーカ は Closed/Open/Half-Open の状態機構。失敗率しきい値で Open に落として fail fast し、Half-Open の試験リクエストで自動復旧と再障害抑止を両立する。
- バルクヘッド は依存先ごとにリソースプールを区切り、1区画の枯渇を他へ波及させない。スレッドプール隔離は遅延を切り離せ、セマフォ隔離は軽量。
- load shedding は容量超過分を有界キューで早期に捨て、goodput の崩落を防ぐ。3つを層として組み、フォールバックと観測で全体を制御する。
DevOps/インフラ Article
サーキットブレーカとバルクヘッドの状態機構を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
サーキットブレーカ
比較で見る軸
難易度: advanced / カテゴリ: DevOps/インフラ / タグ数: 6
導入後に効く点
Open から一定時間後に Half-Open へ移り、少数の試験リクエストだけを通す。成功すれば Closed に戻し、失敗すれば再び Open に落とす。これで自動復旧と再障害の抑止を両立する。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- DevOps/インフラ
- タグ数
- 6
判断チェックリスト
- 自社の用途が「サーキットブレーカ / バルクヘッド」に近いか確認する。
- 強みである「サーキットブレーカは Closed/Open/Half-Open の3状態を持つ。失敗率がしきい値を超えると Open に落ちて即座に失敗を返し、弱った依存先への呼び出しを止めて回復時間を与える。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。