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)
前七行报告了一些最重要的参数设置。第六行报告了事务因序列化或死锁错误而需要重试的最大次数(有关更多信息,请参阅 失败和序列化/死锁重试)。第八行报告了已完成和预期的事务数量(后者只是客户端数量和每个客户端事务数量的乘积);除非运行在完成前失败或某些 SQL 命令失败,否则这些数字将相等。(在 -T 模式下,只打印实际事务数量。)下一行报告了因序列化或死锁错误而失败的事务数量(有关更多信息,请参阅 失败和序列化/死锁重试)。最后一行报告了每秒事务数。
默认的 TPC-B 风格事务测试需要预先设置特定的表。pgbench 应使用 -i(初始化)选项来创建和填充这些表。(当您测试自定义脚本时,不需要此步骤,而是需要执行测试所需的任何设置。)初始化如下所示:
pgbench -i [other-options]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 接受以下命令行初始化参数:
[-d] 数据库名[--dbname=]数据库名 #指定要测试的数据库的名称。如果未指定,则使用环境变量 PGDATABASE。如果未设置,则使用指定的连接用户名。
-i--initialize #调用初始化模式所必需。
-I init_steps--init-steps=init_steps #仅执行正常初始化步骤中选定的集合。init_steps 指定要执行的初始化步骤,每个字符代表一个步骤。每个步骤按指定顺序调用。默认值为 dtgvp。可用步骤为:
d (Drop - 删除) #删除任何现有的 pgbench 表。
t (create Tables - 创建表) #创建 pgbench 标准场景使用的表,即 pgbench_accounts、pgbench_branches、pgbench_history 和 pgbench_tellers。
g 或 G (Generate data, client-side or server-side - 生成数据,客户端或服务器端) #生成数据并将其加载到标准表中,替换任何已存在的数据。
使用 g(客户端生成数据)时,数据在 pgbench 客户端中生成,然后发送到服务器。这会通过 COPY 大量使用客户端/服务器带宽。pgbench 使用 FREEZE 选项将数据加载到 PostgreSQL 14 或更高版本中的普通(非分区)表中,以加快后续 VACUUM 的速度。使用 g 会导致日志打印每 100,000 行一条消息,同时为所有表生成数据。
使用 G(服务器端生成数据)时,只有少量查询从 pgbench 客户端发送,然后数据实际上是在服务器中生成的。此变体不需要大量带宽,但服务器会执行更多工作。使用 G 会导致日志在生成数据时不打印任何进度消息。
默认的初始化行为使用客户端生成数据(等同于 g)。
v (Vacuum - 清理) #对标准表调用 VACUUM。
p (create Primary keys - 创建主键) #在标准表上创建主键索引。
f (create Foreign keys - 创建外键) #在标准表之间创建外键约束。(注意,此步骤默认不执行。)
-F fillfactor--fillfactor=fillfactor #使用给定的填充因子创建 pgbench_accounts、pgbench_tellers 和 pgbench_branches 表。默认值为 100。
-n--no-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 #使用 NUM 个分区创建分区表 pgbench_accounts,这些分区的尺寸几乎相等,以适应缩放后的账户数量。默认值为 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 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 毫秒的事务会被单独计数和报告,称为延迟。
使用限制(--rate=...)时,与计划开始时间相比滞后超过 limit 毫秒的事务,因此没有希望满足延迟限制,根本不会发送到服务器。它们会被单独计数和报告为跳过。
当使用 --max-tries 选项启用事务重试以应对序列化或死锁错误时,如果该事务所有重试的总时间超过 limit 毫秒,则不会重试。要仅限制重试次数而不限制其总时间,请使用 --max-tries=0。默认情况下,--max-tries 选项设置为 1,并且不会重试具有序列化/死锁错误的事务。有关重试此类事务的更多信息,请参阅 失败和序列化/死锁重试。
-M querymode--protocol=querymode #用于向服务器提交查询的协议:
simple:使用简单查询协议。
extended:使用扩展查询协议。
prepared:使用带预备语句的扩展查询协议。
在 prepared 模式下,pgbench 从第二次查询迭代开始重用解析分析结果,因此 pgbench 的运行速度比其他模式快。
默认使用简单查询协议。(有关更多信息,请参阅 第 54 章。)
-n--no-vacuum #运行测试前不进行清理。如果您正在运行自定义测试场景,其中不包含标准表 pgbench_accounts、pgbench_branches、pgbench_history 和 pgbench_tellers,则此选项是必需的。
-N--skip-some-updates #运行内置的 simple-update 脚本。是 -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 #运行内置的 select-only 脚本。是 -b select-only 的简写。
-t transactions--transactions=transactions #每个客户端运行的事务数量。默认值为 10。
-T seconds--time=seconds #将测试运行指定的秒数,而不是每个客户端固定数量的事务。-t 和 -T 是互斥的。
-v--vacuum-all #在运行测试之前清理所有四个标准表。如果既不使用 -n 也不使用 -v,pgbench 将清理 pgbench_tellers 和 pgbench_branches 表,并截断 pgbench_history。
--aggregate-interval=seconds #聚合间隔的长度(以秒为单位)。只能与 -l 选项一起使用。使用此选项时,日志包含每个间隔的摘要数据,如下所述。
--exit-on-abort #当任何客户端因某种错误而被中止时立即退出。如果不使用此选项,即使客户端被中止,其他客户端也可以按照 -t 或 -T 选项的指定继续运行,并且 pgbench 将在此情况下打印不完整的结果。
请注意,序列化故障或死锁故障不会中止客户端,因此它们不受此选项的影响。有关更多信息,请参阅 失败和序列化/死锁重试。
--failures-detailed #在每个事务和聚合日志中,以及在主报告和每个脚本报告中报告失败,按以下类型分组:
序列化失败;
死锁失败;
有关更多信息,请参阅 失败和序列化/死锁重试。
--log-prefix=prefix #设置 --log 创建的日志文件的文件名前缀。默认值为 pgbench_log。
--max-tries=number_of_tries #为具有序列化/死锁错误的事务启用重试,并设置这些重试的最大次数。此选项可以与 --latency-limit 选项结合使用,该选项限制所有事务重试的总时间;此外,您不能在没有 --latency-limit 或 --time 的情况下使用无限次重试(--max-tries=0)。默认值为 1,并且不会重试具有序列化/死锁错误的事务。有关重试此类事务的更多信息,请参阅 失败和序列化/死锁重试。
--progress-timestamp #在显示进度(-P 选项)时,使用时间戳(Unix 纪元)而不是自运行开始以来的秒数。单位是秒,小数点后有毫秒精度。这有助于比较由各种工具生成的日志。
--random-seed=seed #设置随机生成器种子。种子用于系统随机数生成器,然后为每个线程生成一系列初始生成器状态。seed 的值可以是:time(默认,种子基于当前时间)、rand(使用强随机源,如果不可用则失败)或一个无符号十进制整数值。随机生成器由 pgbench 脚本(random... 函数)显式调用,或隐式调用(例如,--rate 选项使用它来安排事务)。当显式设置时,用于播种的值将显示在终端上。通过环境变量 PGBENCH_RANDOM_SEED 也可以提供 seed 的任何允许值。为了确保提供的种子影响所有可能的用途,请将此选项放在前面或使用环境变量。
显式设置种子可以完全重现 pgbench 运行(在随机数方面)。由于随机状态是按线程管理的,这意味着对于相同的调用,相同的 pgbench 运行(如果每个线程只有一个客户端且没有外部或数据依赖性)将完全相同。从统计学角度来看,精确重现运行是一个坏主意,因为它可能会隐藏性能变异性或不当提高性能,例如,通过命中与先前运行相同的页面。然而,它也可能对调试非常有帮助,例如,重新运行一个导致错误的棘手情况。请谨慎使用。
--sampling-rate=rate #采样率,在写入日志数据时使用,以减少生成的日志量。如果提供了此选项,则只记录指定比例的事务。1.0 表示所有事务都将被记录,0.05 表示只记录 5% 的事务。
请记住,在处理日志文件时要考虑采样率。例如,在计算 TPS 值时,需要相应地乘以数字(例如,采样率为 0.01,您将只得到实际 TPS 的 1/100)。
--show-script=scriptname #在 stderr 上显示内置脚本 scriptname 的实际代码,并立即退出。
--verbose-errors #打印有关所有错误和失败(不带重试的错误)的消息,包括超过了哪个重试限制以及对于序列化/死锁故障超出了多少。(请注意,在这种情况下,输出可能会显著增加。)有关更多信息,请参阅 失败和序列化/死锁重试。
成功运行将以状态 0 退出。退出状态 1 表示静态问题,例如无效的命令行选项或内部错误(这些错误应该永远不会发生)。开始基准测试时出现的早期错误(例如,初始连接失败)也将以状态 1 退出。运行期间的错误(例如,数据库错误或脚本中的问题)将导致退出状态 2。在后一种情况下,如果未指定 --exit-on-abort 选项,pgbench 将打印部分结果。
PGDATABASEPGHOSTPGPORTPGUSER #默认连接参数。
此实用程序与大多数其他 PostgreSQL 实用程序一样,使用 libpq 支持的环境变量(参见 第 32.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 命令行选项预设的任何变量外,还有一些变量是自动预设的,列在 表 301 中。使用 -D 为这些变量指定的值将覆盖自动预设值。一旦设置,变量的值可以通过写入 :variablename 来插入到 SQL 命令中。当运行多个客户端会话时,每个会话都有自己的变量集。pgbench 支持在一个语句中最多使用 255 个变量。
表 301. 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 中的表达式相同,非零值被解释为 true。
\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 设置为 shell 命令 command 及其给定 argument(s) 的结果。命令必须通过其标准输出来返回一个整数值。
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\syncpipeline\endpipeline #这组命令实现了 SQL 语句的管道化。管道必须以 \startpipeline 开始,以 \endpipeline 结束。中间可以有任意数量的 \syncpipeline 命令,它会发送一个 sync 消息,而不会结束正在进行的管道并刷新发送缓冲区。在管道模式下,语句会发送到服务器,而无需等待前一个语句的结果。有关更多详细信息,请参阅 第 32.5 节。管道模式需要使用扩展查询协议。
pgbench 内置了 表 302 中列出的算术、位、比较和逻辑运算符,可以在 \set 中出现的表达式中使用。运算符按优先级升序排列。除非另有说明,否则接受两个数值输入的运算符在任一输入为双精度时产生双精度值,否则产生整数结果。
表 302. pgbench 运算符
|
运算符 描述 示例 |
|---|
|
逻辑 OR。
|
|
逻辑 AND。
|
|
逻辑 NOT。
|
|
布尔值测试。
|
|
空值测试。
|
|
等于。
|
|
不等于。
|
|
不等于。
|
|
小于。
|
|
小于等于。
|
|
大于。
|
|
大于等于。
|
|
位 OR。
|
|
位 XOR。
|
|
位 AND。
|
|
按位非
|
|
位左移。
|
|
位右移。
|
|
加法。
|
|
减法。
|
|
乘法。
|
|
除法(如果两个输入都是整数,则结果向零截断)。
|
|
模(余数)。
|
|
负数。
|
pgbench 内置了 表 303 中列出的函数,可以在 \set 中出现的表达式中使用。
表 303. pgbench 函数
|
函数 描述 示例 |
|---|
|
绝对值。
|
|
将参数打印到 stderr,并返回该参数。
|
|
转换为双精度。
|
|
指数函数(e 的 given power 次方)。
|
|
选择参数中的最大值。
|
|
这是
|
|
计算 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 时,访问分布越平坦(越均匀)。对分布的一个粗略近似是,范围内的最常访问的 1% 的值(接近 min)被抽取 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)
然后,在包含 min 和 max(含)之间的值 i 以概率绘制: 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 生成一个有界的 Zipfian 分布。parameter 定义了分布的偏斜程度。 parameter 值越大,越常绘制接近区间开头的那些值。该分布的特点是,假设范围从 1 开始,绘制 k 的概率与绘制 k+1 的概率之比为 ((。例如,k+1)/k)**parameterrandom_zipfian(1, ..., 2.5) 生成的值 1 的频率大约是生成值 2 的 (2/1)**2.5 = 5.66 倍,而生成值 2 的频率大约是生成值 3 的 (3/2)**2.5 = 2.76 倍,依此类推。
pgbench 的实现基于“Non-Uniform Random Variate Generation”,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.nnnprefix 默认为 pgbench_log,nnn 是 pgbench 进程的 PID。--log-prefix 选项可以更改前缀。如果 -j 选项为 2 或更高,表示有多个工作线程,每个线程将有自己的日志文件。第一个工作线程将为其日志文件使用与标准单线程情况相同的名称。其他工作线程的附加日志文件将命名为 ,其中 prefix.nnn.mmmmmm 是每个工作线程的顺序号,从 1 开始。
日志文件中的每一行描述一个事务。它包含以下空格分隔的字段:
client_id标识运行该事务的客户端会话
transaction_no计数该会话已运行的事务数量
time事务的经过时间(微秒)
script_no标识用于该事务的脚本文件(当使用 -f 或 -b 指定多个脚本时很有用)
time_epoch事务的完成时间(Unix epoch 时间戳)
time_us事务完成时间的秒小数部分(微秒)
schedule_lag事务开始延迟,即事务计划开始时间与实际开始时间之差(微秒)(仅在指定 --rate 时存在)
retries事务期间序列化或死锁错误后的重试次数(仅在 --max-tries 不等于 1 时存在)
当同时使用 --rate 和 --latency-limit 时,跳过事务的 time 将报告为 skipped。如果事务以失败结束,其 time 将报告为 failed。如果使用 --failures-detailed 选项,则失败事务的 time 将根据故障类型报告为 serialization 或 deadlock(有关更多信息,请参阅 故障和序列化/死锁重试)。
以下是单客户端运行生成的日志文件片段。
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 epoch 时间戳)
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 不等于 1,否则为零)
retries序列化或死锁错误后的重试次数(除非 --max-tries 不等于 1,否则为零)
serialization_failures收到序列化错误且之后未重试的事务数量(除非指定 --failures-detailed,否则为零)
deadlock_failures收到死锁错误且之后未重试的事务数量(除非指定 --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 收集每个语句的以下统计信息:
latency — 每个语句的事务经过时间。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 错误(这种情况不应发生……)。
线程管理其客户端时的错误(例如,客户端无法启动到数据库服务器的连接/客户端与数据库服务器之间的套接字无效)。在这种情况下,该线程的所有客户端都会停止,而其他线程继续工作。但是,如果指定了 --exit-on-abort,在这种情况下所有线程都会立即停止。
直接客户端错误。在内部 pgbench 错误(这种情况不应发生……)或指定 --exit-on-abort 的情况下,它们会导致 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 的数据库配置选项(请参阅 第 19.1.4 节)。例如,可以使用以下方式为 pgbench 创建的表指定一个假设的默认表访问方法,名为 wuzza:
PGOPTIONS='-c default_table_access_method=wuzza'
使用 pgbench 产生完全无意义的数字非常容易。以下是一些指南,可帮助您获得有用的结果。
首先,切勿相信仅运行几秒钟的测试。使用 -t 或 -T 选项使运行持续至少几分钟,以平均掉噪声。在某些情况下,您可能需要几个小时才能获得可重现的数字。最好尝试运行几次测试,以找出您的数字是否可重现。
对于默认的 TPC-B 类测试场景,初始化比例因子(-s)应至少与您打算测试的最大客户端数量(-c)一样大;否则,您将主要测量更新争用。 pgbench_branches 表只有 -s 行,并且每个事务都想更新其中一行,因此超过 -s 的 -c 值无疑会导致大量事务被阻塞,等待其他事务。
默认测试场景还对表自初始化以来的时间非常敏感:累积的死行和死空间会改变结果。要理解结果,您必须跟踪更新的总数以及何时发生 vacuuming。如果启用了 autovacuum,它可能导致测得的性能发生不可预测的变化。
pgbench 的一个限制是,在测试大量客户端会话时,它本身可能成为瓶颈。这可以通过在与数据库服务器不同的机器上运行 pgbench 来缓解,尽管低网络延迟是必不可少的。甚至可能有用的是在多个客户端机器上同时运行多个 pgbench 实例,针对同一个数据库服务器。
如果不受信任的用户可以访问尚未采用 安全模式使用模式 的数据库,请勿在该数据库中运行 pgbench。pgbench 使用非限定名称,并且不操纵搜索路径。