# 機能設計書 50-RateLimiter

## 概要

本ドキュメントは、Symfony RateLimiterコンポーネントの機能設計を記述する。RateLimiterはトークンバケット、固定ウィンドウ、スライディングウィンドウ等のアルゴリズムによるレートリミット機能を提供し、API利用制限やスロットリング等に使用されるコンポーネントである。

### 本機能の処理概要

RateLimiterコンポーネントは、アプリケーションにおけるリクエスト頻度制限（レートリミット）を実現する。RateLimiterFactoryを通じてリミッターインスタンスを生成し、consume()メソッドでトークン消費を行い、RateLimitオブジェクトで制限状態を返却する。

**業務上の目的・背景**：API公開やWebアプリケーションにおいて、DDoS攻撃の防止、サービスの公平な利用、外部APIの利用制限遵守、ブルートフォース攻撃の防止等のために、リクエスト頻度を制限する必要がある。RateLimiterコンポーネントは、設定ベースで多様なレートリミットポリシーを適用できる柔軟な仕組みを提供する。

**機能の利用シーン**：API エンドポイントのリクエスト数制限、ログイン試行回数の制限、フォーム送信頻度の制限、メール送信のスロットリング、外部API呼び出し頻度の制御等。

**主要な処理内容**：
1. RateLimiterFactory::create() - キーに基づくリミッターインスタンス生成
2. TokenBucketLimiter - トークンバケットアルゴリズムによるレート制限
3. FixedWindowLimiter - 固定ウィンドウアルゴリズムによるレート制限
4. SlidingWindowLimiter - スライディングウィンドウアルゴリズムによるレート制限
5. consume() - トークン消費とRateLimit結果の返却
6. reserve() - トークン予約と待機時間の計算
7. CompoundLimiter - 複数リミッターの組み合わせ

**関連システム・外部連携**：Lockコンポーネント（アトミックなトークン操作のための排他制御）、Cacheコンポーネント等のStorageInterface実装と連携する。

**権限による制御**：RateLimiterコンポーネント自体には権限制御機構はないが、レートリミット機能自体がセキュリティ制御（ブルートフォース防止等）として機能する。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | RateLimiterコンポーネントには関連画面はない |

## 機能種別

レート制限 / アクセス制御

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| config.id | string | Yes | リミッター識別子 | - |
| config.policy | string | Yes | レートリミットポリシー | token_bucket、fixed_window、sliding_window、no_limit |
| config.limit | int | Yes（no_limit以外） | 最大トークン数/リクエスト数 | 正の整数 |
| config.interval | string | Yes（fixed/sliding_window時） | ウィンドウ間隔 | PHPのDateInterval形式（例: "1 hour"） |
| config.rate.amount | int | No（token_bucket時） | トークン補充量（デフォルト: 1） | 正の整数 |
| config.rate.interval | string | No（token_bucket時） | トークン補充間隔 | PHPのDateInterval形式 |
| key | string\|null | No | リミッターキー（ユーザーID、IPアドレス等） | - |
| tokens | int | No | 消費するトークン数（デフォルト: 1） | maxBurst以下 |

### 入力データソース

- Symfony設定ファイル（framework.rate_limiter）
- アプリケーションコードからのRateLimiterFactory経由のリミッター生成

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| RateLimit | RateLimit | レート制限結果（accepted、availableTokens、retryAfter、limit） |
| Reservation | Reservation | トークン予約（waitDuration、rateLimit） |
| isAccepted | bool | リクエストが受理されたか |
| remainingTokens | int | 残りトークン数 |
| retryAfter | DateTimeImmutable | 次にリクエスト可能な時刻 |
| limit | int | 最大トークン数 |

### 出力先

- アプリケーションコード（制限結果の判定）
- HTTPレスポンスヘッダー（X-RateLimit-*, Retry-After）

## 処理フロー

### 処理シーケンス

```
【TokenBucketLimiter::consume()】
1. reserve(tokens, maxTime=0) を呼び出し
   └─ Lock::acquire(true) でロック取得
   └─ Storage::fetch(id) でバケット取得（未存在時は新規作成）
   └─ bucket.getAvailableTokens(now) で利用可能トークン計算
   └─ maxBurstを超えないよう調整
2. トークン判定
   └─ 利用可能トークン >= 要求トークン → 消費してRateLimit(accepted=true)
   └─ 利用可能トークン < 要求トークン → 待機時間計算
      └─ maxTime超過 → MaxWaitDurationExceededException
      └─ maxTime以下 → Reservation(waitDuration, rateLimit)
3. Storage::save(bucket) でバケット状態を永続化
4. Lock::release() でロック解放
5. consume()はreserve(tokens, 0)を呼び、例外をキャッチしてRateLimitを返す
```

### フローチャート

```mermaid
flowchart TD
    A[consume tokens] --> B[reserve tokens, maxTime=0]
    B --> C[Lock::acquire]
    C --> D[Storage::fetch bucket]
    D --> E{バケット存在?}
    E -->|No| F[新規TokenBucket生成]
    E -->|Yes| G[getAvailableTokens]
    F --> G
    G --> H{tokens <= available?}
    H -->|Yes| I[トークン消費]
    I --> J[RateLimit accepted=true]
    H -->|No| K[待機時間計算]
    K --> L{maxTime超過?}
    L -->|Yes| M[MaxWaitDurationExceededException]
    L -->|No| N[Reservation 生成]
    M --> O[RateLimit accepted=false]
    J --> P[Storage::save]
    N --> P
    O --> P
    P --> Q[Lock::release]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-50-01 | トークン上限 | 1回のconsume/reserveで要求できるトークン数はmaxBurst以下 | reserve()時にInvalidArgumentException |
| BR-50-02 | ポリシー選択 | token_bucket、fixed_window、sliding_window、no_limitの4ポリシーをサポート | RateLimiterFactory::create()時 |
| BR-50-03 | ウィンドウ間隔 | intervalはPHPのDateInterval形式で指定し、DST考慮のためUnixタイムスタンプで計算 | configureOptions()時 |
| BR-50-04 | トークン補充レート | token_bucketポリシーでは、rate.intervalごとにrate.amountのトークンが補充される | TokenBucketLimiter使用時 |
| BR-50-05 | ロック統合 | LockFactoryが設定されている場合、トークン操作はロック保護下で実行される | create()時にlockFactory設定済みの場合 |

### 計算ロジック

**トークンバケット**:
- 利用可能トークン: `bucket.getAvailableTokens(now)` - 最後の更新からの経過時間に基づくトークン補充
- 待機時間: `rate.calculateTimeForTokens(remainingTokens)` - 不足トークンの補充に必要な時間

**固定ウィンドウ/スライディングウィンドウ**: ウィンドウ期間内のリクエスト数をカウントし、limit以下かを判定

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

### 操作別データベース影響一覧

RateLimiterコンポーネント自体はStorageInterface経由でデータを永続化する。CacheStorage使用時はCacheコンポーネントのバックエンドに依存する。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | InvalidArgumentException | tokens > maxBurst | maxBurst以下のトークン数を指定する |
| - | MaxWaitDurationExceededException | 待機時間がmaxTimeを超過 | リクエストを拒否するか、後で再試行する |
| - | RateLimitExceededException | ensureAccepted()で非受理時 | retryAfterまで待機する |
| - | LogicException | 不明なポリシー名 | token_bucket/fixed_window/sliding_window/no_limitのいずれかを指定 |

### リトライ仕様

- RateLimit::wait() メソッドで、retryAfterまでusleep()で待機可能（**RateLimit.php 63-71行目**）
- Reservation::wait() メソッドで予約時間まで待機可能

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

Lockコンポーネントによるトークン操作のアトミック性を確保。Lock::acquire(true)でブロッキング取得し、操作完了後にLock::release()で解放する。

## パフォーマンス要件

- トークン消費はLock保護下でアトミックに実行される
- StorageInterface経由のデータ永続化がボトルネックとなりうる
- NoLimiterを使用することでレートリミットを無効化可能

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

- レートリミットはブルートフォース攻撃、DDoS攻撃の緩和に有効
- キーにIPアドレスやユーザーIDを使用し、個別の制限を適用する
- RateLimit情報をHTTPレスポンスヘッダー（X-RateLimit-*, Retry-After）に含めることが推奨される

## 備考

- CompoundRateLimiterFactoryで複数のレートリミッターを組み合わせ可能
- ResetLimiterTraitでリミッターのリセット機能を提供
- OptionsResolverを使用した設定バリデーション

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | RateLimit.php | `src/Symfony/Component/RateLimiter/RateLimit.php` | レート制限結果のデータ構造 |
| 1-2 | Reservation.php | `src/Symfony/Component/RateLimiter/Reservation.php` | トークン予約のデータ構造 |
| 1-3 | LimiterInterface.php | `src/Symfony/Component/RateLimiter/LimiterInterface.php` | リミッターインターフェース |
| 1-4 | Policy/TokenBucket.php | `src/Symfony/Component/RateLimiter/Policy/TokenBucket.php` | トークンバケットの状態データ |
| 1-5 | Policy/Rate.php | `src/Symfony/Component/RateLimiter/Policy/Rate.php` | トークン補充レートの定義 |

**読解のコツ**: RateLimitクラスの4つのプロパティ（availableTokens、retryAfter、accepted、limit）がレート制限結果の全情報を保持する。`ensureAccepted()`（**39-46行目**）はリクエスト拒否時にRateLimitExceededExceptionを投げるヘルパーメソッド。`wait()`（**63-71行目**）はretryAfterまでusleep()で待機する。

#### Step 2: ファクトリを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | RateLimiterFactory.php | `src/Symfony/Component/RateLimiter/RateLimiterFactory.php` | リミッター生成ファクトリ |

**主要処理フロー**:
1. **31-40行目**: コンストラクタ - OptionsResolverで設定バリデーション
2. **42-54行目**: create() - ポリシーに基づくリミッターインスタンス生成（token_bucket/fixed_window/sliding_window/no_limit）
3. **56-99行目**: configureOptions() - intervalのDateInterval変換、rateの定義

#### Step 3: トークンバケットリミッターを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | TokenBucketLimiter.php | `src/Symfony/Component/RateLimiter/Policy/TokenBucketLimiter.php` | トークンバケットアルゴリズムの実装 |

**主要処理フロー**:
- **53-114行目**: reserve() - ロック取得、バケットフェッチ、トークン判定、保存、ロック解放
- **55-57行目**: tokens > maxBurstのバリデーション
- **62-65行目**: バケットフェッチ（未存在時は新規作成）
- **74-87行目**: トークン充足時の消費処理
- **88-104行目**: トークン不足時の待機時間計算
- **116-123行目**: consume() - reserve(tokens, 0)のラッパー

#### Step 4: その他のリミッターを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | FixedWindowLimiter.php | `src/Symfony/Component/RateLimiter/Policy/FixedWindowLimiter.php` | 固定ウィンドウアルゴリズム |
| 4-2 | SlidingWindowLimiter.php | `src/Symfony/Component/RateLimiter/Policy/SlidingWindowLimiter.php` | スライディングウィンドウアルゴリズム |
| 4-3 | NoLimiter.php | `src/Symfony/Component/RateLimiter/Policy/NoLimiter.php` | 制限なし（常に許可） |

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

```
RateLimiterFactory::create(key)
    |
    ├─ Lock::createLock(id)  [LockFactory設定時]
    |
    └─ match(policy)
           ├─ TokenBucketLimiter(id, maxBurst, rate, storage, lock)
           |      ├─ consume(tokens)
           |      |      └─ reserve(tokens, 0)
           |      └─ reserve(tokens, maxTime)
           |             ├─ Lock::acquire(true)
           |             ├─ Storage::fetch(id) → TokenBucket
           |             ├─ getAvailableTokens(now)
           |             ├─ Rate::calculateTimeForTokens()
           |             ├─ Storage::save(bucket)
           |             └─ Lock::release()
           |
           ├─ FixedWindowLimiter(id, limit, interval, storage, lock)
           ├─ SlidingWindowLimiter(id, limit, interval, storage, lock)
           └─ NoLimiter()
```

### データフロー図

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

config                   RateLimiterFactory              LimiterInterface
  (policy, limit,        └─ OptionsResolver検証
   interval, rate)            |
                         TokenBucketLimiter              RateLimit
key (string)             ├─ Lock取得                     ├─ accepted (bool)
tokens (int)             ├─ バケットフェッチ              ├─ remainingTokens
                         ├─ トークン計算                  ├─ retryAfter
                         ├─ Storage保存                   └─ limit
                         └─ Lock解放
                              |                          Reservation
                         Storage                         ├─ waitDuration
                         └─ CacheStorage等                └─ rateLimit
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| RateLimiterFactory.php | `src/Symfony/Component/RateLimiter/RateLimiterFactory.php` | ソース | リミッター生成ファクトリ |
| RateLimit.php | `src/Symfony/Component/RateLimiter/RateLimit.php` | ソース | レート制限結果 |
| Reservation.php | `src/Symfony/Component/RateLimiter/Reservation.php` | ソース | トークン予約 |
| LimiterInterface.php | `src/Symfony/Component/RateLimiter/LimiterInterface.php` | ソース | リミッターインターフェース |
| CompoundLimiter.php | `src/Symfony/Component/RateLimiter/CompoundLimiter.php` | ソース | 複合リミッター |
| TokenBucketLimiter.php | `src/Symfony/Component/RateLimiter/Policy/TokenBucketLimiter.php` | ソース | トークンバケットリミッター |
| FixedWindowLimiter.php | `src/Symfony/Component/RateLimiter/Policy/FixedWindowLimiter.php` | ソース | 固定ウィンドウリミッター |
| SlidingWindowLimiter.php | `src/Symfony/Component/RateLimiter/Policy/SlidingWindowLimiter.php` | ソース | スライディングウィンドウリミッター |
| NoLimiter.php | `src/Symfony/Component/RateLimiter/Policy/NoLimiter.php` | ソース | 無制限リミッター |
| Rate.php | `src/Symfony/Component/RateLimiter/Policy/Rate.php` | ソース | トークン補充レート |
| TokenBucket.php | `src/Symfony/Component/RateLimiter/Policy/TokenBucket.php` | ソース | トークンバケット状態 |
| Storage/ | `src/Symfony/Component/RateLimiter/Storage/` | ソース | ストレージインターフェース/実装 |
