バルクヘッドパターン

英語名 Bulkhead Pattern
読み方 バルクヘッド パターン
難易度
所要時間 設計・実装に2〜5日
提唱者 船舶工学の隔壁構造からの着想
目次

ひとことで言うと
#

船の隔壁(バルクヘッド)のように、システムのリソースを独立した区画に分離し、一部の障害がシステム全体に波及するのを防ぐパターン。

押さえておきたい用語
#

押さえておきたい用語
バルクヘッド(隔壁)
船舶で浸水を一区画に留める防水壁を指す。ソフトウェアでは障害の影響範囲を物理的に区切る設計を指す。
スレッドプール
一定数のスレッドを事前に生成し、リクエスト処理に再利用する仕組み。プール枯渇はシステム全体の停止を招く。
コネクションプール
DB等への接続を事前に確保し再利用する仕組み。接続数の上限を超えると新規リクエストが処理できなくなる。
フォールバック
主処理が失敗した際に実行する代替処理。キャッシュ応答やデフォルト値の返却など。
カスケード障害
1つの障害が連鎖的に他のコンポーネントに波及し、システム全体が停止する現象である。バルクヘッドはこれを防ぐ。

バルクヘッドパターンの全体像
#

リソース特定→プール分割→フォールバック設計→モニタリングの4ステップ
リソース特定共有リソースの障害波及ポイントを洗い出すプール分割用途別の独立したリソースプールに分割するフォールバックプール枯渇時の代替処理を設計するモニタリング各区画の使用率・待ち行列・拒否率を可視化する障害の影響範囲を物理的に限定する
バルクヘッドパターンの適用フロー
1
リソース特定
共有ポイントを洗い出す
2
プール分割
用途別に独立させる
3
フォールバック
枯渇時の代替処理を設計
4
モニタリング
各区画の健全性を可視化

こんな悩みに効く
#

  • 1つのAPIの遅延で、すべてのリクエスト処理が詰まってしまう
  • 特定の処理がスレッドプールやコネクションプールを占有し、他の処理が動けなくなる
  • 障害が発生した機能とは無関係な機能まで巻き添えで停止する

基本の使い方
#

ステップ1: 分離すべきリソースを特定する

障害が波及する可能性のあるリソースの共有ポイントを洗い出す。

  • スレッドプール、コネクションプール、メモリ、CPUなどの共有リソース
  • 外部サービスへの接続ごとにリソース消費パターンを整理する
  • 過去の障害事例から、どのリソース共有が問題を引き起こしたか分析する

ポイント: すべてを分離する必要はない。クリティカルな境界に絞って適用する。

ステップ2: リソースプールを分割する

特定したリソースを用途別の独立したプールに分割する。

  • 外部サービスA用、外部サービスB用にそれぞれ専用のスレッドプールを作る
  • コネクションプールも接続先ごとに独立させる
  • 各プールにサイズの上限を設定する

ポイント: プールサイズは**対象サービスの特性(レイテンシ、スループット)**に合わせて設計する。

ステップ3: 過負荷時のフォールバックを設計する

プールが枯渇した場合の代替処理を定義する。

  • キューイングして後で処理する
  • キャッシュから応答を返す
  • 縮退モードで最低限の機能だけ提供する

ポイント: プールを分離しただけでは不十分。枯渇時の振る舞いまで設計して初めて完成。

ステップ4: モニタリングで各区画を監視する

各プールの使用率・待ち行列・拒否率を可視化する。

  • プールごとのアクティブスレッド数、待機リクエスト数をメトリクスとして記録
  • 使用率が閾値を超えたらアラートを発報する
  • ダッシュボードで全区画の健全性を一覧表示する

ポイント: 分離した区画ごとに独立して監視できることが、バルクヘッドの大きな利点。

具体例
#

例1:注文サービスのスレッドプール分割で決済機能の停止を防ぐ

状況: 注文サービスが決済API・在庫API・通知APIの3つを呼び出している。すべて同じHTTPクライアント(共有スレッドプール: 50スレッド)を使用。

問題: 通知APIが遅延(レスポンス10秒)→ 通知API呼び出しで50スレッドがすべて占有 → 決済API・在庫APIへの正常なリクエストも送れなくなり、注文処理が全面停止。

バルクヘッド適用: スレッドプールを分割。

  • 決済API用: 20スレッド(最重要、タイムアウト3秒)
  • 在庫API用: 20スレッド(タイムアウト2秒)
  • 通知API用: 10スレッド(タイムアウト5秒、失敗時は非同期リトライ)

通知APIが再び遅延しても、影響は通知機能の10スレッドだけに限定された。決済と在庫は正常に動作し続け、注文処理の全面停止を完全に回避した。

例2:マルチテナントSaaSでテナント別のリソース分離を実現する

状況: 50テナントが共有するSaaSプラットフォーム。1つの大口テナント(全トラフィックの40%)のバッチ処理が深夜に走ると、DBコネクションプール(上限100)を60本占有し、他49テナントの処理が遅延。

バルクヘッド適用:

  • 大口テナント専用プール: 40コネクション
  • その他テナント共有プール: 50コネクション
  • バッチ処理専用プール: 10コネクション(優先度低)

大口テナントのバッチ処理中も他テナントのレスポンスタイムが210msで安定した場合、以前の2,800msへの悪化と比べてどれほどの信頼性向上になるだろうか。

例3:APIゲートウェイにバルクヘッドを適用し、障害の波及を3サービスから1サービスに限定する

状況: APIゲートウェイが6つのマイクロサービスへのルーティングを担当。共有スレッドプール(200スレッド)。レコメンドサービスが月に1〜2回スローダウンし、その度に全サービスへのリクエストが滞る。

バルクヘッド適用:

  • 認証サービス用: 40スレッド(最優先)
  • 決済サービス用: 40スレッド
  • 商品サービス用: 40スレッド
  • 注文サービス用: 40スレッド
  • レコメンドサービス用: 20スレッド(フォールバック: 人気商品の静的リストを返す)
  • 通知サービス用: 20スレッド

レコメンドサービスのスローダウン時、影響がレコメンド機能だけに限定された。他5サービスは正常動作を維持し、障害影響範囲は6サービスから1サービスに縮小、MTTRも45分から5分に短縮した。

やりがちな失敗パターン
#

  1. プールサイズの合計を考慮しない — 分割したプールのサイズ合計が元のリソースキャパシティを超えると、逆にリソース不足になる。全体のキャパシティプランニングを忘れない
  2. すべてのリソースを分離しようとする — 過度な分離はリソース効率を下げ、運用の複雑さを増す。障害リスクが高い境界に絞って適用する
  3. 静的な分割のまま運用する — トラフィックパターンは変化する。負荷テストと本番データをもとにプールサイズを定期的に見直す
  4. フォールバックなしで分離だけする — プールが枯渇した時にエラーを返すだけでは、ユーザー体験が悪い。キャッシュ応答やデフォルト値など、gracefulなフォールバックを必ず設計する

まとめ
#

バルクヘッドパターンは、リソースを区画化することで障害の波及を防ぐシンプルだが強力な手法。サーキットブレーカーが 「障害を検知して遮断する」 のに対し、バルクヘッドは 「そもそも影響範囲を物理的に限定する」 アプローチ。両者を組み合わせることで、より堅牢なシステムを構築できる。