システム設計に役立つ7つのリトライ手法

[![表紙](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/da5810a4-8b26-41d5-8033-10ce29d3e368.png)](https://leapcell.io/?lc_t=q_7retry)

## はじめに

業務システムにおいて、障害は日常茶飯事です。ネットワークの不安定、サービスの過負荷、あるいは外部 API の不安定さなど、システムは突発的な異常に対処する「自己回復力」を備えていなければなりません。

リトライメカニズムは、その自己回復力の中核の一つです。

しかし、リトライは諸刃の剣でもあります。うまく設計すれば成功率を高め、ユーザー体験を改善できますが、設計を誤ればリクエストの嵐や雪崩のような障害を引き起こし、問題を事故にまで拡大させかねません。

この記事では、よく使われるリトライの 7 つの手法について紹介します。

## 1. 暴力ループ法（無条件ループ）

### 問題の場面

ユーザー登録時に SMS を送信する API を、`while`ループで繰り返し第三者の SMS 送信インターフェースを呼び出すケース。

コード例：

```java
public void sendSms(String phone) {
    int retry = 0;
    while (retry < 5) { // 無条件ループ
        try {
            smsClient.send(phone);
            break;
        } catch (Exception e) {
            retry++;
            Thread.sleep(1000); // 固定1秒のスリープ
        }
    }
}
```

### インシデント現場

ある時、SMS サーバーが過負荷となり、全リクエストに 3 秒の遅延が発生しました。

この暴力的なループコードは、0.5 秒の間に数万件のリトライを同時に実行し、SMS プラットフォームをパンクさせて遮断（サーキットブレーカー）が発動。通常のリクエストさえも拒否されました。

**教訓：**

- 遅延間隔を調整していない：固定間隔だとリトライが集中して爆発する
- 例外タイプを無視：一時的でないエラー（例：パラメータミス）もリトライする
- 修正案：リトライ間隔にランダム性を加え、リトライ対象とする例外を選別する

## 2. Spring Retry

### 適用シーン

中小規模のプロジェクトに適しており、アノテーションによって基本的なリトライとサーキットブレーカーが実現可能です（例：注文状況の確認 API など）。

`@Retryable` アノテーションを使ってリトライ機能を実現します。

### 設定例

```java
@Retryable(
    value = {TimeoutException.class}, // タイムアウト時のみリトライ
    maxAttempts = 3,
    backoff = @Backoff(delay = 1000, multiplier = 2) // 1秒→2秒→4秒
)
public boolean queryOrderStatus(String orderId) {
    return httpClient.get("/order/" + orderId);
}

@Recover // フォールバック処理
public boolean fallback() {
    return false;
}
```

### 利点

- 宣言的なアノテーション：コードがシンプルで業務ロジックと分離される
- 指数バックオフ：リトライ間隔を自動的に延長
- サーキットブレーカーと統合可能：`@CircuitBreaker`と組み合わせて異常流量をすばやく遮断

## 3. Resilience4j

### 高度な利用シーン

カスタムのバックオフアルゴリズムやサーキットブレーカー、多層防御が必要な中〜大規模システム（例：決済の中核 API）に適しています。

主なコードは以下の通り：

```java
// 1. リトライ設定：指数バックオフ + ランダムジッター
RetryConfig retryConfig = RetryConfig.custom()
    .maxAttempts(3)
    .intervalFunction(IntervalFunction.ofExponentialRandomBackoff(
        1000L,  // 初期間隔1秒
        2.0,    // 指数倍数
        0.3     // ジッター係数
    ))
    .retryOnException(e -> e instanceof TimeoutException)
    .build();

// 2. サーキットブレーカー設定：失敗率50％超えで遮断
CircuitBreakerConfig cbConfig = CircuitBreakerConfig.custom()
    .slidingWindow(10, 10, CircuitBreakerConfig.SlidingWindowType.COUNT_BASED)
    .failureRateThreshold(50)
    .build();

// 組み合わせて使用
Retry retry = Retry.of("payment", retryConfig);
CircuitBreaker cb = CircuitBreaker.of("payment", cbConfig);

// 業務ロジックを実行
Supplier<Boolean> supplier = () -> paymentService.pay();
Supplier<Boolean> decorated = Decorators.ofSupplier(supplier)
    .withRetry(retry)
    .withCircuitBreaker(cb)
    .decorate();
```

### 効果

ある企業ではこの方式を導入した結果、決済 API のタイムアウト率が 60％低下し、サーキットブレーカーの発動頻度も 90％近く減少しました。

## 4. MQ キュー

### 適用シーン

高トラフィックで多少の遅延が許容される非同期シナリオ（例：物流ステータスの同期）に適しています。

### 実現原理

1. 初回リクエストが失敗したら、メッセージを遅延キューに投入
2. キューは事前に設定された遅延時間（例：5 秒、30 秒、1 分）に基づいて再試行を行う
3. 最大リトライ回数に達した場合、デッドレターキュー（人工対応）に移送される

RocketMQ のコードスニペット：

```java
// プロデューサーが遅延メッセージを送信
Message<String> message = new Message();
message.setBody("注文データ");
message.setDelayTimeLevel(3); // RocketMQ 事前定義の10秒遅延レベル
rocketMQTemplate.send(message);

// コンシューマー側の再試行処理
@RocketMQMessageListener(topic = "DELAY_TOPIC")
public class DelayConsumer {
    @Override
    public void handleMessage(Message message) {
        try {
            syncLogistics(message);
        } catch (Exception e) {
            // リトライ回数 +1 して、より長い遅延で再送
            resendWithDelay(message, retryCount + 1);
        }
    }
}
```

RocketMQ の場合、コンシューマーが処理に失敗すると自動で再試行されます。

## 5. 定時ジョブ（スケジューラー）

### 適用シーン

リアルタイム性を求めず、バッチ処理が許容される業務（例：ファイルインポート）に適しています。

ここでは Quartz を例に挙げます。

コード例：

```java
@Scheduled(cron = "0 0/5 * * * ?") // 5分ごとに実行
public void retryFailedTasks() {
    List<FailedTask> list = failedTaskDao.listUnprocessed(5); // 失敗タスクを取得
    list.forEach(task -> {
        try {
            retryTask(task);
            task.markSuccess();
        } catch (Exception e) {
            task.incrRetryCount();
        }
        failedTaskDao.update(task);
    });
}
```

## 6. 2 フェーズコミット（Two-Phase Commit）

### 適用シーン

データの整合性を厳密に保証する必要がある場面（例：資金移動）において使用されます。

### 実装のポイント

1. フェーズ 1：操作ログを DB に記録（ステータスは「処理中」）
2. フェーズ 2：リモート API を呼び出し、結果に応じてログの状態を更新
3. タイマー補償：タイムアウトした「処理中」ログをスキャンし、再試行

コード例：

```java
@Transactional
public void transfer(TransferRequest req) {
    // 1. ログ記録
    transferRecordDao.create(req, PENDING);

    // 2. 銀行API呼び出し
    boolean success = bankClient.transfer(req);

    // 3. ステータス更新
    transferRecordDao.updateStatus(req.getId(), success ? SUCCESS : FAILED);

    // 4. 失敗時は非同期再試行へ
    if (!success) {
        mqTemplate.send("TRANSFER_RETRY_QUEUE", req);
    }
}
```

## 7. 分散ロック

### 適用シーン

複数サービスインスタンス・スレッド環境下での重複リクエスト防止（例：フラッシュセール）に利用されます。

Redis + Lua を使った分散ロックの例：

```java
public boolean retryWithLock(String key, int maxRetry) {
    String lockKey = "api_retry_lock:" + key;
    for (int i = 0; i < maxRetry; i++) {
        // 分散ロックを取得
        if (redis.setnx(lockKey, "1", 30, TimeUnit.SECONDS)) {
            try {
                return callApi();
            } finally {
                redis.delete(lockKey);
            }
        }
        Thread.sleep(1000 * (i + 1)); // ロック解放を待つ
    }
    return false;
}
```

## まとめ

リトライはデータセンターの消火器のような存在です——使わないに越したことはないが、いざという時には命綱になる。

業務でどの方式を選ぶか？

技術トレンドだけに目を向けるのではなく、業務の「矛」と「盾」のバランスに応じて選ぶべきです。

システムの安定性を保つ秘訣は、「リトライ」に常に畏敬の念を持つことです。

---

### [私たちはLeapcell、バックエンド・プロジェクトのホスティングの最適解です。](https://leapcell.io/?lc_t=q_7retry)

[![Leapcell](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/f518b88e-bc51-44f0-b43b-a028ecca5fc6.png)](https://leapcell.io/?lc_t=q_7retry)

[Leapcell](https://leapcell.io/?lc_t=q_7retry)は、Webホスティング、非同期タスク、Redis向けの次世代サーバーレスプラットフォームです：

**複数言語サポート**

- Node.js、Python、Go、Rustで開発できます。

**無制限のプロジェクトデプロイ**

- 使用量に応じて料金を支払い、リクエストがなければ料金は発生しません。

**比類のないコスト効率**

- 使用量に応じた支払い、アイドル時間は課金されません。
- 例: $25で6.94Mリクエスト、平均応答時間60ms。

**洗練された開発者体験**

- 直感的なUIで簡単に設定できます。
- 完全自動化されたCI/CDパイプラインとGitOps統合。
- 実行可能なインサイトのためのリアルタイムのメトリクスとログ。

**簡単なスケーラビリティと高パフォーマンス**

- 高い同時実行性を容易に処理するためのオートスケーリング。
- ゼロ運用オーバーヘッド — 構築に集中できます。

[ドキュメントで詳細を確認！](https://docs.leapcell.io/)

[![Try Leapcell](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/ac6b942e-fa73-4574-a4c1-4fa524f0da34.png)](https://leapcell.io/?lc_t=q_7retry)

Xでフォローする：[@LeapcellHQ](https://x.com/LeapcellHQ)

---

[ブログでこの記事を読む](https://leapcell.io/blog/seven-retry-patterns)