27.1 调优是一套实验方法
参数调优回答的不是:
这个参数设多大最好?
而是:
在哪一个 workload、SLO、硬件、版本和失效状态下,改变哪个机制,能以可接受的 副作用改善哪一个目标?
这句话里每一项都不能省。
一个参数没有脱离上下文的最优值:
对一条串行分析查询可能合理;对 300 个并发 backend、每条 plan 有多个 hash/sort、 每条还启动 4 个 parallel worker 的系统,可能是 OOM 设计。
调优因此和第 26 章容量实验使用同一套科学方法,只把 factor 从 workload/load 换成 GUC:
27.1.1 先定义目标、瓶颈和不可退化指标
先写 objective function
“数据库变快”不可验收。把目标写成有 scope 的 metric:
没有 constraints 的“优化”会把成本转移到别处。
metric 分四类
| 类别 | 例子 | 作用 |
|---|---|---|
| primary objective | p95、throughput、batch duration | 想改善什么 |
| correctness | rows、checksum、serialization result | 不能算错 |
| safety | OOM、disk、WAL、replica/archive gap | 不能失控 |
| operability | restart、rollback、recovery、observability | 不能不可运维 |
性能 A/B 必须同时校验结果正确。一次 hash join 因错误 filter 少处理 30% 行而“快了” 不是调优。
找 service center,不找最高指标
候选瓶颈:
每个假设至少需要:
例如:
| 假设 | symptom | PostgreSQL | OS/Pigsty | 反证 |
|---|---|---|---|---|
| sort spill | p95 随数据量跳升 | EXPLAIN disk、temp blocks |
disk temp I/O | 无 Sort/Hash 节点 |
| WAL sync | commit tail | WAL I/O wait | WAL device fsync tail | CPU saturated |
| CPU | throughput knee | active/no wait、exec time | busy/run queue | client/pool/lock wait |
| lock | long tail | blockers/wait event | CPU 可低 | 无 lock queue |
“CPU 84%”本身不是参数假设。它只说明 CPU 是候选 service center。还要问:
把 evidence 映射到 parameter mechanism
反例:
问号前缺少机制,不应进入变更。
不可退化指标要在实验前写
如果跑完才决定什么算 regression,人会自然挑对 candidate 有利的解释。预先写:
某些指标是 hard gate:
某些是 tradeoff:
是否接受取决于预先声明的 objective 和 budget。
baseline 必须仍然存在
“改前数据是上个月 dashboard,改后是今天”不是 A/B。至少固定:
对于 restart 参数,不能同时:
然后把差异归给参数。
27.1.2 一次改变一个机制并准备回退
one factor 不一定等于 one GUC
有些机制需要一组一致变化:
它们可以作为一个 factor,但要明确:
- 为什么必须一起变化;
- 每项如何约束同一机制;
- 哪项是 hard upper bound;
- 如何整体回退。
相反,同时改:
是四个机制,不能从结果归因。
优先用最小作用域证伪
PostgreSQL 的 parameter context 决定最小 scope。若参数允许 user context,先尝试:
或为一次客户端进程:
PostgreSQL 官方
Setting Parameters
说明,session SET 和 libpq PGOPTIONS 不影响其他 session。这使证伪成本和 blast
radius 最小。
但“session 能设”不代表生产应由每个请求随便设。通过实验后仍要决定:
哪个 scope 与 ownership 一致。
paired experiment
当环境噪声随时间变化,A/B 配对比“先跑十次 A,再跑十次 B”更可靠:
每对 effect:
$$ r_i
\frac{X_{B,i}}{X_{A,i}} $$
报告:
不是:
二者在配对噪声下可能不同。
最小显著收益
统计上可分辨不等于工程上值得。预声明:
若 candidate 提高 0.3%,却增加 configuration exception、plan risk 和 review burden, 通常不值得持久化。
第 27 章正式 run 的规则:
结果 lower bound 0.9808,所以拒绝。
rollback 不是“把值改回去”
回退合同包含:
例子:把 synchronous_commit 改回 on,不会重新赋予此前已向客户端确认但尚未 flush
的 transaction durability。参数值能回退,历史语义不能。
增加 max_connections 后又减回去,若当前 session 已超过新上限,现有 session 不会
神奇消失;还要协调 pool 与 reconnect。
变更状态机
每一步有 evidence 和 owner。不要把“SQL 执行成功”当作 accepted。
stop condition
实验中发现以下任何一项立即停止:
- correctness mismatch;
- failed/late/skipped 越界;
- memory safety floor;
- replica/archive gap 无界增长;
- HA role/topology 变化;
- client saturation;
- config source/pending restart 异常;
- cleanup 不再可证明。
stop 后保留失败 evidence,不重新跑到“漂亮”为止。
27.1.3 参数变化不修复错误 SQL 和错误模型
参数是系统 policy,不是 query patch
慢查询的优先顺序通常是:
不是所有问题都严格按此顺序解决,但越靠前的错误越不应由全局参数掩盖。
cardinality 错误
planner 估 10 行,实际 10,000,000 行,可能选择 nested loop。把:
设为全局只是在压制一个 plan type。更好的证据路径:
PostgreSQL
Query Planning
也把 enable_* 描述为粗粒度临时影响手段,并优先建议统计与 cost calibration。
缺索引或错误索引
把 random_page_cost 调低不会创建缺失的 access path。它可能让 planner 更偏爱现有
索引,但:
- index key/order 不支持 predicate/order;
- expression/collation/type 不匹配;
- partial predicate 无法证明;
- selectivity 太低;
- index-only scan visibility 不足;
仍然存在。
无界查询
没有 pagination/limit、一次返回数百万行:
可能让数据库更快地向网络和客户端制造巨大结果集,但没有修复 API contract。
长事务
idle_in_transaction_session_timeout 可以限制 idle transaction 伤害,却不应代替:
- 应用 transaction boundary;
- retry/timeout handling;
- connection return;
- cursor lifecycle;
- batch chunking。
timeout 是最后防线,触发时 transaction 被中断;它不是无副作用的清理器。
N+1 与 chatty workload
把 max_connections 从 100 提到 1,000,不会把 1,000 次 serial round trip 变成一条
set-based SQL。它只允许更多 N+1 同时进入。
数据模型不匹配
把大 JSON 文档塞进一行,再频繁修改一个字段,可能带来:
wal_compression 或 checkpoint 参数只能改变代价的一部分,不能修复 update model。
durability 不是性能参数
以下变化会改变业务承诺:
它们不能与普通性能参数放在同一 A/B 后,仅因 TPS 更高就接受。
synchronous_commit=off 在某些明确允许“数据库 crash 时丢失最近成功确认事务”的
业务中可以成为产品设计,但需要:
而不是 DBA 的秘密加速。
参数模板是起点
Pigsty 的 tiny、oltp、olap、crit profile 根据硬件和 workload 类别生成合理
起点;官方
parameter optimization policy
也按场景区分模板。模板不知道:
- 你的 query;
- tenant skew;
- SLO;
- batch overlap;
- failure budget;
- hardware/storage tail;
- 应用 pool 与 retry。
所以正确流程:
不是:
一张问题分类表
| 现象 | 先查 | 参数候选出现的条件 |
|---|---|---|
| 单条 query 慢 | plan/rows/buffers/I/O | 机制已指向 memory/cost/parallel |
| 全局 p95 上升 | load/queue/wait/resource | service center 已定位 |
| temp 暴增 | Sort/Hash、并发 | spill 与 memory budget 同时量化 |
| WAL 暴增 | write mix/FPI/checkpoint | WAL composition 已分解 |
| connection 拒绝 | pool/leak/admission | demand 与 reserved slot 已设计 |
| replica lag | WAL/replay/query conflict | source/replay bottleneck 已区分 |
| OOM | active plan/node/worker | 最坏并发模型已还原 |
本章后续所有参数都遵循这个约束:先解释机制和预算,再讲值。
返回本章目录 · 下一节:内存预算 · 查看全书目录 · 查看索引中心