【Haskell学習|実務向け】Haskellで実現する堅牢なシステム:Circuit Breakerパターンの実装

1. 導入: なぜCircuit Breakerが必要なのか

現代の分散システムでは、マイクロサービス間の連携が不可欠です。しかし、依存先サービスが障害を起こした際、何も対策をしていないと「リクエストのタイムアウト待ち」が連鎖し、自システムまでリソース枯渇を引き起こす「連鎖倒産」が発生します。Circuit Breakerパターンは、一定以上のエラーが続いた際に呼び出しを即座に遮断し、システム全体の健全性を守るための重要なガードレールです。

2. 基礎知識: 状態管理と副作用の分離

Circuit Breakerには、Closed(通常)、Open(遮断)、Half-Open(テスト中)の3つの状態があります。Haskellでこれを実装する場合、状態の変化を保持するために「可変な参照」が必要です。実務では `IORef` を使って状態を管理し、`IO` モナド内で副作用を制御するのが一般的です。これにより、純粋な関数によるロジックと、外部サービスとの通信という副作用を綺麗に分離できます。

3. 実装/解決策: 状態遷移の論理

今回のアプローチでは、`IORef` を用いて「連続失敗回数」と「現在の状態」を保持します。リクエストを実行する関数をラップし、失敗時にカウンタをインクリメントします。カウンタが閾値を超えたら状態を `Open` に切り替え、一定時間経過後に `Half-Open` へと遷移させることで、自動復旧の仕組みを構築します。

4. サンプルプログラム

以下は、簡潔なCircuit Breakerの実装例です。

import Data.IORef
import Control.Concurrent (threadDelay)
import Control.Exception (try, SomeException)

-- 回路の状態を定義
data State = Closed | Open deriving (Show, Eq)

-- 回路の管理用データ構造
data CircuitBreaker = CircuitBreaker {
    failureCount :: IORef Int,
    state        :: IORef State,
    threshold    :: Int
}

-- Circuit Breakerを呼び出す関数
withCircuitBreaker :: CircuitBreaker -> IO a -> IO (Either String a)
withCircuitBreaker cb action = do
    st <- readIORef (state cb)
    if st == Open
        then return (Left "回路が遮断されています (Open)")
        else do
            result <- try action
            case result of
                Right val -> do
                    writeIORef (failureCount cb) 0 -- 成功時はカウンタをリセット
                    return (Right val)
                Left (_ :: SomeException) -> do
                    count <- readIORef (failureCount cb)
                    let newCount = count + 1
                    writeIORef (failureCount cb) newCount
                    -- 閾値を超えたら回路を遮断
                    if newCount >= threshold cb
                        then writeIORef (state cb) Open
                        else return ()
                    return (Left "アクション実行中にエラーが発生しました")

5. 応用・注意点: 現場で運用する際のヒント

実務で運用する際は、以下の点に注意してください。

遮断からの復帰(Half-Open)
上記コードは簡易的なため、一度 `Open` になると手動で戻す必要があります。現場では `threadDelay` を活用した非同期プロセスで、一定時間後に状態を `Half-Open` に戻し、次のリクエストで正常性が確認できたら `Closed` に戻すロジックを組み込むのが定石です。

並行アクセスの制御
複数のスレッドから同時に `IORef` にアクセスする場合、競合が発生します。より堅牢にするには `Control.Concurrent.STM` を使用し、`TVar` によるトランザクション処理を行うことを推奨します。これにより、マルチスレッド環境でも安全に状態遷移を管理できます。

モニタリング
Circuit Breakerが遮断状態になったことは、システムにとって深刻なアラートです。遮断が発生した際にログを出力したり、Prometheus等の監視ツールへメトリクスを送る仕組みを必ず統合してください。

コメント

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