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

63 lines
5.5 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**(洋葱序 Telemetry→Cache→…→Retry 内限流,命中行 latency=0 从未过闸) | RPM 口径排除 cache_hit 行 |
修复后离线重裁(`--rescore`,不产流量): **RPM PASS**。精确说明(独立 verifier 复核): 剔除缓存行后仍有 **2 个窗口 6 行**,各含 1 行距右边界 0.2-0.7s——落在 created_at 秒级截断噪声的合理方向(截断只低估准入时刻,行只可能实属右邻窗),由**分侧贴边豁免**判为非击穿;并非"剔除后全部 ≤5"。verifier 另证明偏移敏感性: 就本数据集,offset=0 与 +31s 裁决相同(口径同源化仍是原则正确)。豁免逻辑经 verifier 对抗样例加固: 分侧(左贴边只可借左邻余量)+ 邻窗余量全局一次性消耗,防漏报真实击穿。
## 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` 新增 9 用例(先红后绿,含 2 个 verifier 对抗样例),全套 CI 绿覆盖 90%。
- 重裁报告: `tests/outputs/soak/soak_20260721_050750.md`(全 PASS/SKIP)。
## 6. 经验沉淀
1. **不变量的口径必须与执法者同源**: 限流器用什么时钟、在哪个时刻、对哪些流量执法,记分板就必须用同样的三元组重算;任何一元错位都产生伪击穿。
2. 分布式系统验收前先测**时钟偏移**(本实验室 Redis 快 ~31s)。
3. `ru_maxrss` 不可用于"平稳性"断言;峰值与当前值是两个量。
4. "遥测必录"(含缓存命中)与"限流口径"(仅真实过闸流量)是两个集合,消费遥测时必须显式选择。