# Blue-Green Deployment構成と旧・単一runtime構成の比較

この資料は、リポジトリに併存する2つのproduction実行方式を比較したものです。**Blue-Green Deployment構成はproduction用local blue/green設定、旧構成はsingle production runtime / Compose実装**を表します。実ホストには接続していないため、どちらが過去または現在採用されているかは未確認です。

## 先に結論

- Blue-Green Deployment構成は、Cloudflare Tunnelの接続先を固定HAProxyに保ち、空slotを準備してから新規要求の行き先を切り替えます。
- AI Worker containerは平常時1つ、更新中だけ新旧2つです。各container内では3種類の論理processorが動きます。
- 切替後も旧Webは接続数が0になるまで、旧Workerは旧releaseのAI処理が全て完了するまで残します。
- 旧・単一runtime構成は、Web/APIとAI Workerが1つのcombined processで動きます。更新時は旧containerを停止・削除した後に新containerを起動するため、blue/greenの同時稼働やdrainはありません。
- 停止時間を生じさせない切替の対象は、**同じmigrationVersionで行う通常リリース**です。可用性SLAや実環境での実績を示すものではありません。

## 1. Blue-Green Deployment構成

[![Cloudflare Tunnel、固定HAProxy、blueとgreenのWeb・AI Worker、共有PostgreSQL、OpenAIからなるBlue-Green Deployment構成。平常時はactive slotのAI Workerが1 container、更新中だけ新旧2 containersが並行稼働し、旧Workerは旧releaseのAI処理完了後に停止する。](03-blue-green-deployment.png)](03-blue-green-deployment.svg)

### AI Workerはいくつか

- **平常時は1 container**です。active slotにWeb 1 containerとAI Worker 1 containerが動き、もう一方のslotは空です。
- **更新中だけ2 containers**です。candidate slotのAI Workerを先に起動するため、旧releaseと新releaseのWorkerが一時的に並行稼働します。
- 1つのAI Worker process内では、durable feedback worker、durable AI generation worker、production command materializerの3 processorを起動します。したがって、1 containerで同時に1 jobしか処理できないという意味ではありません。

通常の更新は次の順序です。

1. 現在使われていないslotで、新releaseのAI Worker containerを起動し、続いてWeb containerを起動する。
2. WebとWorkerの認証済みreadiness成功を連続3回確認する。
3. HAProxyのweightを変更し、新規要求をcandidate slotへ一括で切り替える。
4. 旧Webへの新規流入を止め、既存接続が0になるまでdrainする。
5. 旧Workerは旧release向けのAI処理が0になるまで残し、その後に旧slotを回収する。

PostgreSQLは新旧slotが共有する正本です。業務データ、session、永続queueをcontainer外に置き、WorkerはreleaseVersionに対応するjobをclaimします。旧slotを保持している間は、HAProxyを戻すrollback経路もあります。

### AI処理中に更新した場合

1. 切替前に開始されたAI処理は、旧Workerが旧releaseの契約のまま継続します。新WorkerはreleaseVersionが一致する新release側のjobを担当し、旧jobを横取りしません。
2. 通常のreaperは、旧releaseについてbackground job、実行中attempt、command execution、OpenAI provider leaseの4項目を確認します。
3. 4項目が全て0になるまで旧Worker containerを残し、`drained=true`になってから停止します。
4. Worker containerにはshutdown mode `drain`が設定されます。generation／feedback処理は停止signal時も実行中controllerをabortせず、command materializerも実行中promiseの完了を待ちます。

したがって、通常のBlue-Green Deploymentでは、OpenAIへ要求中という理由だけで旧Workerを直ちに終了しません。ただし、強制kill、host停止、電源断、drain timeout、外部provider障害まで処理完了を保証するものではありません。

### 適用範囲

- 通常deployでは現在とcandidateの`migrationVersion`が同一である必要があります。異なる場合はdeployが拒否されます。
- 非互換なDB変更は通常切替に含めず、計画停止を伴うmigrationとして扱います。
- 単一runtimeからblue/greenへ初めて移行する際は、1回限りの計画停止が必要です。
- 切替中は新旧slotを同時に動かすため、アプリ部分はおおむね2倍のresourceを必要とします。
- drain timeoutや外部依存障害まで無停止を保証する図ではありません。実稼働の可用性はhost、HAProxy state、container、監視記録で別途確認が必要です。

## 2. 旧・単一runtime構成

[![Cloudflare Tunnelが単一のcombined SaaS containerへ接続し、そのcontainerがPostgreSQLとOpenAIを利用する旧構成。host-runtimeが旧containerを停止・削除して新containerを起動し、readiness後にTunnelを開始する。](04-legacy-single-runtime.png)](04-legacy-single-runtime.svg)

`deploy/production/host-runtime.mjs` は、単一のSaaS containerをloopback `:3000` で管理します。runtime roleを指定しない場合は`combined`となり、Next.js Web/APIとdurable AI Workerが同じprocessで起動します。

更新または再起動の流れは、既存のcloudflaredとcontainerを停止し、既存containerを削除してから、新containerを作成・起動し、readiness成功後にcloudflaredを開始する順序です。したがって、更新中に新旧アプリを並行稼働させるslotや、旧processを段階的にdrainする仕組みはありません。

AI jobの正本がPostgreSQLにあることは、process停止とデータ消失が同義ではないことを意味します。一方でcombined process自体は停止するため、その間のWeb受付とWorker実行は連続しません。この図は停止時間やデータ損失の実績を示すものではありません。

## 比較表

| 観点 | Blue-Green Deployment構成 | 旧・単一runtime構成 |
|---|---|---|
| 公開入口 | Tunnel → 固定HAProxy `:3001` | Tunnel → 単一container `:3000` |
| アプリ実行単位 | active slotにWeb ×1 + AI Worker ×1 | Web/APIとWorkerを含む1 combined container |
| AI Worker container数 | 平常時1、更新中のみ新旧2 | 平常時1。更新時は旧を停止後に新を起動 |
| Worker内processor | feedback、generation、command materializerの3系統 | 同じ3系統がcombined process内で動作 |
| 更新前の準備 | 空slotを起動しreadinessを確認 | 旧containerを先に停止・削除 |
| トラフィック切替 | HAProxy weightで新規要求を一括切替 | 新container readiness後にTunnel開始 |
| 旧Web | 新規流入を止め、接続0までdrain | container停止時に終了 |
| AI処理中の更新 | 旧Workerが旧release処理を完了。drained確認後に停止 | combined process停止時に終了。既定のshutdown drainはOFF |
| rollback | 旧slot保持中にHAProxyを戻せる | 新旧の同時保持を前提とした切替はない |
| DB | 新旧slotで共有するPostgreSQL | container外のPostgreSQL |
| 通常更新の制約 | 同一migrationVersion。非互換DB変更は計画停止 | blue/greenのmigration gateなし |
| 実稼働確認 | 未確認 | 採用履歴・実稼働とも未確認 |

## 根拠

- Blue-Green Deploymentの全体手順と制約：`docs/operations/bluegreen/local-bluegreen-deployment.md:3-38,40-47`（R041）
- production slot・port・drain設定：`config/local-bluegreen.production.example.json:1-52`（R042）
- Web/Worker container分離とworker drain mode：`deploy/local-bluegreen/local-bluegreen.mjs:26,232-294`（R043）
- candidate起動、readiness、HAProxy切替、旧slot回収、rollback：`deploy/local-bluegreen/local-bluegreen.mjs:583-688,985-1050,1177-1236`（R044）
- 単一containerのcombined role、停止・再作成、readiness、Tunnel起動：`deploy/production/host-runtime.mjs:29-45,65-72,98-140`（R045）
- role未指定時のcombinedとshutdown drain既定値：`okr-cai-saas/src/lib/runtime-role.ts:1-33`（R046）
- Worker process内の3 processor起動：`okr-cai-saas/src/instrumentation.ts:9-44`（R047）
- 単一SaaS serviceとPostgreSQL：`compose.yaml:1-121`（R048）
- 旧releaseのdrained判定：`okr-cai-saas/src/lib/db/postgres/runtime-control.ts:170-240`（R049）
- generation workerのrelease fenceとshutdown drain：`okr-cai-saas/src/lib/ai/generation-job.ts:540-555,1979-2011`（R050）
- feedback workerのrelease別claimとshutdown drain：`okr-cai-saas/src/lib/ai/feedback-job.ts:235-332,950-971`（R051）
- command materializerのrelease別claimと完了待ち：`okr-cai-saas/src/lib/ai/production-command-job.ts:557-593,869-900`（R052）

ノード・接続・根拠参照は [共通モデル](../architecture-model.json)、調査上の不整合と未確認事項は [調査・検証記録](../verification.md) にまとめています。
