サーキットブレーカーの実装と状態遷移の検証
目標
サーキットブレーカーを自分で実装して、CLOSED、OPEN、HALF_OPENの間の遷移を目で確認し、何を失敗として数えるかの決定が、なぜ最も重要かを、事故の事例で実感します。
なぜ重要なのか
サーキットブレーカーは、障害を直す仕組みではありません。障害が確実なときに素早く失敗して、呼び出し側のコネクションプールが枯渇しないように守る仕組みです。そのため、性能の指標ではなく、分離の指標で評価しなければなりません。実務でサーキットが生む事故は、ほとんどが2つです。1つ目は、4xxを失敗として集計して、正常なトラフィックまで遮断してしまうケースです。在庫サービスが特定の商品に400を返し始めたところ、サーキットが開いて、正常な商品の取得まで塞がれた事例があります。2つ目は、HALF_OPENの試験的な呼び出しを1件だけ許可して、OPENとHALF_OPENを行き来してフラッピングしてしまうケースです。このラボでは、その2つの落とし穴をそれぞれ1ステップずつ割り当てて、自分で作り、自分で防ぎます。
ステップ
/opt/app/flaky.pyを127.0.0.1:8110で起動してください。GET /countが、これまでに受け取ったリクエスト数をJSONで返します。/root/cb/breaker.pyを作成して、/fastを3回呼び出してください。/root/cb/state.jsonに{"state":"CLOSED", ...}を書いてください。/always500を5回呼び出して、失敗率のしきい値を超えさせてください。設定は、ウィンドウ10、最小呼び出し数5、失敗率50%です。state.jsonのstateがOPENになります。- OPENの状態で、さらに10回呼び出してください。
/root/cb/fastfail.txtにbefore=<n> after=<n> blocked=10 max_ms=<밀리초>を書いてください(プレースホルダーはミリ秒です)。beforeとafterが同じで、max_msが50未満でなければなりません。 waitDurationInOpenStateを3秒にして、3秒以上待った後に1回呼び出してください。state.jsonのstateがHALF_OPENになります。- HALF_OPENで
/fastを3回成功させて、CLOSEDに戻してください。permitted_in_half_openの値が3以上で記録されなければなりません。 - 新しいブレーカーで
/bad(400)を10回呼び出してください。/root/cb/ignore4xx.jsonのstateは依然としてCLOSEDで、failuresは0でなければなりません。 - 全体のシナリオを一度に実行して、
/root/cb/transitions.logにCLOSED->OPEN、OPEN->HALF_OPEN、HALF_OPEN->CLOSEDの3行を、この順序で残してください。
参考
- 出発点の設定: ウィンドウ10、最小呼び出し数5、失敗率50%、遅い呼び出し3秒、OPENの待機30秒(ラボでは3秒)、HALF_OPENの試験3件
- サービスごとのしきい値: 決済30–40%、通知60–70%
- よくあるミス1:
minimumNumberOfCallsを満たす前に判定して、呼び出し1件の失敗でサーキットが開くことです。 - よくあるミス2: フォールバックを
return Noneにすることです。サーキットが、障害を別の例外に変えただけです。
ダウンストリームと呼び出しカウンターを準備する
/opt/app/flaky.pyを127.0.0.1:8110で起動してください。GET /countが、これまでに受け取ったリクエスト数をJSONで返します。
/opt/app/flaky.pyは、自分が受け取ったリクエスト数を/countで知らせます。この値が、後のステップで証拠になります。
CLOSED状態で通す
/root/cb/breaker.pyを作成して、/fastを3回呼び出してください。/root/cb/state.jsonに{"state":"CLOSED", ...}を書いてください。
状態をファイルに残さなければ、採点できません。状態の名前、失敗数、ウィンドウのサイズを、JSONで書いてください。
失敗率のしきい値でOPENにする
/always500を5回呼び出して、失敗率のしきい値を超えさせてください。設定は、ウィンドウ10、最小呼び出し数5、失敗率50%です。state.jsonのstateがOPENになります。
最小の呼び出し数を満たす前には、判定しません。ウィンドウ10、最小5、しきい値50%を、そのまま実装してください。
OPENではダウンストリームに行かないことを証明する
OPENの状態で、さらに10回呼び出してください。/root/cb/fastfail.txtにbefore=<n> after=<n> blocked=10 max_ms=<밀리초>を書いてください(プレースホルダーはミリ秒です)。beforeとafterが同じで、max_msが50未満でなければなりません。
遮断の前後で、ダウンストリームの呼び出しカウンターがそのままでなければなりません。そして、レスポンスがとても速くなければなりません。
待機時間の後にHALF_OPENへ遷移する
waitDurationInOpenStateを3秒にして、3秒以上待った後に1回呼び出してください。state.jsonのstateがHALF_OPENになります。
OPENに入った時刻を記録しておいて、経過を比較します。自動で遷移しなければ、人が介入することになってしまいます。
試験的な呼び出しの成功でCLOSEDに復帰する
HALF_OPENで/fastを3回成功させて、CLOSEDに戻してください。permitted_in_half_openの値が3以上で記録されなければなりません。
試験的な呼び出しの数を1にすると、フラッピングが起きます。なぜ3以上でなければならないのかを考えながら、実装してください。
4xxを失敗として数えない
新しいブレーカーで/bad(400)を10回呼び出してください。/root/cb/ignore4xx.jsonのstateは依然としてCLOSEDで、failuresは0でなければなりません。
400はクライアントの誤りなので、サーキットが介入することではありません。何を失敗と見なすかを、一覧で管理してください。
遷移履歴で総合的に検証する
全体のシナリオを一度に実行して、/root/cb/transitions.logにCLOSED->OPEN、OPEN->HALF_OPEN、HALF_OPEN->CLOSEDの3行を、この順序で残してください。
前のステップを1つのシナリオにつなげて、状態遷移が順番に記録されるようにしてください。