跳至内容
8.7 实战:三种“慢”只修真正瓶颈

8.7 实战:三种“慢”只修真正瓶颈

三个 case 都制造“请求没有及时返回”,但修复方向互斥:

case决定性证据被排除的直接解释正确方向
estimate/plangeneric error 900x,custom 1x,无 Lock/ClientWrite当前锁链、慢客户端参数/统计/计划与访问路径
lock waitactive + Lock/transactionid,blocker=1ClientWrite、正在推进的 planroot blocker 与事务边界
client slow consumeractive + Client/ClientWrite,blocker=0数据库锁链返回规模、客户端/网络/读取方式

实验不以单次 elapsed time 或节点名作为 golden。它保存机器可读 signal,再由一个不能读取答案文件的分类器只按证据关系判断。

8.7.1 估算错误、锁等待与客户端慢消费

确认 service 指向可写、可演练的 L1:

export PGSERVICEFILE=/absolute/private/path/pg_service.conf
export PGSERVICE=pg36-admin
cd static/labs/ch08
export PG36_EVIDENCE_DIR="$PWD/evidence/ch08/all-$(date -u +%Y%m%dT%H%M%SZ)"

./task.sh all

若 ch07 fixture 已通过,all 直接复用;若缺失,则调用 ch07 marker guard 做受控重建。之后顺序为:

ch04/ch05 preflight
  → estimate case → normalize signals → diagnose
  → lock case     → normalize signals → diagnose
  → client case   → normalize signals → diagnose
  → seeded mystery
  → deliberately wrong diagnosis → reveal must fail
  → answer-blind diagnosis → reveal must pass
  → final worker/model/fixture verify
  → semantic review

一次实测输出:

status=ok
classifications=estimate-plan,lock-wait,client-slow-consumer
mystery=client-slow-consumer/matched=true/wrong-guess-rejected=true
same-seed-reproducible=true/answer-mode=0600
state-restored=true/remaining-workers=0/
relation-checksum=f8a7bfae59c6d16cd323abecfefe1014

case A:estimate/plan

estimate-case.sh 复用 ch07 tenant skew,用同一 SQL、同一 hot parameter 分别捕获:

force_generic_plan:
  estimate=100
  actual=90000
  cardinality error=900x

force_custom_plan:
  estimate=90000
  actual=90000
  cardinality error=1x

本次机器上 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 章的确定性编排:

blocker 在事务内更新 order 1002 并进入受控 hold
  → 普通 reader 仍看见旧 committed version
  → waiter 尝试更新同一行
  → observer 捕获 waiter → blocker 精确边
  → 只取消 exact blocker
  → blocker rollback
  → waiter 前进并 rollback
  → order fingerprint 不变

中性 signals:

{
  "state": "active",
  "wait_event_type": "Lock",
  "wait_event": "transactionid",
  "blocking_pid_count": 1,
  "plan": null,
  "state_restored": true,
  "remaining_workers": 0
}

直接原因是锁等待,不是 waiter 的执行计划。长期修复应回到 blocker 所属事务:为何持锁、是否在事务内睡眠/调用外部服务、锁顺序是否一致、更新集合是否过宽。取消是止血且只允许精确命中实验身份。

case C:client slow consumer

client-write-lab.shgenerate_series 生成大结果,由 slow-reader.py 每次只读少量字节并等待。它不读业务表,也不创建对象。

observer 必须同时看到:

state=active
wait_event_type=Client
wait_event=ClientWrite
blocking_pid_count=0

随后用 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:

export PGSERVICEFILE=/absolute/private/path/pg_service.conf
export PGSERVICE=pg36-admin
export PG36_EVIDENCE_DIR="$PWD/evidence/ch08/mystery-$(date -u +%Y%m%dT%H%M%SZ)"

# 可选:同一 seed 会选择同一 case;不设置则自动生成
export PG36_CASE_SEED='team-drill-2026-07-29'

./task.sh mystery
python3 -m json.tool "$PG36_EVIDENCE_DIR/public/signals.json"

PG36_CASE_SEED 经 SHA-256 后按三类取模。public 目录只给出 metadata、raw evidence 与中性 signals.json;ground truth 写到:

$PG36_EVIDENCE_DIR/.sealed/answer.json

其 mode 为 0600。这防止正常步骤误读,不是同一 OS 用户之间的加密安全边界;知道源代码和 seed 的人可以作弊。盲测的纪律是先不读取 .sealed,独立填写诊断记录模板

观察到的 state/wait/blocker/plan 事实
最可能分类
两个被排除的替代解释
还缺什么证据
最小修复实验与回退

再运行答案盲诊断器:

# diagnose 只读 public/signals.json;离线运行,不需要数据库连接
env -u PGSERVICEFILE ./task.sh diagnose
python3 -m json.tool "$PG36_EVIDENCE_DIR/public/diagnosis.json"

diagnose.py 的三条规则是:

active + Lock + blockers>0
  → lock-wait

active + Client/ClientWrite + blockers=0
  → client-slow-consumer

no Lock/ClientWrite + generic error>=100x + custom error<=2x
  → estimate-plan

它的输出固定声明:

"answer_artifact_read": false

最后揭晓:

env -u PGSERVICEFILE ./task.sh reveal
python3 -m json.tool "$PG36_EVIDENCE_DIR/public/reveal.json"

若 diagnosis 与 sealed ground truth 不一致,revealmatched=false 并返回非零,不能把错误猜测包装成通过。task.sh all 内置一个故意错误的负对照,稳定验收要求该负对照失败。

想提交人工分类时,可在 public 目录创建最小 JSON:

{
  "diagnosis": "lock-wait"
}

然后指定:

export PG36_DIAGNOSIS_FILE="$PG36_EVIDENCE_DIR/public/my-diagnosis.json"
export PG36_REVEAL_FILE="$PG36_EVIDENCE_DIR/public/my-reveal.json"
./mystery.sh reveal

先写假设再 reveal 的顺序比“猜错后改答案”更接近真实事件复盘。

8.7.3 输出证据包、假设树、修复前后对照与复位结果

task.sh all 的 evidence 结构按用途分层:

manifest.txt                  versions + target facts + source hashes
preflight.txt                 ch04/ch05 state before
ch07-fixture.txt              reused or controlled-rebuild
estimate/
  generic-hot.json
  custom-hot.json
  signals.json
  diagnosis.json
lock/
  raw/activity.csv
  raw/locks.csv
  raw/summary.txt
  signals.json
  diagnosis.json
client/
  raw/activity.csv
  raw/summary.txt
  signals.json
  diagnosis.json
mystery/
  public/...
  .sealed/answer.json
verify.txt
review.json

其中 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 的成功条件:

estimate session ends
lock transactions rollback
client worker exactly cancelled
all lab workers disappear
ch07 fixture remains valid
ch04-v1 business checksum unchanged

手动复核:

export PG36_EVIDENCE_DIR="$PWD/evidence/ch08/verify-$(date -u +%Y%m%dT%H%M%SZ)"
./task.sh verify
cat "$PG36_EVIDENCE_DIR/verify.txt"

all 为本章自动重建了 ch07 fixture,它会保留供后续计划/索引实验使用。清理 ch07 属于另一个 R2 动作,只能按第 7 章的双 token reset 执行;本章不会替读者擅自删除。

完成实验后,读者应能在看到“请求卡住”时先问:

它尚未进入数据库、正在执行、正在等谁,还是等客户端?
哪条原生证据能区分?
最小动作怎样让不同假设产生不同结果?
怎样证明动作没有留下新问题?

下一章只有在证据指向访问路径时才进入索引设计,避免把索引当所有慢请求的通用药方。


上一节:从可观测面板回到原生证据 · 返回本章目录 · 下一章:巧夺天工:索引设计与效果验证 · 查看全书目录 · 查看索引中心

最后更新于