Caution

pgbench

pgbench —在 PostgreSQL 上运行基准测试

Synopsis

pgbench -i [ option ...] [ dbname ]

pgbench [ option ...] [ dbname ]

Description

pgbench 是一个用于在 PostgreSQL 上运行基准测试的简单程序。它可能在多个并发数据库会话中反复运行相同的 SQL 命令序列,然后计算平均事务速率(每秒事务数)。默认情况下,pgbench 测试基于 TPC-B 的松散方案,每个事务涉及五个SELECTUPDATEINSERT命令。但是,通过编写自己的事务脚本文件来测试其他情况很容易。

pgbench 的典型输出如下所示:

transaction type: <builtin: TPC-B (sort of)>
scaling factor: 10
query mode: simple
number of clients: 10
number of threads: 1
number of transactions per client: 1000
number of transactions actually processed: 10000/10000
tps = 85.184871 (including connections establishing)
tps = 85.296346 (excluding connections establishing)

前六行报告一些最重要的参数设置。下一行报告已完成和打算进行的 Transaction 数量(后者仅是 Client 数量与每个 ClientTransaction 数量的乘积);除非运行在完成前失败,否则它们将相等。 (在-T模式下,仅打印实际的事务数.)最后两行报告每秒的事务数,计算有无开始数据库会话的时间。

默认的类似 TPC-B 的事务测试需要预先设置特定的表。应该使用-i(初始化)选项调用 pgbench 来创建和填充这些表。 (在测试自定义脚本时,不需要此步骤,但是需要执行测试所需的任何设置.)初始化看起来像:

pgbench -i [ other-options ] dbname

其中* dbname *是已创建的要测试的数据库的名称。(您可能还需要-h-p和/或-U选项来指定如何连接到数据库服务器.)

Warning

pgbench -i创建四个表pgbench_accountspgbench_branchespgbench_historypgbench_tellers,销毁这些名称的任何现有表。如果您的表具有这些名称,请务必小心使用其他数据库!

默认的“比例因子”为 1 时,表最初包含以下行:

table                   # of rows
---------------------------------
pgbench_branches        1
pgbench_tellers         10
pgbench_accounts        100000
pgbench_history         0

您可以(并且,出于大多数目的,应该)通过使用-s(比例因子)选项来增加行数。此时也可以使用-F(填充因子)选项。

完成必要的设置后,您可以使用不包含-i的命令来运行基准测试,即

pgbench [ options ] dbname

在几乎所有情况下,您都需要一些选项来进行有用的测试。最重要的选项是-c(Client 数量),-t(Transaction 数量),-T(时间限制)和-f(指定自定义脚本文件)。请参阅下面的完整列表。

Options

以下分为三个小节。在数据库初始化期间和运行基准测试时,会使用不同的选项,但是在两种情况下,某些选项都是有用的。

Initialization Options

pgbench 接受以下命令行初始化参数:

Benchmarking Options

pgbench 接受以下命令行基准测试参数:

使用限制(--rate=...)时,比计划滞后时间多* limit * ms 的事务将因此根本不发送到服务器,因此它们不希望达到延迟限制。它们被计数并分别报告为“已跳过”。

默认为简单查询协议。 (有关更多信息,请参见Chapter 53。)

通过按照 Poisson 分配的时间表时间线开始 Transaction 来确定汇率。预期的开始时间表是根据 Client 首次启动的时间而不是先前的事务结束的时间而向前移动。这种方法意味着,当 Transaction 超过其原始计划的结束时间时,以后的 Transaction 有可能再次赶上。

当节流处于活动状态时,将根据计划的开始时间计算运行结束时报告的事务延迟,因此它包括每个事务必须 await 上一个事务完成的时间。await 时间称为调度延迟时间,它的平均值和最大值也分别报告。相对于实际事务开始时间的事务 await 时间,即在数据库中执行事务所花费的时间,可以通过从报告的 await 时间中减去调度延迟时间来计算。

如果将--latency-limit--rate一起使用,则事务可能会滞后很多,以至于上一个事务结束时它已经超过了延迟限制,因为延迟是根据计划的开始时间计算的。此类事务不会发送到服务器,但会完全跳过并单独计数。

较高的调度滞后时间表示系统无法使用选定数量的 Client 端和线程以指定的速率处理事务。当平均事务执行时间长于每个事务之间的计划时间间隔时,每个连续事务将进一步落后,并且调度滞后时间将随着测试运行时间的延长而持续增加。发生这种情况时,您将不得不降低指定的 Transaction 率。

就随机数而言,显式设置种子可以精确地再现pgbench运行。由于每个线程都 Management 随机状态,因此,如果每个线程有一个 Client 端并且没有外部或数据依赖性,则对于相同的调用,完全相同的pgbench运行。从统计的角度来看,精确复制运行是一个坏主意,因为它可以隐藏性能差异或不适当地提高性能,例如点击与上次运行相同的页面。但是,它对于调试也可能有很大帮助,例如重新运行棘手的情况导致错误。明智地使用。

在处理日志文件时,请记住要考虑采样率。例如,在计算 TPS 值时,您需要相应地将数字相乘(例如,以 0.01 的采样率,您只会得到实际 TPS 的 1/100)。

Common Options

pgbench 接受以下命令行通用参数:

Notes

在 pgbench 中实际执行的“事务”是什么?

pgbench 执行从指定列表中随机选择的测试脚本。它们包括带有-b的内置脚本和带有-f的用户提供的自定义脚本。可以给每个脚本一个@之后指定的相对权重,以更改其绘制概率。默认权重为1。权重为0的脚本将被忽略。

默认的内置事务脚本(也用-b tpcb-like调用)在随机选择的aidtidbiddelta上为每个事务发出七个命令。该方案的灵感来自 TPC-B 基准,但实际上不是 TPC-B,因此得名。

如果您选择simple-update内置(也是-N),则事务中不包含步骤 4 和 5.这样可以避免在这些表上发生更新争用,但是这会使测试用例像 TPC-B 一样少。

如果选择内置的select-only(也为-S),则仅发出SELECT

Custom Scripts

pgbench 通过使用从文件中读取的事务脚本(-f选项)替换默认事务脚本(如上所述)来支持运行自定义基准测试方案。在这种情况下,“事务”被视为脚本文件的一次执行。

脚本文件包含一个或多个以分号终止的 SQL 命令。空行和以--开头的行将被忽略。脚本文件还可以包含“元命令”,这由 pgbench 本身解释,如下所述。

Note

在 PostgreSQL 9.6 之前,脚本文件中的 SQL 命令以换行符终止,因此不能跨行 continue。现在,“ *”号是必需的,以分隔连续的 SQL 命令(尽管如果 SQL 命令后面跟有 meta 命令,则不需要一个分号)。如果您需要创建一个适用于新旧版本 pgbench 的脚本文件,请确保将每条 SQL 命令写在以分号结尾的一行上。

脚本文件有一个简单的变量替换工具。变量名称必须由字母(包括非拉丁字母),数字和下划线组成。可以通过上面说明的命令行-D选项或下面说明的 meta 命令来设置变量。除了-D命令行选项预设的任何变量外,还有一些自动预设的变量,列在Table 241中。使用-D为这些变量指定的值优先于自动预设。设置后,可以通过写入: * variablename *将变量的值插入到 SQL 命令中。当运行多个 Client 端会话时,每个会话都有自己的一组变量。

表 241.自动变量

Variable Description
client_id 标识 Client 端会话的唯一编号(从零开始)
default_seed 默认情况下,哈希函数中使用的种子
random_seed 随机生成器种子(除非被-D覆盖)
scale 当前比例因子

脚本文件元命令以反斜杠(\)开头,并且通常延伸到该行的末尾,尽管可以通过编写反斜杠-返回将它们 continue 到其他行。 meta 命令的参数由空格分隔。支持以下 meta 命令:

函数和大多数运算符在NULLImporting 上返回NULL

出于条件目的,非零数值是TRUE,零数值和NULLFALSE

如果没有为CASE提供最终的ELSE子句,则默认值为NULL

Examples:

\set ntellers 10 * :scale
\set aid (1021 * random(1, 100000 * :scale)) % \
           (100000 * :scale) + 1
\set divx CASE WHEN :x <> 0 THEN :y/:x ELSE NULL END

Example:

\sleep 10 ms

Example:

\setshell variable_to_be_assigned command literal_argument :variable ::literal_starting_with_colon

Example:

\shell command literal_argument :variable ::literal_starting_with_colon

Built-In Operators

Table 242中列出的算术,按位,比较和逻辑运算符内置在 pgbench 中,可以在\set中出现的表达式中使用。

表 242. pgbench 运算符的优先级提高

Operator Description Example Result
OR logical or 5 or 0 TRUE
AND logical and 3 and 0 FALSE
NOT logical not not false TRUE
IS [NOT] (NULL|TRUE|FALSE) value tests 1 is null FALSE
ISNULL|NOTNULL null tests 1 notnull TRUE
= is equal 5 = 4 FALSE
<> 不相等 5 <> 4 TRUE
!= 不相等 5 != 5 FALSE
< lower than 5 < 4 FALSE
<= 低于或等于 5 <= 4 FALSE
> greater than 5 > 4 TRUE
>= 大于或等于 5 >= 4 TRUE
| 整数按位或 1 | 2 3
# 整数按位异或 1 # 3 2
& 整数按位与 1 & 3 1
~ 整数按位非 ~ 1 -2
<< 整数左移 1 << 2 4
>> 整数按位右移 8 >> 2 2
+ addition 5 + 4 9
- subtraction 3 - 2.0 1.0
* multiplication 5 * 4 20
/ 除(整数将结果截断) 5 / 3 1
% modulo 3 % 2 1
- opposite - 2.0 -2.0

Built-In Functions

Table 243中列出的函数内置在 pgbench 中,可以在\set中出现的表达式中使用。

表 243. pgbench 函数

Function Return Type Description Example Result
abs(a) 与* a *相同 absolute value abs(-17) 17
debug(a) 与* a *相同 打印* a 到 stderr,然后返回 a * debug(5432.1) 5432.1
double(i) double 投双 double(5432) 5432.0
exp(x) double exponential exp(1.0) 2.718281828459045
greatest(a [, ... ] ) 如果有任何* a *为 double,则为 double,否则为整数 参数中的最大值 greatest(5, 4, 3, 2) 5
hash(a [, seed ] ) integer hash_murmur2()的别名 hash(10, 5432) -5817877081768721676
hash_fnv1a(a [, seed ] ) integer FNV-1a hash hash_fnv1a(10, 5432) -7793829335365542153
hash_murmur2(a [, seed ] ) integer MurmurHash2 hash hash_murmur2(10, 5432) -5817877081768721676
int(x) integer 转换为 int int(5.4 + 3.8) 9
least(a [, ... ] ) 如果有任何* a *为 double,则为 double,否则为整数 参数中的最小值 least(5, 4, 3, 2.1) 2.1
ln(x) double natural logarithm ln(2.718281828459045) 1.0
mod(i, j) integer modulo mod(54, 32) 22
pi() double 常数 PI 的值 pi() 3.14159265358979323846
pow(x, y), power(x, y) double exponentiation pow(2.0, 10) , power(2.0, 10) 1024.0
random(lb, ub) integer [lb, ub]中均匀分布的随机整数 random(1, 10) 110之间的整数
random_exponential(lb, ub, parameter) integer [lb, ub]中的指数分布随机整数,请参见下文 random_exponential(1, 10, 3.0) 110之间的整数
random_gaussian(lb, ub, parameter) integer [lb, ub]中的高斯分布随机整数,请参见下文 random_gaussian(1, 10, 2.5) 110之间的整数
random_zipfian(lb, ub, parameter) integer [lb, ub]中的 Zipfian 分布随机整数,请参见下文 random_zipfian(1, 10, 1.5) 110之间的整数
sqrt(x) double square root sqrt(2.0) 1.414213562

random函数使用均匀分布生成值,即所有值均以相等的概率绘制在指定范围内。 random_exponentialrandom_gaussianrandom_zipfian函数需要一个附加的 double 参数,该参数确定分布的精确形状。

f(x)= exp(-参数*(x-最小)/(max-最小 1))/(1-exp(-参数))

然后以f(i) - f(i + 1)的概率得出* min max *之间的值i1 *。

直观地,* parameter 越大,访问 min 的值的频率越高,访问 max *的值的频率越低。越接近 0 * parameter ,访问分布越平坦(越均匀)。分布的粗略估计是,在 min 范围内,最接近 1%的值被绘制为 parameter *%的时间。 * parameter *值必须严格为正。

f(x)= PHI(2.0 参数(x-mu)/(max-min 1))/
(2.0 * PHI(参数)-1)

然后以概率f(i + 0.5) - f(i - 0.5)绘制* min max *之间的值i1 。直观地, parameter 越大,绘制到区间中间的值越频繁,而接近 min max 边界的值越少。大约 67%的值来自中间的1.0 / parameter,即平均值附近的相对0.5 / parameter,而 95%的中间值2.0 / parameter则是平均值附近的1.0 / parameter;例如,如果 parameter 为 4.0,则从间隔的中间四分之一(1.0/4.0)(即从3.0 / 8.05.0 / 8.0)中抽取 67%的值,从间隔的中间一半(2.0 / 4.0)中抽取 95%的值(第二和第三个四分位数)。对于 Box-Muller 变换,最小值 parameter *为 2.0.

哈希函数hashhash_murmur2hash_fnv1a接受 Importing 值和可选的种子参数。如果未提供种子,则使用:default_seed的值,除非通过命令行-D选项设置,否则它将随机初始化。哈希函数可用于分散随机函数(例如random_zipfianrandom_exponential)的分布。例如,以下 pgbench 脚本模拟了社交媒体和博客平台典型的可能的现实工作负载,其中很少有帐户会产生过多的负载:

\set r random_zipfian(0, 100000000, 1.07)
\set k abs(hash(:r)) % 1000000

在某些情况下,需要几个彼此不相关的不同分布,这在隐式种子参数派上用场时:

\set k1 abs(hash(:r, :default_seed + 123)) % 1000000
\set k2 abs(hash(:r, :default_seed + 321)) % 1000000

例如,内置 TPC-B 类事务的完整定义是:

\set aid random(1, 100000 * :scale)
\set bid random(1, 1 * :scale)
\set tid random(1, 10 * :scale)
\set delta random(-5000, 5000)
BEGIN;
UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid;
SELECT abalance FROM pgbench_accounts WHERE aid = :aid;
UPDATE pgbench_tellers SET tbalance = tbalance + :delta WHERE tid = :tid;
UPDATE pgbench_branches SET bbalance = bbalance + :delta WHERE bid = :bid;
INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (:tid, :bid, :aid, :delta, CURRENT_TIMESTAMP);
END;

该脚本允许事务的每次迭代引用不同的随机选择的行。 (此示例还显示了为什么每个 Client 端会话都有自己的变量很重要,否则它们将不会独立地涉及不同的行.)

Per-Transaction Logging

使用-l选项(但不使用--aggregate-interval选项),pgbench 将有关每个事务的信息写入日志文件。日志文件将命名为prefix.nnn,其中* prefix 默认为pgbench_log nnn 是 pgbench 进程的 PID。可以使用--log-prefix选项更改前缀。如果-j选项为 2 或更高,则有多个工作线程,每个工作线程都有自己的日志文件。第一个工作程序将在其日志文件中使用与标准单工作程序情况下相同的名称。其他工作程序的其他日志文件将命名为prefix.nnn.mmm,其中 mmm *是每个工作程序从 1 开始的序号。

日志格式为:

client_id transaction_no time script_no time_epoch time_us [ schedule_lag ]

其中* client_id 指示哪个 Client 端会话运行了该事务, transaction_no 计算该会话已运行了多少个事务, time 是经过的总事务处理时间(以微秒为单位), script_no 标识使用了哪个脚本文件(使用-f-b指定了多个脚本,并且 time_epoch / time_us *是 Unix 时代的时间戳,其偏移量以微秒为单位(适用于创建带有小数秒的 ISO 8601 时间戳),用于显示 Transaction 完成的时间。 * schedule_lag 字段是事务的计划开始时间与实际开始时间之间的差,以微秒为单位。仅在使用--rate选项时存在。当同时使用--rate--latency-limit时,跳过事务的 time *将报告为skipped

这是在单 Client 端运行中生成的日志文件的片段:

0 199 2241 0 1175850568 995598
0 200 2465 0 1175850568 998079
0 201 2513 0 1175850569 608
0 202 2038 0 1175850569 2663

另一个带有--rate=100--latency-limit=5的示例(请注意附加的* schedule_lag *列):

0 81 4621 0 1412881037 912698 3005
0 82 6173 0 1412881037 914578 4304
0 83 skipped 0 1412881037 914578 5217
0 83 skipped 0 1412881037 914578 5099
0 83 4722 0 1412881037 916203 3108
0 84 4142 0 1412881037 918023 2333
0 85 2465 0 1412881037 919759 740

在此示例中,事务 82 晚了,因为它的 await 时间(6.173 ms)超过了 5 ms 的限制。接下来的两个事务被跳过,因为它们已经很晚才开始。

在可以处理大量事务的硬件上进行长时间测试时,日志文件可能会变得非常大。 --sampling-rate选项只能用于记录随机的事务 samples。

Aggregated Logging

使用--aggregate-interval选项,日志文件将使用其他格式:

interval_start num_transactions sum_latency sum_latency_2 min_latency max_latency [ sum_lag sum_lag_2 min_lag max_lag [ skipped ] ]

其中* interval_start 是间隔的开始(以 Unix 纪元时间戳记), num_transactions 是间隔内的事务数, sum_latency 是间隔内的事务延迟之和, sum_latency_2 是间隔内事务延迟的平方和, min_latency 是间隔内的最小延迟, max_latency 是间隔内的最大延迟。接下来的字段 sum_lag sum_lag_2 min_lag max_lag 仅在使用--rate选项时出现。它们提供有关每个事务必须 await 上一个事务完成的时间的统计信息,即每个事务的计划开始时间与实际开始时间之间的差额。仅当也使用--latency-limit选项时,才出现最后一个字段 skipped *。它计算因 Transaction 开始得太晚而跳过的 Transaction 数量。每个事务在提交后的时间间隔中进行计数。

这是一些示例输出:

1345828501 5601 1542744 483552416 61 2573
1345828503 7884 1979812 565806736 60 1479
1345828505 7208 1979422 567277552 59 1391
1345828507 7685 1980268 569784714 60 1398
1345828509 7073 1979779 573489941 236 1411

请注意,虽然普通(未聚合)日志文件显示了每个事务使用的脚本,但聚合日志未显示。因此,如果需要按脚本的数据,则需要自己汇总数据。

Per-Statement Latencies

使用-r选项,pgbench 会收集每个 Client 端执行的每个语句的经过的事务时间。然后,它会在基准测试完成后报告这些值的平均值,称为每个语句的 await 时间。

对于默认脚本,输出将类似于以下内容:

starting vacuum...end.
transaction type: <builtin: TPC-B (sort of)>
scaling factor: 1
query mode: simple
number of clients: 10
number of threads: 1
number of transactions per client: 1000
number of transactions actually processed: 10000/10000
latency average = 15.844 ms
latency stddev = 2.715 ms
tps = 618.764555 (including connections establishing)
tps = 622.977698 (excluding connections establishing)
statement latencies in milliseconds:
        0.002  \set aid random(1, 100000 * :scale)
        0.005  \set bid random(1, 1 * :scale)
        0.002  \set tid random(1, 10 * :scale)
        0.001  \set delta random(-5000, 5000)
        0.326  BEGIN;
        0.603  UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid;
        0.454  SELECT abalance FROM pgbench_accounts WHERE aid = :aid;
        5.528  UPDATE pgbench_tellers SET tbalance = tbalance + :delta WHERE tid = :tid;
        7.335  UPDATE pgbench_branches SET bbalance = bbalance + :delta WHERE bid = :bid;
        0.371  INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (:tid, :bid, :aid, :delta, CURRENT_TIMESTAMP);
        1.212  END;

如果指定了多个脚本文件,则将分别报告每个脚本文件的平均值。

请注意,收集每个语句 await 时间计算所需的其他时序信息会增加一些开销。这将降低平均执行速度并降低计算的 TPS。减速的程度因平台和硬件而异。比较启用和未启用延迟报告的平均 TPS 值是衡量计时开销是否重大的一种好方法。

Good Practices

使用 pgbench 产生完全没有意义的数字非常容易。以下是一些指南,可帮助您获得有用的结果。

首先,永远不要相信任何运行仅几秒钟的测试。使用-t-T选项可以使运行至少持续几分钟,以平均噪音。在某些情况下,您可能需要几个小时才能获得可重现的数字。尝试运行几次测试是一个好主意,以查明您的数字是否可重复。

对于默认的类似 TPC-B 的测试方案,初始化比例因子(-s)至少应与要测试的最大 Client 端数量(-c)一样大。否则,您将主要衡量更新争用。 pgbench_branches表中只有-s行,并且每个事务都想要更新其中之一,因此-c值超过-s无疑会导致许多事务被阻塞,以 await 其他事务。

默认的测试方案对于初始化表以来已经有多长时间非常敏感:死行和死空间的累积会改变结果。要了解结果,您必须跟踪更新的总数以及清理发生的时间。如果启用了自动真空,则可能会导致测量性能发生不可预测的变化。

pgbench 的局限性在于,它在尝试测试大量 Client 端会话时本身可能成为瓶颈。可以通过在与数据库服务器不同的机器上运行 pgbench 来缓解这种情况,尽管低网络延迟是必不可少的。在多台 Client 端计算机上针对同一数据库服务器同时运行多个 pgbench 实例甚至可能很有用。

Security

如果不受信任的用户可以访问未采用安全模式使用模式的数据库,则不要在该数据库中运行 pgbench。 pgbench 使用非限定名称,并且不操纵搜索路径。

上一章 首页 下一章