# 機能設計書 19-cluster

## 概要

本ドキュメントは、Node.jsのclusterモジュール（マルチプロセスクラスタ機能）の機能設計を記述したものである。

### 本機能の処理概要

cluster機能は、単一のNode.jsプロセスを複数のワーカープロセスに分散して実行するためのAPIを提供する。マルチコアCPUを活用し、同一ポートをリッスンする複数のワーカープロセスで負荷分散を実現する。プライマリプロセスがワーカーの生成・管理を行い、ラウンドロビンまたは共有ハンドルによる接続分散をサポートする。

**業務上の目的・背景**：Node.jsはシングルスレッドで動作するため、マルチコアCPUの能力を最大限活用するにはマルチプロセス構成が必要である。clusterモジュールにより、スケールアウトとフォールトトレランスを実現できる。

**機能の利用シーン**：
- HTTPサーバーの負荷分散
- 高可用性アプリケーションの構築
- CPUバウンド処理の並列化
- ゼロダウンタイムリスタート
- マルチコアCPUの活用

**主要な処理内容**：
1. プライマリプロセスによるワーカー生成（fork）
2. ワーカーの状態管理（online、listening、disconnect、exit）
3. 接続の負荷分散（ラウンドロビン/共有ハンドル）
4. ワーカー間のIPC通信
5. グレースフルシャットダウン

**関連システム・外部連携**：
- child_process: ワーカープロセスの生成（内部でforkを使用）
- net: TCPサーバーのハンドル共有
- dgram: UDPソケットのハンドル共有

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

## 関連画面

本機能はCLI/APIレベルの機能であり、直接関連する画面はない。

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | - |

## 機能種別

プロセス管理 / 負荷分散 / フォールトトレランス

## 入力仕様

### 入力パラメータ

#### cluster.setupPrimary([settings])

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| settings.exec | string | No | ワーカー実行ファイル | デフォルトprocess.argv[1] |
| settings.args | string[] | No | ワーカー引数 | デフォルトprocess.argv.slice(2) |
| settings.execArgv | string[] | No | Node.js引数 | デフォルトprocess.execArgv |
| settings.cwd | string | No | 作業ディレクトリ | 有効なパス |
| settings.silent | boolean | No | stdio非継承 | デフォルトfalse |
| settings.stdio | Array | No | stdioオプション | 配列 |
| settings.uid | number | No | ユーザーID | 整数 |
| settings.gid | number | No | グループID | 整数 |
| settings.inspectPort | number/function | No | インスペクタポート | ポート番号 |
| settings.serialization | string | No | シリアライズ方式 | 'json'/'advanced' |
| settings.windowsHide | boolean | No | Windowsコンソール非表示 | デフォルトfalse |

#### cluster.fork([env])

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| env | Object | No | 追加環境変数 | オブジェクト |

#### worker.send(message[, sendHandle][, options][, callback])

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| message | any | Yes | 送信メッセージ | シリアライズ可能 |
| sendHandle | Handle | No | ハンドル | net.Server/net.Socket |
| options | Object | No | オプション | オブジェクト |
| callback | Function | No | コールバック | 関数 |

### 入力データソース

- 環境変数（NODE_UNIQUE_ID、NODE_CLUSTER_SCHED_POLICY）
- child_processからのIPCメッセージ
- ネットワーク接続

## 出力仕様

### 出力データ（プライマリ）

| 項目名 | 型 | 説明 |
|--------|-----|------|
| cluster.workers | Object | ワーカーオブジェクトのマップ |
| cluster.settings | Object | クラスター設定 |
| cluster.isPrimary | boolean | プライマリ判定（true） |
| cluster.isWorker | boolean | ワーカー判定（false） |
| cluster.schedulingPolicy | number | スケジューリング方式 |

### 出力データ（ワーカー）

| 項目名 | 型 | 説明 |
|--------|-----|------|
| cluster.worker | Worker | 現在のWorkerインスタンス |
| cluster.isPrimary | boolean | プライマリ判定（false） |
| cluster.isWorker | boolean | ワーカー判定（true） |

### Workerオブジェクト

| 項目名 | 型 | 説明 |
|--------|-----|------|
| worker.id | number | ワーカーID |
| worker.process | ChildProcess | 子プロセスオブジェクト |
| worker.state | string | 状態（none/online/listening/disconnected/dead） |
| worker.exitedAfterDisconnect | boolean | 切断後終了フラグ |

### 出力先

- アプリケーションコード（イベント、プロパティ）
- IPCチャネル

## 処理フロー

### 処理シーケンス

```
1. プライマリプロセス起動
   └─ cluster.isPrimary = true
   └─ setupPrimary()でワーカー設定
2. ワーカー生成
   └─ cluster.fork()
   └─ child_process.fork()呼び出し
   └─ NODE_UNIQUE_ID環境変数設定
3. ワーカープロセス起動
   └─ cluster.isWorker = true
   └─ _setupWorker()でワーカー初期化
   └─ 'online'メッセージ送信
4. サーバーリッスン
   └─ net.createServer().listen()
   └─ _getServer()でハンドル取得
   └─ プライマリにqueryServerメッセージ
5. 接続分散
   └─ ラウンドロビン: プライマリが接続をワーカーに振り分け
   └─ 共有ハンドル: OSが接続を振り分け
```

### フローチャート

```mermaid
flowchart TD
    A[Node.js起動] --> B{NODE_UNIQUE_ID?}
    B -->|なし| C[プライマリモード]
    B -->|あり| D[ワーカーモード]
    C --> E[setupPrimary]
    E --> F[cluster.fork]
    F --> G[ワーカープロセス生成]
    G --> H[Workerインスタンス作成]
    D --> I[_setupWorker]
    I --> J[onlineメッセージ送信]
    J --> K[サーバー起動]
    K --> L[_getServer]
    L --> M{ラウンドロビン?}
    M -->|Yes| N[RoundRobinHandle]
    M -->|No| O[SharedHandle]
    N --> P[接続待機]
    O --> P
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-001 | スケジューリング方式 | SCHED_RR（ラウンドロビン）またはSCHED_NONE（OS任せ） | プライマリ |
| BR-002 | Windows制限 | Windowsではデフォルトでラウンドロビン無効 | Windows |
| BR-003 | UDP除外 | UDPはラウンドロビン対象外 | dgram使用時 |
| BR-004 | プロファイル分離 | --prof使用時は自動で--logfile=v8-%p.log追加 | V8プロファイル |
| BR-005 | ワーカーID | NODE_UNIQUE_IDで識別（1から連番） | ワーカー生成時 |

### 計算ロジック

#### プライマリ/ワーカー判定
```javascript
const childOrPrimary = ObjectHasOwn(process.env, 'NODE_UNIQUE_ID')
  ? 'child'
  : 'primary';
```

#### スケジューリングポリシー決定
```javascript
let schedulingPolicy = process.env.NODE_CLUSTER_SCHED_POLICY;
if (schedulingPolicy === 'rr')
  schedulingPolicy = SCHED_RR;
else if (schedulingPolicy === 'none')
  schedulingPolicy = SCHED_NONE;
else if (process.platform === 'win32')
  schedulingPolicy = SCHED_NONE;
else
  schedulingPolicy = SCHED_RR;
```

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

本機能はデータベースを直接操作しない。

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| - | - | - | - |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| ERR_SOCKET_BAD_PORT | TypeError | 不正なinspectPort | 有効なポートを指定 |
| AssertionError | Error | スケジューリングポリシー不正 | SCHED_RRまたはSCHED_NONEを使用 |
| Resource leak detected | AssertionError | ワーカー終了時にハンドルが残存 | ハンドルを適切にクローズ |

### リトライ仕様

本機能にリトライ仕様はない。ワーカー異常終了時は明示的にfork()を再実行する必要がある。

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

本機能にトランザクション仕様はない。

## パフォーマンス要件

- ラウンドロビンはプライマリがボトルネックになりうる
- WindowsではIOCPの特性上、共有ハンドルの方が効率的
- ワーカー数はCPUコア数が目安（os.cpus().length）

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

- ワーカーはプライマリと同じ権限で実行される
- 環境変数がワーカーに継承される
- IPCメッセージは信頼できる通信

## 備考

- cluster.isMasterはcluster.isPrimaryの非推奨エイリアス
- cluster.setupMasterはcluster.setupPrimaryの非推奨エイリアス
- ワーカーはexitedAfterDisconnectでグレースフル終了を判定
- maxConnectionsを超えた接続は他のワーカーに再配布される

---

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

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

### 推奨読解順序

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

プライマリ/ワーカーの振り分けを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | cluster.js | `lib/cluster.js` | NODE_UNIQUE_IDによる振り分け（28-29行目） |

**読解のコツ**: 環境変数NODE_UNIQUE_IDの有無でプライマリかワーカーかが決まる。

#### Step 2: プライマリ側の実装を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | internal/cluster/primary.js | `lib/internal/cluster/primary.js` | モジュール初期化・定数（29-60行目） |
| 2-2 | internal/cluster/primary.js | `lib/internal/cluster/primary.js` | setupPrimary（62-109行目） |
| 2-3 | internal/cluster/primary.js | `lib/internal/cluster/primary.js` | fork（161-217行目） |

**主要処理フロー**:
- **62-109行目**: setupPrimary() - ワーカー設定
- **118-140行目**: createWorkerProcess() - child_process.fork呼び出し
- **161-217行目**: cluster.fork() - ワーカー生成
- **223-238行目**: cluster.disconnect() - 全ワーカー切断
- **268-330行目**: queryServer() - サーバーハンドル処理

#### Step 3: ワーカー側の実装を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | internal/cluster/child.js | `lib/internal/cluster/child.js` | _setupWorker（36-64行目） |
| 3-2 | internal/cluster/child.js | `lib/internal/cluster/child.js` | _getServer（67-129行目） |
| 3-3 | internal/cluster/child.js | `lib/internal/cluster/child.js` | rr/shared関数（144-225行目） |

**主要処理フロー**:
- **36-64行目**: _setupWorker() - ワーカー初期化
- **67-129行目**: _getServer() - サーバーハンドル取得
- **144-159行目**: shared() - 共有ハンドルモード
- **162-225行目**: rr() - ラウンドロビンモード
- **228-248行目**: onconnection() - 接続受信処理

#### Step 4: ラウンドロビン処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | internal/cluster/round_robin_handle.js | `lib/internal/cluster/round_robin_handle.js` | RoundRobinHandle全体 |

**主要処理フロー**:
- **17-48行目**: コンストラクタ - サーバー作成・接続ハンドラ設定
- **50-75行目**: add() - ワーカー追加
- **99-113行目**: distribute() - 接続分配
- **115-139行目**: handoff() - ワーカーへの接続転送

#### Step 5: Workerクラスを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | internal/cluster/worker.js | `lib/internal/cluster/worker.js` | Worker全体 |

**主要処理フロー**:
- **16-39行目**: Workerコンストラクタ
- **44-46行目**: kill() - destroy()へのエイリアス
- **48-50行目**: send() - メッセージ送信
- **52-58行目**: isDead()/isConnected() - 状態確認

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

```
cluster (lib/cluster.js)
    |
    +-- NODE_UNIQUE_IDなし --> internal/cluster/primary
    |                              |
    |                              +-- setupPrimary(options)
    |                              |       +-- 設定マージ
    |                              |       +-- setupイベント発火
    |                              |
    |                              +-- fork(env)
    |                              |       +-- createWorkerProcess()
    |                              |       |       +-- child_process.fork()
    |                              |       +-- new Worker()
    |                              |       +-- イベント設定
    |                              |       +-- forkイベント発火
    |                              |
    |                              +-- disconnect(cb)
    |                              |       +-- 全ワーカーdisconnect
    |                              |
    |                              +-- queryServer(worker, message)
    |                              |       +-- new SharedHandle() or
    |                              |       +-- new RoundRobinHandle()
    |                              |
    |                              +-- Worker.disconnect()
    |                              +-- Worker.destroy(signal)
    |
    +-- NODE_UNIQUE_IDあり --> internal/cluster/child
                                   |
                                   +-- _setupWorker()
                                   |       +-- new Worker()
                                   |       +-- onlineメッセージ送信
                                   |
                                   +-- _getServer(obj, options, cb)
                                   |       +-- queryServerメッセージ送信
                                   |       +-- shared() or rr()
                                   |
                                   +-- onconnection(message, handle)
                                   |       +-- 接続受け入れ
                                   |
                                   +-- Worker.disconnect()
                                   +-- Worker.destroy()

internal/cluster/worker
    |
    +-- Worker
            +-- constructor()
            +-- kill() --> destroy()
            +-- send() --> process.send()
            +-- isDead()
            +-- isConnected()

internal/cluster/round_robin_handle
    |
    +-- RoundRobinHandle
            +-- constructor() - net.createServer()
            +-- add(worker) - ワーカー登録
            +-- remove(worker) - ワーカー削除
            +-- distribute(err, handle) - 接続分配
            +-- handoff(worker) - 接続転送
```

### データフロー図

```
[プライマリプロセス]                     [ワーカープロセス]

cluster.fork()
      |
      v
child_process.fork() ----------------> NODE_UNIQUE_ID設定
      |                                       |
      v                                       v
Worker生成                            _setupWorker()
      |                                       |
      +-- IPC Channel ----------------+       |
      |                               |       v
      |                               +-- 'online' message
      v                                       |
cluster.workers[id]                   cluster.worker
      |                                       |
      |                                       v
      |                               net.createServer().listen()
      |                                       |
      |                               _getServer()
      |                                       |
      +-- queryServer <-----------------------+
      |                                       |
      v                                       |
RoundRobinHandle or SharedHandle              |
      |                                       |
      +-- onconnection ---------------------> |
      |                                       v
      +-- 'newconn' message ----------> onconnection()
                                              |
                                              v
                                        リクエスト処理
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| cluster.js | `lib/cluster.js` | ソース | エントリポイント（振り分け） |
| internal/cluster/primary.js | `lib/internal/cluster/primary.js` | ソース | プライマリプロセス実装 |
| internal/cluster/child.js | `lib/internal/cluster/child.js` | ソース | ワーカープロセス実装 |
| internal/cluster/worker.js | `lib/internal/cluster/worker.js` | ソース | Workerクラス |
| internal/cluster/round_robin_handle.js | `lib/internal/cluster/round_robin_handle.js` | ソース | ラウンドロビンハンドル |
| internal/cluster/shared_handle.js | `lib/internal/cluster/shared_handle.js` | ソース | 共有ハンドル |
| internal/cluster/utils.js | `lib/internal/cluster/utils.js` | ソース | ユーティリティ |
| child_process.js | `lib/child_process.js` | ソース | fork()の実装 |
