ひとことで言うと#
船の隔壁(バルクヘッド)のように、システムのリソースを独立した区画に分離し、一部の障害がシステム全体に波及するのを防ぐパターン。
押さえておきたい用語#
- バルクヘッド(隔壁)
- 船舶で浸水を一区画に留める防水壁を指す。ソフトウェアでは障害の影響範囲を物理的に区切る設計を指す。
- スレッドプール
- 一定数のスレッドを事前に生成し、リクエスト処理に再利用する仕組み。プール枯渇はシステム全体の停止を招く。
- コネクションプール
- DB等への接続を事前に確保し再利用する仕組み。接続数の上限を超えると新規リクエストが処理できなくなる。
- フォールバック
- 主処理が失敗した際に実行する代替処理。キャッシュ応答やデフォルト値の返却など。
- カスケード障害
- 1つの障害が連鎖的に他のコンポーネントに波及し、システム全体が停止する現象である。バルクヘッドはこれを防ぐ。
バルクヘッドパターンの全体像#
こんな悩みに効く#
- 1つのAPIの遅延で、すべてのリクエスト処理が詰まってしまう
- 特定の処理がスレッドプールやコネクションプールを占有し、他の処理が動けなくなる
- 障害が発生した機能とは無関係な機能まで巻き添えで停止する
基本の使い方#
障害が波及する可能性のあるリソースの共有ポイントを洗い出す。
- スレッドプール、コネクションプール、メモリ、CPUなどの共有リソース
- 外部サービスへの接続ごとにリソース消費パターンを整理する
- 過去の障害事例から、どのリソース共有が問題を引き起こしたか分析する
ポイント: すべてを分離する必要はない。クリティカルな境界に絞って適用する。
特定したリソースを用途別の独立したプールに分割する。
- 外部サービスA用、外部サービスB用にそれぞれ専用のスレッドプールを作る
- コネクションプールも接続先ごとに独立させる
- 各プールにサイズの上限を設定する
ポイント: プールサイズは**対象サービスの特性(レイテンシ、スループット)**に合わせて設計する。
プールが枯渇した場合の代替処理を定義する。
- キューイングして後で処理する
- キャッシュから応答を返す
- 縮退モードで最低限の機能だけ提供する
ポイント: プールを分離しただけでは不十分。枯渇時の振る舞いまで設計して初めて完成。
各プールの使用率・待ち行列・拒否率を可視化する。
- プールごとのアクティブスレッド数、待機リクエスト数をメトリクスとして記録
- 使用率が閾値を超えたらアラートを発報する
- ダッシュボードで全区画の健全性を一覧表示する
ポイント: 分離した区画ごとに独立して監視できることが、バルクヘッドの大きな利点。
具体例#
状況: 注文サービスが決済API・在庫API・通知APIの3つを呼び出している。すべて同じHTTPクライアント(共有スレッドプール: 50スレッド)を使用。
問題: 通知APIが遅延(レスポンス10秒)→ 通知API呼び出しで50スレッドがすべて占有 → 決済API・在庫APIへの正常なリクエストも送れなくなり、注文処理が全面停止。
バルクヘッド適用: スレッドプールを分割。
- 決済API用: 20スレッド(最重要、タイムアウト3秒)
- 在庫API用: 20スレッド(タイムアウト2秒)
- 通知API用: 10スレッド(タイムアウト5秒、失敗時は非同期リトライ)
通知APIが再び遅延しても、影響は通知機能の10スレッドだけに限定された。決済と在庫は正常に動作し続け、注文処理の全面停止を完全に回避した。
状況: 50テナントが共有するSaaSプラットフォーム。1つの大口テナント(全トラフィックの40%)のバッチ処理が深夜に走ると、DBコネクションプール(上限100)を60本占有し、他49テナントの処理が遅延。
バルクヘッド適用:
- 大口テナント専用プール: 40コネクション
- その他テナント共有プール: 50コネクション
- バッチ処理専用プール: 10コネクション(優先度低)
大口テナントのバッチ処理中も他テナントのレスポンスタイムが210msで安定した場合、以前の2,800msへの悪化と比べてどれほどの信頼性向上になるだろうか。
状況: APIゲートウェイが6つのマイクロサービスへのルーティングを担当。共有スレッドプール(200スレッド)。レコメンドサービスが月に1〜2回スローダウンし、その度に全サービスへのリクエストが滞る。
バルクヘッド適用:
- 認証サービス用: 40スレッド(最優先)
- 決済サービス用: 40スレッド
- 商品サービス用: 40スレッド
- 注文サービス用: 40スレッド
- レコメンドサービス用: 20スレッド(フォールバック: 人気商品の静的リストを返す)
- 通知サービス用: 20スレッド
レコメンドサービスのスローダウン時、影響がレコメンド機能だけに限定された。他5サービスは正常動作を維持し、障害影響範囲は6サービスから1サービスに縮小、MTTRも45分から5分に短縮した。
やりがちな失敗パターン#
- プールサイズの合計を考慮しない — 分割したプールのサイズ合計が元のリソースキャパシティを超えると、逆にリソース不足になる。全体のキャパシティプランニングを忘れない
- すべてのリソースを分離しようとする — 過度な分離はリソース効率を下げ、運用の複雑さを増す。障害リスクが高い境界に絞って適用する
- 静的な分割のまま運用する — トラフィックパターンは変化する。負荷テストと本番データをもとにプールサイズを定期的に見直す
- フォールバックなしで分離だけする — プールが枯渇した時にエラーを返すだけでは、ユーザー体験が悪い。キャッシュ応答やデフォルト値など、gracefulなフォールバックを必ず設計する
まとめ#
バルクヘッドパターンは、リソースを区画化することで障害の波及を防ぐシンプルだが強力な手法。サーキットブレーカーが 「障害を検知して遮断する」 のに対し、バルクヘッドは 「そもそも影響範囲を物理的に限定する」 アプローチ。両者を組み合わせることで、より堅牢なシステムを構築できる。