Files
PolyGateway/research-wiki/findings/2026-07-21-p6-soak-baseline.md
T

63 lines
4.8 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.
# P6 混合浸泡首跑基线与记分板三重伪击穿修复
- **日期**: 2026-07-21;**run_id**: `soak_20260721_050750`
- **结论**: 8000 次调用全部完成,库自身零缺陷;记分板两项 FAIL 均为**测量口径缺陷**(三重伪击穿),修复后离线重裁全部 PASS。修复分支 `fix/soak-scoreboard-clock`
## 1. 运行参数与规模
| 项 | 值 |
|---|---|
| 场景 | P6 混合(P1 .10 / P2 .20 / P3 .35 / P4 .15 / P5 .20),SOAK 五源故障池 |
| 预算/实际 | 8000 次(签字帽)/ 8000 次全部完成;token 1401 万(硬顶 2 亿) |
| 拓扑 | 2 worker × 32 并发 = 64 在途;限流/熔断/缓存全 Redis(db3);Postgres 遥测 |
| 用时 | 05:0706:54,约 1h47m(3h 上限内) |
| 成本 | **23.42 元**(价格表首次生效: 输入 2.1/输出 8.4 元/M tok;缓存命中 token=0 天然零成本) |
## 2. 结果基线(P6 首跑,后续回归对照)
| 指标 | 值 | 说明 |
|---|---|---|
| 总成功率 | 4648/8000 = **58.1%** | 五源池 3 坏 1 紧闸;窗口成功率从开局 40% 爬至尾段 73%(熔断学习 + 缓存变热) |
| 缓存命中 | 2373 次 | P4 复读流量生效,命中秒回零成本 |
| 延迟(成功行) | 总时长 p50/p95 = 6.8s/48.1s;TTFT p50/p95 = 22.0s/53.2s | p50 远低于 P3 纯健康池(16.9s)系缓存命中行拉低;非缓存行与 P3 同量级 |
| P3 结构化成功率(P6 内嵌) | 0.218 | 故障池 + 网关 429 高压下的口径,**不可**与纯健康池基线 0.942 直接对比 |
| RSS | 实时 ps 采样: 10min 119MB → 40min 起稳定 36MB 至结束 | 无泄漏;峰值系 harness 自身无界入队(见 §4) |
错误分布大头(共 20146 遥测行): 网关侧 429 限速 4845(minimax_5 3773 + minimax_1 996 + minimax_4 76)、紧看门狗掐流 3622(inter_token 3390 + ttft 232)、真实空补全 2211、retry_exhausted 终态 3318、黑洞超时 230、坏 key 31。
**治理行为实证**: 黑洞源走完 攒失败→开路→仅探针漏过 全周期(全程 230 次超时,开路后每 5 分钟仅 1 次探针);坏 key 源 force_open 后基本从遥测消失;跑后记账归零、探针不悬挂、call_id 唯一全 PASS。
## 3. 三重伪击穿: RPM "FAIL" 根因链
记分板判 `minimax_4`(RPM=5)单分钟 6-10 次。逐层排查(每层修完仍 FAIL 才发现下一层):
| # | 口径缺陷 | 证据 | 修复 |
|---|---|---|---|
| 1 | 用**完成时刻**(created_at=落库时刻)聚桶,调用延迟达分钟级,多个准入窗口的调用挤进同一完成分钟 | 准入时刻 = created_at latency 重算后分布改变 | 按准入时刻聚桶 |
| 2 | 用**本机钟**聚桶,而限流器窗口 id 来自 **Redis 服务器钟**(`limiter.py:_window_id`,CHS 移植的多进程口径统一设计) | 实测 Redis 钟偏移 **+30.98s**;密集分钟 10 次准入在第 ~29 秒恰可切 5+5 | 记分时实测偏移并校正;秒级截断噪声给 ±2s 贴边豁免(邻窗有余量才可豁免) |
| 3 | **缓存命中行计入 RPM**——缓存在限流闸之前返回,未耗名额未打网关,但"遥测必录"使其带原源名落库 | minimax_4 共 579 行中 **347 行 cache_hit=1**;剔除后所有窗口 ≤5 | RPM 口径排除 cache_hit 行 |
修复后离线重裁(`--rescore`,不产流量): **RPM PASS**
## 4. RSS "FAIL" 根因: 峰值口径 + 无界入队
- `_rss_mb``ru_maxrss`——**历史峰值只增不减**;判"增长 782MB"实为开局峰值。
- 峰值来源是 harness 自身: 分发循环 15 秒内把 4000 任务/worker(含 P2 ~313KB 图负载)全量 `create_task`,信号量只限执行不限入队;顺带使 `--max-hours` 截止检查随分发结束而失效、执行期无进度打印。
- 库无泄漏: 运行期实时 ps 采样 36MB 稳定 90 分钟(本 finding §2)。
**修复**: ① `_rss_mb``ps -o rss=` 当前值;② 分发改 `_paced_dispatch` 有界模式(先占并发名额再建任务),截止检查全程有效、内存峰值受限、进度行全程可见;③ 旧格式结果(仅峰值序列)重裁时判 SKIP 不判 FAIL。
## 5. 交付物
- `tools/soak/scoreboard.py`: `inv_rpm_never_exceeded` 新口径(准入时刻 + 服务器钟偏移 + 贴边豁免 + 排除缓存行)。
- `tools/soak/run_soak.py`: `_rss_mb` 当前值、`_paced_dispatch` 有界分发、`_redis_clock_offset_s``--rescore RUN_ID` 离线重裁模式。
- 测试: `tests/unit/test_soak_corpus.py` 新增 6 用例(先红后绿),全套 356 过 0 败覆盖 90%。
- 重裁报告: `tests/outputs/soak/soak_20260721_050750.md`(全 PASS/SKIP)。
## 6. 经验沉淀
1. **不变量的口径必须与执法者同源**: 限流器用什么时钟、在哪个时刻、对哪些流量执法,记分板就必须用同样的三元组重算;任何一元错位都产生伪击穿。
2. 分布式系统验收前先测**时钟偏移**(本实验室 Redis 快 ~31s)。
3. `ru_maxrss` 不可用于"平稳性"断言;峰值与当前值是两个量。
4. "遥测必录"(含缓存命中)与"限流口径"(仅真实过闸流量)是两个集合,消费遥测时必须显式选择。