# 生产同构验收模式与高媒体压力测试

## 目标与边界

生产同构验收使用当前生产 API、双站点 Worker、PostgreSQL、River、候选路由、限流、
账务、回调和文件物化链路。只有候选确定后的上游连接会被克隆并指向集群内的协议模拟器，
因此不会绕过真实 Gemini 与 Volces 客户端实现。

验收脚本是显式生产写操作，会暂停新正式任务、调整 Worker 容量并在恢复场景删除一个
Worker Pod。它不会自动发布新版本；只能针对已经发布、完整 SHA 和 digest 固定的 manifest
执行。进入线上前必须先通过[本地三节点同构验收](local-isomorphic-acceptance.md)。

## 隔离与流量门禁

Gateway 流量模式保存在数据库中，支持 `live` 和 `validation`：

- `live`：正常接收正式任务，带验收 Header 的请求被拒绝。
- `validation`：普通新任务返回 `503 validation_in_progress`；已有正式任务继续排空。
- 验收请求必须同时匹配当前 Run ID、随机 Run Token，以及 Run 中登记的专属 API Key/User
  身份对；任意错配都被拒绝。
- 普通生产请求体不能打开 `simulation`。
- `X-EasyAI-Acceptance-Upstream: real` 只对已授权验收身份有效，用于最后两条真实小流量请求。

验收请求使用以下 Header，Token 只存在于私有环境和进程内，不写入数据库、报告或日志：

```text
X-EasyAI-Acceptance-Run: <run-id>
X-EasyAI-Acceptance-Token: <run-token>
X-EasyAI-Acceptance-Upstream: real
```

Run 记录保存 release SHA、API/Worker image digest、容量档位和脱敏报告。切回 `live` 必须
同时匹配 Run ID、流量模式 revision、release SHA 与两个 digest，并且 Run 已标记为
`passed`。CAS 任一项变化都会拒绝切换。

## 验收负载

Gemini 原生图片编辑调用 `generateContent`，输入 `contents.parts` 同时包含文本和
`inlineData`，并设置 `responseModalities=["IMAGE"]`。压测端边读 Base64 边解码和计算
SHA-256，不集中保存响应：

| Profile | 请求数 | 输入图片 | 输出图片 | 模拟延迟 |
|---|---:|---:|---:|---:|
| `gemini-baseline` | 1000 | 256 KiB | 256 KiB | 4 秒 |
| `gemini-large` | 128 | 2 MiB | 4 MiB | 8–15 秒 |
| `gemini-peak` | 32 | 8 MiB | 8 MiB | 15–30 秒 |

多参考图视频使用正式 `content` 结构，按 60%/30%/10% 分配 3/6/9 图，包含 JPEG、PNG、
WebP 和 4K 图片。128 组输入组合避免只命中相同缓存；每四个任务至少一个携带 4K 图片并
要求在到达上游前完成缩放或重编码。

- `video-throughput`：1200 个任务并发提交，提交窗口不超过 10 秒。
- `video-recovery`：96 个 2–3 分钟任务，运行中删除香港 Worker Pod，验证远程任务恢复、
  租约续期和重复提交保护。
- `real-canary`：一条 Gemini 带图编辑和一条多参考图视频，使用真实候选与真实上游。

模拟器实现 Gemini `generateContent` 与 Volces
`/contents/generations/tasks` 提交、查询、取消协议，还提供格式混合的图片池和独立回调
收集器。它校验参考图数量、角色、格式、尺寸与内容哈希，并统计重复上游提交和重复回调。

默认通过 `AI_GATEWAY_ACCEPTANCE_IDENTITY_SHARDS=32` 幂等准备 32 个仅属于验收用户组的
身份、API Key 和独立钱包，并按请求序号轮询使用。这样可模拟真实多租户并发，避免把 1000
个任务全部压在同一钱包行锁上；每个任务仍执行真实预留、结算和重复扣费检查。该值可在
`1..128` 内通过环境变量调整，Run 只保存 Key/User ID 对，Key Secret 仅通过 stdin 传入
集群内压测进程，不进入 Pod spec、数据库 Run 配置、报告或日志。

## Worker 配置

执行槽、连接池和媒体并发进入 Pod 环境变量，副本数由发布工具更新 Deployment：

```text
AI_GATEWAY_ASYNC_WORKER_INSTANCE_HARD_LIMIT
AI_GATEWAY_DATABASE_MAX_CONNS
AI_GATEWAY_DATABASE_MIN_IDLE_CONNS
AI_GATEWAY_DATABASE_MAX_CONN_IDLE_SECONDS
AI_GATEWAY_MEDIA_MATERIALIZATION_CONCURRENCY
AI_GATEWAY_MEDIA_REQUEST_CONCURRENCY
AI_GATEWAY_WORKER_REPLICAS_NINGBO
AI_GATEWAY_WORKER_REPLICAS_HONGKONG
```

`AI_GATEWAY_WORKER_REPLICAS_*` 是发布工具配置，不会传入容器。容量档位先固定宁波、香港
各一个 2 GiB Worker，再按实时内存、CPU 和 PostgreSQL 预算验证 `1+2`、条件允许时的
`2+2`，以及安全 drain 回到 `1+1`：

| 档位 | 单实例执行槽 | 数据库池 | 媒体并发 | 全局执行槽 |
|---|---:|---:|---:|---:|
| P24 | 24 | 32 | 24 | 48 |
| P28 | 28 | 36 | 28 | 56 |
| P32 | 32 | 40 | 32 | 64 |

API Pod 使用独立发布配置 `AI_GATEWAY_API_DATABASE_MAX_CONNS`，生产同构验收默认使用 `64`；
也可通过 `AI_GATEWAY_ACCEPTANCE_API_DATABASE_MAX_CONNS` 覆盖本次验收值，无需修改 Go 代码。
所有 API/Worker Pool 的 `AI_GATEWAY_DATABASE_MIN_IDLE_CONNS` 固定为 `4`，仅预热必要连接，
`AI_GATEWAY_DATABASE_MAX_CONN_IDLE_SECONDS` 为 `30`，突发扩出的空闲连接会及时回落，
避免四个 Pod 在空闲时占满 PostgreSQL 连接预算。各档表中的数据库池仍表示单个 Worker
的最大连接数。中止旧 Run 后还必须等待未提交任务、退款 outbox、回调 outbox 和连接高水位
全部收敛，才会启动下一轮负载。

每档依次运行全部五个模拟 Profile 三次。失败时恢复上一个已完整通过的档位，流量保持
`validation`，不会自动放开正式请求。

常规容量调整只需修改服务器上的私有发布配置并执行获授权的滚动命令，不要求源码改动或
新镜像：

```bash
easyai-ai-gateway-cluster-release capacity
```

## 执行

先把主验收用户、钱包、API Key、管理员 Token、两个 Gateway 地址和三张真实小流量参考图
写入本地私有环境。脚本会以主身份为模板幂等准备其余隔离身份；模型变量可省略，脚本会从
当前启用候选中选择 Gemini 图片编辑模型，以及 `omni_video.max_images >= 9` 的视频模型
并优先 Seedance 2.0/fast。

为保证请求严格各有一半命中宁波和香港，可以把两个 Gateway URL 配成两个节点的
`https://<节点IP>`，并设置
`AI_GATEWAY_ACCEPTANCE_GATEWAY_TLS_SERVER_NAME=ai.51easyai.com`。压测器会继续校验正式
证书，并同时使用该域名作为 HTTP Host；这不是跳过 TLS 校验。

```bash
scripts/cluster/run-production-acceptance.sh \
  --execute dist/releases/<完整SHA>.json \
  --local-report dist/acceptance/local/<本地run-id>/acceptance-report.json
```

脚本依次执行：

1. 校验工作区、manifest、线上 release SHA、digest、双站点镜像和副本数。
2. 准备专属验收用户组、身份分片、独立钱包与候选访问规则。
3. 部署 digest 固定且仅集群内可访问的协议模拟器。
4. 创建 Run、切换 `validation`、等待已有正式任务排空。
5. 按 P24、P28、P32 执行三轮负载和 Worker 强杀。
6. 执行两条真实小流量请求。
7. 校验任务、账务、回调、队列、连接池、RSS、节点内存、数据库同步、六向 WireGuard 和
   公网健康。
8. 完成 Run 并输出 `acceptance-report/v1`，保持 `validation`，等待人工确认。

报告保存在 `dist/acceptance/<run-id>/`，包含每阶段吞吐、p50/p95/p99、队列和资源 CSV、
协议模拟器统计及最终摘要。报告不包含图片原文、Base64、Token、密码或连接串。

确认整体报告后单独执行人工开闸：

```bash
scripts/cluster/run-production-acceptance.sh \
  --promote dist/releases/<完整SHA>.json \
  --run-id <线上run-id>
```

`--promote` 会重新读取 Run、流量 revision、release SHA、镜像 digest、配置哈希及暂存容量
配置，任一 CAS 不一致即拒绝。成功切到 `live` 后同一命令继续运行有限期监控：前 2 小时
每 10 秒采样，之后每 60 秒采样至 24 小时，不恢复已经停用的周期性巡检。队列、RSS、
连接池、同步复制、租约、重复结算、WireGuard 或公网健康触发硬门禁时，监控通过 CAS
自动切回 `validation`，并恢复 Run 开始前保存的精确容量快照；不会自动回滚数据库。

## 通过和失败处理

通过要求包括：任务全部成功、Gemini 输出可解码且哈希/字节数正确、视频参考图校验全部
通过、无重复任务/上游提交/结算/回调、队列归零、Worker 心跳小于 30 秒、单实例分配不
超过当前档位、无 OOM/重启、Pod RSS 小于 1.5 GiB、节点内存低于 80%、PostgreSQL 连接
低于 75%、连接池不连续满载、双副本同步、六向链路和公网健康通过。

失败 Run 会记录原因、恢复上一稳定容量并保持 `validation`。成功 Run 也保持
`validation`，只有上述人工 `--promote` 才能开闸。排除失败问题后调用 Run 的
`retry` 接口重新执行。只有人工决定放弃验收时，才使用带相同 CAS 字段的 `abort` 接口
恢复 `live`；该操作不会把失败验收标记为通过。
