# 機能設計書 65-リトライ

## 概要

本ドキュメントは、Juliaにおける自動リトライ機構（`retry` / `ExponentialBackOff`）の設計と実装を記述するものである。

### 本機能の処理概要

本機能は、失敗する可能性のある操作に対する自動リトライ機構を提供する。`retry` 関数は任意の関数をラップし、例外発生時に指定された遅延パターンに従ってリトライを行う。`ExponentialBackOff` は指数的に増加する遅延パターンを生成するイテレータである。

**業務上の目的・背景**：ネットワーク通信やファイルI/Oなどの一時的な障害が発生しうる操作において、自動リトライは信頼性向上の基本パターンである。手動でリトライロジックを実装する場合のボイラープレートコードを排除し、ジッターや最大遅延などのベストプラクティスを組み込んだリトライ機構を提供する。

**機能の利用シーン**：HTTPリクエストの一時的な失敗（503エラー）からのリカバリー、ファイルロック待ち、データベース接続のリトライ、外部APIの呼び出し失敗時の再試行に利用される。

**主要な処理内容**：
1. `retry(f; delays, check)` による関数のリトライラップ
2. `ExponentialBackOff` イテレータによる指数的バックオフ遅延の生成
3. `check` 関数による条件付きリトライ制御
4. 最終試行は try/catch なしで実行（例外をそのまま伝播）

**関連システム・外部連携**：`sleep` 関数による遅延、`Libc.rand` によるジッター生成。

**権限による制御**：なし。

## 関連画面

該当なし。本機能はプログラマティックに使用されるAPI。

## 機能種別

制御構造 / エラーリカバリー

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| f | `Any` | Yes | リトライ対象の関数 | callable であること |
| delays | イテレータ | No | 遅延秒数のイテレータ（デフォルト: `ExponentialBackOff()`） | - |
| check | `Function` or `Nothing` | No | リトライ条件判定関数 `(state, exception) -> Bool` | - |
| n | `Int` | No | リトライ回数（ExponentialBackOff） | >= 0 |
| first_delay | `Float64` | No | 初回遅延秒数 | >= 0 |
| max_delay | `Float64` | No | 最大遅延秒数 | >= 0 |
| factor | `Float64` | No | 遅延増加倍率 | >= 0 |
| jitter | `Float64` | No | ジッター範囲（0.0〜1.0） | >= 0 |

### 入力データソース

ユーザーコードから直接呼び出し

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| wrapped_function | `Function` | リトライロジックをラップした無名関数 |

### 出力先

呼び出し元に関数オブジェクトとして返却

## 処理フロー

### 処理シーケンス

```
1. retry(f) の呼び出し
   └─ リトライロジックをラップした無名関数を返却
2. ラップされた関数の呼び出し
   └─ 実際の実行開始
3. 初回実行
   └─ f(args...; kwargs...) を try/catch で実行
4. 例外発生時
   └─ check 関数で条件判定、true なら遅延後リトライ
5. 遅延
   └─ sleep(delay) で指定秒数待機
6. リトライ
   └─ delays イテレータの次の値で Step 3 に戻る
7. 最終試行
   └─ delays が尽きたら try/catch なしで f を実行
```

### フローチャート

```mermaid
flowchart TD
    A[retry f 呼び出し] --> B[delays イテレータ初期化]
    B --> C[f 実行 try/catch]
    C --> D{成功?}
    D -->|Yes| E[結果を返却]
    D -->|No| F{check 関数?}
    F -->|check あり| G{check true?}
    F -->|check なし| H[sleep delay]
    G -->|Yes| H
    G -->|No| I[rethrow]
    H --> J{delays 残りあり?}
    J -->|Yes| C
    J -->|No| K[f 実行 try/catch なし]
    K --> L[成功なら結果返却・失敗なら例外伝播]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-65-01 | 最終試行 | delays イテレータが尽きた後の最終試行は try/catch なしで実行する | delays が枯渇した場合 |
| BR-65-02 | 条件付きリトライ | check 関数が false を返した場合はリトライせず rethrow する | check が指定されている場合 |
| BR-65-03 | 遅延クランプ | 遅延値は max_delay を超えない | ExponentialBackOff 使用時 |
| BR-65-04 | ジッター | 遅延値に ±jitter の範囲でランダムなゆらぎを加える | jitter > 0 の場合 |

### 計算ロジック

ExponentialBackOff の遅延計算:
```
next_delay = min(max_delay, current_delay * factor * (1.0 - jitter + rand() * 2.0 * jitter))
```

デフォルト値: `n=1, first_delay=0.05, max_delay=10.0, factor=5.0, jitter=0.1`

## データベース操作仕様

該当なし。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | ArgumentError | ExponentialBackOff のパラメータが負の場合 | 非負値を指定する |
| - | 任意の例外 | 最終試行で f が失敗した場合 | 例外がそのまま伝播する |

### リトライ仕様

本機能自体がリトライ仕様そのものである。

## トランザクション仕様

該当なし。

## パフォーマンス要件

- `sleep` による遅延は OS のスケジューラに依存する
- ジッター生成は `Libc.rand` を使用（高速だが暗号学的に安全ではない）

## セキュリティ考慮事項

- リトライにより同一操作が複数回実行されるため、冪等性（idempotency）が保証されていない操作に使用する場合は注意が必要

## 備考

- `retry` は Julia 1.2 以降で `f` の型制約が `Function` から `Any` に緩和された
- `check` 関数は `(state, exception)` を受け取り、`state` を更新することも可能

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | error.jl | `base/error.jl` | `ExponentialBackOff` 構造体（253-264行目）: n, first_delay, max_delay, factor, jitter の5フィールド。コンストラクタで全値が非負であることを検証 |

**読解のコツ**: `ExponentialBackOff` は Julia のイテレータプロトコル（`iterate`, `length`, `eltype`）を実装しており、`for delay in ExponentialBackOff(...)` のように使用可能。

#### Step 2: イテレータ実装を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | error.jl | `base/error.jl` | `iterate(ebo::ExponentialBackOff, state)` 関数（275-281行目）: 遅延値の生成ロジック |

**主要処理フロー**:
1. **276行目**: `state[1] < 1` でイテレーション終了
2. **279行目**: `next_delay = min(max_delay, current * factor * (1.0 - jitter + rand * 2 * jitter))` で次の遅延を計算
3. **280行目**: `(curr_delay, (next_n, next_delay))` を返却

#### Step 3: retry 関数を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | error.jl | `base/error.jl` | `retry` 関数（305-325行目）: リトライロジックの本体 |

**主要処理フロー**:
1. **306行目**: 無名関数（クロージャ）を返却
2. **307行目**: `iterate(delays)` で初回遅延を取得
3. **310-311行目**: `try return f(args...; kwargs...)` で関数実行
4. **312-316行目**: `catch` で check 関数を呼び出しリトライ判定
5. **319-320行目**: `sleep(delay)` + `iterate(delays, state)` で次の遅延
6. **323行目**: delays 枯渇後の最終試行（try/catch なし）

### プログラム呼び出し階層図

```
retry(f; delays, check)
    │
    └─ (args...; kwargs...) -> begin  # 返却されるクロージャ
           │
           ├─ iterate(delays)         # 遅延イテレータ開始
           │
           ├─ [ループ]
           │      ├─ f(args...; kwargs...)  # 対象関数実行
           │      ├─ check(state, e)        # リトライ条件判定
           │      ├─ sleep(delay)           # 遅延待機
           │      └─ iterate(delays, state) # 次の遅延取得
           │
           └─ f(args...; kwargs...)    # 最終試行（try/catchなし）
```

### データフロー図

```
[入力]                    [処理]                    [出力]

f (関数) ───────▶ retry()                   ──▶ ラップされた関数
delays ─────────▶     │
check ──────────▶     │
                      ▼
                 ラップ関数呼び出し
                      │
                      ├─ f() 成功 ──▶ 結果
                      │
                      ├─ f() 失敗
                      │    ├─ check() → true ──▶ sleep → リトライ
                      │    └─ check() → false ──▶ rethrow
                      │
                      └─ delays 枯渇 ──▶ f() 最終試行
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| error.jl | `base/error.jl` | ソース | retry / ExponentialBackOff の実装 |
