8.5 设计受控实验
诊断实验的目标不是“让一次执行变快”,而是区分竞争解释。一次同时 ANALYZE、建索引、改 SQL、增大内存又重启实例的操作即使奏效,也不知道哪项有效、哪项多余、哪项埋下副作用。
8.5.1 每次只改变一个解释变量
把假设写成实验合同:
现象:
hot tenant 的同一 query family 资源量异常。
假设:
generic plan 使用全体 tenant 平均选择率,严重低估 hot tenant。
保持不变:
PostgreSQL 版本、数据快照、SQL 语义、参数、连接、cache 条件、
并发、session settings(除 plan_cache_mode)。
唯一改变:
force_generic_plan → force_custom_plan。
预测:
estimate/actual error 从 >=100x 降到 <=2x;
无 Lock/Client wait;可能改变路径和 buffers。
判错:
custom estimate 仍严重偏离,或主要时间其实属于 wait/client。
回退:
session 结束即恢复 plan_cache_mode;不修改持久对象。“每次只改变一个变量”不等于一次只能执行一个命令。重建同一 deterministic fixture、采集 before/after、运行 analyzer 可以是一项完整操作;关键是两组之间只有被检验机制不同。
实验级别应逐级提升:
- 只读观察:activity、wait、统计、日志、已有计划;
- session-local probe:同一 session 改一个 setting,事务结束恢复;
- 隔离 fixture/副本:重放代表数据与并发;
- 受控 canary:少量真实流量、明确 SLO 与自动退出;
- 生产变更:审批、回退、监控和审计齐全。
不要跳级只是为了快。特别是:
- 不在生产对未知写 SQL 随意
EXPLAIN ANALYZE; - 不为对比清空全局 query statistics;
- 不用
pg_terminate_backend代替理解事务; - 不把 planner debug setting 留在连接池 session;
- 不在没有磁盘/写放大评估时直接创建大索引;
- 不把生产 traffic 同时承担探索、验证和上线三个阶段。
若无法控制某个重要变量,应把它记录为混杂因素,而不是从报告中删掉。例如 A/B 两次恰好跨 checkpoint、流量 mix 不同,则结论降级为“支持但未确认”,需要新的对照。
8.5.2 冷热缓存、参数与数据规模控制
数据库性能至少受四种实验条件影响:
缓存
“冷缓存”可能指:
application cache
PgBouncer/prepared state
PostgreSQL shared buffers
kernel page cache
storage controller/device cacheDISCARD ALL 不会清空 shared buffers 或 OS page cache;重连也不等于冷缓存。生产执行 echo 3 > /proc/sys/vm/drop_caches 或重启实例会影响全机 workload,不是普通诊断动作。若真的需要 cold/warm 对照,应在隔离、可重建环境明确 cache 层并记录方法。
更常见也更安全的策略是:
- 先跑 warm-up,不计入测量;
- A/B 交替或随机顺序,减少随时间漂移;
- 重复多次,报告分布和原始值,不只选最快一次;
- 同时保存 buffers、I/O timing 与 OS 指标;
- 若无法制造 cold,明确结论只适用于 warm steady state。
参数
均匀随机参数会掩盖业务分布。先按机制分层:
hot/cold tenant
existent/missing key
small/large time range
few/many result rows
common/rare status combination
first/subsequent page每层使用脱敏、可重现的代表值,并按真实 traffic weight 汇总。一个只让 cold tenant 快 2 ms、却让占 90% 流量的 hot tenant 慢 100 ms 的索引/计划不是总体改善。
数据规模与分布
一万行测试库上的 Index Scan 不能证明十亿行生产行为。fixture 至少应保持:
- relation/partition 数量级;
- row count、row width、null fraction;
- distinct count、MCV、列间相关;
- index 与 heap physical correlation;
- dead tuple/bloat 与 statistics 状态;
- 参数访问倾斜。
无法复制完整规模时,目标是复制决策边界,而不是复制全部数据。例如第 7/8 章用 90000/10 的 tenant skew 让 generic/custom plan 面临清晰选择率差异。
并发
单 session 提速不保证系统吞吐改善。并发会改变:
- buffer/cache 命中与 I/O queue;
- CPU run queue;
- lock contention;
- 每 query 可用内存与 spill;
- connection pool queue;
- background vacuum/checkpoint 干扰。
至少分别回答:
single-query latency 是否改善?
固定并发下 throughput/p95/p99 是否改善?
达到 SLO 的最大可持续吞吐是否改善?
错误、超时、WAL、CPU/I/O、内存副作用怎样?不要用无限并发压到崩溃后,只比较“谁最后倒下”。容量实验应有 ramp、steady state、 abort threshold 与恢复验证,第 26 章会完整展开。
8.5.3 反证、回退与副作用观察
一个强实验既设计正结果,也预先声明什么会证明自己错。示例:
| 假设 | 支持结果 | 反证/降级 |
|---|---|---|
| 锁是直接原因 | blocker 释放后 waiter 立即前进,其他变量不变 | 无 blocker edge;释放后仍慢 |
| generic estimate 是原因 | custom estimate/资源显著改善 | 两者 estimate 与资源相近 |
| ClientWrite 是原因 | 无 blocker,慢 reader 恢复后 query 完成 | server 内仍有主要 I/O/Lock wait |
work_mem 太小 | 单 session 增大后 spill 消失且端到端改善 | spill 消失但延迟不变/内存压力恶化 |
| 缺索引 | 代表参数与并发下 blocks/延迟改善 | 只冷门参数改善,写放大/SLO 变差 |
“未能反证”不等于“证明”。尽量加入负对照:
- 同一 SQL 的 unaffected parameter;
- 同时间的 unaffected instance/route;
- 同 seed 重跑;
- 故意错误的诊断必须被 reveal 拒绝;
- 恢复原设置后现象按预测返回。
效果报告应同时包含:
correctness/result equivalence
latency distribution + sample count
throughput/concurrency/errors
calls/rows/buffers/temp/WAL
CPU/I/O/memory/locks
plan/estimate/settings
change and rollback duration
known confounders副作用经常决定一个“快方案”不可上线:
- 新索引增加写 latency、WAL、磁盘、vacuum 工作;
- 提高
work_mem增加并发 OOM 风险; - 缩短 timeout 降低资源占用,却提高错误/重试风暴;
- 增大 pool 提高 backend 并发,却让数据库饱和;
- 缓存结果改善读取,却引入失效与一致性问题;
- denormalization 减少 join,却增加写路径和校验复杂度。
回退不是文档最后一行“必要时回滚”。实验前就应验证:
谁有权限回退
回退触发阈值
是否真的可逆
回退耗时和锁影响
回退后怎样证明状态恢复
外部副作用如何补偿本章三个 case 都把复位写进执行路径:
- estimate 只改变 session-local plan mode;
- lock 的 blocker 与 waiter 最终 rollback;
- client 只生成派生结果并精确取消本实验 backend;
- final
verify.sql检查 worker=0、ch07 fixture 和 ch04-v1 checksum。
运行全量实验:
export PGSERVICEFILE=/absolute/private/path/pg_service.conf
export PGSERVICE=pg36-admin
export PG36_EVIDENCE_DIR="$PWD/evidence/ch08/all-$(date -u +%Y%m%dT%H%M%SZ)"
./task.sh all
cat "$PG36_EVIDENCE_DIR/review.json"这里 all 是 L1 教学环境动作。它会在必要时受控重建 ch07 专属 fixture、制造短暂锁与 socket backpressure,并精确取消自己的 worker;不会成为生产诊断脚本。
下一节把同一方法映射到 Pigsty:用面板快速发现范围,用原生证据确认,而不依赖某个版本的按钮位置。
上一节:建立而不是猜测假设 · 返回本章目录 · 下一节:从可观测面板回到原生证据 · 查看全书目录 · 查看索引中心