跳至内容

26.3 建立可信实验

可信实验不等于“环境完全没有噪声”。更现实的标准是:

问题明确
因素声明
控制可验证
响应可重算
噪声被观察
顺序偏差被限制
失败没有消失
结论不越过证据

性能实验有两类误差:

random error
  run-to-run fluctuation
  -> repetition / interval may reveal

systematic error
  wrong workload, wrong path, client bottleneck, warm-only cache
  -> more repetitions do not repair

跑 1,000 次错误 workload,只会非常精确地回答错误问题。

26.3.1 固定硬件、版本、配置、数据与随机种子

把实验写成一个不可缺字段的 tuple

result =
  f(
    hardware,
    virtualization,
    kernel,
    filesystem/storage,
    PostgreSQL build/version,
    extensions,
    configuration,
    schema,
    data,
    statistics,
    connection path,
    client,
    workload,
    time/background state
  )

少一个字段,结果就多一种解释。

硬件不只记录“8C32G”

至少记录:

CPU model / architecture / socket / core / SMT
frequency policy / steal time / power state
NUMA topology
memory total / bandwidth / swap
storage device / controller / filesystem / mount
IOPS / latency / throughput / queue assumptions
network path / RTT / bandwidth
virtualization / cloud instance / noisy-neighbor boundary

参考 run:

architecture  aarch64
client        pg-meta-1, 2 vCPU, 4,089,262,080 bytes RAM
server        pg-test-1, 1 vCPU, 2,048,679,936 bytes RAM
storage       Vagrant virtual disk on shared laptop hypervisor

所以它不能支持 production IOPS、endurance 或 failure-domain claim。

PostgreSQL provenance

版本至少包括完整 build:

SELECT version();
SHOW server_version_num;

还要:

extension versions
compiler/architecture
block size / WAL segment size
checksums
locale / encoding
huge pages / io_method

参考:

PostgreSQL 18.4 Ubuntu build
server_version_num 180004
block size         8192
data checksums     on

升级 minor version 也应重新跑 regression baseline。minor release 可能修复 planner、 executor、WAL、I/O 或 correctness 问题;“major 没变”不是等价环境。

配置要保存 value、source 与 pending restart

只保存 postgresql.conf 不够:

SELECT
    name,
    setting,
    unit,
    source,
    sourcefile,
    sourceline,
    pending_restart
FROM pg_settings
ORDER BY name;

容量关键项:

shared_buffers
work_mem / hash_mem_multiplier
maintenance_work_mem / autovacuum_work_mem
max_connections
max_worker_processes / parallel workers
effective_cache_size
random_page_cost / effective_io_concurrency
io_method / io_workers
checkpoint_timeout / max_wal_size
wal_compression / full_page_writes
synchronous_commit / fsync
track_io_timing / track_wal_io_timing

effective_cache_size 是 planner estimate,不是分配的 cache。work_mem 是每个 operation 可能使用的基础限制,不是整台 server 的总内存。PostgreSQL resource consumption 提醒,一个复杂 query 可能有多个 sort/hash,多 session 和 parallel worker 会把 总内存放大许多倍。

数据版本必须可证明

记录:

schema migration/version
DDL hash
generator/seed
row counts
relation and index sizes
distribution parameters
statistics/analyze timestamp
live/dead tuple and bloat state
partition set
sequence position

参考 fixture 每个 scale 都重建 immutable table:

customer
product
order_history

每次 measured run 前只重置:

inventory
order_live

这样五次 run 有相同 initial mutable state,同时保留自然 cache 与 background 演化。它不代表 production bloat/churn。

随机 seed 与 run order 都要固定

seed 固定:

  • script choice;
  • random key;
  • Zipf sample;
  • rate schedule(若使用)。

run order 也会影响结果。若总是:

c1 -> c8

c8 总是得到更暖 cache,也总是承受 c1 留下的 dirty page。

参考采用 counterbalanced order:

r1 c1 -> c8
r2 c8 -> c1
r3 c1 -> c8
r4 c8 -> c1
r5 c1 -> c8

scale 仍按 S -> M -> L,因为重建大数据代价高。这是保留的 order confound, 报告必须写明。

source hash 绑定执行实现

正式 evidence 保存 21 个 source file SHA-256:

contracts
SQL
pgbench scripts
samplers
runner
validator
reviewer
task

若脚本改了一个 cast、weight 或采样窗口,旧 evidence 不再声称由当前 source 产生。参考 run 在 CPU sampler 口径修正后完整重跑,而不是把旧 transaction 结果与新 CPU 算法拼在一起。

manifest 最小字段

{
  "run_id": "...",
  "captured_at": "...",
  "target": "...",
  "versions": {},
  "settings": {},
  "source_hashes": {},
  "dataset": {},
  "workload_id": "...",
  "run_order": [],
  "raw_files": {},
  "cleanup": {},
  "claims_not_made": []
}

raw file 要有 size/hash;只保存总结无法重算 quantile。

26.3.2 预热、重复、置信区间与异常值

预热要说明预热什么

可能需要预热:

client process and connections
authentication/TLS
prepared statements
PostgreSQL shared buffers
OS page cache
JIT compilation
relation extension
filesystem allocation
storage cache
statistics/exporter discovery

“跑 5 秒预热”只是动作,不是证明。

参考 run 对每个 scale/concurrency 做 5 秒自然预热,随后 reset mutable table。 它能:

  • 验证正式 protocol 与 script;
  • 建立连接;
  • 触碰部分 immutable working set;
  • 暴露 parameter typing。

它不能保证 899 MiB L dataset 全部 warm,也不能制造 cold-cache baseline。

正式时长取决于最慢周期

观察窗口要覆盖:

checkpoint cycle
autovacuum cycle
backup overlap
storage cache behavior
CPU thermal/power changes
application burst length
replica/archive catch-up

PostgreSQL 官方建议至少几分钟,可能需要数小时。

本章每 run 8 秒,是为了在 disposable laptop sandbox 中:

  • 演示完整 30-run evidence pipeline;
  • 观察短时 curve 与测量偏差;
  • 保持负载有界;
  • 不批准生产数字。

它不是 production baseline 的推荐时长。

重复的实验单位必须独立定义

参考每次 run:

reset order_live and inventory
snapshot native counters
start wait and OS samplers
run 8 s pgbench
stop at exact measured window
wait for stats flush
snapshot counters

immutable data 与 cache 不重建,所以 run 并非完全独立;它是“同一 scale 阶段内的 重复”。报告不能把五次当五台独立机器。

生产比较建议:

  • 多个 run;
  • 多个时间段;
  • 至少一次 fresh environment;
  • 若硬件采购,多个同型 node;
  • candidate/control 交错,而不是先跑完 A 再跑 B。

不要用一个平均数吞掉分布

每个 run 保存 transaction latency sample:

$$ {x_1,x_2,\dots,x_n} $$

cell pooled p95:

$$ Q_{0.95}\left( \bigcup_{r=1}^{5} X_r \right) $$

它回答“该 cell 所有保存 transaction sample 的 p95”。

run-level p95:

$$ q_r=Q_{0.95}(X_r) $$

再报告 $q_r$ 的 median/interval,回答“一个典型 run 的 p95 如何波动”。

这两者不能混名。

参考 public summary 保存:

pooled p50/p95/p99/max
run TPS median
run TPS deterministic bootstrap 95 interval

bootstrap interval 的边界

对五个 TPS:

resample five runs with replacement
compute median
repeat 10,000 times with fixed bootstrap seed
take 2.5% and 97.5%

这给出教学用 interval,但:

  • n=5 很小;
  • run 不完全独立;
  • systematic bias 不会进入 interval;
  • interval 不是“真实 TPS 有 95% 概率在里面”;
  • performance distribution 可能非平稳。

它主要让读者看见“不确定性不是零”。

异常值不能看结果再删除

L-c8 五次 TPS:

2216.087
2739.827
2774.267
2824.359
2798.272

第一轮显著低,p95 也更高。可能原因:

  • L 初始化尾部 dirty write;
  • working set 尚未稳定;
  • background checkpoint/archive/replication;
  • virtual neighbor;
  • random interleaving。

本章保留它,因此:

median TPS         2774.267
bootstrap interval [2216.087, 2824.359]

正确流程:

  1. 在实验前声明 outlier rule;
  2. 保存原始样本;
  3. 检查 concurrent evidence;
  4. 报告含/不含敏感性分析;
  5. 只有明确测量故障才排除;
  6. 排除要留 audit record。

“看起来不正常”不是删除依据。

candidate 与 baseline 的比较

对每个 matched run:

$$ \Delta_r

\frac{candidate_r-baseline_r}{baseline_r} $$

优先比较 paired distribution,而不是两个独立平均。验收可以是:

throughput regression < 3%
p95 regression < 5%
p99 no material tail expansion
error/late/skipped unchanged
resource demand does not worsen beyond budget

阈值来自业务风险与实验噪声,不是固定模板。

26.3.3 冷热缓存、后台任务与邻居噪声

PostgreSQL 有至少两层 cache

PostgreSQL shared buffers
  -> OS page cache
      -> device/controller cache
          -> physical/virtual storage

blks_hit

found in PostgreSQL shared buffers

blks_read

PostgreSQL requested a block read

不代表 physical disk read。OS 可能立即从 page cache 返回。

参考:

S/M cells  blks_read = 0 during measured runs
L-c1       2,500
L-c8       14,491

可以说 L 触发了 PostgreSQL-level reads,不能说发生了同样数量的 physical I/O。 因此 runner 同时采 /proc disk counters,并用 Pigsty node metrics 做较粗的 corroboration。

真 cold cache 是破坏性实验

常见做法:

restart PostgreSQL
drop OS page cache
reboot host
recreate VM

它们会影响同机其他 workload,不能在共享或生产节点随便执行。若要测 cold start:

  • dedicated environment;
  • 明确授权;
  • 固定动作与复位;
  • 同时记录 storage cache;
  • 区分 crash recovery、clean restart 和 first read;
  • 多次重建,不把一次 boot 当分布。

本章禁止 drop cache、restart 和 checkpoint on demand,只声明 warm-natural。

autovacuum 是 workload 的一部分

PostgreSQL 官方 pgbench 指南提醒,dead row/space 与 autovacuum 会改变结果。

两种实验都合理:

controlled engine microbenchmark
  deliberately isolate maintenance

production representative benchmark
  keep realistic autovacuum and churn

错误做法是关闭 autovacuum 获得数字,却不在 claim 里写。

若保留 autovacuum,记录:

SELECT
    relname,
    n_live_tup,
    n_dead_tup,
    autovacuum_count,
    autoanalyze_count,
    last_autovacuum,
    last_autoanalyze
FROM pg_stat_user_tables;

并观察 progress、I/O、WAL 和 latency。

checkpoint 与 full-page image

checkpoint 后第一次修改某 page,full_page_writes=on 时可能产生 full-page image。于是相同 transaction mix 的 WAL/tx 会随 checkpoint phase 改变。

观察:

pg_stat_checkpointer
pg_stat_wal.wal_fpi
pg_stat_wal.wal_bytes
Pigsty PGSQL Persist

不要为了“稳定”关闭 full-page writes;那会改变 durability。

PostgreSQL WAL 配置 说明这些参数的恢复语义。本章保持 fsync=onfull_page_writes=onsynchronous_commit=on

backup、archive 与 replica 会消耗真实资源

写 workload 同时驱动:

primary WAL generation
WAL sender
replica receive/write/replay
archive command
backup repository

参考全实验 Pigsty window:

WAL rate max            46.2 MB/s
replica replay gap max   3.47 MB
replay gap median        0

该 window 包含 bulk initialization、warm-up 和 measured run。max 不能归因给 某个 cell;median 0 也不证明 read-your-writes。它只是“复制路径在实验期间有过 推进和短时距离”的 corroboration。

virtual neighbor 让“同一台机器”也不相同

shared hypervisor 可能同时运行:

  • load generator;
  • database VMs;
  • monitoring;
  • local build;
  • host desktop workload。

guest 中看到的 CPU busy 不包含所有 host contention 细节。需要:

steal time
host load
storage latency
run-to-run spread
dedicated-host rerun

本章把 shared-hypervisor 写入 why_null,而不是用五次重复假装消除。

background inventory

每次 run 保存:

pg_stat_database delta
pg_stat_wal delta
pg_stat_io delta
pg_stat_checkpointer delta
pg_stat_bgwriter delta
pg_stat_statements queryid-only delta
pg_stat_activity wait sample
client/server /proc sample
Pigsty full-window range

统计不 reset。PostgreSQL cumulative statistics 默认有更新延迟,并可能在 transaction 内缓存 snapshot;官方 viewing statistics 解释了 PGSTAT_MIN_INTERVALstats_fetch_consistencypg_stat_clear_snapshot()。runner 在 run 后等待 flush,并比较 reset timestamp。

26.3.4 绝对性能结论只适用于记录过的环境

一条绝对数字的完整名称

不是:

PostgreSQL = 2920 TPS

而是:

pg36_shop shop-mix-v1
S dataset, 8 closed-loop clients, zero think time
prepared persistent sessions
direct pg-meta-1 -> pg-test-1 primary
PostgreSQL 18.4, Pigsty v4.4.0 sandbox
five 8-second teaching runs
median 2920.5 TPS
pooled p95 9.448 ms
zero observed failures/late
server work median 84.2%

标题很长,因为 claim 的边界本来就长。

absolute capacity 与 regression baseline 分开

短、噪声较大的实验仍可做 CI regression signal:

same controlled environment
same workload
same run order
candidate vs baseline interleaved

它不能自动成为生产采购依据。

用途证据要求
script smoke能运行、事务语义正确
CI regression相对、matched、噪声已知
sandbox demand estimateOS/PG evidence + 明确边界
production capacityrepresentative environment + SLO load
procurementproduction candidate hardware + failure/maintenance

环境变化触发 rebaseline

至少在以下变化后重建:

  • PostgreSQL/Pigsty/kernel/extension 升级;
  • CPU、RAM、storage、filesystem、VM type;
  • shared_buffers、WAL、checkpoint、I/O、pool 配置;
  • schema/index/partition/constraint;
  • query/plan/protocol;
  • operation mix、key skew、data size/bloat;
  • backup/replica/topology;
  • application retry/timeout/admission;
  • SLO 或 failure model。

不能把三年前、旧 instance type 的 TPS 线性乘 CPU 核数。

外推必须带假设

参考 CPU 投影:

$$ \lambda_{65}

\frac{0.65}{D_{cpu}} $$

这是 local linear model。它没有证明:

$$ D_{cpu}(\lambda)

\text{constant} $$

靠近 contention/knee 后,service demand 可能随负载增加。外推距离越大,越需要 新测量点。

生产 gate 的证据矩阵

维度sandbox referenceproduction gate
hardwareshared laptop VMstarget compute/storage/network
run length8 s × 5minutes/hours covering cycles
arrivalclosed-loop c1/c8open-loop rate sweep
pathdirect primaryapp + HAProxy + PgBouncer
datasynthetic S/M/Lrepresentative shape/skew/bloat
failurenonedeclared node/path loss
maintenanceobserved incidentalscheduled overlap
backupincidentalbackup/restore window
cachewarm-naturaldeclared warm/cold scenarios
approvalsandbox passedindependent review

任何一行空缺,production claim 就保持 pending。

可信实验检查单

  • target 与 production boundary 已验证;
  • load generator 和 server 身份独立;
  • source、版本、配置、schema、data 有 hash/manifest;
  • seed 与 run order 固定;
  • warm-up 的对象与限制明确;
  • 正式时长覆盖所需周期;
  • 每个 cell 有足够重复;
  • raw transaction sample 可重算 quantile;
  • failed/retry/late/skipped 没有消失;
  • client/server/PG/Pigsty 证据时间对齐;
  • statistics reset timestamp 未变化;
  • cache/background/neighbor 被观察;
  • outlier rule 预先声明;
  • absolute、relative、production claim 分级;
  • unknown 与 cleanup evidence 同时发布。

上一节:设计代表性工作负载 · 返回本章目录 · 下一节:找到饱和点与瓶颈 · 查看全书目录 · 查看索引中心

最后更新于