pgbench — 在 PostgreSQL 上运行基准测试
pgbench
-i
[选项
...] [数据库名
]
pgbench
[选项
...] [数据库名
]
pgbench 是一个用于在 PostgreSQL 上运行基准测试的简单程序。它一遍又一遍地运行相同的 SQL 命令序列,可能在多个并发数据库会话中运行,然后计算平均事务率(每秒事务数)。默认情况下,pgbench 测试一个松散基于 TPC-B 的场景,涉及每个事务五个 SELECT
、UPDATE
和 INSERT
命令。但是,通过编写自己的事务脚本文件,可以轻松测试其他情况。
pgbench 的典型输出如下所示
transaction type: <builtin: TPC-B (sort of)> scaling factor: 10 query mode: simple number of clients: 10 number of threads: 1 maximum number of tries: 1 number of transactions per client: 1000 number of transactions actually processed: 10000/10000 number of failed transactions: 0 (0.000%) latency average = 11.013 ms latency stddev = 7.351 ms initial connection time = 45.758 ms tps = 896.967014 (without initial connection time)
前七行报告了一些最重要的参数设置。第六行报告了序列化或死锁错误事务的最大重试次数(有关更多信息,请参见 Failures and Serialization/Deadlock Retries)。第八行报告已完成和预期的事务数(后者只是客户端数和每个客户端的事务数的乘积);除非运行在完成前失败或某些 SQL 命令失败,否则这两个数字将相等。(在 -T
模式下,只打印实际事务数。)下一行报告由于序列化或死锁错误而失败的事务数(有关更多信息,请参见 Failures and Serialization/Deadlock Retries)。最后一行报告每秒事务数。
默认的类似 TPC-B 的事务测试需要事先设置特定的表。pgbench 应使用 -i
(初始化)选项调用,以创建和填充这些表。(当您测试自定义脚本时,不需要此步骤,但需要执行测试所需的任何设置。)初始化如下所示
pgbench -i [other-options
]dbname
其中 dbname
是要测试的已创建数据库的名称。(您可能还需要 -h
、-p
和/或 -U
选项来指定如何连接到数据库服务器。)
pgbench -i
创建四个表 pgbench_accounts
、pgbench_branches
、pgbench_history
和 pgbench_tellers
,销毁任何具有这些名称的现有表。如果您有具有这些名称的表,请务必小心使用另一个数据库!
在默认 “比例因子” 为 1 时,表最初包含这么多行
table # of rows --------------------------------- pgbench_branches 1 pgbench_tellers 10 pgbench_accounts 100000 pgbench_history 0
您可以(并且在大多数情况下可能应该)使用 -s
(比例因子)选项增加行数。-F
(填充因子)选项也可以在此处使用。
完成必要的设置后,您可以使用不包含 -i
的命令运行基准,即
pgbench [options
]dbname
在几乎所有情况下,您都需要一些选项才能进行有用的测试。最重要的选项是 -c
(客户端数)、-t
(事务数)、-T
(时间限制)和 -f
(指定自定义脚本文件)。请参阅以下内容以获取完整列表。
以下分为三个小节。在数据库初始化和运行基准测试时使用不同的选项,但某些选项在这两种情况下都很有用。
pgbench 接受以下命令行初始化参数
dbname
#指定要测试的数据库的名称。如果未指定,则使用环境变量 PGDATABASE
。如果未设置,则使用为连接指定的用户名。
-i
--initialize
#需要调用初始化模式。
-I init_steps
--init-steps=init_steps
#仅执行选定的常规初始化步骤。 init_steps
指定要执行的初始化步骤,每个步骤使用一个字符。每个步骤按指定顺序调用。默认值为 dtgvp
。可用的步骤有
d
(删除) #删除任何现有的 pgbench 表。
t
(创建表) #创建标准 pgbench 场景使用的表,即 pgbench_accounts
、pgbench_branches
、pgbench_history
和 pgbench_tellers
。
g
或 G
(生成数据,客户端或服务器端) #生成数据并将其加载到标准表中,替换任何已存在的数据。
使用 g
(客户端数据生成),数据在 pgbench
客户端生成,然后发送到服务器。这通过 COPY
大量使用客户端/服务器带宽。 pgbench
在 PostgreSQL 14 或更高版本中使用 FREEZE 选项来加速后续 VACUUM
,除非启用了分区。使用 g
会导致日志在为 pgbench_accounts
表生成数据时每 100,000 行打印一条消息。
使用 G
(服务器端数据生成),仅从 pgbench
客户端发送小查询,然后数据实际在服务器中生成。此变体不需要大量带宽,但服务器将执行更多工作。使用 G
会导致日志在生成数据时不打印任何进度消息。
默认初始化行为使用客户端数据生成(等同于 g
)。
v
(Vacuum) #在标准表上调用 VACUUM
。
p
(创建主键) #在标准表上创建主键索引。
f
(创建外键) #在标准表之间创建外键约束。(请注意,此步骤不会在默认情况下执行。)
-F
fillfactor
--fillfactor=
fillfactor
#使用给定的填充因子创建 pgbench_accounts
、pgbench_tellers
和 pgbench_branches
表。默认值为 100。
-n
--no-vacuum
#在初始化期间不执行 vacuum。 (此选项会禁止 v
初始化步骤,即使在 -I
中指定了该步骤。)
-q
--quiet
#将日志记录切换到安静模式,每 5 秒仅生成一条进度消息。默认日志记录每 100,000 行打印一条消息,这通常会每秒输出多行(尤其是在良好的硬件上)。
如果在 -I
中指定了 G
,则此设置无效。
-s
scale_factor
--scale=
scale_factor
#将生成的行数乘以比例因子。例如,-s 100
将在 pgbench_accounts
表中创建 10,000,000 行。默认值为 1。当比例为 20,000 或更大时,用于保存帐户标识符的列(aid
列)将切换为使用更大的整数(bigint
),以便足够大以保存帐户标识符范围。
--foreign-keys
#在标准表之间创建外键约束。(如果尚未存在,此选项会将 f
步骤添加到初始化步骤序列中。)
--index-tablespace=index_tablespace
#在指定表空间中创建索引,而不是默认表空间。
--partition-method=NAME
#使用NAME
方法创建分区pgbench_accounts
表。预期值是range
或hash
。此选项要求--partitions
设置为非零。如果未指定,则默认为range
。
--partitions=NUM
#创建分区pgbench_accounts
表,其中NUM
分区大小几乎相等,用于扩展帐户数。默认为0
,表示无分区。
--tablespace=tablespace
#在指定表空间中创建表,而不是默认表空间。
--unlogged-tables
#将所有表创建为未记录表,而不是永久表。
pgbench接受以下命令行基准测试参数
-b
scriptname[@weight]
--builtin
=scriptname[@weight]
#将指定的内置脚本添加到要执行的脚本列表中。可用的内置脚本有:tpcb-like
、simple-update
和select-only
。内置名称的不含歧义的前缀是可以接受的。使用特殊名称list
,显示内置脚本列表并立即退出。
另外,可以在@
后面写一个整数权重,以调整选择此脚本与其他脚本相比的概率。默认权重为 1。有关详细信息,请参见下文。
-c
clients
--client=
clients
#模拟的客户端数,即并发数据库会话数。默认为 1。
-C
--connect
#为每个事务建立一个新连接,而不是每个客户端会话只建立一次连接。这对于测量连接开销很有用。
-d
--debug
#打印调试输出。
-D
varname
=
value
--define=
varname
=
value
#定义一个变量,供自定义脚本使用(见下文)。允许使用多个 -D
选项。
-f
filename[@weight]
--file=
filename[@weight]
#将从 filename
中读取的事务脚本添加到要执行的脚本列表中。
或者,在 @
之后写入一个整数权重,以调整选择此脚本相对于其他脚本的概率。默认权重为 1。(要使用包含 @
字符的脚本文件名,请附加一个权重,以消除歧义,例如 filen@me@1
。)有关详细信息,请参见下文。
-j
threads
--jobs=
threads
#pgbench 中的工作线程数。在多 CPU 机器上使用多个线程可能会有所帮助。客户端尽可能均匀地分布在可用线程之间。默认值为 1。
-l
--log
#将有关每个事务的信息写入日志文件。有关详细信息,请参见下文。
-L
limit
--latency-limit=
limit
#持续时间超过 limit
毫秒的事务被单独计算并报告为 late。
当使用节流 (--rate=...
) 时,落后于计划超过 limit
毫秒的事务,因此没有希望达到延迟限制,根本不会发送到服务器。它们被单独计算并报告为 skipped。
当使用 --max-tries
选项时,如果事务因序列化异常或死锁而失败,则当所有尝试的总时间大于 limit
毫秒时,将不会重试该事务。要仅限制尝试时间而不限制尝试次数,请使用 --max-tries=0
。默认情况下,选项 --max-tries
设置为 1,并且不会重试出现序列化/死锁错误的事务。有关重试此类事务的详细信息,请参阅 Failures and Serialization/Deadlock Retries。
-M
querymode
--protocol=
querymode
#用于向服务器提交查询的协议
simple
:使用简单查询协议。
extended
:使用扩展查询协议。
prepared
:使用带有预处理语句的扩展查询协议。
在 prepared
模式中,pgbench 从第二次查询迭代开始重复使用解析分析结果,因此 pgbench 运行速度比其他模式更快。
默认值为简单查询协议。(有关详细信息,请参阅 Chapter 55。)
-n
--no-vacuum
#在运行测试之前不执行清理。如果您正在运行不包含标准表 pgbench_accounts
、pgbench_branches
、pgbench_history
和 pgbench_tellers
的自定义测试场景,则此选项是必需的。
-N
--skip-some-updates
#运行内置的简单更新脚本。相当于 -b simple-update
。
-P
sec
--progress=
sec
#每 sec
秒显示一次进度报告。该报告包括自运行开始以来的时间、自上次报告以来的 TPS,以及自上次报告以来的事务延迟平均值、标准差和失败事务数。在节流 (-R
) 下,延迟是根据事务计划的开始时间而不是实际事务开始时间计算的,因此还包括平均计划滞后时间。当 --max-tries
用于在序列化/死锁错误后启用事务重试时,报告包括重试事务数和所有重试的总和。
-r
--report-per-command
#基准测试完成后,报告以下每个命令的统计信息:每个语句的平均延迟(从客户端角度来看的执行时间)、失败次数以及此命令中序列化或死锁错误后的重试次数。仅当 --max-tries
选项不等于 1 时,报告才会显示重试统计信息。
-R
rate
--rate=
rate
#执行针对指定速率的事务,而不是尽可能快地运行(默认)。速率以每秒事务数为单位给出。如果目标速率高于最大可能速率,则速率限制不会影响结果。
通过沿泊松分布时间线启动事务来针对速率。预期的开始时间表根据客户端首次启动的时间向前移动,而不是根据前一个事务结束的时间向前移动。该方法意味着当事务超过其原始的预定结束时间时,后面的事务有可能再次赶上。
当节流处于活动状态时,运行结束时报告的事务延迟是从预定的开始时间计算的,因此它包括每个事务等待前一个事务完成的时间。等待时间称为计划滞后时间,其平均值和最大值也分别报告。相对于实际事务开始时间的的事务延迟,即在数据库中执行事务所花费的时间,可以通过从报告的延迟中减去计划滞后时间来计算。
如果 --latency-limit
与 --rate
一起使用,则事务可能会落后很多,以至于在前一个事务结束时已经超过延迟限制,因为延迟是从预定的开始时间计算的。此类事务不会发送到服务器,而是完全跳过并单独计数。
较高的计划滞后时间表明系统无法以所选的客户端和线程数以指定速率处理事务。当平均事务执行时间长于每个事务之间的预定间隔时,每个连续的事务都会进一步落后,并且计划滞后时间会随着测试运行时间的延长而不断增加。当这种情况发生时,您将不得不降低指定的事务速率。
-s
scale_factor
--scale=
scale_factor
#在 pgbench 的输出中报告指定的比例因子。对于内置测试,这是不必要的;正确的比例因子将通过计算 pgbench_branches
表中的行数来检测。但是,当仅测试自定义基准 (-f
选项) 时,除非使用此选项,否则比例因子将报告为 1。
-S
--select-only
#运行内置的仅选择脚本。 -b select-only
的简写。
-t
transactions
--transactions=
transactions
#每个客户端运行的事务数。默认值为 10。
-T
seconds
--time=
seconds
#运行测试的时间(以秒为单位),而不是每个客户端的固定事务数。 -t
和 -T
互斥。
-v
--vacuum-all
#在运行测试之前,对所有四个标准表进行 Vacuum。如果没有 -n
或 -v
, pgbench 将对 pgbench_tellers
和 pgbench_branches
表进行 Vacuum,并将截断 pgbench_history
。
--aggregate-interval=seconds
#聚合间隔的长度(以秒为单位)。只能与 -l
选项一起使用。使用此选项,日志将包含按间隔划分的摘要数据,如下所述。
--failures-detailed
#在按事务和聚合日志以及在主报告和按脚本报告中报告故障,按以下类型分组
序列化失败;
死锁失败;
有关更多信息,请参见 Failures and Serialization/Deadlock Retries。
--log-prefix=prefix
#设置 --log
创建的日志文件的名称前缀。默认值为 pgbench_log
。
--max-tries=number_of_tries
#启用对序列化/死锁错误的事务进行重试,并设置这些重试的最大次数。此选项可以与 --latency-limit
选项结合使用,后者限制所有事务尝试的总时间;此外,如果没有 --latency-limit
或 --time
,则不能使用无限次数的尝试 (--max-tries=0
)。默认值为 1,并且不会重试带有序列化/死锁错误的事务。有关重试此类事务的更多信息,请参见 Failures and Serialization/Deadlock Retries。
--progress-timestamp
#在显示进度(选项 -P
)时,使用时间戳(Unix 纪元)而不是自运行开始以来的秒数。单位为秒,小数点后为毫秒精度。这有助于比较由各种工具生成的日志。
--random-seed=
seed
#设置随机生成器种子。播种系统随机数生成器,然后生成一系列初始生成器状态,每个线程一个。seed
的值可以是:time
(默认值,种子基于当前时间)、rand
(使用强随机源,如果不可用则失败)或无符号十进制整数值。随机生成器显式地从 pgbench 脚本(random...
函数)或隐式地调用(例如,选项 --rate
使用它来计划事务)。显式设置时,用于播种的值显示在终端上。允许 seed
的任何值也可以通过环境变量 PGBENCH_RANDOM_SEED
提供。为了确保提供的种子影响所有可能的用途,请首先放置此选项或使用环境变量。
显式设置种子允许在随机数方面完全重现 pgbench
运行。由于随机状态是按线程管理的,这意味着如果每个线程一个客户端并且没有外部或数据依赖性,则对于相同的调用,完全相同的 pgbench
运行。从统计角度来看,精确地重现运行是一个坏主意,因为它可以隐藏性能可变性或不当地提高性能,例如,通过访问与前一次运行相同的页面。但是,它也可能对调试非常有帮助,例如重新运行导致错误的棘手案例。明智地使用。
--sampling-rate=rate
#采样率,在将数据写入日志时使用,以减少生成日志的数量。如果给出了此选项,则只记录指定部分的事务。1.0 表示将记录所有事务,0.05 表示只记录 5% 的事务。
在处理日志文件时,请记住考虑采样率。例如,在计算 TPS 值时,您需要相应地乘以数字(例如,使用 0.01 的采样率,您只将获得实际 TPS 的 1/100)。
--show-script=
脚本名称
#在 stderr 上显示内置脚本 脚本名称
的实际代码,并立即退出。
--verbose-errors
#打印有关所有错误和故障的消息(不重试的错误),包括超出重试的哪个限制以及超出多远以进行序列化/死锁故障。(请注意,在这种情况下,输出可能会大大增加。)有关更多信息,请参见 故障和序列化/死锁重试。
成功运行将以状态 0 退出。退出状态 1 表示静态问题,例如无效的命令行选项或应该永远不会发生的内部错误。在启动基准时发生的早期错误,例如初始连接故障,也会以状态 1 退出。运行期间的错误,例如数据库错误或脚本中的问题,将导致退出状态 2。在后一种情况下,pgbench 将打印部分结果。
PGDATABASE
PGHOST
PGPORT
PGUSER
#默认连接参数。
此实用程序与大多数其他 PostgreSQL 实用程序一样,使用 libpq 支持的环境变量(请参阅 第 34.15 节)。
环境变量 PG_COLOR
指定是否在诊断消息中使用颜色。可能的值为 always
、auto
和 never
。
pgbench 从指定列表中随机选择测试脚本并执行。脚本可能包括使用 -b
指定的内置脚本和使用 -f
指定的用户提供的脚本。每个脚本都可以指定一个 @
之后的相对权重,以更改其选择概率。默认权重为 1
。权重为 0
的脚本将被忽略。
默认内置事务脚本(也可以使用 -b tpcb-like
调用)在随机选择的 aid
、tid
、bid
和 delta
上对每个事务发出七个命令。该场景的灵感来自 TPC-B 基准,但实际上并不是 TPC-B,因此得名。
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;
如果您选择 simple-update
内置(也 -N
),步骤 4 和 5 不会包含在事务中。这将避免这些表上的更新争用,但它使测试用例更不像 TPC-B。
如果您选择 select-only
内置(也 -S
),则只发出 SELECT
。
pgbench 支持通过用从文件中读取的事务脚本(-f
选项)替换默认事务脚本(如上所述)来运行自定义基准场景。在这种情况下,“事务”算作脚本文件的一次执行。
脚本文件包含一个或多个以分号结尾的 SQL 命令。空行和以 --
开头的行将被忽略。脚本文件还可以包含 “元命令”,这些命令由 pgbench 本身解释,如下所述。
在 PostgreSQL 9.6 之前,脚本文件中的 SQL 命令以换行符结尾,因此它们不能跨行继续。现在,分号是 必需的 分隔连续的 SQL 命令(尽管如果 SQL 命令后面跟着元命令,则不需要分号)。如果您需要创建一个适用于旧版和新版 pgbench 的脚本文件,请务必将每个 SQL 命令写在一行上,并以分号结尾。
假设 pgbench 脚本不包含不完整的 SQL 事务块。如果在运行时客户端在未完成最后一个事务块的情况下到达脚本的末尾,它将被中止。
脚本文件有一个简单的变量替换工具。变量名必须由字母(包括非拉丁字母)、数字和下划线组成,第一个字符不能是数字。变量可以通过上面解释的命令行 -D
选项或下面解释的元命令来设置。除了通过 -D
命令行选项预设的任何变量外,还有一些变量会自动预设,列在 表 293 中。使用 -D
为这些变量指定的值优先于自动预设。一旦设置,可以通过编写 :
变量名
将变量值插入到 SQL 命令中。在运行多个客户端会话时,每个会话都有自己的一组变量。pgbench 支持在一个语句中最多使用 255 个变量。
表 293. pgbench 自动变量
变量 | 说明 |
---|---|
client_id |
唯一标识客户端会话的数字(从零开始) |
default_seed |
默认情况下哈希和伪随机排列函数中使用的种子 |
random_seed |
随机生成器种子(除非用 -D 覆盖) |
scale |
当前比例因子 |
脚本文件元命令以反斜杠 (\
) 开头,通常扩展到行的末尾,但可以通过编写反斜杠换行符继续到其他行。元命令的参数用空格分隔。支持以下元命令
\gset [prefix
]
\aset [prefix
]
#这些命令可用于结束 SQL 查询,取代终止分号 (;
)。
当使用 \gset
命令时,前面的 SQL 查询应返回一行,其列存储在以列名为名的变量中,如果提供了,则以 prefix
为前缀。
当使用 \aset
命令时,所有组合的 SQL 查询(用 \;
分隔)的列都存储在以列名为名的变量中,如果提供了,则以 prefix
为前缀。如果查询没有返回任何行,则不会进行赋值,并且可以测试变量是否存在以检测到这一点。如果查询返回多行,则保留最后的值。
\gset
和 \aset
不能在管道模式中使用,因为在命令需要查询结果时,查询结果尚未可用。
以下示例将第一个查询中的最终帐户余额放入变量 abalance
中,并使用第三个查询中的整数填充变量 p_two
和 p_three
。第二个查询的结果将被丢弃。最后两个组合查询的结果存储在变量 four
和 five
中。
UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid RETURNING abalance \gset -- compound of two queries SELECT 1 \; SELECT 2 AS two, 3 AS three \gset p_ SELECT 4 AS four \; SELECT 5 AS five \aset
\if
expression
\elif
expression
\else
\endif
#这组命令实现可嵌套的条件块,类似于 psql
的 \if
expression
。条件表达式与 \set
中的表达式相同,非零值解释为真。
\set varname
expression
#将变量 varname
设置为根据 expression
计算出的值。该表达式可能包含 NULL
常量、布尔常量 TRUE
和 FALSE
、整数常量(例如 5432
)、双精度常量(例如 3.14159
)、对变量的引用 :
variablename
、运算符(具有其通常的 SQL 优先级和结合性)、函数调用、SQL CASE
通用条件表达式 和括号。
函数和大多数运算符在 NULL
输入时返回 NULL
。
出于条件目的,非零数值为 TRUE
,零数值和 NULL
为 FALSE
。
过大或过小的整数和双精度常量,以及整数算术运算符 (+
、-
、*
和 /
) 在溢出时引发错误。
如果未向 CASE
提供最终 ELSE
子句,则默认值为 NULL
。
示例
\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
\sleep number
[ us | ms | s ]
#导致脚本执行休眠指定持续时间(以微秒 (us
)、毫秒 (ms
) 或秒 (s
) 为单位)。如果省略单位,则默认值为秒。number
可以是整数常量,也可以是引用具有整数值的变量的 :
variablename
引用。
示例
\sleep 10 ms
\setshell varname
command
[ argument
... ]
#将变量 varname
设置为具有给定 argument
(s) 的 shell 命令 command
的结果。该命令必须通过其标准输出返回整数值。
command
和每个 argument
可以是文本常量,也可以是引用变量的 :
variablename
引用。如果您想使用以冒号开头的 argument
,请在 argument
的开头再写一个冒号。
示例
\setshell variable_to_be_assigned command literal_argument :variable ::literal_starting_with_colon
\shell command
[ argument
... ]
#与 \setshell
相同,但会丢弃命令的结果。
示例
\shell command literal_argument :variable ::literal_starting_with_colon
\startpipeline
\endpipeline
#这些命令界定了一系列 SQL 语句的开始和结束。在管道模式中,语句会发送到服务器,而无需等待前一个语句的结果。有关更多详细信息,请参阅第 34.5 节。管道模式需要使用扩展查询协议。
在表 294中列出的算术、位运算、比较和逻辑运算符内置于pgbench中,并且可以在出现在\set
中的表达式中使用。运算符按优先级顺序排列。除非另有说明,否则,如果任一输入是 double,则采用两个数字输入的运算符将产生 double 值,否则它们将产生整数结果。
表 294. pgbench 运算符
运算符 说明 示例 |
---|
逻辑 OR
|
逻辑 AND
|
逻辑 NOT
|
布尔值测试
|
空值测试
|
等于
|
不等于
|
不等于
|
小于
|
小于或等于
|
大于
|
大于或等于
|
按位或
|
按位异或
|
按位与
|
按位非
|
按位左移
|
按位右移
|
加法
|
减法
|
乘法
|
除法(如果两个输入都是整数,则结果向下舍入为零)
|
模数(余数)
|
取反
|
表 295 中列出的函数内置于 pgbench 中,可用于 \set
中出现的表达式。
表 295. pgbench 函数
函数 说明 示例 |
---|
绝对值
|
将参数打印到 stderr,并返回参数。
|
转换为 double。
|
指数(
|
从参数中选择最大值。
|
这是
|
计算 FNV-1a 哈希。
|
计算 MurmurHash2 哈希。
|
转换为整数。
|
从参数中选择最小值。
|
自然对数
|
模数(余数)
|
|
π 的近似值
|
|
计算
|
计算
|
计算
|
计算
|
平方根
|
random
函数使用均匀分布生成值,即在指定范围内以相等的概率绘制所有值。 random_exponential
、random_gaussian
和 random_zipfian
函数需要一个额外的双精度参数来确定分布的精确形状。
对于指数分布,parameter
通过在 parameter
处截断快速递减的指数分布,然后投影到边界之间的整数来控制分布。准确地说,使用
f(x) = exp(-parameter * (x - min) / (max - min + 1)) / (1 - exp(-parameter))
然后在 min
和 max
(包括)之间的值 i
的绘制概率为:f(i) - f(i + 1)
。
直观地说,parameter
越大,越接近 min
的值被访问的频率越高,而越接近 max
的值被访问的频率越低。 parameter
越接近 0,访问分布越平坦(越均匀)。对分布的一个粗略近似是,接近 min
的范围内最频繁的 1% 的值在 parameter
% 的时间内被绘制。 parameter
值必须严格为正。
对于高斯分布,区间映射到一个标准正态分布(经典的钟形高斯曲线),在左侧截断为 -parameter
,在右侧截断为 +parameter
。区间中间的值更有可能被绘制。确切地说,如果 PHI(x)
是标准正态分布的累积分布函数,均值 mu
定义为 (max + min) / 2.0
,其中
f(x) = PHI(2.0 * parameter * (x - mu) / (max - min + 1)) /
(2.0 * PHI(parameter) - 1)
则值 i
在 min
和 max
(包括)之间绘制的概率为:f(i + 0.5) - f(i - 0.5)
。直观地讲,parameter
越大,区间中间附近的值绘制得越频繁,而 min
和 max
边界附近的值绘制得越不频繁。大约 67% 的值从中间 1.0 / parameter
绘制,即均值周围的相对 0.5 / parameter
,而 95% 从中间 2.0 / parameter
绘制,即均值周围的相对 1.0 / parameter
;例如,如果 parameter
为 4.0,则 67% 的值从区间的中间四分之一(1.0 / 4.0)绘制(即,从 3.0 / 8.0
到 5.0 / 8.0
),而 95% 从区间的中间一半(2.0 / 4.0
)绘制(第二和第三四分位数)。允许的最小 parameter
值为 2.0。
random_zipfian
生成一个有界的齐夫分布。parameter
定义分布的偏斜程度。parameter
越大,区间开头附近的值绘制得越频繁。分布是这样的,假设范围从 1 开始,绘制 k
与绘制 k+1
的概率之比为 ((
。例如,k
+1)/k
)**parameter
random_zipfian(1, ..., 2.5)
产生的值 1
大约比 2
频繁 (2/1)**2.5 = 5.66
倍,而 2
本身比 3
频繁 (3/2)**2.5 = 2.76
倍,依此类推。
pgbench 的实现基于“非均匀随机变量生成”,Luc Devroye,第 550-551 页,Springer 1986。由于该算法的限制,parameter
值被限制在范围 [1.001, 1000] 内。
在设计一个非均匀选择行的基准时,请注意,所选行可能与其他数据相关,例如序列中的 ID 或物理行顺序,这可能会扭曲性能测量。
为避免这种情况,你可以使用 permute
函数或其他具有类似效果的附加步骤,以随机排列选定的行并移除此类关联。
哈希函数 hash
、hash_murmur2
和 hash_fnv1a
接受一个输入值和一个可选的种子参数。如果未提供种子,则使用 :default_seed
的值,该值在未通过命令行 -D
选项设置时会随机初始化。
permute
接受一个输入值、一个大小和一个可选的种子参数。它生成一个 [0, size)
范围内的整数的伪随机排列,并返回排列值中输入值所在的索引。所选的排列由种子参数化,如果未指定,则默认为 :default_seed
。与哈希函数不同,permute
确保输出值中没有冲突或空洞。区间外的输入值将按大小进行模运算。如果大小不是正数,则该函数会引发错误。permute
可用于分散诸如 random_zipfian
或 random_exponential
等非均匀随机函数的分布,以便更频繁抽取的值不会产生平凡关联。例如,以下 pgbench 脚本模拟了社交媒体和博客平台的典型实际工作负载,其中少数几个帐户会产生过大的负载
\set size 1000000 \set r random_zipfian(1, :size, 1.07) \set k 1 + permute(:r, :size)
在某些情况下,需要几个互不关联的不同分布,这时可选的种子参数就派上用场了
\set k1 1 + permute(:r, :size, :default_seed + 123) \set k2 1 + permute(:r, :size, :default_seed + 321)
也可以用 hash
近似实现类似的行为
\set size 1000000 \set r random_zipfian(1, 100 * :size, 1.07) \set k 1 + abs(hash(:r)) % :size
但是,由于 hash
会产生冲突,因此某些值将不可访问,而其他值将比从原始分布中预期的情况更频繁。
例如,内置类 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;
此脚本允许事务的每次迭代引用不同的随机选择的行。(此示例还说明了每个客户端会话拥有自己的变量的重要性——否则它们将无法独立地接触不同的行。)
使用 -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
标识用于事务的脚本文件(当使用 -f
或 -b
指定多个脚本时很有用)
time_epoch
事务完成时间,作为 Unix 纪元时间戳
time_us
事务完成时间的秒数部分(以微秒为单位)
schedule_lag
事务开始延迟,即事务的计划开始时间与实际开始时间之间的差值(以微秒为单位)(仅在指定 --rate
时出现)
retries
事务期间序列化或死锁错误后的重试次数(仅在 --max-tries
不等于 1 时出现)
当同时使用 --rate
和 --latency-limit
时,跳过事务的 time
将报告为 skipped
。如果事务以失败告终,则其 time
将报告为 failed
。如果您使用 --failures-detailed
选项,则失败事务的 time
将报告为 serialization
或 deadlock
,具体取决于失败类型(有关详细信息,请参见 Failures and Serialization/Deadlock Retries)。
以下是单客户端运行中生成的日志文件片段
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 已延迟,因为其延迟(6.173 毫秒)超过了 5 毫秒的限制。跳过了接下来的两个事务,因为它们在开始之前就已经延迟了。
以下示例显示了一个带有故障和重试的日志文件片段,其中将最大尝试次数设置为 10(注意其他 retries
列)
3 0 47423 0 1499414498 34501 3 3 1 8333 0 1499414498 42848 0 3 2 8358 0 1499414498 51219 0 4 0 72345 0 1499414498 59433 6 1 3 41718 0 1499414498 67879 4 1 4 8416 0 1499414498 76311 0 3 3 33235 0 1499414498 84469 3 0 0 failed 0 1499414498 84905 9 2 0 failed 0 1499414498 86248 9 3 4 8307 0 1499414498 92788 0
如果使用了 --failures-detailed
选项,则故障类型会在此处以 time
形式报告
3 0 47423 0 1499414498 34501 3 3 1 8333 0 1499414498 42848 0 3 2 8358 0 1499414498 51219 0 4 0 72345 0 1499414498 59433 6 1 3 41718 0 1499414498 67879 4 1 4 8416 0 1499414498 76311 0 3 3 33235 0 1499414498 84469 3 0 0 serialization 0 1499414498 84905 9 2 0 serialization 0 1499414498 86248 9 3 4 8307 0 1499414498 92788 0
在能够处理大量事务的硬件上运行长时间测试时,日志文件可能会变得非常大。 --sampling-rate
选项可用于仅记录事务的随机样本。
使用 --aggregate-interval
选项时,日志文件将使用不同的格式。每行日志描述一个聚合间隔。它包含以下空格分隔的字段
interval_start
间隔的开始时间,作为 Unix 纪元时间戳
num_transactions
间隔内的事务数
sum_latency
事务延迟的总和
sum_latency_2
事务延迟平方和
min_latency
最小事务延迟
max_latency
最大事务延迟
sum_lag
事务开始延迟的总和(除非指定 --rate
,否则为零)
sum_lag_2
事务开始延迟平方和(除非指定 --rate
,否则为零)
min_lag
最小事务开始延迟(除非指定 --rate
,否则为零)
max_lag
最大事务开始延迟(除非指定 --rate
,否则为零)
skipped
跳过的事务数,因为它们开始得太晚了(除非指定 --rate
和 --latency-limit
,否则为零)
retried
重试事务数(除非 --max-tries
不等于一,否则为零)
retries
序列化或死锁错误后的重试次数(除非 --max-tries
不等于一,否则为零)
serialization_failures
获取序列化错误且之后未重试的事务数(除非指定 --failures-detailed
,否则为零)
死锁失败
发生死锁错误且之后未重试的事务数(除非指定 --failures-detailed
,否则为零)
以下是使用这些选项生成的一些示例输出
pgbench --aggregate-interval=10 --time=20 --client=10 --log --rate=1000 --latency-limit=10 --failures-detailed --max-tries=10 test
1650260552 5178 26171317 177284491527 1136 44462 2647617 7321113867 0 9866 64 7564 28340 4148 0
1650260562 4808 25573984 220121792172 1171 62083 3037380 9666800914 0 9998 598 7392 26621 4527 0
请注意,虽然普通(未聚合)日志格式显示了每个事务使用的脚本,但聚合格式没有显示。因此,如果您需要按脚本统计数据,则需要自行聚合数据。
使用 -r
选项,pgbench 为每个语句收集以下统计信息
延迟
— 每个语句的已用事务时间。pgbench 报告语句的所有成功运行的平均值。
此语句中的失败次数。有关更多信息,请参见 失败和序列化/死锁重试。
此语句中序列化或死锁错误后的重试次数。有关更多信息,请参见 失败和序列化/死锁重试。
仅当 --max-tries
选项不等于 1 时,报告才显示重试统计信息。
为每个客户端执行的每个语句计算所有值,并在基准测试完成后报告这些值。
对于默认脚本,输出将类似于此
starting vacuum...end. transaction type: <builtin: TPC-B (sort of)> scaling factor: 1 query mode: simple number of clients: 10 number of threads: 1 maximum number of tries: 1 number of transactions per client: 1000 number of transactions actually processed: 10000/10000 number of failed transactions: 0 (0.000%) number of transactions above the 50.0 ms latency limit: 1311/10000 (13.110 %) latency average = 28.488 ms latency stddev = 21.009 ms initial connection time = 69.068 ms tps = 346.224794 (without initial connection time) statement latencies in milliseconds and failures: 0.012 0 \set aid random(1, 100000 * :scale) 0.002 0 \set bid random(1, 1 * :scale) 0.002 0 \set tid random(1, 10 * :scale) 0.002 0 \set delta random(-5000, 5000) 0.319 0 BEGIN; 0.834 0 UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid; 0.641 0 SELECT abalance FROM pgbench_accounts WHERE aid = :aid; 11.126 0 UPDATE pgbench_tellers SET tbalance = tbalance + :delta WHERE tid = :tid; 12.961 0 UPDATE pgbench_branches SET bbalance = bbalance + :delta WHERE bid = :bid; 0.634 0 INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (:tid, :bid, :aid, :delta, CURRENT_TIMESTAMP); 1.957 0 END;
使用可序列化默认事务隔离级别的默认脚本的另一个输出示例(PGOPTIONS='-c default_transaction_isolation=serializable' pgbench ...
)
starting vacuum...end. transaction type: <builtin: TPC-B (sort of)> scaling factor: 1 query mode: simple number of clients: 10 number of threads: 1 maximum number of tries: 10 number of transactions per client: 1000 number of transactions actually processed: 6317/10000 number of failed transactions: 3683 (36.830%) number of transactions retried: 7667 (76.670%) total number of retries: 45339 number of transactions above the 50.0 ms latency limit: 106/6317 (1.678 %) latency average = 17.016 ms latency stddev = 13.283 ms initial connection time = 45.017 ms tps = 186.792667 (without initial connection time) statement latencies in milliseconds, failures and retries: 0.006 0 0 \set aid random(1, 100000 * :scale) 0.001 0 0 \set bid random(1, 1 * :scale) 0.001 0 0 \set tid random(1, 10 * :scale) 0.001 0 0 \set delta random(-5000, 5000) 0.385 0 0 BEGIN; 0.773 0 1 UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid; 0.624 0 0 SELECT abalance FROM pgbench_accounts WHERE aid = :aid; 1.098 320 3762 UPDATE pgbench_tellers SET tbalance = tbalance + :delta WHERE tid = :tid; 0.582 3363 41576 UPDATE pgbench_branches SET bbalance = bbalance + :delta WHERE bid = :bid; 0.465 0 0 INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (:tid, :bid, :aid, :delta, CURRENT_TIMESTAMP); 1.933 0 0 END;
如果指定了多个脚本文件,则会为每个脚本文件分别报告所有统计信息。
请注意,收集按语句延迟计算所需的其他时间信息会增加一些开销。这将降低平均执行速度并降低计算出的 TPS。减速量会根据平台和硬件而异。比较启用和禁用延迟报告时的平均 TPS 值是衡量时间开销是否显着的不错方法。
执行 pgbench 时,有三种主要类型的错误
主程序错误。它们是最严重的,并且总是导致 pgbench 立即退出并显示相应的错误消息。它们包括
pgbench 开始时的错误(例如,无效选项值);
初始化模式中的错误(例如,为内置脚本创建表的查询失败);
在启动线程之前出现的错误(例如,无法连接到数据库服务器、元命令中的语法错误、线程创建失败);
内部 pgbench 错误(理论上永远不会发生...)。
线程管理其客户端时出现的错误(例如,客户端无法启动与数据库服务器的连接/用于将客户端连接到数据库服务器的套接字已失效)。在这种情况下,此线程的所有客户端都会停止,而其他线程会继续工作。
直接客户端错误。它们会导致 pgbench 立即退出,并且仅在内部 pgbench 错误(理论上永远不会发生...)的情况下显示相应的错误消息。否则,在最坏的情况下,它们只会导致失败的客户端中止,而其他客户端会继续运行(但某些客户端错误会在不中止客户端的情况下得到处理,并单独报告,见下文)。在本节后面,假定讨论的错误仅为直接客户端错误,而不是内部 pgbench 错误。
如果出现严重错误,客户端运行将被中止;例如,与数据库服务器的连接丢失或在未完成上一个事务的情况下到达脚本结尾。此外,如果执行 SQL 或元命令失败,原因不是序列化或死锁错误,客户端将被中止。否则,如果 SQL 命令因序列化或死锁错误而失败,客户端不会被中止。在这种情况下,当前事务将回滚,其中还包括将客户端变量设置为该事务运行前的状态(假设一个事务脚本仅包含一个事务;有关详细信息,请参阅 pgbench 中实际执行的“事务”是什么?)。具有序列化或死锁错误的事务在回滚后会重复,直到成功完成或达到最大尝试次数(由 --max-tries
选项指定)/最大重试时间(由 --latency-limit
选项指定)/基准结束(由 --time
选项指定)。如果最后一次试运行失败,此事务将报告为失败,但客户端不会被中止,并继续工作。
如果不指定 --max-tries
选项,则在发生序列化或死锁错误后,事务将永远不会被重试,因为其默认值为 1。使用无限次数的尝试 (--max-tries=0
) 和 --latency-limit
选项来仅限制最大尝试时间。您还可以使用 --time
选项在无限次数的尝试下限制基准持续时间。
在重复包含多个事务的脚本时要小心:脚本总是被完全重试,因此成功的交易可以执行多次。
在使用 shell 命令重复事务时要小心。与 SQL 命令的结果不同,shell 命令的结果不会回滚,除了 \setshell
命令的变量值。
成功事务的延迟包括事务执行的整个时间,包括回滚和重试。延迟仅针对成功的事务和命令进行测量,而不针对失败的事务或命令进行测量。
主报告包含失败事务的数量。如果 --max-tries
选项不等于 1,则主报告还包含与重试相关的统计信息:重试事务的总数和重试的总数。每个脚本报告从主报告继承所有这些字段。每个语句报告仅在 --max-tries
选项不等于 1 时显示重试统计信息。
如果您想按每个事务和聚合日志中的基本类型对失败进行分组,以及在主报告和每个脚本报告中,请使用 --failures-detailed
选项。如果您还想按类型区分所有错误和失败(未重试的错误),包括超过了哪些重试限制以及序列化/死锁失败超出了多少,请使用 --verbose-errors
选项。
您可以为 pgbench 表指定 表访问方法。环境变量 PGOPTIONS
指定通过命令行传递给 PostgreSQL 的数据库配置选项(请参见 第 20.1.4 节)。例如,可以通过以下方式指定 pgbench 创建的表的一个假设的默认表访问方法,称为 wuzza
PGOPTIONS='-c default_table_access_method=wuzza'
使用 pgbench 来生成完全无意义的数字非常容易。以下是一些指导原则,可帮助您获得有用的结果。
首先,切勿相信仅运行几秒钟的任何测试。使用 -t
或 -T
选项使运行至少持续几分钟,以便平均化噪声。在某些情况下,您可能需要数小时才能获得可重现的数字。最好尝试多次运行测试,以了解您的数字是否可重现。
对于默认的类似 TPC-B 的测试场景,初始化比例因子 (-s
) 应至少与您打算测试的最大客户端数 (-c
) 一样大;否则,您将主要测量更新争用。在 pgbench_branches
表中只有 -s
行,并且每个事务都想更新其中一行,因此 -c
值超过 -s
无疑会导致许多事务被阻止,等待其他事务。
默认测试场景对于表初始化后的时间也相当敏感:表中死行和死空间的累积会改变结果。要了解结果,您必须跟踪更新的总数以及何时发生 vacuum。如果启用了自动 vacuum,则可能导致所测量性能发生不可预测的变化。
pgbench 的一个限制是,在尝试测试大量客户端会话时,它本身可能会成为瓶颈。虽然低网络延迟至关重要,但可以通过在与数据库服务器不同的机器上运行 pgbench 来缓解此问题。甚至可以在多台客户端机器上同时运行多个 pgbench 实例,针对同一数据库服务器。
如果不受信任的用户可以访问尚未采用 安全模式使用模式 的数据库,请不要在该数据库中运行 pgbench。 pgbench 使用不合格的名称,并且不操作搜索路径。