Skip to content

カスケード障害のメカニズム

1つの遅延が全体を死滅させるメカニズムと、その対策を詳しく解説します。

1. 外部APIが遅延(通常100ms → 5s)
2. 接続待ちスレッドが解放されずプールが飽和
3. 全エンドポイントが応答不能に陥る
4. 不完全なDBトランザクションを残して全プロセスダウン
→ 1つの遅延が全体を死滅させる
時刻: 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 - スレッドプールが飽和(10/10スレッドが外部API待ち)
10:00:01.100 - リクエスト11受信(スレッドプールが満杯、待機)
10:00:01.200 - リクエスト12受信(スレッドプールが満杯、待機)
...
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 - システム全体が応答不能に陥る
// ❌ 問題のあるコード: タイムアウトなし、サーキットブレーカーなし
@Service
public class PaymentService {
@Autowired
private RestTemplate restTemplate;
public PaymentResult chargePayment(Long orderId, BigDecimal amount) {
// 問題: タイムアウトが設定されていない
// 問題: サーキットブレーカーがない
ResponseEntity<PaymentResult> response = restTemplate.postForEntity(
"https://payment-api.example.com/charge",
new PaymentRequest(orderId, amount),
PaymentResult.class
);
return response.getBody();
}
}

なぜ事故るか:

  1. タイムアウトなし: 外部APIが遅延すると、スレッドが長時間占有される
  2. サーキットブレーカーなし: 外部APIが障害状態でも呼び出し続ける
  3. スレッドプールの飽和: すべてのスレッドが外部API待ちで、他のリクエストが処理できない
// ✅ 良い例: タイムアウトを設定
@Service
public class PaymentService {
@Autowired
private RestTemplate restTemplate;
public PaymentResult chargePayment(Long orderId, BigDecimal amount) {
// タイムアウトを設定(接続: 1秒、読み取り: 3秒)
HttpComponentsClientHttpRequestFactory factory =
new HttpComponentsClientHttpRequestFactory();
factory.setConnectTimeout(1000);
factory.setReadTimeout(3000);
restTemplate.setRequestFactory(factory);
try {
ResponseEntity<PaymentResult> response = restTemplate.postForEntity(
"https://payment-api.example.com/charge",
new PaymentRequest(orderId, amount),
PaymentResult.class
);
return response.getBody();
} catch (ResourceAccessException e) {
// タイムアウト時の処理
throw new PaymentTimeoutException("Payment API timeout", e);
}
}
}

2. サーキットブレーカーの実装

Section titled “2. サーキットブレーカーの実装”
// ✅ 良い例: Resilience4jを使用したサーキットブレーカー
@Service
public class PaymentService {
private final CircuitBreaker circuitBreaker;
public PaymentService() {
// サーキットブレーカーの設定
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
.failureRateThreshold(50) // 失敗率50%でオープン
.waitDurationInOpenState(Duration.ofSeconds(30)) // 30秒後にハーフオープン
.slidingWindowSize(10) // 直近10回のリクエストを監視
.build();
this.circuitBreaker = CircuitBreaker.of("payment", config);
}
public PaymentResult chargePayment(Long orderId, BigDecimal amount) {
return circuitBreaker.executeSupplier(() -> {
// 外部API呼び出し
ResponseEntity<PaymentResult> response = restTemplate.postForEntity(
"https://payment-api.example.com/charge",
new PaymentRequest(orderId, amount),
PaymentResult.class
);
return response.getBody();
});
}
}
// ✅ 良い例: 外部API呼び出し用の専用スレッドプール
@Configuration
public class ThreadPoolConfig {
@Bean("externalApiExecutor")
public ExecutorService externalApiExecutor() {
// 外部API呼び出し専用のスレッドプール
// サイズを制限して、他の処理に影響を与えない
return new ThreadPoolExecutor(
5, // コアスレッド数
10, // 最大スレッド数
60L, TimeUnit.SECONDS, // アイドルスレッドの生存時間
new LinkedBlockingQueue<>(100), // キューサイズ
new ThreadFactoryBuilder()
.setNameFormat("external-api-%d")
.build(),
new ThreadPoolExecutor.CallerRunsPolicy() // キューが満杯の場合、呼び出し元で実行
);
}
}
@Service
public class PaymentService {
@Autowired
@Qualifier("externalApiExecutor")
private ExecutorService externalApiExecutor;
public CompletableFuture<PaymentResult> chargePayment(Long orderId, BigDecimal amount) {
return CompletableFuture.supplyAsync(() -> {
// 外部API呼び出し(専用スレッドプールで実行)
ResponseEntity<PaymentResult> response = restTemplate.postForEntity(
"https://payment-api.example.com/charge",
new PaymentRequest(orderId, amount),
PaymentResult.class
);
return response.getBody();
}, externalApiExecutor);
}
}

カスケード障害のメカニズムのポイント:

  • 発生フロー: 外部APIの遅延 → スレッドプールの飽和 → 全エンドポイントの応答不能 → システム全体のダウン
  • 対策: タイムアウト設定、サーキットブレーカー、スレッドプールの分離
  • 原則: すべての設計はこのフローを想定して、「時間とリソース」で守る

適切な対策により、1つの遅延が全体を死滅させることを防げます。