カスケード障害のメカニズム
カスケード障害のメカニズム
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 - スレッドプールが飽和(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 - システム全体が応答不能に陥る問題のあるコード
Section titled “問題のあるコード”// ❌ 問題のあるコード: タイムアウトなし、サーキットブレーカーなし@Servicepublic 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(); }}なぜ事故るか:
- タイムアウトなし: 外部APIが遅延すると、スレッドが長時間占有される
- サーキットブレーカーなし: 外部APIが障害状態でも呼び出し続ける
- スレッドプールの飽和: すべてのスレッドが外部API待ちで、他のリクエストが処理できない
1. タイムアウトの設定
Section titled “1. タイムアウトの設定”// ✅ 良い例: タイムアウトを設定@Servicepublic 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を使用したサーキットブレーカー@Servicepublic 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(); }); }}3. スレッドプールの分離
Section titled “3. スレッドプールの分離”// ✅ 良い例: 外部API呼び出し用の専用スレッドプール@Configurationpublic 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() // キューが満杯の場合、呼び出し元で実行 ); }}
@Servicepublic 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つの遅延が全体を死滅させることを防げます。