【入門編】 サーキットブレーカーパターンによる障害の連鎖防止 – Web APIアーキテクチャ・データ連携実践ガイド

こんにちは!ネットワークやインフラの世界へようこそ。プロトコルスペシャリストの私です。

日頃からWeb APIを使ったシステム開発やインフラの保守に携わっていると、「外部のサービスと連携していたせいで、自分のシステムまで一緒に共倒れしてしまった…」という、エンジニアなら誰もが冷や汗をかく瞬間に出会うことがあります。

今回は、そんな最悪のシナリオを防ぐための強力な盾、「サーキットブレーカーパターン」について、難しい専門用語をできるだけ取っ払って、現実世界の仕組みに例えながら優しく紐解いていきたいと思います。一歩ずつ、リラックスして理解していきましょう!

—

外部APIの「お友達依存」が引き起こす大惨事

私たちが作るWebアプリケーションは、今や自分たちの力だけで動いているものはほとんどありません。お天気情報を知るために天気予報のAPIを呼び出し、お買い物の決済のためにクレジットカード会社のAPIを呼び出し、ログインのために外部の認証基盤を頼っています。これは例えるなら、たくさんの下請け業者さんにお仕事を分担してもらいながら、大きなお店を切り盛りしているような状態です。

ここで、こんな想像をしてみてください。
あなたのお店で一番人気の商品を売るために、毎秒100件ものペースで「在庫確認の電話」を特定の卸問屋さんにかけたとします。ところが、その卸問屋さんで大規模なシステム障害が起きてしまい、電話がまったく繋がらなくなってしまいました。

このとき、あなたの店で働くスタッフ(プログラム)が、次のような行動をとったらどうなるでしょうか?

1. 卸問屋さんに電話をかける。
2. 「あれ?繋がらないな」と思って、すぐにまた同じ番号に電話をかける(リトライ)。
3. それでも繋がらないので、さらに必死になって何度も何度も電話をかけ続ける。

結果はどうなるでしょうか。卸問屋さんはすでにパンクしているため、あなたのスタッフからの大量の電話の嵐によって、あなたの店の電話回線も完全に埋め尽くされてしまいます。その結果、在庫確認とは全く関係のない、別のお客さまからの大切なお問い合わせ電話まで一切繋がらなくなってしまいました。

これが、ITの世界でいう「障害の連鎖(カスケード障害)」です。たった一つの外部APIの不調が、システム全体を巻き込む大ダウンを引き起こしてしまうのです。

—

家の「ブレーカー」と同じ仕組みで身を守る

こうした悲劇を防ぐために考案されたのが、今回お話しする「サーキットブレーカーパターン」です。

名前に「サーキットブレーカー」とついていますが、これは皆さんのご自宅の壁にある、あの「配線用遮断器(ブレーカー)」と全く同じ考え方です。
お家で一度にたくさんの家電(エアコン、電子レンジ、ドライヤーなど)を同時に使って、許容量を超える電気を流してしまうと、パチン!と音がしてブレーカーが落ちますよね。あれは、家中の配線が熱を持って火事になったり、電気機器が壊れたりするのを防ぐための安全装置です。

Web APIの呼び出しにおいてもこれと同じことを行います。
「外部のAPIが不調だな」と察知したら、それ以上の無駄なリクエストを送り続けるのをパチンと遮断(オープン)し、システム全体が共倒れするのを防ぐのです。

サーキットブレーカーの心臓部は、以下の3つの状態(ステータス)の遷移によって美しく管理されています。

[ 正常な状態:Closed ]
         (エラーが頻発する)
           |
           v
       [ 遮断状態:Open ]
         (即座にエラーを返す)
           |
         (一定時間経過)
           v
       [ 半開状態:Half-Open ]
         (恐る恐る1件だけ試す)
           |
      +----+----+
      |         |
    (成功)    (失敗)
      |         |
      v         v
   [Closed]   [Open]

1. クローズ(Closed)状態:普段の元気な状態

門番がしっかりと扉を開けていて、リクエストが普段通りに外部APIへと流れていく状態です。この状態のときは、エラーの発生回数を裏でこっそりカウントしています。もしエラーが短時間に連続して発生すると、次の「オープン」状態へとジャンプします。

2. オープン(Open)状態:危険を察知してシャッターを下ろした状態

エラーの連続発生により、サーキットブレーカーが作動した状態です。
この状態の最大の特徴は、外部APIへ実際にリクエストを送りに行くことすらしなくなる点です。外部APIを呼び出そうとした瞬間、サーキットブレーカーが「今、向こう側は倒れてるから行くだけ無駄だよ!」と即座にエラー(フォールバック応答)を返します。これにより、自分のシステムのリソース(CPUやメモリ、通信の枠)を守り抜きます。

3. ハーフオープン(Half-Open)状態:回復したか恐る恐る確かめる状態

オープン状態のまま一定の時間が経過すると、「そろそろ向こうのシステムも復旧した頃かな?」と確認するために、サーキットブレーカーがそっと片目を開けます。この状態のとき、テストとして1件だけリクエストを外部APIに送ってみます。

  • 成功した場合: 「おっ、無事に返事が返ってきたぞ!」ということで、無事に「クローズ」状態に戻り、通常の営業を再開します。
  • 失敗した場合: 「ダメだ、まだ向こうは治っていない!」と判断し、再び「オープン」状態に逆戻りして、シャッターを固く閉ざします。

—

実践!コードで見るサーキットブレーカーのイメージ

言葉だけではイメージしづらいと思いますので、Pythonを例にとって、サーキットブレーカーがどのように動くのか、シンプルなコードで見てみましょう。実務でもこうした考え方をベースにしたライブラリ(Pythonなら pybreaker や resilience4j など)がよく使われます。

import time
import requests

class SimpleCircuitBreaker:
    def __init__(self, failure_threshold=3, recovery_time=10):
        self.state = "CLOSED"  # 初期状態はクローズ(正常)
        self.failure_count = 0  # エラーが起きた回数
        self.failure_threshold = failure_threshold  # 遮断するまでのエラー回数のしきい値
        self.recovery_time = recovery_time  # オープン状態を維持する時間(秒)
        self.last_failure_time = None  # 最後にエラーが起きた時刻

    def call_api(self, url):
        # 1. 現在の状態が「OPEN」の場合の処理
        if self.state == "OPEN":
            # 一定時間が経過したかチェックする
            if time.time() - self.last_failure_time > self.recovery_time:
                print("【サーキットブレーカー】一定時間経過しました。様子見(HALF-OPEN)に移行します。")
                self.state = "HALF-OPEN"
            else:
                # まだ回復時間が経っていないので、APIを呼び出さずに即座にエラーを返す
                print("【サーキットブレーカー】現在はOPEN状態です。API呼び出しをスキップします。")
                return {"error": "サービスは一時的に利用できません(急速防衛中)"}

        try:
            # 2. 実際に外部APIを呼び出す(ハーフオープンまたはクローズ状態)
            print(f"APIにリクエストを送信中... [{self.state}]")
            response = requests.get(url, timeout=3)
            
            # ステータスコードが500番台などのエラーだった場合
            if response.status_code >= 500:
                raise Exception("外部APIサーバーエラー")

            # 3. 呼び出し成功時の処理
            self.on_success()
            return response.json()

        except Exception as e:
            # 4. 呼び出し失敗時の処理
            self.on_failure()
            return {"error": f"API呼び出しに失敗しました: {str(e)}"}

    def on_success(self):
        print("API呼び出しに成功しました!")
        self.failure_count = 0
        self.state = "CLOSED"  # 正常状態に戻す

    def on_failure(self):
        self.failure_count += 1
        self.last_failure_time = time.time()
        print(f"API呼び出しが失敗しました。(累計失敗回数: {self.failure_count})")

        # 失敗回数がしきい値を超えたらオープン状態にする
        if self.failure_count >= self.failure_threshold:
            self.state = "OPEN"
            print("【警告】エラーが多発したため、サーキットブレーカーが【OPEN】になりました!")

# --- 使い方(シミュレーション) ---
breaker = SimpleCircuitBreaker(failure_threshold=2, recovery_time=5)

# わざと存在しない、あるいは応答しないURLを指定してテストしてみます
target_url = "https://httpbin.org/status/500" # 常に500エラーを返すテスト用URL

for i in range(5):
    print(--- f"試行回数: {i+1} ---")
    result = breaker.call_api(target_url)
    print(f"結果: {result}")
    time.sleep(1)

このコードを実行すると、最初の数回でエラーが規定値(今回は2回)に達した瞬間にサーキットブレーカーが「OPEN」になり、それ以降は外部APIへ無駄な通信を行わずに即座にフォールバック(身代わりの応答)を返す様子がよく分かります。

—

現場で役立つ!パラメーター設計のコツ

実務のインフラ構築やアプリケーション設計において、サーキットブレーカーを導入する際は、以下のパラメーターチューニングがエンジニアの腕の見せ所となります。

  • 失敗しきい値(failure_threshold)
  • 「何回連続で失敗したら扉を閉ざすか」の設定です。厳しすぎると、一瞬の通信の揺らぎ(パケットロスなど)ですぐに遮断されてしまい、逆に優しすぎるとシステムのダウンを防げません。一般的には 3回〜5回 程度に設定されることが多いです。
  • 回復待機時間(recovery_time)
  • 「一度遮断したあと、どれくらい待ってから様子見(ハーフオープン)をするか」の設定です。外部のAPI提供元がシステムを再起動したり、負荷が収まるまでに必要な時間を考慮して決めます(例: 30秒 や 60秒 など)。

—

まとめ

いかがでしたでしょうか? サーキットブレーカーパターンは、一見すると難しそうな名前をしていますが、その本質は「お互いのシステムを守るための、優しくて賢い安全装置」です。

インフラやネットワークの世界では、「すべてが常に正常に動く」という前提で設計するのではなく、「いつか必ず失敗する(Fail Fast)」という前提に立ってシステムをデザインすることが何よりも大切です。外部連携の設計で頭を悩ませたときは、ぜひこの「サーキットブレーカー」の仕組みを思い出してみてくださいね。

それでは、また次のネットワークの深淵でお会いしましょう!

コメント

タイトルとURLをコピーしました