Files
Jyotisha/docs/tasks/TASK-beta-capacity-20261014.md
T

93 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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. 其余(排队长度与等待时间、每码点数、首批人数)按任务书建议默认值执行,产品后续可改。