# 機能設計書 42-メモリキャッシュ

## 概要

本ドキュメントは、webpackのメモリキャッシュ機能について、MemoryCachePluginおよびMemoryWithGcCachePluginを中心に設計内容を記述する。

### 本機能の処理概要

本機能は、コンパイル結果をメモリ上にキャッシュし、同一コンパイラインスタンス内でのリビルド時にモジュール解析・変換結果を再利用することで、ビルド速度を向上させる。

**業務上の目的・背景**：開発時のファイル監視モード(watch mode)では、ソースコード変更のたびにリビルドが実行される。変更されていないモジュールの再処理を回避するため、メモリキャッシュによりコンパイル結果を保持し、インクリメンタルビルドの高速化を実現する。

**機能の利用シーン**：webpack設定の`cache.type`が`memory`の場合に自動的に有効化される。開発サーバー(webpack-dev-server)使用時のデフォルトキャッシュ戦略として利用される。

**主要な処理内容**：
1. MemoryCachePlugin: Map構造でidentifier-etag-dataの組をメモリに保持
2. MemoryWithGcCachePlugin: ガベージコレクション付きメモリキャッシュ。maxGenerations設定に基づき未使用エントリを段階的に削除
3. Cache.STAGE_MEMORY段階でstore/getフックに登録

**関連システム・外部連携**：Cache基盤クラス(lib/Cache.js)のフックシステムを利用。ファイルキャッシュ(No.43)との連携で多段キャッシュを構成可能。

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

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | 画面との直接的な関連なし（内部キャッシュ機構） |

## 機能種別

キャッシュ管理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| identifier | string | Yes | キャッシュエントリの一意識別子 | - |
| etag | Etag \| null | Yes | キャッシュの有効性検証用タグ | - |
| data | Data | Yes | キャッシュするデータ | - |
| maxGenerations | number | No | GCキャッシュの最大世代数(MemoryWithGcCachePlugin用) | 正の整数 |

### 入力データソース

- Compiler.cacheインスタンスのstore/getフック経由

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| data | Data | キャッシュされたデータ(get時) |
| null | null | キャッシュミスまたはetag不一致時 |

### 出力先

- メモリ上のMap構造

## 処理フロー

### 処理シーケンス

```
1. store フック: identifier-etag-dataの組をMapに保存
   └─ Cache.STAGE_MEMORYで登録

2. get フック: identifierでMapを検索
   └─ エントリが存在し、etagが一致: dataを返却
   └─ エントリが存在するがetag不一致: nullを返却
   └─ エントリが存在しない: gotHandlersにコールバックを追加

3. shutdown フック: Mapをクリア
   └─ コンパイラ終了時にメモリを解放

4. (GC版のみ) afterDone フック: 世代管理に基づく未使用エントリの削除
   └─ oldCacheのuntilが現世代を超過したエントリを削除
   └─ cacheの一部をoldCacheに移動
```

### フローチャート

```mermaid
flowchart TD
    A[cache.get呼び出し] --> B{identifierで検索}
    B -->|エントリあり| C{etag一致?}
    C -->|Yes| D[データ返却]
    C -->|No| E[null返却]
    B -->|エントリなし| F[gotHandlersに登録]
    F --> G{後段キャッシュから取得?}
    G -->|データあり| H[メモリキャッシュに保存]
    G -->|データなし| I[nullを保存]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-42-01 | etag検証 | キャッシュ取得時にetagが一致しない場合はキャッシュミスとして扱う | 常時 |
| BR-42-02 | null保存 | 後段キャッシュでもデータが見つからない場合、nullを保存して次回の無駄な検索を防止 | gotHandler実行時 |
| BR-42-03 | GC世代管理 | maxGenerations回のビルド後、未アクセスのエントリを削除 | MemoryWithGcCachePlugin使用時 |
| BR-42-04 | 段階的GC | ビルドごとにcache全体の1/maxGenerations分をoldCacheに移動 | MemoryWithGcCachePlugin使用時 |

### 計算ロジック

- GC削除タイミング: `entry.until > generation` で判定。untilは `generation + maxGenerations` で設定。
- 一回のGCで移動するエントリ数: `(cache.size / maxGenerations) | 0`

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

該当なし

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | - | メモリ不足 | maxGenerationsを小さくして早期GCを促進 |

### リトライ仕様

リトライは行わない。

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

該当なし

## パフォーマンス要件

- メモリキャッシュはO(1)のMap操作で高速
- MemoryWithGcCachePluginはundefined設定によりMap再ハッシュを回避

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

特になし

## 備考

- MemoryCachePluginはwatch mode向けの最もシンプルなキャッシュ実装
- MemoryWithGcCachePluginはメモリ使用量とキャッシュヒット率のバランスを取る

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | MemoryCachePlugin.js | `lib/cache/MemoryCachePlugin.js` | **21行目**: `Map<string, { etag: Etag | null, data: Data } | null>` - キャッシュのデータ構造 |
| 1-2 | MemoryWithGcCachePlugin.js | `lib/cache/MemoryWithGcCachePlugin.js` | **36-39行目**: cache(現世代)とoldCache(旧世代)の2つのMap |

**読解のコツ**: cacheのエントリ値がnullの場合は「キャッシュミスが確認済み」、undefinedの場合は「GCでoldCacheに移動済み」を意味する。この区別が重要。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | MemoryCachePlugin.js | `lib/cache/MemoryCachePlugin.js` | **20-54行目**: apply()メソッド全体 - 3つのフック(store, get, shutdown)登録 |

**主要処理フロー**:
1. **23-28行目**: storeフック - identifierをキーにetag/dataの組を保存
2. **29-47行目**: getフック - キャッシュ検索とgotHandlers登録
3. **48-53行目**: shutdownフック - cache.clear()でメモリ解放

#### Step 3: GCキャッシュの世代管理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | MemoryWithGcCachePlugin.js | `lib/cache/MemoryWithGcCachePlugin.js` | **43-93行目**: afterDoneフックでのGC処理 |
| 3-2 | MemoryWithGcCachePlugin.js | `lib/cache/MemoryWithGcCachePlugin.js` | **100-131行目**: getフックでoldCacheからの復元処理 |

**主要処理フロー**:
- **50-59行目**: oldCacheのuntilが現世代を超過したエントリを削除
- **71-92行目**: cacheの一部をundefinedに設定しoldCacheに移動
- **109-121行目**: getでoldCacheからエントリを復元しcacheに再登録

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

```
compiler.cache.hooks
    |
    +-- store.tap (STAGE_MEMORY)
    |       +-- cache.set(identifier, {etag, data})
    |
    +-- get.tap (STAGE_MEMORY)
    |       +-- cache.get(identifier)
    |       +-- [GC版] oldCache.get(identifier)
    |       +-- gotHandlers.push(callback)
    |
    +-- shutdown.tap (STAGE_MEMORY)
    |       +-- cache.clear()
    |
    +-- [GC版] compiler.hooks.afterDone.tap
            +-- oldCache期限切れ削除
            +-- cacheからoldCacheへ移動
```

### データフロー図

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

identifier     ───▶  MemoryCachePlugin         ───▶  Data (キャッシュヒット)
etag           ───▶  ├─ store: Map.set()       ───▶  null (キャッシュミス)
data           ───▶  ├─ get: Map.get()
                     └─ shutdown: Map.clear()

                     MemoryWithGcCachePlugin
                     ├─ cache (現世代Map)
                     ├─ oldCache (旧世代Map)
                     └─ afterDone: GC処理
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| MemoryCachePlugin.js | `lib/cache/MemoryCachePlugin.js` | ソース | シンプルなメモリキャッシュ |
| MemoryWithGcCachePlugin.js | `lib/cache/MemoryWithGcCachePlugin.js` | ソース | GC付きメモリキャッシュ |
| Cache.js | `lib/Cache.js` | ソース | キャッシュ基盤クラス(フック定義) |
| getLazyHashedEtag.js | `lib/cache/getLazyHashedEtag.js` | ソース | Etag生成ユーティリティ |
| mergeEtags.js | `lib/cache/mergeEtags.js` | ソース | Etagマージユーティリティ |
