跳至内容
26.6 实战:`pg36_shop` 容量基线

26.6 实战:`pg36_shop` 容量基线

本节把前五节收敛成一份可执行实验。目标不是发布一个炫目的 TPS,而是完整回答:

在记录过的 Pigsty 教学沙箱上,pg36_shop 的固定读写混合,面对三档合成 数据规模和一、八两个并发档位时,吞吐、延迟、CPU、I/O、WAL 与空间怎样变化?

实验边界刻意很窄:

environment   Pigsty disposable teaching sandbox
server        pg-test-1, primary, 1 vCPU, about 2 GiB
client        pg-meta-1, separate host, 2 vCPU, about 4 GiB
PostgreSQL    18.4
path          direct primary :5432
workload      synthetic shop-mix-v1
arrival       closed-loop, zero think time
protocol      prepared, persistent connections
scale         S / M / L
clients       1 / 8
repetitions   5 per cell

它不测 HAProxy、PgBouncer、应用、WAN,也不审批生产容量。完整合同在 lab-contract.md,公共参考结果在 capacity-run.json

26.6.1 运行三个规模和两个并发档位

先辨认风险等级

本章 runner 有六个动作:

动作等级行为
lintL0本地校验合同和反例
captureL0只读采集目标身份与环境
exerciseL2 bounded创建、装载、压测、清理专用 fixture
verifyL0验证已有证据
reviewL0独立复算与审阅已有证据
allL2 bounded顺序执行完整流程

exercise/all 会真实消耗 CPU、I/O、WAL、复制与归档资源。只允许在 disposable teaching sandbox 运行,绝不能把“只创建测试库”误判为 L0。

runner 只接受:

cluster       pg-test
primary       pg-test-1 / 10.10.10.11
database      pg36_capacity
role          dbuser_pg36bench
environment   pg36-l2-vagrant disposable teaching sandbox

若目标身份、primary、marker、预存对象或 session 状态不符合合同,它会 fail closed,而不是“尽量继续”。

fixture 与权限边界

实验创建两个一次性对象:

database  pg36_capacity
login     dbuser_pg36bench, non-superuser

库和角色都带本次 run 的 shared-object comment marker。清理前必须重新读回 marker, 并确认没有非本章会话;清理不用:

DROP DATABASE ... WITH (FORCE)

也不终止其他 session。

Pigsty 当前内网 HBA 通过 +dbrole_readonly 角色组识别业务连接。runner 不修改 HBA; 它把临时 login 加入 dbrole_readwrite,后者已间接属于 readonly 组,同时把临时 membership 设置为:

INHERIT FALSE
SET FALSE

这个 membership 只参与 HBA member 判断,不允许 benchmark login 继承或切换成平台 角色。实验结束后角色与 membership 一并删除。

这条细节揭示了压测中常见的错误:为了“让测试先跑起来”修改 HBA、使用超级用户, 最终测到的 connection/auth path 与生产不同,还留下永久权限。

数据规模

setup.sql 用固定 seed 生成三档 synthetic data:

scalefactorcustomersproductshistorical orders
S110,0002,000100,000
M880,00016,000800,000
L32320,00064,0003,200,000

参考 run 实际初始化结果:

scaleschema bytesdatabase bytesinitialization
S29,704,19237,869,2472.17 s
M235,175,936243,594,94317.09 s
L942,268,416950,982,33584.82 s

三档不是简单复制行数。每次切换 scale 都重新生成、ANALYZE 并捕获 row count、 relation size 与统计 provenance。压测报告必须保存 observed size,而不是只写 scale=32

事务合同

operationweightkey distributiontransaction
read-product50%product Zipf 1.15商品与库存点查
read-order30%customer Zipf 1.08最近五个历史订单
place-order20%customer uniform、product Zipf 1.10行锁、扣库存、追加订单

三个 pgbench script 分开保存:

每个 script 自己定义 transaction 边界。place-order 的锁和库存检查是 workload 语义的一部分,不应为了提高 TPS 删除。

实验矩阵

每个 scale 分别运行 c1、c8,每个 cell 五次:

3 scales × 2 client counts × 5 repetitions = 30 measured runs

每次:

5 s natural warm-up
8 s measured window
prepared protocol
persistent connection
zero think time
250 ms latency limit
max tries = 1

并发顺序做 counterbalance,seed 由公开公式从 scale、clients、repetition 推导。 这不能消除所有 background noise,但能避免总是先跑 c1、后跑 c8 的固定时间偏差。

runner 明确禁止:

statistics reset
OS cache drop
forced checkpoint
PostgreSQL restart
failover
autovacuum pause
archive/replica/backup pause
fsync or synchronous_commit disable
unlogged production-equivalent tables

如果 baseline 只有在关掉 durability 后才“通过”,它没有测量目标系统。

运行方法

先做不连接目标的本地检查:

static/labs/ch26/task.sh lint

完整实验要使用一个不存在或为空的 私密绝对路径

export PG36_EVIDENCE_DIR=/absolute/private/new-empty/ch26-run
static/labs/ch26/task.sh all

也可分步:

export PG36_EVIDENCE_DIR=/absolute/private/new-empty/ch26-run
static/labs/ch26/task.sh capture
static/labs/ch26/task.sh exercise
static/labs/ch26/task.sh verify
static/labs/ch26/task.sh review

capture/all 拒绝覆盖非空 evidence directory。verifyreview 可以反复读取 已有 evidence,不重跑 workload。

不要把私密目录放进 web root 或 Git。它包含 raw transaction log、system sample、 SQL snapshot 和监控响应;公共仓库只提交经过 allowlist 的摘要。

参考 run 的结果

正式参考 run:

run id          6c44ebdb-2206-48c3-8089-d90fdff45204
transactions    511,709
measured runs   30
failures        0
late            0
deadlocks       0
temp bytes      0

每个 cell 的 TPS 是五次 repetition 的中位数;括号是 repetition bootstrap 95% 区间。latency quantile 从该 cell 的 raw transaction sample 合并后重算,不是 五个 p95 再取平均。

cellTPS median (bootstrap 95%)p50p95p99max
S-c11,508.5 (1,446.5–1,546.3)0.3342.1522.59520.434
S-c82,920.5 (2,869.3–3,005.1)1.3349.44812.92756.481
M-c11,555.8 (1,462.0–1,570.1)0.3302.1232.59810.774
M-c82,911.5 (2,755.4–2,930.3)1.3709.39812.57141.174
L-c11,423.6 (1,226.1–1,431.4)0.3602.2592.84730.307
L-c82,774.3 (2,216.1–2,824.4)1.44710.08714.49494.330

latency 单位均为 ms。L-c8 的第一次 repetition 约 2,216 TPS,后续约 2,740–2,824 TPS;实验没有把它删成“异常值”。它可能包含初始化/cache/background 效应,宽区间本身就是证据,下一轮应延长 duration 并增加 repetition。

PostgreSQL 官方 pgbench good practices 明确提醒,数秒测试不能得到可信的平均值,且 client machine 也可能成为瓶颈。 所以这组 8 s × 5 结果用于教学如何建立证据管道和发现下一问题,不是 production characterization。

26.6.2 用 Pigsty 与原生视图解释饱和点

四层证据各回答一个问题

每个 measured run 同时采集:

pgbench transaction log
  -> client observed completion and latency distribution

PostgreSQL start/end snapshots
  -> committed work, blocks, temp, deadlock, WAL delta

PostgreSQL wait samples
  -> active session currently executing or waiting for what

client/server OS samples
  -> CPU work/iowait and load-generator saturation

Pigsty time series
  -> full exercise context and independent corroboration

不能用其中一层替代所有层。例如 p95 增加并不能单独证明 CPU 饱和;CPU 84% 也不能 单独证明 p95 来自 CPU queue。

sampler 必须严格裁剪 measured window

原始 sampler 从 warm-up 前启动、pgbench 后结束是正常的,但 cell arithmetic 必须 只使用:

measured pgbench start <= sample timestamp <= measured pgbench end

参考实现保存 start/end monotonic timestamp,并对 client/server sample 严格裁剪。 如果把 pgbench 结束后两秒 idle tail 算入 CPU median,单位事务 CPU 会被系统性低估。 增加 repetition 无法修复这种 systematic error。

Pigsty query 的 full exercise window 则故意保留:

initialization
warm-up
measured runs
between-run gaps
cleanup

因此:

native per-run evidence
  authoritative for cell arithmetic

Pigsty full-window evidence
  authoritative for context and corroboration

两个窗口不能直接做逐项相等断言。

server 已进入高 CPU 区域,client 没有饱和

参考结果:

cellserver work medianserver iowait medianclient work median
S-c148.94%7.66%17.74%
S-c884.19%0.21%29.50%
M-c150.10%7.52%17.79%
M-c883.96%0.20%29.07%
L-c146.31%8.20%16.17%
L-c884.22%0.31%27.81%

client 是独立 2-vCPU 主机,c8 work ratio 仍低于 30%;当前证据反对“load generator 已先饱和”这一解释。server 只有 1 vCPU,c8 约 84% work,CPU pressure 是合理的 候选瓶颈。

但不能立即下结论:

root cause = CPU

还要检查 active/wait、lock、WAL sync、I/O、context switch 与 plan。单核上的 84% aggregate work 也不等于每一毫秒都有可运行 SQL。

throughput 增长约 1.9 倍,p95 放大约 4.4 倍

scalec8/c1 TPSc8/c1 p95
S1.936x4.390x
M1.871x4.427x
L1.949x4.465x

解释:

c1 -> c8
  throughput still rises materially
  tail latency rises much faster
  server CPU moves toward high utilization

这说明 queue/competition 已增加,却仍不能定位精确 knee。只有两个并发点:

exact_knee_known = false
interpretation = knee-not-bracketed-by-one-and-eight-clients

下一轮至少补:

c2 c4 c8 c12 c16 c24 c32

并用更长 duration。若目标是 arrival SLO,再做 open-loop rate sweep,记录 offered、 achieved、late、skipped、failure 和 schedule lag。

数据规模开始改变 I/O 行为

cellblock readsblock hits
S-c10675,976
S-c801,323,054
M-c10733,198
M-c801,326,143
L-c12,500629,335
L-c814,4911,344,632

S/M 的 measured window 没记录到 PostgreSQL block read,L 则开始读 block。这里的 边界是:

  • 自然 warm cache,不代表 cold start;
  • PostgreSQL block read 可能由 OS page cache 服务;
  • 短窗口未覆盖完整 working set;
  • aggregate hit ratio 会掩盖特定 table/index;
  • 虚拟磁盘不能代表 production storage。

下一轮可以选择:

保持自然状态,延长到跨越多个 working-set cycle
增加 XL 数据量,使 working set 显著超过 RAM
分别测试 steady warm 和 restart-recovery 场景
从 pg_stat_io 分解 relation/temp/WAL context

不要为了“可重复 cold cache”在共享主机随意 drop_caches。那是高影响系统动作, 而且同时改变整台主机。

WAL 与持久化证据

cellWAL bytes/mixed txdurable bytes/place-order
S-c1160.33415.21
S-c8159.53409.12
M-c1175.95412.81
M-c8161.88405.97
L-c1157.56411.50
L-c8196.18407.88

这些值是窗口 delta 除以相应分母,不是 PostgreSQL 配置常量。L-c8 的 WAL/tx 较高 可能与 page state、full-page images 或背景活动有关;仅凭 aggregate delta 不能 定位。下一轮应同时比较:

wal_records
wal_fpi
wal_bytes
checkpoint timing
per-operation successful count

实验没有关闭 fsyncfull_page_writessynchronous_commit,也没有强制 checkpoint。若要研究 checkpoint phase,应把它作为显式 factor,而不是偷偷在每次 run 前 checkpoint。

lock、failure 与 temp 的反证

30 个 measured run 中:

failed transactions  0
late transactions    0
deadlocks            0
temp bytes           0

这反对“当前点因 deadlock/temp spill 失效”,但不能证明:

there is no lock wait
there can never be a deadlock
all queries are memory-safe
250 ms SLO will hold at higher offered load

deadlock counter 只计被 detector 处理的 deadlock;普通 row-lock queue 不是 deadlock。temp_bytes=0 也只针对本窗口与当前 plan/data。

在 Pigsty 中做独立旁证

Pigsty 的 PGSQL dashboards 把 Overview、Activity、Persist、Database、Table、Query 等视角连接起来。实验时 至少对齐:

PGSQL Activity
  active/wait/backend state

PGSQL Persist
  WAL/checkpoint/archive/replication

PGSQL Database
  commit/rollback/cache/temp/deadlock

PGSQL Table / Query
  relation workload and statement behavior

NODE / disk dashboards
  client and server CPU, disk, network

参考 run 的 Pigsty full-window 摘要:

signalmedianmaximum
server CPU busy64.37%100.00%
server CPU iowait2.77%5.10%
client CPU busy23.00%28.97%
server disk read0.41 MiB/s13.34 MiB/s
server disk write4.69 MiB/s77.45 MiB/s
WAL generation44.04 MiB/s
database commit2,459.53/s
replica replay byte gap0 median3,471,848 max

full-window 含初始化和间隔,所以 commit max 不应等于 cell TPS,disk write max 也不应 除以 measured transaction。它们用于确认“什么时候发生了什么”和检查观测层是否 互相矛盾。

replica replay byte gap 最大约 3.47 MiB,不能直接转译为:

replica stale for N milliseconds

byte gap、replay time lag、应用 freshness 与 synchronous durability 是不同语义。 容量实验只记录这一旁证,没有声称 replica correctness。

建立 hypothesis table

这次实验后的调查表:

假设支持证据反证/缺口下一实验
c8 有 CPU pressureserver work ~84%、client <30%无 run queue/更密曲线c2–c32 sweep
client 先饱和client work <30%增大 clients 并监控 client queue
L 进入数据读路径block reads >0可能命中 OS cacheXL、pg_stat_io、更长运行
lock 是主瓶颈写事务会行锁无 deadlock,wait 需更细分提高热点/写比例
WAL 限制 throughputL-c8 WAL/tx 增加iowait 低,缺 WAL fsync tailwrite-only + checkpoint factor
exact knee 已知仅 c1/c8密集 closed/open-loop sweep

这张表比“CPU 是瓶颈”更有用,因为它告诉下一笔实验预算该花在哪里。

26.6.3 输出可复现实验报告、容量模型和未知项

私密 evidence bundle

完整运行产生:

preflight-evidence.json
remote-benchmark.log
remote/
  capacity-evidence.json
  initialize-{S,M,L}.txt
  runs/<cell-run>/
    pgbench.stdout
    pgbench.stderr
    transactions.*
    stats-before.json
    stats-after.json
    client-system.jsonl
    server-system.jsonl
    database-waits.jsonl
remote-cleanup.json
validation-report.json
negative-report.json
public-summary.json
review.txt

证据包目录权限为 0700,文件为 0600。review 会检查 mode,避免 raw evidence 因默认 umask 变成多用户可读。

raw transaction log 对 latency 重算必不可少,却可能含时间、client/session 与 workload 行为;SQL snapshot、queryid 明细和监控 payload 也不应直接公开。发布流程 是:

private raw evidence
  -> schema and invariant validation
  -> independent recomputation
  -> explicit allowlist
  -> public capacity-run.json

不是“从 raw JSON 删除几个看起来敏感的键”。

报告中的 provenance

一份可复现实验报告至少固定:

run id and timestamps
target identity and topology
client/server hardware
PostgreSQL/Pigsty/OS version
settings with source
schema and source hashes
data generator and seeds
connection path and protocol
workload scripts and weights
arrival model and retry policy
warm-up/duration/repetition/order
measured-window boundaries
raw artifact inventory
cleanup evidence
known unknowns and claim gate

参考 run 验证了 255 个 raw artifact,并对约 21.8 MB evidence 做 secret scan。这个 数字不是质量本身;它的意义在于 manifest 能证明报告引用的 sample 没有悄悄缺失。

对抗性验证

negative-cases.json 定义了 26 个变体,validator 必须逐一拒绝。类别包括:

wrong target / primary / environment
pre-existing or marker-mismatched fixture
client and server accidentally same host
missing runs or unbalanced matrix
wrong script weight / seed / protocol
statistics reset
sampler outside measured window
client saturation hidden
quantiles averaged instead of recomputed
failed/late/skipped hidden
cleanup unproven
raw/query/secret fields leaked into public summary
exact knee or production TPS fabricated

“positive fixture 能通过”只证明 happy path;错误 evidence 也能通过的 validator 不能保护结论。

参考 run 的验收:

positive validation       passed
counterexamples rejected  26 / 26
database cleanup          verified
role cleanup              verified
remote temp cleanup       verified
nonchapter sessions killed 0
production gate           pending

容量模型只发布条件结论

公共报告给出的 65% CPU 投影:

scaleCPU-s/mixed txconditional TPS at 65% CPU
S0.0002912,236.79
M0.0002922,222.96
L0.0003072,116.98

它依赖四个显式假设:

50/30/20 mix unchanged
CPU demand near observation scales linearly
cache/IO/lock/client/background work does not become tighter
same recorded sandbox

所以报告写:

{
  "production_sustainable_tps": null,
  "exact_knee_known": false
}

null 不是实验失败,而是 claim 与 evidence 对齐。

未知项就是下一轮 backlog

本轮没有回答:

  1. c8 左右的精确 knee 在哪里;
  2. open-loop offered rate 下的 completion、late、skipped 与 queue;
  3. 真实应用经 HAProxy/PgBouncer/TLS 的端到端成本;
  4. realistic think time、connection churn、retry storm;
  5. 真实 query 和 tenant/key skew;
  6. cold/restart、working set 超 RAM 与 production storage;
  7. checkpoint、autovacuum、backup overlap;
  8. replica loss、failover、rebuild、archive outage;
  9. 长稳态下 bloat、WAL、slot 与空间增长;
  10. production hardware 的 N+1 SLO envelope。

将它们排成下一轮最小实验:

Experiment A
  same sandbox, M scale
  c2/c4/c8/c12/c16/c24/c32
  5 min × repetitions
  purpose: bracket knee

Experiment B
  rates around accepted SLO point
  open-loop, realistic timeout/retry
  purpose: offered-load envelope

Experiment C
  direct / PgBouncer / HAProxy / application path
  same workload and target
  purpose: attribute service-path cost

Experiment D
  accepted load + maintenance/failure scenarios
  purpose: validate headroom

Experiment E
  production-like hardware and data
  N0 and N1
  purpose: production capacity decision

一次只改变少量 factor,保留 control;否则结果发生变化时无法归因。

独立练习

练习一:改变 mix。

把写比例从 20% 提到 40%,保持总权重 100%。运行 lint,解释哪些合同 hash、WAL 模型 和 capacity claim 必须失效。不要直接修改并覆盖正式参考结果。

练习二:增加并发点。

复制 experiment contract 到个人分支,加入 c2/c4/c12/c16,设计 counterbalanced order。 先写出“怎样才算 bracket knee”的 machine-checkable rule,再运行。

练习三:检查窗口偏差。

从一个私密 evidence bundle 取某次 run,分别计算:

all sampler records CPU median
strict measured-window CPU median

解释 idle tail 对 CPU-s/transaction 和 65% 投影的方向性影响。

练习四:设计 production gate。

为自己的应用补齐:

SLO
operation catalog
arrival forecast
N+1 topology
maintenance overlap
lead time
pass/fail rule
stop condition

如果仍写不出 production sustainable TPS,保留 null,并列出最小缺失证据。

完成检查表

  • 能区分 business request、database transaction 与 SQL statement。
  • workload mix、arrival model、key distribution 和 connection path 已固定。
  • client 与 server 分离,且两侧都监控。
  • warm-up、duration、repetition、seed 与 run order 可复现。
  • per-run sample 严格裁剪 measured window。
  • quantile 从 raw sample 重算,没有平均 p99。
  • PostgreSQL counter 以 snapshot delta 计算,没有 reset shared stats。
  • Pigsty full-window 指标没有冒充 cell arithmetic。
  • failure、late、skipped、deadlock、temp 与 cleanup 均显式报告。
  • unit resource cost 的分母是明确 operation。
  • exact knee 不由两个并发点伪造。
  • production TPS 在生产证据不足时保持 null
  • 未知项已变成带目的和 pass/fail rule 的下一实验。

做到这些,容量压测才从“跑过一张 TPS 截图”变成可重复、可反驳、可用于决策的工程 过程。


上一节:从测量推导容量与成本 · 返回本章目录 · 下一章:精益求精:参数调优与资源治理 · 查看全书目录 · 查看索引中心

最后更新于