You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
chajia/docs/报价提速与稳定性-改动清单.md

187 lines
8.6 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 报价提速与 API 稳定性 — 改动清单
> 版本2026-06-30
> 目标:端到端 **15s 内 P50**Direct 成功路径)、**并发不互相挡死**、**失败快速返回**(禁 Widget 回退)。
> 基准:当前 `probe:human-doc-system` 10/10 约 2.4min~14s/单),候选 515s + 报价 520s + 队列/轮询开销。
---
## 总览(按优先级)
| 批次 | 项数 | 预期收益 | 风险 | 建议工期 |
|------|------|----------|------|----------|
| **P0 运维** | 5 | 稳定性 + 避免假慢 | 低 | 0.5 天 |
| **P1 小改** | 4 | 候选 -38s报价 -25s | 低 | 12 天 |
| **P2 架构** | 2 | 整单 -38s去队列+轮询) | 中 | 23 天 |
| **P3 隔离** | 2 | 并发稳定性 | 中 | 12 天 |
**推荐实施顺序:** P0 → P1-1 → P1-2 → P1-3 → P1-4 → P2-1 → P2-2 → P3按需
---
## P0 — 运维与配置(不改代码)
| ID | 改动 | 操作 | 验收 |
|----|------|------|------|
| **P0-1** | 生产 Direct + 禁 Widget | `.env``RPA_QUOTE_MODE=direct`、`RPA_DISABLE_WIDGET_QUOTE_FALLBACK=true` | worker 日志出现 `node-direct 成功`**无** `Widget fallback` |
| **P0-2** | Nginx 超时对齐 | `proxy_read_timeout 420s`(方式 A submit | submit 不在 60s 被 502420s 内正常返回 |
| **P0-3** | storageState 预热的 | worker 启动日志无 bootstrap 失败;`.rpa/mothership-storage.json` 存在且 `rpa_state` 卷挂载 | 冷启动首单不额外 +3060s |
| **P0-4** | orphan job 巡检 | 重启后看 `已回收 N 条 orphan`Bull Board `/admin/queues` 无长期 active | 连续 2 单报价第二单不无故 +30s |
| **P0-5** | 宿主传完整 placeId | 第二步候选对象含 `option_id`ChIJ… | worker 日志无重复 `searchLocation` |
---
## P1 — 小改快赢(低风险代码)
### P1-1 去掉报价重复 `fetchPlace`
| 项 | 内容 |
|----|------|
| **根因** | `resolveSide``fetchPlace`7579 行又对同一 placeId 再 fetch 一次 |
| **文件** | `lib/axel/quote-from-request.ts` |
| **改法** | `resolveSide` 返回 `{ placeId, description, place?: AxelPlaceLocation }`;已知 placeId 路径复用 place 对象search 路径只 fetch 一次 |
| **并行** | `pickupSide` / `deliverySide``Promise.all` |
| **测试** | 扩展 `__tests__/lib/axel/` 或 mock client断言 `fetchPlace` 每侧最多 1 次 |
| **验收** | 同址报价 worker 日志 place 请求减少;`probe:human-doc-system` 仍 10/10 |
| **预期** | **-13s/单** |
### P1-2 候选 `verifyCandidates` 只校验 Top-K
| 项 | 内容 |
|----|------|
| **根因** | 每条联想都 `fetchPlace`N 条候选 = N 次 HTTP |
| **文件** | `lib/axel/candidates.ts` |
| **改法** | 新增 `AXEL_CANDIDATE_VERIFY_TOP_K`(默认 **3**);仅前 K 条 `verifyCandidate`,其余 `selectable: true`(或 `unknown` 由前端展示「未校验」— 推荐默认 true 保持 UX |
| **测试** | `__tests__/lib/axel/candidates.test.ts`5 条候选 mock 断言 fetchPlace ≤ K |
| **验收** | candidates API P50 下降;仍至少 1 条 `selectable:true` |
| **预期** | **候选 -38s**(候选多时明显) |
### P1-3 宿主同步接口轮询间隔缩短
| 项 | 内容 |
|----|------|
| **根因** | `waitForQuoteDetail``POLL_INTERVAL_MS=2000`,完成后再等平均 +1s |
| **文件** | `modules/host/wait-for-quote.ts`;可选 `lib/frontend/constants.ts``HOST_POLL_INTERVAL_MS=500` |
| **改法** | 仅 host submit 路径用 500msembed 前端可保持 2s 减 DB 压力 |
| **测试** | `__tests__/modules/host/wait-for-quote.test.ts` mock 时间 |
| **验收** | submit 在 worker 完成后 **<1s** 内返回 |
| **预期** | **-0.51.5s/单**(方式 A |
### P1-4 候选结果缓存(短 TTL
| | 内容 |
|----|------|
| **根因** | 同街道重复联想仍打 MotherShip |
| **文件** | `modules/address/mothership-candidates.ts` `lib/axel/candidates.ts` + `modules/cache/redis-cache.ts` |
| **改法** | key `addr-cand:{hash(pickup+delivery)}`TTL **120s**,仅缓存成功结果 |
| **测试** | 集成测试:第二次同址命中缓存 |
| **验收** | 重复候选 <500msTTL 后失效 |
| **预期** | **重复查询 -512s** |
---
## P2 — 架构提速(中风险,收益最大)
### P2-1 API 层 Direct 同步报价(跳过 BullMQ 快乐路径)
| | 内容 |
|----|------|
| **根因** | L2 未命中必 `enqueueForRpa`node-direct 本可在 API 进程完成 |
| **文件** | `modules/quote/orchestrator.ts`(主)、`workers/rpa/job-handler.ts`(复用 `completeQuoteSuccess` 逻辑抽共享函数)、`lib/rpa/env.ts``isInlineDirectQuoteEnabled()` |
| **改法** | 1) 新增 `tryInlineDirectQuote(cargo)``fetchAxelQuoteItems` L2 + `quote_record.done`2) 成功则直接返回 `status:done`3) 失败且 `RPA_DISABLE_WIDGET_QUOTE_FALLBACK=true` `failed`4) 失败且允许 fallback 则入队 Worker |
| **开关** | `RPA_INLINE_DIRECT_QUOTE=true`(生产默认开,可关回滚) |
| **约束** | 仍计 1 MotherShip 报价;须写 L2/L3metrics、幂等一致 |
| **测试** | 单测 mock axel`probe:human-doc-system`;并发 2 单不串单 |
| **验收** | 日志 `inline-direct 成功`;无 queue 等待;10/10 PASS |
| **预期** | **整单 -38s**(去队列 + 部分轮询) |
### P2-2 方式 A submit 合并为「创建即等待」单事务感知
| | 内容 |
|----|------|
| **根因** | P2-1 host submit `submitQuote` 同步得到 `done`,无需 `waitForQuoteDetail` 长轮询 |
| **文件** | `modules/host/public-orchestrator.ts` |
| **改法** | `hostPublicSubmitAndWait`:若 `submitQuote` 返回 `done` 直接 `getQuoteDetail`;仅 `processing` 时轮询 |
| **测试** | host submit 单测 |
| **验收** | submit inline 成功时 **0 次** DB 轮询 |
| **预期** | 叠加 P2-1,方式 A **-12s** |
---
## P3 — 并发与稳定性隔离
### P3-1 Priority1 与 MotherShip 分队列
| | 内容 |
|----|------|
| **根因** | `priority1-quote` `quote-rpa` Worker、`concurrency=1`,实验挡生产 |
| **文件** | `lib/constants/rpa.ts`、`workers/rpa/index.ts`、`workers/rpa/queue.ts` |
| **改法** | 新队列 `priority1-rpa` + 可选独立 worker 容器;或 Priority1 job dev 环境入队 |
| **测试** | 入队 priority1 MotherShip quote 仍可并发消费(若 P3-2 |
| **验收** | 生产 compose `rpa-worker` 消费 `quote-rpa` |
| **预期** | **消除实验性挡队列** |
### P3-2 node-direct 专用 Worker 并发(可选)
| | 内容 |
|----|------|
| **根因** | `RPA_WORKER_CONCURRENCY=1` Playwright 设计;HTTP-only 可并行 |
| **文件** | `workers/rpa/job-handler.ts`、`workers/rpa/mothership.ts` |
| **改法** | 仅当 job `fetchAxelQuoteItems` 成功路径时无 session lock;浏览器 fallback concurrency=1或拆 `rpa-http-worker` 进程 concurrency=3 |
| **风险** | MotherShip 限流;storageState 并发读 |
| **验收** | 3 并发 HTTP 报价无串扰;失败率不升 |
| **预期** | **高峰 P95 -1030s** |
---
## 观测与门禁(每批完成后必跑)
```bash
# 功能回归
npm run test:unit
npm run probe:human-doc-system
# 耗时对比(改前改后各跑 1 次,记录总秒数)
npm run probe:human-doc-system
# 服务器
curl -w "candidates: %{time_total}s\n" -X POST .../api/host/quote/candidates ...
# worker 日志关键字
docker logs <rpa-worker> 2>&1 | grep -E "node-direct|inline-direct|Widget fallback"
```
| 指标 | 改前参考 | P1 目标 | P2 目标 |
|------|----------|---------|---------|
| 候选 API P50 | 515s | 38s | 38s |
| 报价(含队列)P50 | 1025s | 818s | **512s** |
| 方式 A submit P50 | 1530s | 1222s | **815s** |
| `probe:human-doc-system` | 10/10 ~144s | 10/10 120s | 10/10 90s |
---
## 不建议做
| | 原因 |
|----|------|
| 生产开启 Widget fallback | 612min/单,堵死 worker |
| 仅增大超时 | 不解决排队与冗余 HTTP |
| 盲目 `concurrency=4` 同进程 Playwright | session 争用、orphan job |
| 跳过 placeId 校验 | 报价 400 / 地址不可选返工更慢 |
---
## implementation-plan 对应 Step
| Step | 对应 ID | 状态 |
|------|---------|------|
| 12.1 | P1-1 去重 fetchPlace + 并行 resolve | 待执行 |
| 12.2 | P1-2 候选 Top-K verify | 待执行 |
| 12.3 | P1-3 host 轮询 500ms | 待执行 |
| 12.4 | P1-4 候选 Redis 短缓存 | 待执行 |
| 12.5 | P2-1 inline direct quote | 待执行 |
| 12.6 | P2-2 host submit 免轮询 | 待执行 |
| 12.7 | P3-1 Priority1 分队列 | 待执行 |
| 12.8 | P3-2 HTTP worker 并发(可选) | 待执行 |
详细执行记录在 `cursor_project_rules/implementation-plan.mdc` Phase 12