バルクヘッドパターンとは?障害隔離の仕組みと実装方法・採用判断を実装者目線で解説

バルクヘッドパターンは、スレッドや接続といった有限のリソースを依存先ごとに区切り、片方が詰まってももう片方が動き続けるようにする設計パターンです。難所は区画を作ること自体ではなく、どの境界で切るか、1区画にいくつ枠を割り当てるかの判断にあります。この記事では、リソース枯渇が連鎖障害へ広がる経路、Resilience4jの2方式の既定値、KubernetesとIstioによるインフラ層での区画化、リトルの法則を使った区画サイズの算出、採用を見送るべき条件までを実装目線で追いました。数値はMicrosoft・Resilience4j・Polly・Istioの各公式ドキュメントを2026年8月5日に参照した内容に合わせています。

まとめ:バルクヘッドを入れる境界と、区画化で捨てる処理容量の見積もり

先に結論を置きます。バルクヘッドが効くのは「1つの呼び出し元が複数の依存先を持つ」構造だけ。依存先が1本しかないサービスに区画を切っても、詰まったときに守る相手が存在しません。境界は依存先の単位、またはテナントや優先度の単位で引きます。

実装は3層に分かれます。ライブラリ層はResilience4jのセマフォまたはスレッドプール、インフラ層はKubernetesのリソース制限とサービスメッシュの接続プール、配置層はセルやシャードによるインスタンス分割。Microsoftの公式ガイダンスは2026年3月時点で、プラットフォーム側の制御を先に使い、同じ仕組みをアプリコードで作り直さないよう明記しています。

見落とされるのが分割損です。共有プール100枠を4区画に切ると、1区画が遊んでいても他区画はその枠を借りられません。区画数は増やすほど安全という話にならず、実測レイテンシから算出した必要枠に少し余裕を足した値で止めるのが実務解になります。

バルクヘッドパターンの定義と、リソース枯渇が連鎖障害へ広がる経路

まず押さえるのは、このパターンが守っている対象が「呼び出し先」ではなく「呼び出し元の手持ちリソース」だという点です。

船舶の隔壁に由来する障害分離と、Azureが示す2種類の区画モデル

名称の由来は船体を仕切る隔壁です。船底に穴が開いても、浸水するのは破損した区画だけで済み、船全体は沈まない。この構造をソフトウェアのリソース割り当てに写したものがバルクヘッドパターンにあたります。

Microsoftのアーキテクチャセンターは2026年3月更新版で、このパターンをセルベースアーキテクチャの別名として扱い、区画の作り方を2つ示しています。1つはコンシューマー側の分割で、複数のサービスを呼ぶクライアントが呼び先ごとに接続プールを持つ形。サービスAが応答しなくなっても、サービスBとCへ向かうプールは無傷で残ります。もう1つはサービス側の分割で、1つのサービスをクライアントごとに別インスタンスへ割り当てる形です。クライアント1が過剰な要求でインスタンスを飽和させても、クライアント2と3は影響を受けません。

選択の軸は障害の発生源です。外部APIの不調に巻き込まれたくないならコンシューマー側、特定テナントの負荷を封じ込めたいならサービス側を採ります。

接続プールの枯渇が無関係な依存先まで巻き込む障害発生の順序と兆候

区画を切らない場合に何が起きるか、順を追うと理解しやすくなります。ある注文APIが決済・在庫・通知の3サービスを呼んでいて、HTTPクライアントの接続プールを200枠で共有しているとしましょう。

  1. 決済サービスの応答が通常50msから30秒へ悪化する
  2. 決済向けの呼び出しがプール枠を掴んだまま返らず、枠が滞留する
  3. 数十秒で200枠すべてが決済待ちに占有される
  4. 在庫と通知への呼び出しが枠を取得できず、正常なサービスへの処理まで失敗する
  5. 注文APIのスレッドも待機で埋まり、ヘルスチェックが落ちて全断に至る

所要時間はプール枠数と流量で決まり、毎秒40リクエストで200枠なら理論上5秒で全枠が埋まります。兆候として先に現れるのは、遅延した依存先ではなく無関係な依存先のエラー率上昇。ここを見誤ると健全な在庫サービス側を調査して時間を溶かします。

サーキットブレーカー・リトライ・レートリミットとの役割分担と併用順

この4つは同じ「障害を広げない」目的を持ちますが、作用する対象が違います。混同したまま片方だけ入れると、守れる範囲に穴が残ります。

パターン 制御する対象 効き始める条件
バルクヘッド 手持ちの並列枠 枠の上限に達した時点
サーキットブレーカー 呼び出しの可否 失敗率が閾値を超えた後
リトライ 失敗後の再送 個々の呼び出しが失敗した時
レートリミット 単位時間の流量 毎秒の回数が上限を超えた時

併用の順序には定石があります。外側から順にバルクヘッド、サーキットブレーカー、リトライ、そして実際の呼び出し。バルクヘッドを最外周に置くのは、リトライパターンによる再送がバルクヘッドの内側で発生し、区画の枠を消費する形にするためです。逆順にすると再送が枠の外で増殖します。失敗が続く依存先の呼び出し自体を止める役目はサーキットブレーカーが担い、受け手側で毎秒の回数を絞る役目はレートリミットが担います。

アプリケーション層でのバルクヘッド実装、セマフォとスレッドプールの選択

Java系ではResilience4jが事実上の標準です。同ライブラリは方式の異なる2種類のバルクヘッドを持ち、既定値も別々に設定されています。

セマフォ方式の仕組みと、Resilience4jの既定値25という数字の意味

セマフォ方式はjava.util.concurrent.Semaphoreで同時実行数を数えるだけの軽い実装です。呼び出しは元のスレッドでそのまま実行され、スレッドの切り替えが発生しません。枠を取れなかった呼び出しは即座に例外で弾かれます。

公式ドキュメントを2026年8月5日時点で確認すると、maxConcurrentCallsの既定値は25、maxWaitDurationの既定値は0msでした。後者が0という設定の意味は明快で、枠が空くのを待たずに落とすということ。待たせない挙動を既定に置いているのは、待ち行列そのものがリソース枯渇の原因になるという判断からです。

25という数字は汎用の初期値であり、根拠のある値ではありません。実際の設定では後述するリトルの法則で必要枠を出します。制約も1つあり、セマフォ方式は呼び出しがブロッキングI/Oだと呼び出し元スレッドが待機したままになるため、スレッドプール側の枯渇を防げません。

スレッドプール方式が持つ待ち行列100件とタイムアウト分離の効果

スレッドプール方式は、依存先ごとに専用のスレッドプールと有界キューを与える実装です。呼び出しは別スレッドで走るため、呼び出し元スレッドは待機から解放されます。ブロッキングI/Oを含む依存先には、こちらを選びます。

設定項目 既定値 調整の観点
maxConcurrentCalls 25 セマフォ方式の同時実行数
maxWaitDuration 0ms 枠待ちの許容時間
maxThreadPoolSize CPUコア数 区画の最大並列数
coreThreadPoolSize CPUコア数から1減 常時確保する本数
queueCapacity 100 枠待ちの滞留許容数
keepAliveDuration 20ms 余剰スレッドの生存時間

表のqueueCapacityが既定100という点は、設定の前に必ず見直してください。CPUコア4のマシンなら実行枠4に対して待機100件、最大104件が区画に滞留し得ます。1件30秒かかる依存先で満杯になれば、キュー末尾は十数分待たされる計算。キュー長は「タイムアウト時間内に処理し切れる件数」まで縮めるのが作法です。

Polly 8系でBulkheadが消えた理由とConcurrencyLimiter

.NET側では扱いが変わりました。Pollyの移行ガイドによると、7系にあったBulkheadポリシーは8系で独立した戦略としては提供されず、レートリミッター戦略の一種であるConcurrencyLimiterに統合されています。実装基盤はSystem.Threading.RateLimitingパッケージで、旧BulkheadAsyncに対応するのがAddConcurrencyLimiterです。

公式が挙げる理由は、バルクヘッドが本質的にレートリミッターの特殊形、すなわち「時間あたり」ではなく「同時実行数」で絞る形式にすぎないという整理でした。概念が消えたのではなく、正確な名前が与えられたと読むのが正しい理解になります。

移行時の注意は、8系のパッケージが旧APIも同梱しているため古い書き方のまま動いてしまう点。段階移行できる利点の裏返しで、置き換え漏れが検知されません。レートリミット系の機能にはPolly.RateLimitingパッケージの参照追加が別途必要です。

インフラ層での区画化、コンテナ制限とサービスメッシュによる隔離設定

コードを触らずに区画を作れる層があります。Microsoftのガイダンスが2026年3月時点で推す順序も、まずプラットフォーム側の制御です。

Kubernetesのrequestsとlimitsで作るコンテナ単位の資源区画

サービスを別コンテナへ分けること自体が、すでにバルクヘッドとして機能します。公式ドキュメントが例示する構成では、Podのコンテナにrequestsとしてメモリ64Mi・CPU 250m、limitsとしてメモリ128Mi・CPU 1を与えていました。CPU 1の上限が付いていれば、そのコンテナが暴走してもノード上の他コンテナへ食い込みません。

仮想マシン分割に比べてオーバーヘッドが小さい割に、CPUとメモリの両方を実効的に隔離できる点が利点です。限界も明確で、コンテナのリソース制限は自分のプロセスが使う資源を縛るだけ。外部APIの応答待ちで接続プールが埋まる事態は防げません。インフラ層の区画とライブラリ層の区画は代替関係になく、担当する枯渇の種類が違います。

IstioのconnectionPoolが既定で無制限である事実と設定の勘所

サービスメッシュを入れているなら、DestinationRuleのconnectionPoolが区画そのものになります。宛先サービスごとにルールを書けるため、依存先単位の分割と相性が良い作りです。

ただし2026年8月5日時点のIstioリファレンスを読むと、主要項目の既定値は事実上の無制限でした。maxConnectionshttp1MaxPendingRequestshttp2MaxRequestsはいずれも2の32乗から1を引いた値、maxRequestsPerConnectionは0で上限なし。connectTimeoutのみ10秒です。メッシュを導入しただけでは区画にならず、明示的に値を書いて初めて隔壁が立ちます。

併せて設定したいのがoutlierDetectionで、こちらは既定値が入っています。consecutive5xxErrorsが5、判定間隔10秒、最小排除時間30秒、maxEjectionPercentが10%。排除上限が10%のため、半数のホストが不調な状況では効果が出ません。区画側で流量を絞る設定と組み合わせます。

キュー分離とシャッフルシャーディングによるテナント単位の隔離

非同期メッセージで繋がる構成では、キューを分けること自体が区画化になります。処理の種類や優先度ごとに別キューを持ち、専用のコンシューマー群を割り当てる形です。重い一括処理が滞留しても、対話的な処理のキューは流れ続けます。

テナント単位で分けたい場合、全テナントに専用インスタンスを与えるのはコストが見合いません。ここで使われるのがシャッフルシャーディングで、各テナントにインスタンスの組み合わせを割り当てます。8ノードから2つを選ぶ組み合わせは28通りあるため、8ノードでも28テナントをほぼ重ならない形で収容できる計算です。1テナントが2ノードを飽和させても、完全に巻き込まれるのは組み合わせが一致した相手だけ。

境界の引き方そのものは、サービス分割の設計と同じ問題です。Microsoftのガイダンスも、ドメイン駆動設計で区切る場合は区画の境界を境界付けられたコンテキストへ一致させるよう述べています。分割単位はマイクロサービスとモノリスの選び方を先に押さえると判断しやすくなります。

区画サイズの決め方と、分割によって失う処理容量の具体的な見積もり

ここからが独自の論点です。バルクヘッドの解説は「分けましょう」で終わりがちですが、実務で詰まるのは枠数を何にするかという一点に集中します。

リトルの法則で並列数を出す手順と、実測レイテンシからの逆算例

待ち行列理論のリトルの法則を使うと、必要な枠数が計算で出ます。式は「系内の平均件数=到着率×平均滞在時間」。バルクヘッドに当てはめると、必要な同時実行枠=目標スループット×平均レスポンスタイムになります。

  1. 依存先ごとの実測レイテンシを取得する(p50ではなくp95を使う)
  2. その依存先へ向かうピーク時の毎秒リクエスト数を取得する
  3. 両者を掛けて必要枠を算出する
  4. 算出値に1.5倍から2倍の余裕を掛け、区画の枠数とする

具体例を置きます。決済APIのp95が400ms、ピークが毎秒50リクエストなら、必要枠は50×0.4で20枠。余裕を1.5倍取って30枠が設定値になります。Resilience4jの既定値25はこのケースでは僅かに不足しており、繁忙時に弾かれ始める水準でした。

p95を使う理由は、枠が足りなくなるのが遅い側の呼び出しだからです。平均値で設計すると、分布の右裾が伸びた瞬間に枠が溢れます。

区画化で失う処理容量の試算と、共有プールを残すべき判断の基準線

分割には必ず損があります。共有プール100枠を4依存先へ25枠ずつ割った構成を考えてください。依存先Aへの流量が急増して25枠を使い切ったとき、B・C・Dが合計10枠しか使っていなくても、Aは残り65枠を借りられません。共有なら捌けた処理が、区画化したせいで弾かれます。

分割損の大きさは、依存先ごとのピークがどれだけずれるかで決まります。全依存先が同時にピークを迎えるなら損はほぼゼロ。ずれるほど遊ぶ枠が増えます。

判断の基準線は次のように引きます。依存先が3本以下で、そのうち1本でも落ちたら業務が止まる構成なら、区画化しても守る対象がないため共有プールのまま上限とタイムアウトだけ締める。依存先が4本以上あり、片方だけ落ちても縮退運転で価値を出せるなら区画化する。優先度の異なる呼び出し元が混在するなら、依存先ではなく優先度で区切るほうが効きます。区画数と稼働率の関係は可用性の計算方法を併せて見ると数字で比較できます。

飽和率と拒否数で区画を評価する監視設計、RED指標との対応付け

区画は設定した瞬間から陳腐化します。トラフィックが伸びれば必要枠も伸びるため、飽和率の監視が前提条件になります。

見るべき指標は3つ。使用中の枠数を上限で割った飽和率、枠を取れずに弾かれた拒否数、待ち行列の滞留件数です。飽和率が常時80%超なら枠不足、常時20%未満なら枠が過剰で分割損だけ払っている状態にあたります。拒否数はゼロが正解ではありません。障害時に拒否が立つことこそ区画が働いた証拠で、平常時ゼロ・障害時に立つ形が望ましい姿です。

サービス全体の指標と結び付ける際は、流量・失敗数・所要時間で見るREDメソッドの枠組みに、区画ごとの飽和率を足す形が扱いやすくなります。失敗数が跳ねたとき、それが依存先の障害なのか自区画の枠不足なのかを切り分けられます。

バルクヘッドを採用しない条件と、現場で起きる3つの失敗パターン

最後に見送り条件を言い切ります。Microsoftの公式ガイダンスも、適さない場合として「リソースの効率の低い使用が許容できない」「追加の複雑さは必要ない」の2点を挙げています。

バルクヘッドを入れない3条件、依存先1本と低流量での過剰設計

次の3条件のいずれかに当たるなら、区画化しないほうが総合的に良い結果になります。

  • 依存先が1本しかない。詰まった時点で価値がゼロになるため、区画で守る相手がいない
  • ピーク流量が毎秒数リクエスト程度で、プールが枯渇し得ない。設定項目だけが増える
  • 依存先がすべて同一の外部サービス。区画は独立障害が前提で、共通原因障害には無力

2番目については、代わりにタイムアウトの設定を優先してください。低流量の環境で実際に起きるのは枠の枯渇ではなく、タイムアウト未設定の呼び出しが無限に待つ事故です。

区画の切りすぎとタイムアウト未設定、現場で起きる3つの失敗例

導入したのに効かない、あるいは悪化させる典型を挙げます。

1つ目は区画の切りすぎ。エンドポイント単位まで細分化すると、1区画あたりの枠が数個まで落ち、通常時から拒否が発生します。粒度は依存先サービス単位、多くても機能グループ単位で止めるべきです。

2つ目はタイムアウト未設定のまま区画だけ入れたケース。区画は枠数の上限を課しますが、掴んだ枠がいつ返るかは制御しません。呼び出しにタイムアウトが無いと、25枠が永久に埋まったまま区画ごと死にます。区画とタイムアウトはセットで、単独導入は効果が半減します。

3つ目はセマフォ方式をブロッキングI/Oに当てた場合。同時実行数は制限できても、スレッド自体が待機で埋まる問題は解消されません。冗長構成で継続性を確保するフォールトトレラントな設計と違い、区画は「壊れる範囲を限る」だけの仕組みです。設定した区画が隔壁として機能するかは、AWS Fault Injection Serviceのような障害注入で確かめます。

導入順序の決め方と、外部API連携で区画化を外注する際の相談範囲

着手順序には優先度があります。タイムアウトの設定、コンテナのリソース制限、依存先単位の接続プール分割、最後にアプリ層の区画。上から順に費用対効果が高い並びです。

設計判断が難しくなるのは、外部SaaSや決済ゲートウェイなど自社で制御できない依存先を複数抱える構成でしょう。実測レイテンシが取れていない、区画サイズの根拠を示せない、既存コードのどこに挟むか決められない、といった段階で止まっているなら、構成の棚卸しから設計を引き受ける形が現実的です。外部連携の信頼性設計を含むAPI開発・システム連携では、実測に基づく区画サイズの設定と縮退方針まで相談を受けています。

逆に依存先が1本で流量も小さいなら、外注してまで区画を入れる話になりません。タイムアウトとリトライの見直しで足ります。

よくある質問

バルクヘッドパターンの実装と運用でよく聞かれる点をまとめました。

バルクヘッドパターンとサーキットブレーカーはどちらを先に入れるべきですか?

タイムアウトを設定済みという前提なら、サーキットブレーカーを先に入れる構成が扱いやすくなります。失敗が続く依存先への呼び出しを止めるだけで、枠の滞留の大半が解消するためです。バルクヘッドが要るのは、ブレーカーが開くまでの検知期間中にも枠を守りたい場合と、失敗ではなく遅延だけが起きて閾値に届かない場合。両方入れるときはバルクヘッドを外側に置きます。

Resilience4jのセマフォ方式とスレッドプール方式はどう使い分けますか?

呼び出しがノンブロッキング、つまりリアクティブなHTTPクライアントや非同期APIならセマフォ方式で足ります。オーバーヘッドがほぼ無く、同時実行数の上限だけを課せます。ブロッキングI/Oの場合はスレッドプール方式を選んでください。呼び出し元スレッドを解放でき、依存先の遅延が波及しません。ただしスレッド切り替えのコストが乗るため、レイテンシ要件が厳しい経路では実測して判断します。

バルクヘッドの枠数はどう決めればよいですか?

リトルの法則で算出します。必要枠=ピーク時の毎秒リクエスト数×p95レスポンスタイム(秒)。毎秒50リクエスト・p95が400msなら20枠が理論値で、1.5倍から2倍の余裕を掛けた値を設定します。Resilience4jのmaxConcurrentCalls既定値25は実測に基づく値ではありません。設定後は飽和率を監視し、常時80%超なら増やし、常時20%未満なら減らします。

Kubernetesのリソース制限だけでバルクヘッドになりますか?

部分的にはなります。CPUとメモリはコンテナ単位で隔離でき、暴走したコンテナが他へ食い込む事態を防げます。ただし外部APIの応答待ちで接続プールやスレッドが埋まる枯渇は、リソース制限では止まりません。この種の枯渇に効くのはアプリ層の区画かサービスメッシュの接続プール設定です。守る対象が「自分の資源消費」か「他者を待つ資源」かで必要な層が変わります。

バルクヘッドを入れるとスループットは落ちますか?

共有プールと同じ総枠数で分割した場合、ピークがずれる依存先ほどスループットは落ちます。ある区画が枠を余らせていても、他区画が借りられないためです。損失は区画数が多いほど大きくなります。総枠数を据え置いて細かく割るのではなく、各区画の必要枠を実測から積み上げて総枠を決め直すのが正しい進め方です。

関連記事

資料請求

RELATED POSTS 関連記事