2.5 最小 pgbench 工作负载
pgbench 既能运行内置类 TPC-B 工作负载,也能执行自定义事务脚本。本节只用它验证“连接—变量—事务—查询—结果采集”链路;20 次 tiny 查询不足以评价 PostgreSQL、Pigsty、硬件或参数。
2.5.1 初始化自定义脚本与参数
pgbench -i 会创建它自己的 pgbench_accounts、pgbench_branches 等内置基准表。本章不使用这些对象;我们的“初始化”是先运行 setup.sql,得到 100 行确定性夹具,再运行自定义工作负载:
\set fixture_id random(1, 100)
BEGIN;
SET LOCAL ROLE pg36_owner;
SELECT payload
FROM shop.ch02_fixture
WHERE fixture_id = :fixture_id;
COMMIT;pgbench 脚本的 \set 与 psql 元命令不是同一套完整语言。这里调用 pgbench 的 random(min, max) 表达式,把结果存为变量;SQL 中 :fixture_id 再被替换为整数。
自定义脚本不会自动包裹事务。显式 BEGIN/COMMIT 让“一次 pgbench transaction”对应一次数据库事务。SET LOCAL ROLE 只在该事务内切换为对象 owner,提交后自动恢复;它服务于本章管理员直连实验,不是应用运行时的推荐身份。
先确认夹具:
psql -X -w "service=pg36-admin" \
-v ON_ERROR_STOP=1 \
-f verify.sql再运行:
pgbench \
--random-seed=20260729 \
--no-vacuum \
--client=1 \
--jobs=1 \
--transactions=20 \
--report-per-command \
--file=workload.sql \
"service=pg36-admin application_name=pg36-ch02-pgbench"参数意图:
| 参数 | 本章取值 | 原因 |
|---|---|---|
--random-seed | 20260729 | 固定单线程随机选择序列;放在前面确保覆盖所有随机用途 |
--no-vacuum | 开启 | 不去处理不存在的内置 pgbench 表 |
--client | 1 | 避免并发调度干扰教学输入 |
--jobs | 1 | 保持一个工作线程 |
--transactions | 20 | 让验收快速、计数精确 |
--report-per-command | 开启 | 观察脚本各命令是否执行 |
--file | 自定义脚本 | 不运行默认内置业务 |
这里通过 Pigsty default 服务 5436,路径是 HAProxy 到当前主库 PostgreSQL,不经过 PgBouncer。若改用 primary 服务 5433,必须先确认登录角色已安全配置密码并进入 PgBouncer 用户清单。两次结果属于不同连接路径,不能直接混为一个基准。
pgbench 客户端版本应与清单一并保存。自定义脚本语法和输出字段会随 PostgreSQL 版本演进;不要从另一台机器拿一个未知版本客户端就假设完全等价。
2.5.2 区分吞吐、延迟、错误与环境噪声
一次正确运行的关键输出类似:
number of clients: 1
number of threads: 1
number of transactions per client: 20
number of transactions actually processed: 20/20
number of failed transactions: 0 (0.000%)
latency average = ...
initial connection time = ...
tps = ... (without initial connection time)本章真正验收的是:
- 客户端、线程与事务数符合命令;
20/20个事务实际完成;- failed transactions 为
0; - 标准错误中没有连接、SQL 或变量错误;
- 运行前后夹具校验和一致,因为工作负载只读。
其余数字需要先理解口径:
| 指标 | 回答的问题 | 不能单独回答什么 |
|---|---|---|
| TPS | 该脚本在当前运行条件下每秒完成多少事务 | 单条业务请求能力、生产容量 |
| 平均延迟 | 客户端观察到的平均事务时间 | 尾延迟、每条 SQL 的服务端执行时间 |
| initial connection time | 建立测试连接所需时间 | 长连接应用的稳态延迟 |
| per-command latency | 脚本每类命令的客户端耗时 | 并发下每次调用的完整分布 |
| failed transactions | pgbench 判定失败的事务数 | 所有业务错误和数据正确性 |
固定事务数时,测试持续时间很短,任何一次调度抖动都可能大幅改变 TPS。平均值还会隐藏最慢请求;正式测试至少需要时长、延迟分布、错误分类、预热和重复运行。
噪声从哪里来
即使脚本与种子完全相同,结果仍可能受到:
- 客户端 CPU、时钟与 pgbench 版本;
- DNS、网络、HAProxy 与是否经过 PgBouncer;
- PostgreSQL 数据页和操作系统页缓存;
- autovacuum、检查点、日志、备份与其他会话;
- 虚拟机 steal、CPU 频率、NUMA 和磁盘队列;
- 监控采样与终端输出;
- 第一次连接和第一次执行的初始化成本。
“第二次更快”经常只是缓存变热;“经连接池更慢”可能只是路径、认证和测量窗口不同。没有对照、重复和环境清单时,不应把相关性写成因果。
错误是一级指标
不要为追求 TPS 把错误行藏起来。若使用 --latency-limit,晚于阈值的事务会单独计数;若启用可重试错误处理,还要区分原始失败、重试和最终失败。一个吞吐更高但超时或失败更多的结果通常更差。
本章没有注入并发错误,因此只要求零失败。ch10 会制造隔离与冲突,ch26 才建立完整性能报告。
2.5.3 本节只建立可复现基线,不做性能结论
这里的“基线”指可重放的工作负载定义,不是可外推的性能基线。我们冻结了:
- 数据集:100 行、确定公式、固定校验和;
- 查询:按 1–100 的 ID 读取一行 payload;
- 事务:显式
BEGIN、一次查询、COMMIT; - 随机输入:单客户端、单线程、固定 seed;
- 运行量:20 个事务;
- 连接路径:清单中命名的 Pigsty service;
- 成功条件:20/20、零失败、数据摘要不变。
我们没有冻结操作系统调度、CPU、缓存、网络或后台活动,因此绝不写“应达到 N TPS”。读者在本地实测看到几百、几千或几万 TPS,都只能说明这个 tiny 任务在那个瞬间的观察值。
做一次反证
连续运行两次同一命令:
for run_id in 1 2; do
pgbench \
--random-seed=20260729 \
-n -c 1 -j 1 -t 20 -r \
-f workload.sql \
"service=pg36-admin application_name=pg36-ch02-run-${run_id}" \
>"evidence/ch02/pgbench-${run_id}.txt" \
2>"evidence/ch02/pgbench-${run_id}.stderr"
done两个文件应具有相同事务数与零失败,但 latency 和 TPS 通常不会完全相同。这正好证明“确定输入”与“确定耗时”是两件事。
若两次选择的 fixture ID 也需要逐项核对,可以让工作负载把 ID 写入单独的审计结果;但写日志本身会改变测量。任何观测都会有成本,测试设计要说明成本是否在比较双方中一致。
何时才允许谈性能
到 ch26,至少补齐:
- 明确问题:容量、回归、极限还是组件对比;
- 代表性数据量与事务比例;
- 预热、持续时间、并发阶梯与重复次数;
- 硬件、内核、容器/虚拟化和存储清单;
- 客户端是否成为瓶颈;
- 平均值、分位数、错误、饱和指标和置信边界;
- 数据库、系统与 Pigsty 监控证据;
- 测试后状态和可复位性。
本章的小负载只是让未来这些测试拥有一个已经验证的入口。
本节验收
- 能解释为什么自定义脚本仍需显式事务;
- 能说明
--random-seed固定什么、不固定什么; - 运行结果为 20/20 且零失败;
- 运行前后
verify.sql校验和一致; - 报告不把某次 TPS 当作 PostgreSQL 或 Pigsty 性能承诺。
参考资料
上一节:输入、输出与确定性数据 · 返回本章目录 · 下一节:最小逻辑备份闭环 · 查看全书目录 · 查看索引中心