27.1 调优是一套实验方法
参数调优回答的不是:
这个参数设多大最好?
而是:
在哪一个 workload、SLO、硬件、版本和失效状态下,改变哪个机制,能以可接受的 副作用改善哪一个目标?
这句话里每一项都不能省。
一个参数没有脱离上下文的最优值:
work_mem=256MB对一条串行分析查询可能合理;对 300 个并发 backend、每条 plan 有多个 hash/sort、 每条还启动 4 个 parallel worker 的系统,可能是 OOM 设计。
调优因此和第 26 章容量实验使用同一套科学方法,只把 factor 从 workload/load 换成 GUC:
question
-> mechanism hypothesis
-> baseline
-> one controlled change
-> repeated measurement
-> effect + uncertainty
-> non-regression
-> accept / reject / inconclusive27.1.1 先定义目标、瓶颈和不可退化指标
先写 objective function
“数据库变快”不可验收。把目标写成有 scope 的 metric:
workload: checkout-v7
traffic: 1800 offered requests/s
dataset: 1.2 TiB, tenant skew P95
objective:
checkout_p95_ms: "<= 120"
completion_ratio: ">= 99.95%"
constraints:
replica_freshness_p95_s: "<= 2"
archive_backlog_recovery_min: "<= 15"
node_memory_available_gib: ">= 8"
cpu_busy_p95: "<= 65%"
durability: "local WAL flush before success"
failure_state:
- normal
- one_read_replica_lost没有 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,不找最高指标
候选瓶颈:
CPU execution
planning/JIT
data read
WAL write/sync
lock and transaction queue
pool/admission queue
memory spill/reclaim
checkpoint/writeback
replica/archive backpressure
network/client每个假设至少需要:
symptom
mechanism evidence
corroborating resource evidence
plausible counter-evidence例如:
| 假设 | 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。还要问:
CPU 花在 executor、planner、JIT、compression、TLS、context switch,
还是 client/hypervisor 计费误差?把 evidence 映射到 parameter mechanism
observed spill
-> work_mem/hash_mem_multiplier candidate
requested checkpoints too frequent
-> max_wal_size/checkpoint_timeout candidate
full-page-image WAL dominates
-> checkpoint interval/wal_compression candidate
planner systematically misprices random I/O
-> cost parameter candidate
worker launch starvation
-> worker-pool budget candidate反例:
slow query
-> work_mem?
high CPU
-> shared_buffers?
high connection count
-> max_connections?问号前缺少机制,不应进入变更。
不可退化指标要在实验前写
如果跑完才决定什么算 regression,人会自然挑对 candidate 有利的解释。预先写:
correctness equality
failure/timeout/skipped
p50/p95/p99/max
CPU and memory
temp and WAL bytes
lock waits/deadlocks
replica/archive lag
backup/maintenance duration
recovery behavior某些指标是 hard gate:
wrong result reject
durability changed reject or separate product decision
OOM / restart reject
unbounded WAL retention reject
rollback unproven reject某些是 tradeoff:
batch 20% faster
OLTP p95 2% slower
CPU 8% higher是否接受取决于预先声明的 objective 和 budget。
baseline 必须仍然存在
“改前数据是上个月 dashboard,改后是今天”不是 A/B。至少固定:
source/version
hardware/topology
data snapshot/generator
statistics
workload/arrival
connection path
background policy
measurement window对于 restart 参数,不能同时:
升级 minor version
修改 kernel
换 storage
ANALYZE 全库
改参数然后把差异归给参数。
27.1.2 一次改变一个机制并准备回退
one factor 不一定等于 one GUC
有些机制需要一组一致变化:
parallel worker budget:
max_worker_processes
max_parallel_workers
max_parallel_workers_per_gather它们可以作为一个 factor,但要明确:
- 为什么必须一起变化;
- 每项如何约束同一机制;
- 哪项是 hard upper bound;
- 如何整体回退。
相反,同时改:
work_mem
random_page_cost
max_connections
checkpoint_timeout是四个机制,不能从结果归因。
优先用最小作用域证伪
PostgreSQL 的 parameter context 决定最小 scope。若参数允许 user context,先尝试:
BEGIN;
SET LOCAL work_mem = '32MB';
EXPLAIN (ANALYZE, BUFFERS, WAL, SETTINGS, FORMAT JSON)
SELECT ...;
ROLLBACK;或为一次客户端进程:
env PGOPTIONS="-c plan_cache_mode=force_generic_plan" \
pgbench ...PostgreSQL 官方
Setting Parameters
说明,session SET 和 libpq PGOPTIONS 不影响其他 session。这使证伪成本和 blast
radius 最小。
但“session 能设”不代表生产应由每个请求随便设。通过实验后仍要决定:
query/transaction
role-in-database
role
database
instance
cluster哪个 scope 与 ownership 一致。
paired experiment
当环境噪声随时间变化,A/B 配对比“先跑十次 A,再跑十次 B”更可靠:
pair 1 A -> B, same seed/data
pair 2 B -> A, same seed/data
pair 3 A -> B
pair 4 B -> A每对 effect:
$$ r_i
\frac{X_{B,i}}{X_{A,i}} $$
报告:
median(r_i)
bootstrap interval of median(r_i)不是:
$$ \frac{\text{median}(B)} {\text{median}(A)} $$
二者在配对噪声下可能不同。
最小显著收益
统计上可分辨不等于工程上值得。预声明:
minimum material gain 2%
measurement noise assessed from repetition
tail regression budget <= 5%若 candidate 提高 0.3%,却增加 configuration exception、plan risk 和 review burden, 通常不值得持久化。
第 27 章正式 run 的规则:
paired TPS ratio bootstrap 95% lower >= 1.02
candidate/baseline pooled p95 <= 1.05
plan shapes equal
failure/late/skipped = 0
global settings unchanged结果 lower bound 0.9808,所以拒绝。
rollback 不是“把值改回去”
回退合同包含:
previous value and source
previous desired-state commit
change scope
whether reload/restart is needed
session/pool lifecycle
HA member order
data/WAL produced under candidate
stop condition
rollback validation例子:把 synchronous_commit 改回 on,不会重新赋予此前已向客户端确认但尚未 flush
的 transaction durability。参数值能回退,历史语义不能。
增加 max_connections 后又减回去,若当前 session 已超过新上限,现有 session 不会
神奇消失;还要协调 pool 与 reconnect。
变更状态机
proposed
-> static reviewed
-> sandbox falsified
-> canary
-> observation window
-> accepted
-> rolled back
-> inconclusive每一步有 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
慢查询的优先顺序通常是:
correctness and transaction semantics
-> query shape
-> schema/index/statistics
-> workload/admission
-> parameter policy
-> hardware/topology不是所有问题都严格按此顺序解决,但越靠前的错误越不应由全局参数掩盖。
cardinality 错误
planner 估 10 行,实际 10,000,000 行,可能选择 nested loop。把:
enable_nestloop = off设为全局只是在压制一个 plan type。更好的证据路径:
ANALYZE freshness
per-column statistics target
extended statistics
correlated predicates
parameter-sensitive plan
expression/cast mismatchPostgreSQL
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、一次返回数百万行:
work_mem ↑
parallel workers ↑可能让数据库更快地向网络和客户端制造巨大结果集,但没有修复 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 文档塞进一行,再频繁修改一个字段,可能带来:
TOAST rewrite
WAL amplification
index expression cost
MVCC churnwal_compression 或 checkpoint 参数只能改变代价的一部分,不能修复 update model。
durability 不是性能参数
以下变化会改变业务承诺:
fsync=off
full_page_writes=off
synchronous_commit=off
unlogged table它们不能与普通性能参数放在同一 A/B 后,仅因 TPS 更高就接受。
synchronous_commit=off 在某些明确允许“数据库 crash 时丢失最近成功确认事务”的
业务中可以成为产品设计,但需要:
business owner
loss window
idempotent recovery
observability
failure drill而不是 DBA 的秘密加速。
参数模板是起点
Pigsty 的 tiny、oltp、olap、crit profile 根据硬件和 workload 类别生成合理
起点;官方
parameter optimization policy
也按场景区分模板。模板不知道:
- 你的 query;
- tenant skew;
- SLO;
- batch overlap;
- failure budget;
- hardware/storage tail;
- 应用 pool 与 retry。
所以正确流程:
template baseline
-> measure
-> hypothesis
-> scoped experiment
-> ADR不是:
internet parameter list
-> production一张问题分类表
| 现象 | 先查 | 参数候选出现的条件 |
|---|---|---|
| 单条 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 | 最坏并发模型已还原 |
本章后续所有参数都遵循这个约束:先解释机制和预算,再讲值。