跳至内容
2.5 最小 pgbench 工作负载

2.5 最小 pgbench 工作负载

pgbench 既能运行内置类 TPC-B 工作负载,也能执行自定义事务脚本。本节只用它验证“连接—变量—事务—查询—结果采集”链路;20 次 tiny 查询不足以评价 PostgreSQL、Pigsty、硬件或参数。

2.5.1 初始化自定义脚本与参数

pgbench -i 会创建它自己的 pgbench_accountspgbench_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 脚本的 \setpsql 元命令不是同一套完整语言。这里调用 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-seed20260729固定单线程随机选择序列;放在前面确保覆盖所有随机用途
--no-vacuum开启不去处理不存在的内置 pgbench 表
--client1避免并发调度干扰教学输入
--jobs1保持一个工作线程
--transactions20让验收快速、计数精确
--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 transactionspgbench 判定失败的事务数所有业务错误和数据正确性

固定事务数时,测试持续时间很短,任何一次调度抖动都可能大幅改变 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,至少补齐:

  1. 明确问题:容量、回归、极限还是组件对比;
  2. 代表性数据量与事务比例;
  3. 预热、持续时间、并发阶梯与重复次数;
  4. 硬件、内核、容器/虚拟化和存储清单;
  5. 客户端是否成为瓶颈;
  6. 平均值、分位数、错误、饱和指标和置信边界;
  7. 数据库、系统与 Pigsty 监控证据;
  8. 测试后状态和可复位性。

本章的小负载只是让未来这些测试拥有一个已经验证的入口。

本节验收

  • 能解释为什么自定义脚本仍需显式事务;
  • 能说明 --random-seed 固定什么、不固定什么;
  • 运行结果为 20/20 且零失败;
  • 运行前后 verify.sql 校验和一致;
  • 报告不把某次 TPS 当作 PostgreSQL 或 Pigsty 性能承诺。

参考资料


上一节:输入、输出与确定性数据 · 返回本章目录 · 下一节:最小逻辑备份闭环 · 查看全书目录 · 查看索引中心

最后更新于