カスケード障害のメカニズム
カスケード障害のメカニズム
Section titled “カスケード障害のメカニズム”1つの遅延が全体を死滅させるメカニズムと、その対策を詳しく解説します。
カスケード障害の発生フロー
Section titled “カスケード障害の発生フロー”典型的なシナリオ
Section titled “典型的なシナリオ”1. 外部APIが遅延(通常100ms → 5s) ↓2. 接続待ちプロセスが解放されずプールが飽和 ↓3. 全エンドポイントが応答不能に陥る ↓4. 不完全なDBトランザクションを残して全プロセスダウン
→ 1つの遅延が全体を死滅させる実際のタイムライン
Section titled “実際のタイムライン”時刻: 2024-01-01 10:00:00状況: 外部決済APIが遅延
10:00:00.000 - 外部決済APIが通常の100msから5秒に遅延開始10:00:00.100 - リクエスト1受信(プロセス1を取得、外部API呼び出し開始)10:00:00.200 - リクエスト2受信(プロセス2を取得、外部API呼び出し開始)10:00:00.300 - リクエスト3受信(プロセス3を取得、外部API呼び出し開始)...10:00:01.000 - プロセスプールが飽和(5/5プロセスが外部API待ち)10:00:01.100 - リクエスト6受信(プロセスプールが満杯、待機)10:00:01.200 - リクエスト7受信(プロセスプールが満杯、待機)...10:00:05.000 - 外部APIが応答(5秒経過)10:00:05.100 - プロセス1が解放されるが、すぐに次のリクエストで使用される10:00:05.200 - プロセス2が解放されるが、すぐに次のリクエストで使用される10:00:05.300 - 外部APIが再び遅延(負荷が高い)10:00:10.000 - すべてのプロセスが再び外部API待ちで飽和10:00:10.100 - 新しいリクエストが処理できず、タイムアウトエラーが発生10:00:10.200 - エラーログが大量に出力され、ログシステムも負荷が高い10:00:15.000 - システム全体が応答不能に陥る問題のあるコード
Section titled “問題のあるコード”// ❌ 問題のあるコード: タイムアウトなし、サーキットブレーカーなしfunction chargePayment($orderId, $amount) { // 問題: タイムアウトが設定されていない // 問題: サーキットブレーカーがない $ch = curl_init('https://payment-api.example.com/charge'); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([ 'order_id' => $orderId, 'amount' => $amount, ])); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); curl_close($ch); return json_decode($response, true);}なぜ事故るか:
- タイムアウトなし: 外部APIが遅延すると、プロセスが長時間占有される
- サーキットブレーカーなし: 外部APIが障害状態でも呼び出し続ける
- プロセスプールの飽和: すべてのプロセスが外部API待ちで、他のリクエストが処理できない
1. タイムアウトの設定
Section titled “1. タイムアウトの設定”// ✅ 良い例: タイムアウトを設定function chargePayment($orderId, $amount) { $ch = curl_init('https://payment-api.example.com/charge'); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([ 'order_id' => $orderId, 'amount' => $amount, ])); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 3); // 3秒でタイムアウト curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3);
$response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $error = curl_error($ch); curl_close($ch);
if ($error) { throw new PaymentTimeoutException("Payment API timeout: " . $error); }
return json_decode($response, true);}2. サーキットブレーカーの実装
Section titled “2. サーキットブレーカーの実装”// ✅ 良い例: サーキットブレーカーの実装class CircuitBreaker { private $key; private $failureThreshold; private $timeout; private $cache;
public function __construct($key, $options = []) { $this->key = $key; $this->failureThreshold = $options['failure_threshold'] ?? 5; $this->timeout = $options['timeout'] ?? 30; $this->cache = new Redis(); }
public function call($callback) { $state = $this->getState();
if ($state === 'OPEN') { if (time() - $this->getLastFailureTime() > $this->timeout) { $this->setState('HALF_OPEN'); } else { throw new CircuitBreakerOpenException('Circuit breaker is OPEN'); } }
try { $result = $callback(); $this->onSuccess(); return $result; } catch (\Exception $e) { $this->onFailure(); throw $e; } }
private function getState() { return $this->cache->get("circuit:{$this->key}:state") ?: 'CLOSED'; }
private function setState($state) { $this->cache->set("circuit:{$this->key}:state", $state); }
private function onSuccess() { $this->cache->set("circuit:{$this->key}:failures", 0); $this->setState('CLOSED'); }
private function onFailure() { $failures = (int)$this->cache->get("circuit:{$this->key}:failures") + 1; $this->cache->set("circuit:{$this->key}:failures", $failures); $this->cache->set("circuit:{$this->key}:last_failure", time());
if ($failures >= $this->failureThreshold) { $this->setState('OPEN'); } }
private function getLastFailureTime() { return (int)$this->cache->get("circuit:{$this->key}:last_failure") ?: 0; }}
$circuitBreaker = new CircuitBreaker('payment-api');
function chargePayment($orderId, $amount) { return $circuitBreaker->call(function () use ($orderId, $amount) { $ch = curl_init('https://payment-api.example.com/charge'); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([ 'order_id' => $orderId, 'amount' => $amount, ])); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 3); $response = curl_exec($ch); curl_close($ch); return json_decode($response, true); });}3. キューによる非同期処理
Section titled “3. キューによる非同期処理”// ✅ 良い例: キューで非同期処理function createOrder($orderData) { $pdo = getConnection(); $stmt = $pdo->prepare("INSERT INTO orders (user_id, amount) VALUES (?, ?)"); $stmt->execute([$orderData['user_id'], $orderData['amount']]); $orderId = $pdo->lastInsertId();
// 非同期処理をキューに投入(プロセスプールを占有しない) enqueueJob('process_payment', ['order_id' => $orderId, 'amount' => $orderData['amount']]);
return $orderId;}
// ワーカープロセスでキューを処理function processPaymentJob($orderId, $amount) { chargePayment($orderId, $amount);}カスケード障害のメカニズムのポイント:
- 発生フロー: 外部APIの遅延 → プロセスプールの飽和 → 全エンドポイントの応答不能 → システム全体のダウン
- 対策: タイムアウト設定、サーキットブレーカー、キューによる非同期処理
- 原則: すべての設計はこのフローを想定して、「時間とリソース」で守る
適切な対策により、1つの遅延が全体を死滅させることを防げます。