# 機能設計書 6-メモリ管理

## 概要

本ドキュメントは、Apache Sparkのメモリ管理機能の設計を記述する。UnifiedMemoryManagerにより実行メモリとストレージメモリを統合管理し、動的なメモリ割り当てとスピルを制御する。

### 本機能の処理概要

UnifiedMemoryManagerは、実行メモリ（シャッフル、ソート、集約等の計算に使用）とストレージメモリ（キャッシュ、ブロードキャスト等のデータ保持に使用）の間にソフト境界を設け、相互にメモリを借用可能にする統合メモリ管理機構である。

**業務上の目的・背景**：Sparkアプリケーションでは計算処理とデータキャッシュが同一JVM内で行われるため、両者のメモリ使用を効率的にバランスさせる必要がある。静的なメモリ分割では片方が余剰で他方が不足する非効率が生じるため、動的な境界調整により全体のメモリ利用効率を最大化する。

**機能の利用シーン**：タスク実行中のメモリ割り当て要求時、RDDキャッシュ時、シャッフルデータのソート時など、メモリ管理が必要な全ての場面で利用される。

**主要な処理内容**：
1. 実行メモリの動的割り当て（acquireExecutionMemory）
2. ストレージメモリの動的割り当て（acquireStorageMemory）
3. 実行/ストレージ間のメモリ借用とエビクション
4. On-Heap / Off-Heapメモリの統合管理
5. タスク単位のメモリ管理（TaskMemoryManager）
6. 非管理メモリ（Unmanaged Memory）の追跡
7. メモリ不足時のディスクスピル制御

**関連システム・外部連携**：BlockManager（ストレージ管理）、Executor（タスク実行）、TaskMemoryManager

**権限による制御**：メモリ管理は内部コンポーネントであり、権限制御は持たない。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 4 | Stage Detail（ステージ詳細） | 補助機能 | タスクのメモリスピル情報およびPeak Execution Memoryを表示 |
| 10 | Executors（エグゼキュータ一覧） | 補助機能 | ExecutorのOnHeap/OffHeapメモリ使用状況を表示 |

## 機能種別

リソース管理 / メモリ割り当て / エビクション制御

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| conf | SparkConf | Yes | Spark設定 | - |
| maxHeapMemory | Long | Yes | 最大ヒープメモリ（バイト） | 正の値 |
| onHeapStorageRegionSize | Long | Yes | ストレージ領域サイズ（バイト） | 0以上、maxHeapMemory以下 |
| numCores | Int | Yes | コア数 | 1以上 |
| numBytes | Long | Yes | メモリ割り当て要求サイズ | 0以上 |
| blockId | BlockId | No | ストレージブロックID | - |
| memoryMode | MemoryMode | Yes | ON_HEAP or OFF_HEAP | - |

### 入力データソース

- TaskMemoryManager: タスク実行中の実行メモリ要求
- BlockManager: RDDキャッシュ等のストレージメモリ要求

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| grantedMemory | Long | 割り当てられたメモリサイズ（バイト） |
| enoughMemory | Boolean | ストレージメモリ割り当て成功フラグ |
| memoryUsed | Long | 現在のメモリ使用量 |

### 出力先

呼び出し元（TaskMemoryManager / BlockManager）への返却値

## 処理フロー

### 処理シーケンス

```
1. メモリ割り当て要求受信
   └─ acquireExecutionMemory or acquireStorageMemory
2. 現在のメモリ使用量確認
   └─ 実行メモリ使用量、ストレージメモリ使用量の取得
3. 借用可能メモリの計算
   └─ 相手側の空きメモリを計算
4. エビクション判定（ストレージ→実行の借用時）
   └─ ストレージメモリ使用がストレージ領域を超える場合、キャッシュブロックをエビクト
5. メモリ割り当て
   └─ 要求サイズまたは利用可能サイズの小さい方を割り当て
6. 不足時のスピル処理
   └─ メモリ不足時にディスクへのスピルをトリガー
```

### フローチャート

```mermaid
flowchart TD
    A[メモリ割り当て要求] --> B{実行メモリ要求?}
    B -->|Yes| C[実行メモリ空き確認]
    B -->|No| D[ストレージメモリ空き確認]
    C --> E{空きあり?}
    E -->|Yes| F[実行メモリ割り当て]
    E -->|No| G{ストレージから借用可能?}
    G -->|Yes| H[ストレージブロックエビクト]
    H --> F
    G -->|No| I[スピル要求]
    D --> J{空きあり?}
    J -->|Yes| K[ストレージメモリ割り当て]
    J -->|No| L{実行メモリから借用可能?}
    L -->|Yes| K
    L -->|No| M[割り当て失敗/部分割り当て]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | ソフト境界 | 実行とストレージの境界はソフトで、相互に借用可能 | 常時 |
| BR-02 | 実行メモリ優先 | 実行メモリは**決して**ストレージによってエビクトされない | 常時 |
| BR-03 | ストレージエビクション | ストレージメモリは実行メモリの要求により エビクトされうる | 実行メモリ不足時 |
| BR-04 | デフォルト比率 | spark.memory.fraction=0.6、spark.memory.storageFraction=0.5 | デフォルト設定 |
| BR-05 | 予約メモリ | 300MBがシステム予約として差し引かれる | 常時 |

### 計算ロジック

メモリ領域サイズ計算:
- 総メモリ = (JVMヒープ - 300MB) * spark.memory.fraction
- ストレージ領域 = 総メモリ * spark.memory.storageFraction
- 実行領域 = 総メモリ - ストレージ領域
- デフォルト: ストレージ = 0.6 * 0.5 = ヒープの30%

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| - | - | - | メモリ管理はデータベース操作を行わない |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| SparkOutOfMemoryError | メモリエラー | 実行メモリの割り当て不可 | スピル後に再試行、それでも不足ならタスク失敗 |
| SparkIllegalArgumentException | 設定エラー | 不正なメモリ設定 | 設定値の修正 |

### リトライ仕様

メモリ割り当て失敗時は、まず他のタスクのメモリスピルを試行する。スピル後に再度メモリ割り当てを試行する。

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

メモリの割り当て・解放はMemoryPoolのsynchronizedブロック内で行われ、スレッドセーフが保証される。

## パフォーマンス要件

- メモリ割り当て: マイクロ秒オーダー
- エビクション処理: ブロック数とサイズに比例
- 非管理メモリポーリング間隔: spark.memory.unmanaged.polling.interval

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

メモリ管理は内部コンポーネントであり、直接的なセキュリティ要件は持たない。Off-Heapメモリ使用時はUnsafe操作が使用される。

## 備考

- UnifiedMemoryManagerはSpark 1.6で導入され、従来のStaticMemoryManagerを置き換えた
- spark.memory.offHeap.enabled=trueでOff-Heapメモリを有効化可能
- 非管理メモリ追跡はRocksDBステートストア等の外部メモリ使用の可視化に利用

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | MemoryManager.scala | `core/src/main/scala/org/apache/spark/memory/MemoryManager.scala` | メモリ管理の抽象基底クラス |
| 1-2 | MemoryPool.scala | `core/src/main/scala/org/apache/spark/memory/MemoryPool.scala` | メモリプールの基底クラス |
| 1-3 | ExecutionMemoryPool.scala | `core/src/main/scala/org/apache/spark/memory/ExecutionMemoryPool.scala` | 実行メモリプール |
| 1-4 | StorageMemoryPool.scala | `core/src/main/scala/org/apache/spark/memory/StorageMemoryPool.scala` | ストレージメモリプール |

**読解のコツ**: UnifiedMemoryManager.scalaの34-52行目のScaladocがメモリモデルの全体像を説明している。実行とストレージの境界がソフトであること、実行メモリは決してストレージによってエビクトされないことが重要なポイント。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | UnifiedMemoryManager.scala | `core/src/main/scala/org/apache/spark/memory/UnifiedMemoryManager.scala` | acquireExecutionMemory(), acquireStorageMemory() |

**主要処理フロー**:
- **34-52行目**: Scaladocでメモリモデルの説明
- **58-67行目**: クラス定義とコンストラクタ（maxHeapMemory, onHeapStorageRegionSize, numCores）
- **87-91行目**: 非管理メモリポーリングの初期化
- acquireExecutionMemory(): ストレージからの借用とエビクションロジック
- acquireStorageMemory(): 実行メモリからの借用ロジック

#### Step 3: タスクレベルのメモリ管理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | TaskMemoryManager.java | `core/src/main/java/org/apache/spark/memory/TaskMemoryManager.java` | タスク単位のメモリ管理 |

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

```
Executor (タスク実行)
    |
    +-- TaskMemoryManager.acquireExecutionMemory()
    |       +-- UnifiedMemoryManager.acquireExecutionMemory()
    |               +-- ExecutionMemoryPool.acquireMemory()
    |               +-- StorageMemoryPool.freeSpaceToShrinkPool() [借用時]
    |
    +-- BlockManager.putBytes() / putSingle()
            +-- UnifiedMemoryManager.acquireStorageMemory()
                    +-- StorageMemoryPool.acquireMemory()
                    +-- MemoryStore.evictBlocksToFreeSpace() [エビクション時]
```

### データフロー図

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

タスク実行メモリ要求  ───▶  acquireExecutionMemory()  ───▶  割り当てメモリ量
                             |
                             +-- ストレージエビクション
                             +-- タスク間メモリ調整

キャッシュメモリ要求  ───▶  acquireStorageMemory()    ───▶  成功/失敗
                             |
                             +-- 実行メモリ借用

メモリ解放         ───▶  releaseMemory()            ───▶  プール更新
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| UnifiedMemoryManager.scala | `core/src/main/scala/org/apache/spark/memory/UnifiedMemoryManager.scala` | ソース | 統合メモリ管理のメインクラス |
| MemoryManager.scala | `core/src/main/scala/org/apache/spark/memory/MemoryManager.scala` | ソース | メモリ管理の抽象基底クラス |
| MemoryPool.scala | `core/src/main/scala/org/apache/spark/memory/MemoryPool.scala` | ソース | メモリプール基底 |
| ExecutionMemoryPool.scala | `core/src/main/scala/org/apache/spark/memory/ExecutionMemoryPool.scala` | ソース | 実行メモリプール |
| StorageMemoryPool.scala | `core/src/main/scala/org/apache/spark/memory/StorageMemoryPool.scala` | ソース | ストレージメモリプール |
| TaskMemoryManager.java | `core/src/main/java/org/apache/spark/memory/TaskMemoryManager.java` | ソース | タスク単位メモリ管理 |
| UnmanagedMemoryConsumer.scala | `core/src/main/scala/org/apache/spark/memory/UnmanagedMemoryConsumer.scala` | ソース | 非管理メモリコンシューマ |
| package.scala | `core/src/main/scala/org/apache/spark/memory/package.scala` | ソース | パッケージ定義 |
