8.7 实战:三种“慢”只修真正瓶颈
三个 case 都制造“请求没有及时返回”,但修复方向互斥:
| case | 决定性证据 | 被排除的直接解释 | 正确方向 |
|---|---|---|---|
| estimate/plan | generic error 900x,custom 1x,无 Lock/ClientWrite | 当前锁链、慢客户端 | 参数/统计/计划与访问路径 |
| lock wait | active + Lock/transactionid,blocker=1 |
ClientWrite、正在推进的 plan | root blocker 与事务边界 |
| client slow consumer | active + Client/ClientWrite,blocker=0 |
数据库锁链 | 返回规模、客户端/网络/读取方式 |
实验不以单次 elapsed time 或节点名作为 golden。它保存机器可读 signal,再由一个不能读取答案文件的分类器只按证据关系判断。
8.7.1 估算错误、锁等待与客户端慢消费
确认 service 指向可写、可演练的 L1:
若 ch07 fixture 已通过,all 直接复用;若缺失,则调用 ch07 marker guard 做受控重建。之后顺序为:
一次实测输出:
case A:estimate/plan
estimate-case.sh 复用 ch07 tenant skew,用同一 SQL、同一 hot parameter 分别捕获:
本次机器上 generic 是 Index Scan、custom 是 Seq Scan,但分类器不以节点名判定;不同硬件、cost setting 或 PostgreSQL 版本可能选择不同 shape。稳定事实是 generic 用总体平均选择率严重低估 hot parameter,custom 能看见具体参数并改善估算。
这也不自动宣布永久修复应是 force_custom_plan。生产还要比较:
- hot/cold traffic weight;
- planning 与 execution 成本;
- prepared statement/driver/pool 行为;
- statistics、SQL/index 是否有更好解;
- 并发下延迟、blocks 与写副作用。
case B:lock wait
lock-case.sh 调用第 5 章的确定性编排:
中性 signals:
直接原因是锁等待,不是 waiter 的执行计划。长期修复应回到 blocker 所属事务:为何持锁、是否在事务内睡眠/调用外部服务、锁顺序是否一致、更新集合是否过宽。取消是止血且只允许精确命中实验身份。
case C:client slow consumer
client-write-lab.sh 用 generate_series 生成大结果,由 slow-reader.py 每次只读少量字节并等待。它不读业务表,也不创建对象。
observer 必须同时看到:
随后用 PID + backend_start epoch + database + unique application name 精确执行 pg_cancel_backend,等待 pipeline 退出并确认 worker=0。
这个 case 特意证明 planner 的 elapsed/cost 边界:server 可能很快产生数据,却因客户端不读而长时间不返回。修复应查返回规模、分页/流式协议、driver fetch、客户端线程与网络;取消任意 PID或加索引都没有解释证据。
8.7.2 随机隐藏一种根因,先独立诊断再揭晓
为避免“知道脚本名再写结论”,运行 seeded mystery:
PG36_CASE_SEED 经 SHA-256 后按三类取模。public 目录只给出 metadata、raw evidence 与中性 signals.json;ground truth 写到:
其 mode 为 0600。这防止正常步骤误读,不是同一 OS 用户之间的加密安全边界;知道源代码和 seed 的人可以作弊。盲测的纪律是先不读取 .sealed,独立填写诊断记录模板:
再运行答案盲诊断器:
diagnose.py 的三条规则是:
它的输出固定声明:
最后揭晓:
若 diagnosis 与 sealed ground truth 不一致,reveal 写 matched=false 并返回非零,不能把错误猜测包装成通过。task.sh all 内置一个故意错误的负对照,稳定验收要求该负对照失败。
想提交人工分类时,可在 public 目录创建最小 JSON:
然后指定:
先写假设再 reveal 的顺序比“猜错后改答案”更接近真实事件复盘。
8.7.3 输出证据包、假设树、修复前后对照与复位结果
task.sh all 的 evidence 结构按用途分层:
其中 raw artifact 负责可复核,中性 signals 负责教学分类,diagnosis 负责解释和排除项,review 只断言稳定关系。不要删 raw 只保留 status=ok。
baseline-v0.3-proposal.json
把本章证据追加到 DEFAULT-EVID-009:慢请求证据必须包含影响范围、
UTC 时间窗、wait/blocking edge、query/parameter identity、竞争假设和
复位结果;盲测诊断不得读答案,错误负对照必须失败。它绑定不可变
v0.1 checksum,并绑定 ch07 v0.2 proposal 的 canonical checksum;在 v0.2
尚未晋升前,它仍只是有依赖的 v0.3 candidate,不冒充已发布规约。
一份生产级证据包还应补齐:
- 用户 SLI 时间窗、样本数、吞吐、并发、错误;
- request/trace/application release identity;
- service/cluster/instance/database/user/application;
- PID + backend_start(若做实时会话动作);
- query family、参数分桶与数据敏感处理;
- Prometheus expression、Grafana variables/step/datasource;
- 日志 session、SQLSTATE、duration 与采样率;
- before/after 计划、statistics、settings、版本;
- 假设排序、反证、审批动作与回退阈值;
- 修复前后 correctness/SLO/resource 副作用;
- artifact hash、访问权限与保留期限。
本章没有持久 ch08 对象,所以没有“删库式 reset”。复位是每个 case 的成功条件:
手动复核:
若 all 为本章自动重建了 ch07 fixture,它会保留供后续计划/索引实验使用。清理 ch07 属于另一个 R2 动作,只能按第 7 章的双 token reset 执行;本章不会替读者擅自删除。
完成实验后,读者应能在看到“请求卡住”时先问:
下一章只有在证据指向访问路径时才进入索引设计,避免把索引当所有慢请求的通用药方。
上一节:从可观测面板回到原生证据 · 返回本章目录 · 下一章:巧夺天工:索引设计与效果验证 · 查看全书目录 · 查看索引中心