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.
报价提速与 API 稳定性 — 改动清单
版本:2026-06-30
目标:端到端 15s 内 P50(Direct 成功路径)、并发不互相挡死、失败快速返回(禁 Widget 回退)。
基准:当前 probe:human-doc-system 10/10 约 2.4min(~14s/单),候选 5~15s + 报价 5~20s + 队列/轮询开销。
总览(按优先级)
| 批次 |
项数 |
预期收益 |
风险 |
建议工期 |
| P0 运维 |
5 |
稳定性 + 避免假慢 |
低 |
0.5 天 |
| P1 小改 |
4 |
候选 -3~8s,报价 -2~5s |
低 |
1~2 天 |
| P2 架构 |
2 |
整单 -3~8s(去队列+轮询) |
中 |
2~3 天 |
| P3 隔离 |
2 |
并发稳定性 |
中 |
1~2 天 |
推荐实施顺序: 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 被 502,420s 内正常返回 |
| P0-3 |
storageState 预热的 |
worker 启动日志无 bootstrap 失败;.rpa/mothership-storage.json 存在且 rpa_state 卷挂载 |
冷启动首单不额外 +30~60s |
| 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,75~79 行又对同一 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 |
| 预期 |
-1~3s/单 |
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 |
| 预期 |
候选 -3~8s(候选多时明显) |
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 路径用 500ms;embed 前端可保持 2s 减 DB 压力 |
| 测试 |
__tests__/modules/host/wait-for-quote.test.ts mock 时间 |
| 验收 |
submit 在 worker 完成后 <1s 内返回 |
| 预期 |
-0.5~1.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,仅缓存成功结果 |
| 测试 |
集成测试:第二次同址命中缓存 |
| 验收 |
重复候选 <500ms;TTL 后失效 |
| 预期 |
重复查询 -5~12s |
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/L3、metrics、幂等一致 |
| 测试 |
单测 mock axel;probe:human-doc-system;并发 2 单不串单 |
| 验收 |
日志 inline-direct 成功;无 queue 等待;10/10 PASS |
| 预期 |
整单 -3~8s(去队列 + 部分轮询) |
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 再 -1~2s |
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 -10~30s |
观测与门禁(每批完成后必跑)
# 功能回归
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 |
5~15s |
3~8s |
3~8s |
| 报价(含队列)P50 |
10~25s |
8~18s |
5~12s |
| 方式 A submit P50 |
15~30s |
12~22s |
8~15s |
probe:human-doc-system |
10/10 ~144s |
10/10 ≤120s |
10/10 ≤90s |
不建议做
| 项 |
原因 |
| 生产开启 Widget fallback |
6~12min/单,堵死 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。