# スケーリング

> セルフホスティングの Nodaro を、1 つのコンテナを超えて拡張します。API、メディアワーカー、レンダリングワーカー、オーケストレーターを分割し、同時実行数、Redis、ストレージを調整します。

Source: https://nodaro.ai/ja/docs/self-hosting/scaling

セルフホスティングの Nodaro の**スケーリング**とは、そのプロセスを複数のコンテナで動かすことです。デフォルトの Compose 構成では、API、ワーカー、オーケストレーター、Web サーバーのすべてを 1 つのコンテナで動かします。アクティブユーザーが 5 人程度までなら、この構成で問題ありません。それより多い場合は、ワーカーを専用のコンテナで動かします。各コンテナは 1 つの Redis、1 つのデータベース、1 つのストレージバケットを共有し、Redis のキューだけを通じて連携します。

## プロセス
イメージの起動スクリプト `/app/start.sh` は、`/app/backend` から次のプロセスを並行して起動します。

| コマンド | 役割 | 負荷 |
| --- | --- | --- |
| `node dist/server.js` | HTTP API | CPU 負荷は低く、メモリ使用量は中程度 |
| `node dist/worker.js` | メディアワーカー：ノードごとに 1 つのジョブを処理し、モデルプロバイダーを呼び出します | ネットワーク待ちが中心で、多くのジョブを同時に実行します |
| `node dist/render-worker.js` | レンダラー：ヘッドレス Chrome でコンポジションをレンダリングします | CPU がボトルネック：1 台のマシンにつき 1〜2 個 |
| `node dist/orchestrator.js` | ワークフローのオーケストレーター：各ワークフローのグラフを実行します | ネットワーク待ちが中心で、CPU 負荷は低い |
| `node dist/pipeline-worker.js` | **ストーリー → 動画**（Story → Video）のパイプライン。Nodaro Cloud でのみ動作します | セルフホスティング型のエディションでは、すぐに終了します |

起動スクリプトは、単一のプロセスだけを動かすコンテナでは行われない、次の 4 つの処理も行います。

- API の前段で、ポート `3000` の Web サーバーを動かします。
- 同梱のスタックでは、マイグレーションを適用します。
- `INTERNAL_ORCHESTRATOR_SECRET` が設定されていない場合は、その値を生成します。
- 同梱のスタックでは、暗号化キーを生成します。

プロセスを分割する前に、イメージ内のスクリプトを読んでください。

## 一般的な分割例
- **API コンテナ 1 つ**：`server.js` を実行します。
- **メディアワーカーのコンテナを複数**：それぞれ、デフォルトの `VIDEO_WORKER_CONCURRENCY=50` のままで問題ありません。
- **レンダリングワーカーのコンテナ 1〜2 つ**：それぞれ専用のマシンで動かします。
- **オーケストレーターのコンテナ 1 つ**。

コンテナどうしが直接通信することはありません。すべてのコンテナが同じ Redis、Supabase、ストレージを使い、Redis が唯一の連携ポイントになります。

## すべてのコンテナで共有する必要があるもの
| 変数 | 一致させる必要がある理由 |
| --- | --- |
| `INTERNAL_ORCHESTRATOR_SECRET` | オーケストレーターは、この値を使って API に対して認証します。未設定の場合、起動スクリプトはコンテナごとに新しい値を生成するため、すべてのコンテナで同じ値を明示的に設定してください。 |
| `NODARO_ENCRYPTION_KEY` | 保存されたプロバイダーキーと認証情報は、このキーで暗号化されます。別のキーでは読み取れません。 |
| `RUNTIME_ENV` | 環境の名前です。1 つの環境のすべてのコンテナで、同じ値を使う必要があります。 |
| `EDITION`、`SUPABASE_URL`、`SUPABASE_SERVICE_ROLE_KEY`、`REDIS_URL`、`R2_*` の各変数 | すべてのコンテナで、同じエディション、データベース、キュー、ストレージを使うためです。 |

## 同時実行数
| 変数 | デフォルト | 制限する対象 |
| --- | --- | --- |
| `MAX_CONCURRENT_NODES_PER_EXECUTION` | `6`（最大 `20`） | 1 回のワークフロー実行で、同時に実行できるノードの数。並列ノード数に対する、サーバー全体の上限です。 |
| `VIDEO_WORKER_CONCURRENCY` | `50` | 1 つのメディアワーカーで同時に処理するジョブの数 |
| `ORCHESTRATOR_CONCURRENCY` | `20` | 1 つのオーケストレーターで同時に処理するワークフロージョブの数 |
| `RENDER_WORKER_CONCURRENCY` | `2`（最大 `10`） | 1 つのレンダリングワーカーで同時に行うレンダリングの数。それぞれがヘッドレス Chrome を 1 つ使います。 |
| `REMOTION_CONCURRENCY` | 3D シーンでは `2`、それ以外のレンダリングでは CPU コア数の半分 | 1 回のレンダリングで使うブラウザーのタブ数。複数の 3D ジョブを実行する場合は、低い値にしてください。WebGL のタブはそれぞれスレッドを増やし、コンテナのプロセス数の上限に数えられます。 |
| `FFMPEG_CONCURRENCY` | `4`（最大 `32`） | すべての動画編集ノードとオーディオ編集ノードを合わせて、同時に実行する ffmpeg プロセスの数 |

Compose ファイルは、`.env` のこれらの変数をコンテナに渡しません。必要なサービスごとに、`environment:` に追加してください。

実行の各ノードにかけられる時間は最大 90 分、実行全体では最大 120 分です。独自の制限時間を宣言するノードには、代わりにその時間が割り当てられ、実行全体の上限も同じだけ延びます。[**EDL 適用**（Apply EDL）](https://nodaro.ai/docs/nodes/video/apply-edl)はそのようなノードで、制限時間はレンダリングする編集の内容によって決まります。

## Redis の高可用性
ジョブキューは、Redis のクラスターモードに対応しています。`REDIS_URL` に、クラスターのエンドポイントか Sentinel の URL を設定してください。

API は、キューのほかに、小さな共有キャッシュも Redis に保存します。これにより、複数の API コンテナが、時間のかかる同じプロバイダーの処理をそれぞれ繰り返さずに済みます。現在キャッシュしているのは、HeyGen のアバターとボイスのカタログ（約 4 MB）です。1 つのコンテナが `HEYGEN_CATALOG_REFRESH_HOURS`（デフォルトは 24）時間ごとに、ロックをかけてカタログを更新し、ほかのコンテナは 30 秒ほどで新しいコピーを取り込みます。ここにあるものは、すべてキャッシュです。Redis に接続できない場合、各コンテナは自身のメモリを使い、失われたエントリーは次回の起動時にプロバイダーから再取得されます。

## 2 つの環境で 1 つのデータベースを使う
ステージング用のコピーなど、2 つ目の環境を、**同じ** Supabase プロジェクトと**専用の** Redis に接続できます。その場合は、環境ごとに異なる `RUNTIME_ENV` を設定してください。Railway では、`RAILWAY_ENVIRONMENT_NAME` がすでにこの役割を果たします。

各実行には、その実行を引き受けたオーケストレーターが属する環境の名前が記録され、各環境のクリーンアップ処理は、自分の実行だけを確認します。名前が区別されていないと、各環境は相手の環境のジョブを自分の Redis で探し、見つけられないため、正常な実行を `Execution orphaned` で失敗として扱ってしまいます。環境が名前を記録するようになる前に始まった実行には、名前がありません。これらの実行は、`production` という名前の環境が処理します。

## ストレージのライフサイクル
Nodaro は、保存したメディアを自動では削除しません。ファイルをキーで参照するだけです。古いメディアを期限切れにするには、90 日後に削除するなどのライフサイクルルールをバケットに追加します。ルールには `video-analysis-tmp/` プレフィックスも含めてください。このプレフィックスには分析用の一時ファイルが保存されますが、セルフホスティング環境には、それらを削除するクリーンアップジョブがありません。

## Railway でのコンテナの停止
Railway では、`RAILWAY_DEPLOYMENT_DRAINING_SECONDS` で、置き換えられるコンテナに停止シグナルを送ってから強制停止するまでの時間を設定します。メディアワーカーは、その時間から 5 秒を引いた時間だけドレインします。そのため、時間のかかるモデルの呼び出しも、ジョブが新しいコンテナに移る前に完了して保存されます。未設定の場合、ワーカーは 25 秒間ドレインします。

## Frequently asked questions

### 1 つの Nodaro コンテナで、何人のユーザーに対応できますか？

デフォルトの 1 コンテナ構成は、アクティブユーザーが 5 人程度までなら問題ありません。それを超える場合は、メディアワーカー、レンダリングワーカー、オーケストレーターを別々のコンテナで動かしてください。

### Nodaro のコンテナどうしは、どのように通信しますか？

直接は通信しません。すべてのコンテナが同じ Redis、データベース、ストレージに接続し、Redis のジョブキューが唯一の連携ポイントになります。

### 実行が「Execution orphaned」になるのはなぜですか？

おそらく、2 つの環境が 1 つのデータベースを共有しながら、区別できる名前を付けずに別々の Redis を使っています。環境ごとに固有の RUNTIME_ENV の値を設定し、1 つの環境のすべてのコンテナでは同じ値を使ってください。

### Nodaro は、バケットから古いメディアを削除しますか？

いいえ。Nodaro は保存したメディアを参照するだけで、自動では削除しません。古いファイルを期限切れにするには、バケットにライフサイクルルールを追加し、そのルールに video-analysis-tmp/ プレフィックスも含めてください。
