水平スケーリング
cMindは、オペレーターの手間を最小限に抑えてスケールアウトします。2つのステートフルワークロード — 実行/バックテスト、コピー取引 — はどちらもデータベースを調整ポイントとして使用するため、レプリカを 追加しても外部コディネータ(ZooKeeper、リーダー選出)は不要です。
コピー取引(自己修復リース)
各ノードはCopyEngineSupervisorを実行します(App:Copy:Enabledでゲート)。各レконаcileサイクルで、
スーパーバイザーは以下のことを行います:
- クレーム — アサインされていない、またはリース期限切れのすべての実行中プロファイルを1つのアトミック
UPDATEでクレーム — 2つの競合するスーパーバイザーが同じプロファイルを同時にクレームすることは できないため、プロファイルは正確に1つのノードにコピーされます(二重注文なし)。 - 更新 — 自らがホストするプロファイルのリースを更新。
- アサインされたプロファイルをホストし、実行中のホストにアクセストークンのローテーションをインプレースで 推送(イベントストリームのドロップなし)。
ノードクラッシュ → 更新を停止; App:Copy:LeaseTtlが経過すると、存続するノードが次のサイクルで
プロファイルを回収し、貿易を重複させることなく состояниеを再構築。スケールアウト = レプリカを
追加; 未アサイン/空きプロファイルは自動的に選択されます。
グレースフルスケールイン / ローリングアップデート(S1) = SIGTERM受信時、
CopyEngineSupervisor.StopAsyncはこのノードのリースを解放(AssignedNode/LeaseExpiresAt → null),
そのため存続者はその次のレконаcileサイクルで回収します — TTL経過後ではありません。
ハードクラッシュのみTTLを待機。コピーエージェントのterminationGracePeriodSeconds
(デフォルト30)は、ポッド終了前に解放時間が終わるのを待ちます。
ノブ(App:Copy)
| 設定 | デフォルト | 備考 |
|---|---|---|
Enabled | false | ノードでコピー hosting をオンにします。 |
ReconcileInterval | 30s | ノードがクレーム/更新/レконаcilする頻度。 |
LeaseTtl | 120s | 無応答ノードのプロファイルが回収されるまでの猶予。スローサイクルが偽の手動オーバーを起こさないように、数回のレконаcile間隔に設定してください。 |
NodeName | マシン名 | 2つのスーパーバイザーが同じホストを共有する場合は明確に設定。 |
Kubernetesでは、コピースーパーバイザーはDeploymentとして実行; replicasを必要な並列度に設定。
各ポッドは安定したNodeNameを取得(デフォルト:ポッドホスト名)、そのためリースはポッドごとに
属性付けられます。データベースは単一の情報源 — スティッキーセッション、ポッドごとの移行状態なし。
均衡配分(S4): App:Copy:MaxProfilesPerNode > 0に設定して、ノードがホストできる実行中
プロファイル数に上限を設けます。各スーパーバイザーは、アトミックFOR UPDATE SKIP LOCKEDバウンデッド
クレーム 통해最大残りのヘッドルームまでクレームするため、プロファイルは最初のスーパーバイザーが
すべてを取得する代わりにレプリカに分散されます — 単一のホットポッド/SPOFなし。
スキップロックされたクレームにより、并发クレームの下でも「プロファイルごとに正確に1つのノード」
の保証が維持されます(二重ホストなし)。0(デフォルト)= 無制限(1つのノードがすべてをホスト、
変更なし)。
スケール時(S7/S8): 各ポッドはReconcileIntervalの最大20%までレконаcileをジャター
(CopyEngineSupervisor.JitteredInterval)するため、N個のレプリカが同時にクレーム/更新UPDATEを
発射しません(Postgresサージングル問題)。copyAgent.replicas > 1の場合、チャートはレプリカを
ノード全体に分散(topologySpreadConstraints)し、PodDisruptionBudget(minAvailable: 1)を
追加するため、ドレイン/アップグレード時にコピー容量がゼロになることはありません。
実行/バックテスト実行
NodeSchedulerはMaxInstancesを尊重して最小負荷のigibleノードを選択; リモートノードエージェントは
自己登録してハートビート(App:Discovery)を送信し、NodeHeartbeatMonitorはハートビートが
Discovery:HeartbeatTtlを超えたときにノード到达不能としてマーク。ノードエージェントを追加すると
実行容量が増加; 死亡したエージェントは自動的にルーティング回避されます。
スケールアウト / ローリングデプロイ時のマイグレーション
すべてのWeb/MCPレプリカは起動時にOwnerSeederを実行し、EFマイグレーションを適用して所有者をシード
します。N個のレプリカが同時に起動する場合、これを安全にするため、マイグレーション + シードは
Postgresセッション advisory lock内(MigrationLock.RunExclusiveAsync、キー
DatabaseDefaults.MigrationAdvisoryLockKey)で実行されます:最初のレプリカがそれを取得して
マイグレーションとシードを実行; 残りはロックでブロックし、次にマイグレーションがすでに適用済み
(no-op)であること、所有者がすでに存在することを見つけます。個別のマイグレーションジョブまたは
リーダー選出は不要です。初回シードを追加する場合は、同じガードブロック内に配置して単一ライターにします。
ノードエージェントHTTPレジリエンス
メインノードは3つの目的別に分割されたクライアントを通じて各CtraderCliNodeエージェントとHTTPで通信、
そのためflakeyノードやネットワークが状態を破損することはありません:
- read(
status/report/stats)— べき等GET、リクエストごとにリトライ (指数バックオフ + ジャター、NodeAgentHttp.ReadRetryCount)とPer-attemptおよび合計タイムアウト付き。 - write(
start/stop/clean)— 非べき等POST、タイムアウトするが決してリトライしない: リトライされたstartはコンテナを二重起動する可能性がある。 - stream(
logs)— 寿命の長いdocker logs -fストリームは無限タイムアウトとレジリエンス パイプラインなしで取得されるため、tailが切断されることはない。
到達不能なままのノードは、ハートビート + 孤児インスタンス回収で 処理されます; HTTPレイヤーは只是一時的な障害を平滑化します。
ステートレスタイア
Web(Blazor Server + API)およびMCPサーバーはデータベース背後でステートレス、自由にレプリケート可能。 認証はcookieベース; Webをロードバランサー背後で水平スケール可能。MCPサーバーは別プロセス/Deployment のため、Webとは獨立してスケールします。
データベース接続レジリエンス
データベースを開くすべてのホストは、再試行実行ストラテジーを使用して一時的な切断または 管理されたPostgresフェイルオーバー(RDS / Flexible Serverパッチ)が発生した場合、ユーザーにエラーと して表示する代わりにリトライします:
- WebおよびMCPは、npgsqlコンポーネントで
DisableRetry=falseと明示的なCommandTimeout(DatabaseDefaults.CommandTimeoutSeconds)を使用してコンテキストを登録します。 - CopyAgent(非Aspire)は
UseAppNpgsqlを通じて登録され、同じEnableRetryOnFailure(MaxRetryCount, MaxRetryDelay)+DatabaseDefaultsからのコマンドタイムアウトを 適用します。
すべての書き込みは単一のSaveChanges / 単一のExecuteUpdate / 単一のExecuteSqlステートメントの
ため、再試行ストラテジーは安全です(手動strategy.ExecuteAsyncラッパーが必要なマルチステートメント
トランザクションなし)。論理操作で複数のSaveChangesを持つ手動トランザクションまたはを追加する場合は、
db.Database.CreateExecutionStrategy().ExecuteAsync(...)でラップしてください — そうしないとリトライ下で
スローされます。
スケールアウトのチェックリスト
- Postgresが増設された接続負荷に対応できるようにサイズ設定(各Web/MCP/ノードレプリカがプールを開く)。
- コピー プロファイルをホストする必要があるすべてのノードで
App:Copy:Enabled=true。 - 同一ホストのスーパーバイザーごとに異なる
App:Copy:NodeName(K8s:デフォルトのポッド別で問題なし)。 -
LeaseTtl≥ 3×ReconcileInterval。 - 特権Dockerが利用可能な場所にノードエージェントをデプロイ(AKS/EKS/EC2/VM、Fargate以外)。
- マルチレプリカWeb:
signalr接続文字列(Redisバックプレーン)を設定し、 ingress セッションアfinity(スティッキーセッション)を有効にして、Blazorサーキットが生きているポッドに 再接続するようにします。コンポーネント例外はMainLayoutErrorBoundaryによってキャッチされます (フレンドリーリトライ、サーキットは存続)。