アーキテクチャパターン

バルクヘッドパターン ばるくへっどぱたーん

障害隔離マイクロサービス耐障害性リソース分離サーキットブレーカーレジリエンス
バルクヘッドパターンについて教えて

簡単に言うとこんな感じ!

船の「防水隔壁(バルクヘッド)」と同じ考え方だよ!船体の一部に穴が開いても、隔壁で区切られた区画が浸水を防いで船全体が沈まないようにする仕組みなんだ。システムも同じで、ある機能が壊れても他の機能に影響が広がらないように「区画分け」する設計パターンってこと!


バルクヘッドパターンとは

バルクヘッドパターン(Bulkhead Pattern) とは、システムの構成要素を意図的に隔離・分割することで、ある部分の障害が他の部分へ波及するのを防ぐ設計パターンです。「バルクヘッド(bulkhead)」とは船舶の防水隔壁のことで、一区画が浸水しても船全体が沈まないように区切る構造から命名されました。

マイクロサービスクラウドネイティブなシステムでは、サービスが複雑に連携するため、1つのサービスの障害や高負荷が連鎖的に全体へ広がる「カスケード障害」が起きやすくなります。バルクヘッドパターンはこの連鎖を断ち切るために、スレッドプール・接続プール・プロセス・リソース などを機能・サービス単位で分離し、障害の影響範囲を最小化します。

ビジネスの観点では、「一部の機能が重くなっても、注文処理だけは止めない」「外部APIが詰まっても、ログイン機能には影響させない」といった サービスレベルの保証 に直結する重要なパターンです。


バルクヘッドパターンの構造と仕組み

基本的な隔離の単位

隔離の単位具体例効果
スレッドプール分離機能Aに10スレッド、機能Bに10スレッドを別々に割り当て機能Aが全スレッド消費しても機能Bは動き続ける
接続プール分離DBへの接続を用途ごとに別プールに分ける重いバッチ処理が接続を占有しても参照系は動く
プロセス・コンテナ分離サービスごとに独立したコンテナ/プロセスを持つプロセスクラッシュが他サービスに波及しない
ネットワーク分離VLANサブネットで通信経路を区切る帯域輻輳の影響範囲を限定できる

なぜ「隔離しない」と困るのか

【隔離なし:カスケード障害の例】

外部API呼び出し ─── 詰まる!

    スレッドが全部埋まる

    新しいリクエストも処理できない

    注文もログインも検索も全部ダウン ← 全滅!

【隔離あり:バルクヘッドパターン】

外部API用スレッドプール ─── 詰まる!(ここで止まる)
注文処理用スレッドプール ─── 正常稼働中 ✓
ログイン用スレッドプール  ─── 正常稼働中 ✓

覚え方:「タコ部屋じゃなくて個室にしろ」

全スレッドが一つの「タコ部屋」に押し込まれていると、一人がコロナになったら全滅。でも「個室」に分けていれば、感染が広がらない。バルクヘッドパターンは リソースを個室化する 設計だと覚えよう!


歴史と背景

  • 1800年代: 船舶設計で「防水隔壁(bulkhead)」の概念が確立。タイタニック号はバルクヘッドが不完全で沈没したとも言われ、隔壁設計の重要性が広く認識された
  • 2000年代前半: 大規模なモノリシックアプリケーションでは、スレッドプールの枯渇によるシステム全停止が多発。障害隔離の必要性が現場で強く認識されるようになる
  • 2011年: Michael T. Nygardが著書 Release It! の中でバルクヘッドを耐障害性パターンの一つとして体系化。エンジニアコミュニティに広まる
  • 2012〜2013年: Netflixが Hystrix ライブラリを公開し、スレッドプール分離・サーキットブレーカーJavaアプリに組み込む実装が普及
  • 2015年〜: マイクロサービスアーキテクチャの普及とともに、バルクヘッドパターンはクラウドネイティブ設計の必須知識として定着
  • 2020年代: KubernetesのResource Limits/Requests・Istioのトラフィック管理など、インフラレベルでバルクヘッドを実現する手段が標準化

関連パターンとの比較・組み合わせ

バルクヘッドパターンは単独でも使えますが、関連する耐障害性パターンと組み合わせると強力です。

耐障害性パターンの関係図 バルクヘッド リソースを区画に分け 障害の横展開を防ぐ サーキットブレーカー 障害を検知したら 呼び出しを遮断する タイムアウト 応答待ちの時間を 上限で打ち切る 各パターンの役割比較 パターン 「何を」隔離・制御するか タイミング バルクヘッド リソース(スレッド・接続)の割り当て 常時(予防的) サーキットブレーカー 呼び出し自体のON/OFF 障害検知後(反応的) タイムアウト 待ち時間の上限設定 常時(予防的)

実装例:スレッドプール分離のイメージ(Java/Hystrix風)

// 注文サービス専用のスレッドプール(最大10スレッド)
@HystrixCommand(
  threadPoolKey = "orderServicePool",
  threadPoolProperties = {
    @HystrixProperty(name="coreSize", value="10"),
    @HystrixProperty(name="maxQueueSize", value="20")
  }
)
public Order getOrder(String orderId) { ... }

// 外部API専用のスレッドプール(最大5スレッド)
@HystrixCommand(
  threadPoolKey = "externalApiPool",
  threadPoolProperties = {
    @HystrixProperty(name="coreSize", value="5")
  }
)
public Result callExternalApi() { ... }

// → 外部APIが詰まっても orderServicePool には影響なし!

Kubernetesでのリソース分離(インフラレベルのバルクヘッド)

# 各サービスにCPU/メモリの上限を設けることで
# 1つのPodが暴走しても他のPodへの影響を防ぐ
resources:
  requests:
    memory: "256Mi"
    cpu: "250m"
  limits:
    memory: "512Mi"   # ← ここを超えたらOOMKillで隔離
    cpu: "500m"       # ← ここを超えたらスロットリング

関連用語