arch-circuit-breaker について
障害や遅延のある依存先からサービスを守るためのソフトウェアアーキテクチャスキルです。サーキットブレーカーに加え、タイムアウト、バルクヘッド、バックオフとジッターを伴う再試行、フォールバックの適用場面を解説します。
主な機能
- 01クローズ・オープン・ハーフオープン状態
- 02タイムアウトとバルクヘッドの指針
- 03バックオフとジッターを伴う再試行
- 04フォールバック方針の検討
- 05障害しきい値とクールダウン
- 06レジリエンスライブラリの紹介
活用シーン
- 01遅い呼び出しによるスレッド枯渇の防止
- 02下流障害からサービスを保護
- 03再試行とタイムアウトの動作選定
- 04縮退時のフォールバック計画
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ yonatankarp/ software-design-skills --skill arch-circuit-breakerインストール後、Claude Code で/arch-circuit-breakerを入力して読み込まれたか確認します。
詳細
1行での概要
障害が発生している依存先への呼び出しをしばらく停止し、復旧する時間を確保して、サービスがタイムアウトであふれないようにします。バルクヘッド(依存先ごとにリソースを分離)、バックオフ付き再試行(一時的な障害に対処)、タイムアウト(障害が続く時間を制限)と組み合わせることで、包括的なレジリエンスを実現できます。
このスキルを使う場面
- 下流サービスが障害を起こしたり、遅くなったりする可能性のある、サービス間の呼び出し(REST、gRPC、メッセージブローカー)。
- 劣化する可能性のある外部依存先(データベース、サードパーティ API、決済プロバイダー)。
- 1つの遅い依存先が連鎖的な障害を引き起こしうるシステム全般。スレッドが処理を待ち続け、スレッドプールが埋まり、サービスがリクエストを受け付けられなくなります。
- 「成功するまで再試行すればいい」と考えてしまうあらゆる場面。それは、まさにこれから引き起こそうとしている連鎖的な障害です。
このスキルを使わない場面
- すべての依存先がプロセス内にあり、ネットワーク障害から保護する必要がない場合。
- 単純な同期処理の失敗をそのまま呼び出し元に伝えるべき場合。サーキットブレーカーは失敗を隠すため、常に望ましいとは限りません。
- 呼び出し量が少なく、遅い呼び出しが連鎖的な障害を引き起こさない場合。運用上の負担がメリットを上回ります。
主な内容
サーキットブレーカー。 依存先への呼び出しをラップするステートマシンです。
- Closed(クローズ) — 呼び出しは通常どおり通過します。失敗はスライディングウィンドウでカウントされます。失敗がしきい値を超えると、open(オープン) 状態に移行します。
- Open(オープン) — 依存先に触れることなく、呼び出しを即座に失敗させます。クールダウン後、half-open(ハーフオープン) 状態に移行します。
- Half-open(ハーフオープン) — 少数のプローブ呼び出しを通過させます。成功すれば closed(クローズ) 状態に移行し、失敗すれば open(オープン) 状態に戻ります。
ブレーカーは、失敗をローカルで受け止めることで連鎖的な障害を防ぎます。呼び出し元は、遅い呼び出しに処理が滞留するのではなく、フォールバックまたはエラーを返して即座に失敗します。
バルクヘッド。 依存先ごとにリソースを分離し、1つの不調な隣接サービスがほかのサービスまで巻き込まないようにします。例:下流サービスごとにスレッドプールを分ける、データベースごとに接続プールを分ける。由来:船体を隔壁で区切り、1か所が浸水しても船全体が沈まないようにする仕組みです。
タイムアウト。 すべてのリモート呼び出しにタイムアウトを設定する必要があります。設定しないと、遅い依存先が呼び出し元のスレッドやコルーチンを使い果たし、連鎖的な障害を引き起こします。タイムアウトは有限かつ保守的な値に設定します。永遠に待つより、早めに失敗して再試行するほうが適切です。
バックオフとジッターを伴う再試行。
- 再試行:一時的な障害が発生した場合に、もう一度試します。
- バックオフ:再試行の間に待ち時間を設けます(通常は指数バックオフです:100ms、200ms、400ms、800ms…)。
- ジッター:待ち時間をランダム化し、大量の再試行が同時に発生して、復旧中の依存先を集中攻撃しないようにします。
再試行は、一時的なエラー(タイムアウト、5xx、接続拒否)に対してのみ実行します。4xx に対しては、永続的なクライアントエラーであるため、再試行しないでください。
フォールバック。 依存先が利用できないときに、エラーの代わりに役立つ情報を返します。キャッシュされた値、デフォルト値、「サービスが縮退中です。一部のデータのみを提供しています」などです。フォールバックの方針はプロダクト上の判断であり、デフォルトで決めるものではありません。意図的に選択してください。
ライブラリ。 自分で実装しないでください。JVM:Resilience4j。.NET:Polly。Node:opossum。各ライブラリは、上記のパターンを組み合わせた設定可能なレジリエンスパイプラインを提供します。
判断のヒント
- すべてのリモート呼び出しでは、まずタイムアウト、次にサーキットブレーカー、次にバルクヘッドを適用します。一時的な障害が想定される場合は、再試行を追加します。
- 失敗のしきい値は、すばやく反応できる程度に低く、ランダムな一時障害を許容できる程度に高く設定します。まずは20回の呼び出しに対して失敗率50%から始め、そこから調整します。
- クールダウンは、たまに不調になるものの正常な依存先には数十秒、人的な対応が必要な依存先には数分を設定します。
- 再試行には必ずジッターを加えます。同期した再試行は、集中豪雨のような負荷を引き起こします。
- フォールバックはプロダクト上の判断です。「縮退モード」がどのような状態か、チームの PM も把握しておく必要があります。
アンチパターン
- タイムアウトなし。 呼び出しがハングすると、スレッドプールが枯渇します。連鎖障害の最も一般的な原因です。
- バックオフなしのリトライ。 問題を抱えている依存先への負荷が、即座に倍増します。
- 4xx に対するリトライ。 恒久的なエラーをリトライし続け、恒久的な混乱を招きます。
- 適切でない指標で調整されたサーキットブレーカーのしきい値。 失敗率はウィンドウ単位です。ウィンドウのコンテキストがない生の失敗件数には意味がありません。
- フォールバックのないサーキットブレーカー。 オープン状態は「速やかに失敗する」ことを意味しますが、呼び出し側に代替策がなければ、失敗に別の名前を付けただけです。
- 独自実装のサーキットブレーカー。 Resilience4j や Polly では、長年かけてエッジケースへの対処が整えられています。独自実装は避けてください。
関連項目
arch-microservices— サービス間ではサーキットブレーカーが不可欠です。arch-saga— 他のサービスを呼び出す Saga のステップには、この保護が必要です。arch-event-driven— 障害モデルが異なります。ブレーカーパターンは同期呼び出し向けです。kp-structured-concurrency— Kotlin のwithTimeoutは、プロセス内のコルーチンコードにおけるタイムアウト部分をカバーします。
参考資料
- Michael Nygard 著、『Release It!』(Pragmatic Bookshelf)— レジリエンスパターンの定番書です。サーキットブレーカーもここで紹介されています。
- Richards & Ford 著、『Fundamentals of Software Architecture』のマイクロサービスおよび分散アーキテクチャの章。
- Resilience4j のドキュメント — 具体的な実装の参考資料です。