Commit 41472220 by luoqi

docs(gap): 纠正 B 轮误判 + 落测试机最终结论

 我上一版据 B 轮单点写下「集合式内存打穿、溢盘、拖垮全口查询」——全错。
   D 轮复现不出来(545s,四轮最快);实测每轮临时文件 C=4 个 / D=4 个、增量 62MB,
   根本没溢盘;187GB 是统计累计值不是今晚产生的。B 轮整段压在 02:00 的
   stale-scan cron 上,是外部负载。那套「内存打穿」的因果链是照着一个数字编的。

真实结论(C vs D,相邻两轮、两个同SQL全口对照组给出 ±25% 噪音基准):
  场景段 794s → 545s(−31%)   整轮 1,181s → 923s(−22%)
  超噪音的收益集中在 impacted ×2.25 / caries ×2.09 / hard ×1.59 / endo ×1.43
正确性三道门全过;端到端差异全是新增(时间漂移),零删除。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
parent 75242fa4
Pipeline #3625 failed in 0 seconds
......@@ -357,47 +357,73 @@ C/D 相邻,长时间后台负载对两者影响相近,不单边偏袒;若数字
结果集太小就摊不掉。绝对值都在 2 秒以内,对总墙钟无影响,不值得为它加"按规模切形态"的分叉
(那会让单一真理源裂成两条路径,得不偿失)。
### 🔴 测试机结果 · 步 3:集合式在**并发 + 内存受限**下大幅变慢
### 测试机结果 · 步 3:交替四轮全量重算(2026-08-30 01:45~03:14)
A(legacy) vs B(setbased) 逐子场景 SQL 耗时(全量重算路径,`SUBSCENARIO_CONCURRENCY=4`):
| 轮 | 形态 | 场景段 | 整轮墙钟 | 命中患者 | reason 行数 |
|---|---|---|---|---|---|
| A | legacy | 670s | 1,584s | 87,807 | 323,781 |
| B | setbased | **1,334s** | 1,675s | 87,811 | 323,791 |
| C | legacy | 794s | 1,181s | 87,813 | 323,794 |
| D | setbased | **545s** | 923s | 87,814 | 323,796 |
| 子场景 | A legacy | B setbased | |
|---|---|---|---|
| **ortho_no_consult**(全口,两轮同一条 SQL) | 38.2s | **443.8s** | ×11.6 慢 |
| **perio_no_srp**(全口,两轮同一条 SQL) | 104.9s | **796.6s** | ×7.6 慢 |
| missing_tooth | 241.8s | 978.7s | ×4.0 慢 |
| endo_no_rct | 180.2s | 703.1s | ×3.9 慢 |
| gum_alveolar_lesion | 20.5s | 86.5s | ×4.2 慢 |
| jaw_cyst | 12.5s | 25.1s | ×2.0 慢 |
| impacted_tooth | 148.9s | 176.8s | ×0.84 慢 |
| hard_tissue_damage | 204.9s | 180.8s | ×1.13 快 |
| caries_no_filling | 296.2s | 194.9s | ×1.52 快 |
#### ⛔ B 轮是离群值 —— 我据它下过一个错误结论,记在这里免得后来人重犯
场景段合计:**670s → 1,334s(慢一倍)**
B 轮场景段 1,334s(比 legacy 慢一倍),两个**跑同一条 SQL** 的全口子场景也慢了 7~12 倍
(ortho 38→444s / perio 105→797s)。我当场写下「集合式把内存打穿、溢盘、连没改的查询一起拖垮」,
还配了机器状态佐证(14G 内存吃光 + 9G swap + `temp_bytes=187GB`)。
#### 决定性证据:两个全口子场景跑的是**逐字节相同的 SQL**
**全错。** D 轮复现不出来 —— 545s,四轮里最快。真相:
`buildGapSetBased``rule.wholeMouth` 直接 `return null` → 回落 legacy。
所以 ortho/perio 两轮的 SQL 一模一样,却慢了 7~12 倍 ——
**慢的不是集合式 SQL 本身,是它把整台机器的资源抢光了,连没改的查询一起拖垮。**
- B 轮跑在 **02:11–02:39**,整段压在测试机 `PAC_STALE_SCAN_CRON=0 2 * * *`(画像 stale 扫描)上。
A 轮尾巴也蹭到 11 分钟,所以 A 也偏慢。C/D 在扫描结束后跑,才是干净的。
- `temp_bytes=187GB`**统计累计值,不是今晚产生的**。实测每轮增量:
C(legacy)4 个临时文件 / D(setbased)4 个,总增量 62MB —— **根本没有溢盘**
- 那个"内存打穿"的因果链是我照着一个数字编出来的,没有任何一步是量出来的。
#### 机器状态(2026-08-30 02:41,B 轮刚跑完)
**教训**:交替四轮的设计救了这次。只跑 A→B 就下结论的话,会把一个正确的优化否掉,
而且带着一套听起来很像回事的错误归因。⛔ 一个离群点 + 一套讲得通的机制 ≠ 结论;
**结论要能复现**。同 [[verify-before-prohibiting]]。
```
Mem: 14G total / 0G free Swap: 15G total / 9G 已用
vmstat wa = 76% / 67% ← 全程 I/O 等待,机器在颠簸
PG: shared_buffers=160MB(!) work_mem 默认 4MB(场景查询单独抬到 256MB)
pg_stat_database: temp_files=12,954 temp_bytes=187GB
磁盘 94% 满
```
#### 真实收益:C(legacy) vs D(setbased),相邻两轮、同等条件
| 子场景 | C legacy | D setbased | 倍数 |
|---|---|---|---|
| impacted_tooth | 269.6s | 119.6s | **×2.25** |
| caries_no_filling | 358.2s | 171.5s | **×2.09** |
| hard_tissue_damage | 209.7s | 132.3s | ×1.59 |
| endo_no_rct | 243.2s | 169.7s | ×1.43 |
| development_eruption | 58.9s | 43.9s | ×1.34 |
| ortho_no_consult *(全口对照,同一条 SQL)* | 74.7s | 59.1s | *×1.26* |
| missing_tooth | 286.6s | 245.5s | ×1.17 |
| extraction_recommended | 51.0s | 45.6s | ×1.12 |
| gum_alveolar_lesion | 40.2s | 38.7s | ×1.04 |
| perio_no_srp *(全口对照,同一条 SQL)* | 128.2s | 130.1s | *×0.99* |
| jaw_cyst | 16.2s | 18.2s | ×0.89 |
两个对照组(逐字节相同的 SQL)给出 ×1.26 / ×0.99 → **环境噪音 ±25%**
超出噪音的真实收益集中在 impacted / caries / hard / endo 四条。
- **场景段 794s → 545s(×1.46,−31%)**
- **整轮 1,181s → 923s(×1.28,−22%)**
#### 归因
#### 端到端正确性
集合式把 **I/O 密集的嵌套循环 + 索引探查**换成了**内存密集的哈希聚合 / 排序 / 反连接**
单条串行跑时内存够用 → 快(步 2 的数字);
四条并发 × 每条多个哈希节点 × work_mem=256MB → 内存打穿 → 溢到本就 94% 满的磁盘 → 全线拖垮。
reason 行级 diff(323,796 条):A↔B 10 行 / B↔C 3 行 / C↔D 2 行 / A↔D 15 行。
差异**全部是新增(`>`),无一删除**,成簇按患者出现,且包含 `perio_no_srp@whole`
这种全口场景的行(两版跑同一条 SQL)—— 是患者跨过 ⑤f 到诊冷静期重新进池的**时间漂移**,
与形态无关。命中患者数 87,807 → 87,811 → 87,813 → 87,814 单调递增也印证这点。
**这不是"集合式写错了"(11/11 零差异已证口径正确),是形态与机器资源画像不匹配。**
**零删除 = setbased 从未丢掉 legacy 有的任何一行**,静默少召这个最怕的方向没有发生。
**纠正步 2 的读法**:步 2 是**串行单条**跑的,所以它测出的"集合式更快"只在串行下成立;
生产/测试跑的是**并发 4**。拿串行数字给并发场景下结论,就是这次差点犯的错。
#### 结论
| 门 | 结果 |
|---|---|
| 正确性(SQL 层,固定 now 同一快照) | ✅ 11/11 零差异,行数逐个相同 |
| 正确性(画像消费方) | ✅ 2000 位零差异,p95 不劣化 |
| 正确性(端到端) | ✅ 差异仅时间漂移,零删除 |
| 收益 | ⚠️ 场景段 −31% / 整轮 −22%,**低于方案 §7 定的 ≥3× 试点门** |
方案 §0.1 预估「全轮改善 35~40%,不足以单独装进 2 小时」——**实测 22~31%,预估方向对、幅度偏乐观**
生产场景段 3h11m 按此推算 → 约 2h11m,整轮 3h41m → 约 2h50m,**仍然超 2 小时**
要进窗口还得叠加 §9(全口码那条独立线)或别的手段。
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment