# 機能設計書 47-レスポンスキャッシュ

## 概要

本ドキュメントは、Next.jsのResponseCache（レスポンスキャッシュ）の設計を記述する。ページレンダリング結果をメモリおよびIncrementalCacheに保存し、同一パスへの並行リクエストの重複排除やstale-while-revalidateパターンによるバックグラウンド再検証を提供するミドルウェアレイヤーである。

### 本機能の処理概要

**業務上の目的・背景**：Next.jsアプリケーションでは、同一ページへのリクエストが同時に複数発生する場合がある。ResponseCacheは、同一キーへのリクエストをバッチ処理し重複排除を行うとともに、IncrementalCache（ISR用のキャッシュ）と連携してページレンダリング結果のキャッシュ管理を行う。また、最小モード（Vercel等のサーバーレス環境）ではLRUメモリキャッシュとinvocationIDによるスコープ管理を提供する。

**機能の利用シーン**：ページリクエスト処理、ISR再検証、オンデマンド再検証、プリフェッチ。

**主要な処理内容**：
1. リクエストのバッチ処理（Batcher）による重複排除
2. IncrementalCacheからのキャッシュルックアップ
3. staleエントリのバックグラウンド再検証
4. 最小モードでのLRUメモリキャッシュ管理
5. エラー時のrevalidate値の自動調整（3-30秒範囲に制限）
6. キャッシュエントリの形式変換（ResponseCacheEntry <-> IncrementalResponseCacheEntry）

**関連システム・外部連携**：IncrementalCache、LRUCache、Batcher

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

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | 本機能は画面に直接関連しない。サーバーサイドのリクエスト処理パイプラインで動作する |

## 機能種別

キャッシュ制御 / リクエスト処理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| key | string \| null | Yes | キャッシュキー（通常はページパス） | nullの場合はキャッシュをバイパス |
| responseGenerator | ResponseGenerator | Yes | レスポンス生成関数 | - |
| context.routeKind | RouteKind | Yes | ルートの種別 | - |
| context.isOnDemandRevalidate | boolean | No | オンデマンド再検証か | - |
| context.isPrefetch | boolean | No | プリフェッチか | - |
| context.incrementalCache | IncrementalResponseCache | Yes | IncrementalCacheインスタンス | - |
| context.invocationID | string | No | インフラからのinvocation ID | 最小モードでのスコープ管理用 |

### 入力データソース

Next.jsサーバーのリクエスト処理パイプラインから呼び出される。

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| entry | ResponseCacheEntry \| null | レスポンスキャッシュエントリ。HTML/RSCデータ、キャッシュ制御情報を含む |

### 出力先

呼び出し元のリクエストハンドラに返却。

## 処理フロー

### 処理シーケンス

```
1. get(key, responseGenerator, context) 呼び出し
2. keyがnullの場合 → responseGeneratorを直接実行
3. 最小モードの場合:
   a. LRUキャッシュからルックアップ（invocationID + pathname）
   b. ヒット → 返却
   c. ミス → 通常フローへ
4. Batcher.batch()で重複排除
5. handleGet():
   a. IncrementalCache.get()
   b. フレッシュ&非オンデマンド → resolve + 返却
   c. stale&プリフェッチ → resolve + 返却（再検証しない）
   d. stale / ミス / オンデマンド → revalidate()
6. revalidate():
   a. responseGenerator()でレスポンス生成
   b. IncrementalCache.set()で保存（最小モードの場合はLRUキャッシュ）
   c. エラー時はrevalidate値を3-30秒に自動調整して再保存
```

### フローチャート

```mermaid
flowchart TD
    A[get呼び出し] --> B{key null?}
    B -->|Yes| C[responseGenerator直接実行]
    B -->|No| D{最小モード?}
    D -->|Yes| E[LRUキャッシュルックアップ]
    E --> F{ヒット?}
    F -->|Yes| G[キャッシュ返却]
    F -->|No| H[通常フロー]
    D -->|No| H
    H --> I[Batcher.batch]
    I --> J[handleGet]
    J --> K[IncrementalCache.get]
    K --> L{結果判定}
    L -->|Fresh| M[resolve + 返却]
    L -->|Stale + Prefetch| M
    L -->|Stale / Miss| N[revalidate]
    N --> O[responseGenerator実行]
    O --> P[IncrementalCache.set]
    P --> Q[返却]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-47-01 | プリフェッチ最適化 | プリフェッチ時はstaleデータを返却し再検証しない | isPrefetch === true |
| BR-47-02 | オンデマンド再検証分離 | オンデマンド再検証リクエストは通常リクエストをブロックしない（別キー） | isOnDemandRevalidate === true |
| BR-47-03 | エラー時自動調整 | 再検証失敗時はrevalidateを3-30秒に自動調整して保存 | 再検証エラー時 |
| BR-47-04 | 最小モードスコープ | invocationIDによりキャッシュを1回のrevalidation requestにスコープ | 最小モード + invocationID |
| BR-47-05 | LRU TTL | invocationIDなしの場合はTTL（デフォルト10秒）でキャッシュを管理 | 最小モード + invocationID未設定 |

### 計算ロジック

- エラー時のrevalidate: `Math.min(Math.max(originalRevalidate || 3, 3), 30)`
- エラー時のexpire: `Math.max(revalidate + 3, originalExpire)`
- LRUキャッシュキー: `pathname + '\0' + invocationID`

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

本機能はRDBを使用しない。IncrementalCacheとLRUメモリキャッシュを使用。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | RevalidationError | responseGenerator実行失敗 | revalidate値を自動調整してstaleエントリを保存。resolveが完了していればconsole.error |

### リトライ仕様

エラー時はrevalidate値を3-30秒に制限して再保存し、次回リクエスト時に再試行される。

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

Batcher（`getBatcher`, `revalidateBatcher`）により同一キーへの並行リクエストを一本化。

## パフォーマンス要件

- LRUキャッシュのデフォルトサイズ: 150エントリ（`NEXT_PRIVATE_RESPONSE_CACHE_MAX_SIZE`で調整可能）
- TTLデフォルト: 10秒（`NEXT_PRIVATE_RESPONSE_CACHE_TTL`で調整可能）
- Batcherによる同一キーのリクエスト重複排除
- `scheduleOnNextTick`による非同期処理でイベントループブロックを回避

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

- invocationIDはインフラ側から提供されるため改竄リスクは低い
- LRUキャッシュはプロセスメモリ内に保持され外部からアクセス不可

## 備考

- WebResponseCache（Edge Runtime用）は簡略化された実装で、IncrementalCacheとの連携はない
- `NEXT_PRIVATE_RESPONSE_CACHE_MAX_SIZE`と`NEXT_PRIVATE_RESPONSE_CACHE_TTL`で設定をカスタマイズ可能
- LRUキャッシュのエビクション時にinvocationIDを記録し、サイズ不足の警告を出力

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | types.ts | `packages/next/src/server/response-cache/types.ts` | ResponseCacheEntry型（176-181行目）、IncrementalResponseCacheEntry型（135-147行目）、ResponseGenerator型（187-201行目） |

**読解のコツ**: ResponseCacheEntryとIncrementalResponseCacheEntryの違いは、htmlフィールドがRenderResult vs stringである点。utils.tsの変換関数がこの橋渡しをする。

#### Step 2: エントリーポイントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | index.ts | `packages/next/src/server/response-cache/index.ts` | ResponseCacheクラスの`get()`メソッド（200-299行目） |

**主要処理フロー**:
1. **221-226行目**: keyがnullの場合のearly return
2. **229-259行目**: 最小モードのLRUキャッシュルックアップ
3. **273-296行目**: Batcher.batch()による重複排除
4. **310-387行目**: handleGet()のメインロジック

#### Step 3: 再検証処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | index.ts | `packages/next/src/server/response-cache/index.ts` | `revalidate()`メソッド（403-431行目）、`handleRevalidate()`メソッド（433-511行目） |

**主要処理フロー**:
- **445-448行目**: responseGenerator()実行
- **455-458行目**: fromResponseCacheEntry()で変換
- **462-479行目**: IncrementalCache.set()またはLRUキャッシュ保存
- **485-506行目**: エラー時のrevalidate自動調整

#### Step 4: 変換ユーティリティを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | utils.ts | `packages/next/src/server/response-cache/utils.ts` | fromResponseCacheEntry()（14-40行目）、toResponseCacheEntry()（42-78行目） |

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

```
ResponseCache.get(key, responseGenerator, context)
    │
    ├─ [最小モード] LRUCache.get(compoundKey)
    │
    └─ getBatcher.batch()
           └─ handleGet()
                  │
                  ├─ incrementalCache.get(key)
                  │      └─ resolve(entry)  [フレッシュなら即座にresolve]
                  │
                  └─ revalidateBatcher.batch()
                         └─ handleRevalidate()
                                │
                                ├─ responseGenerator()
                                ├─ fromResponseCacheEntry()
                                ├─ incrementalCache.set()
                                │   └─ [最小モード] LRUCache.set()
                                └─ [エラー時] revalidate自動調整 + 再保存
```

### データフロー図

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

key + context ───▶ ResponseCache.get()
                       │
                       ├─▶ LRUCache (最小モード)
                       ├─▶ IncrementalCache.get()
                       │      │
                       │      ├─ Fresh → resolve
                       │      └─ Stale/Miss → revalidate
                       │
                       └─▶ handleRevalidate()
                              │
                              ├─▶ responseGenerator() → レンダリング
                              ├─▶ fromResponseCacheEntry() → 変換
                              └─▶ IncrementalCache.set() → 保存
                                                              │
                                                    ──▶ ResponseCacheEntry
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| index.ts | `packages/next/src/server/response-cache/index.ts` | ソース | ResponseCacheの本体実装 |
| types.ts | `packages/next/src/server/response-cache/types.ts` | ソース | 型定義 |
| utils.ts | `packages/next/src/server/response-cache/utils.ts` | ソース | 変換ユーティリティ |
| web.ts | `packages/next/src/server/response-cache/web.ts` | ソース | Edge Runtime用簡略版 |
| lru-cache.ts | `packages/next/src/server/lib/lru-cache.ts` | ソース | LRUキャッシュ実装 |
| batcher.ts | `packages/next/src/lib/batcher.ts` | ソース | リクエストバッチ処理 |
