Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HwMNZEzdzmrbPhuRNmZe3C
93 lines
10 KiB
Markdown
93 lines
10 KiB
Markdown
# TASK · 内测容量:按用户限流、重计算满时短排队、10–20 并发冒烟压测(2026-10-14)
|
||
|
||
- 日期:2026-10-14
|
||
- 来源:产品 10-14 拍板「内测跑生产」;内测体检(运维一路,`scratchpad/beta-ops-20261014.md`):重计算并发 2、满了立即 429;对话与校正没有按用户限流;仓库里没有任何并发压测结果。
|
||
- 执行方:**Grok(Windows + Docker)**,分支 `codex/beta-capacity-20261014`,只推这个分支
|
||
- 验收:Claude(Linux)
|
||
- BUG 编号:开工时核对 `docs/BUG_HISTORY.md` 最大号顺延(写作时最大号 BUG-1317;#56 sync7 从 1318 起占号,#58 已预留 BUG-1328,开工时以实际最大号为准)
|
||
- **串行**:本单 T1 改 `frontend/src/lib/request-rate-limit.ts`,#58(`TASK-node-runtime-resources-20261013`)也改这个文件 → **#58 合入 staging 后再开 T1/T2**。T4 压测基线要在 #57(`TASK-rect-scoring-perf-20261013`,校正打分提速)合入后再量,否则测的是旧的打分耗时。T0 可以先做。
|
||
|
||
## 1. 基线
|
||
|
||
- 基线 commit:`origin/staging` `48a2fadd`(开工时以最新 staging 为准,进度记录写明)。
|
||
- staging 主机 `118.26.111.127`,生产 `118.194.235.34`(2 vCPU / 4 GB,`deploy/README.md`);**T0 先核对 staging 规格是否与生产相同**,不同时压测结论要按比例说明,不得直接套到生产。
|
||
|
||
## 2. 事故实证(行号按符号定位)
|
||
|
||
| 位置 | 现状 |
|
||
| --- | --- |
|
||
| `scripts/api_heavy_compute_gate.py` | 进程级 `BoundedSemaphore`,`DEFAULT_CONCURRENCY = 2`,`HeavyComputeBusy` 抛出后服务器立即回 `429 ERR_COMPUTE_BUSY` + `Retry-After: 2`,**不排队**。`HEAVY_COMPUTE_PATHS` 含 `/api/consultation_workflow`(普通对话每轮取证)和全部校正打分端点:**对话与校正抢同两个位** |
|
||
| `scripts/jyotish_api_server.py` POST 分发 | `acquire_heavy_compute_slot(path)`;只有 `consult_card_warm_only` 预热不占位 |
|
||
| `frontend/src/mastra/consultation-workflow.ts` `httpFailureCode` | 429 → `workflow_rate_limited`,没有按 `Retry-After` 重试,这一轮对话直接失败 |
|
||
| `frontend/src/lib/rectification-agentic/v9/engine-client.ts` | 校正已按 `busy` 读 `Retry-After` 退避(BUG-723),不在本单改动范围 |
|
||
| `frontend/src/lib/request-rate-limit.ts` `requestRateLimits` | 只有 locationSearch / locationTimezone / dailyStarlanguage / ephemeris / synastry / sessionWrite 六个桶;**`/api/consult`、校正回合、报告生成都没有按用户限流**(报告只有每日上限 `PERSONAL_REPORT_DAILY_LIMIT`,默认 5) |
|
||
| 压测 | 仓库只有 6 月的压测计划与 9 月单会话容量 / 首屏性能记录,没有并发结果 |
|
||
|
||
推算:内测 20–50 人,只要同一时刻有 3 个以上请求在跑取证或校正打分(单次 2–5 s,#57 之后目标 < 2 s),第三个人的对话就失败。
|
||
|
||
## 3. 根因
|
||
|
||
重计算闸当初是为防止 2 vCPU 被校正扫描打满而设的「宁可拒绝」策略(见文件头注释),后来普通对话的取证也进了同一个闸,但对话侧没有任何等待或重试;按用户限流只给了轻量接口。
|
||
|
||
## 4. 决策记录(产品 2026-10-14)
|
||
|
||
1. 内测跑生产(产品 10-14)。容量问题必须在内测前处理,不能只靠「人少」。
|
||
2. 重计算并发上限**保持 2**(主机 2 vCPU,不放大);改为「满了先等一小会儿」,等不到再明确告诉用户稍后再试。
|
||
3. 对话、校正回合、报告生成加按用户限流;阈值见 §6 建议值,**产品可改**,写进 `requestRateLimits` 常量,环境变量不新增(减少配置面)。
|
||
4. 内测发点数用兑换码(`redemption_codes`,一码一次)当邀请码;内测期间不开支付(`EPAY_CHAT_ENABLED=false` 保持)。每码点数由产品定(§10)。
|
||
5. 压测在 staging 做,不压生产;登录用 staging 测试邮箱 + 固定验证码(产品在会话里提供),脚本从环境变量 `STAGING_TEST_OTP` 读,**不得写进任何文件、日志或提交**。
|
||
|
||
## 5. 硬红线
|
||
|
||
1. 不改生时校正打分、`decision_policy`、闸门与题库;不改校正侧 BUG-723 的退避逻辑(服务端排队后它自然少碰 429)。
|
||
2. `scripts/jyotish_api_server.py` 不得增长(`tests/test_api_server_growth_contract.py`):排队逻辑写在 `scripts/api_heavy_compute_gate.py` 内,主文件只允许改调用处一两行。
|
||
3. 排队有上界:等待者数量与等待时长都有上限;等不到仍回 429 + `Retry-After`,**不得无限挂起请求线程**。健康检查与轻量路由不得进闸(文件头约定保持)。
|
||
4. 限流放行 / 拒绝语义与 `retryAfterSeconds` 计算沿用 `consumeRequestRateLimit`;不改 #58 加的过期清理。
|
||
5. 前端红线(AGENTS §7):tsc 0、lint 0 error、npm test 失败名单与基线逐名相同、build `/` Static、首屏 gzip ±2%;新文案对照 `frontend/docs/VOICE.md`,不写「系统繁忙」「请稍后重试」这类客服腔,参照现有「这边没接上……」口吻。
|
||
6. 压测不得读写真实用户数据;只用测试账号;压测结束清理测试账号会话(或写明保留原因);模型调用花费写进进度记录。
|
||
7. 不改数据库结构(本单不需要迁移);不改 workflow;不推 staging。
|
||
|
||
## 6. 任务分解
|
||
|
||
| 项 | 内容 | 验收 |
|
||
| --- | --- | --- |
|
||
| T0 | 核对 staging 与生产主机规格(vCPU、内存、`JYOTISH_HEAVY_COMPUTE_CONCURRENCY` 实际值);在 staging 用现有代码跑一次 T4 的压测作为**改前基线**(若 #57 未合入,记清楚) | 进度记录有规格对照与改前数字 |
|
||
| T1 | 按用户限流:`requestRateLimits` 新增 `consult`(**10 次 / 分钟、120 次 / 小时**两档,用两个桶)、`rectificationTurn`(**5 次 / 分钟、40 次 / 小时**两档)、`reportGenerate`(建议 3 次 / 小时,与每日 5 份并存)。接入 `/api/consult`、校正回合路由(`frontend/src/app/api/rectification/agent/route.ts`,开案 `cases/open` 不计入)、报告生成 `frontend/src/app/api/reports/route.ts` POST。超限回 429 + `code: "rate_limited"` + `Retry-After`,**在扣点 / 预留之前**判断,不扣点 | 单元测试:各桶边界放行 / 拒绝、拒绝时不调用扣点 RPC;路由测试断言 429 体与头 |
|
||
| T2 | 重计算闸短排队:`acquire_heavy_compute_slot` 改为带超时等待(建议最多等 8 s,等待者上限 = 4),超时或等待者满仍抛 `HeavyComputeBusy`;`Retry-After` 不变。前端 `/api/consult` 对 `workflow_rate_limited` 按 `Retry-After` 最多重试 2 次、总等待 ≤ 15 s,期间进度行显示一句「人有点多,排一下队」(文案进 VOICE);仍失败时用一句话说明并退回本轮预留点数(沿用现有失败结算路径,核实不重复退款) | Python 测试:并发 3 个请求,第 3 个在前一个释放后拿到位;等待者满时立刻 429;超时 429。前端测试:429 → 重试 → 成功;三次都 429 → 失败文案 + 退点(复用现有结算断言) |
|
||
| T3 | 内测点数上限:核实兑换码表能否设单码点数与批量生成(后台「兑换码」页),写一页操作说明 `docs/operations/beta-invite-codes.md`:怎么批量生成邀请码、每码点数、怎么查谁用了、怎么作废。不写真实码 | 文档;若后台缺批量生成或作废能力,写进进度记录与 BLOCKED,不在本单扩功能 |
|
||
| T4 | 冒烟压测脚本(放 `frontend/scripts/research/beta-load-smoke.mts`,不进镜像,确认不被 `frontend/src` 引用——参照 BUG-1140 / `runtime-sources-no-test-imports.test.ts`):N 个虚拟用户(10、20 两档),每人:OTP 登录 → 首轮咨询 → 一次追问;其中 3 人改为开一次校正并答 2 题。记录:每请求状态码、首字时间、整轮完成时间、429 比例;同时在服务器侧采样 API / web 容器内存与 CPU(`docker stats` 每 5 s,执行方没有 staging SSH 时写成环境缺口,只报客户端侧) | 进度记录给改前 / 改后两组数字。**通过线**(10 并发):429(最终失败,不含重试成功)≤ 2%;5xx = 0;咨询首字 P95 ≤ 15 s、整轮 P95 ≤ 90 s;API 容器内存峰值 < 1.5 GB。20 并发只记录不设门,用来定内测同时在线上限 |
|
||
| T5 | 文档:BUG_HISTORY(每项一个号)、CHANGELOG、VOICE(排队与限流文案)、DESIGN(进度行状态)、`docs/testing/beta-capacity-20261014.md` 真机两条(快速连发触发限流的提示;排队提示出现与消失) | 齐 |
|
||
|
||
## 7. 让步顺序
|
||
|
||
T2 后端排队不可让;T1 不可让(防刷与成本)。T2 前端重试文案可简化为不显示「排队」而只多等。T4 20 并发档可让;10 并发档不可让(没有数就不能拉人)。T3 文档可让为「后台已有能力清单」。
|
||
|
||
## 8. 验收门
|
||
|
||
- Python:`ruff`、`py_compile`、新测试、`tests/test_api_server_growth_contract.py`、快速门全量(`failures []` 或与基线同名)。
|
||
- 前端:tsc / lint / npm test(与基线逐名比对,看 `# cancelled` 与退出码)/ build Static / gzip。
|
||
- 压测:脚本可在 Claude 侧(Linux)复跑 10 并发一次,数字量级一致。
|
||
|
||
## 9. 开工前置
|
||
|
||
```
|
||
git fetch origin --prune
|
||
git status -sb
|
||
git worktree add -b codex/beta-capacity-20261014 .worktrees/beta-capacity-20261014 origin/staging
|
||
# 确认 #58 已合入 staging(request-rate-limit.ts 有过期清理)再开 T1/T2;T0 可先做
|
||
```
|
||
|
||
## 10. 需产品提供 / 拍板
|
||
|
||
1. 限流阈值:对话 6 次 / 分钟 + 60 次 / 小时、校正 10 次 / 分钟、报告 3 次 / 小时(+ 每日 5 份)——同意或改数。
|
||
2. 排队上限:最多等 8 s、最多 4 人排队——同意或改。
|
||
3. 每个邀请码给多少点;内测第一批多少人(压测 20 并发的结果出来后再定同时在线上限)。
|
||
4. 压测会花模型调用费用(约 20 人 × 2 轮 × 两次),同意在 staging 跑。
|
||
5. 若 Grok 侧拿不到 staging 主机 SSH,服务器内存 / CPU 采样由谁跑(Claude 也没有主机权限时写成环境缺口)。
|
||
|
||
## 11. 产品答复(2026-10-14,覆盖上文对应建议值)
|
||
|
||
1. 限流:产品要「对话多一些、校正少一些」→ 对话 10 次 / 分钟 + 120 次 / 小时;校正回合 5 次 / 分钟 + 40 次 / 小时;报告 3 次 / 小时(+ 每日 5 份)不变。T1 表格已按此改。
|
||
2. 压测花模型费:**同意**(staging 10 / 20 并发各一轮,按 T4)。
|
||
3. 其余(排队长度与等待时间、每码点数、首批人数)按任务书建议默认值执行,产品后续可改。
|