pgbench是一种在PostgreSQL上运行基准测试的简单程序。它可能在并发的数据库会话中一遍一遍地运行相同序列的 SQL 命令,并且计算平均事务率(每秒的事务数)。默认情况下,pgbench会测试一种基于 TPC-B 但是要更宽松的场景,其中在每个事务中涉及五个SELECT、UPDATE以及INSERT命令。但是,通过编写自己的事务脚本文件很容易用来测试其他情况。
pgbench 的典型输出像这样:
transaction type: 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)
前六行报告一些最重要的参数设置。接下来的行报告完成的事务数以及预期的事务数(后者就是客户端数量与每个客户端事务数的乘积),除非运行在完成之前失败,这些值应该是相等的(在-T模式中,只有实际的事务数会被打印出来)。最后两行报告每秒的事务数,分别代表包括和不包括开始数据库会话所花时间的情况。
默认的类 TPC-B 事务测试要求预先设置好特定的表。可以使用-i(初始化)选项调用pgbench来创建并且填充这些表(当你在测试一个自定义脚本时,你不需要这一步,但是需要按你自己的测试需要做一些设置工作)。初始化类似这样:
pgbench -i [ other-options ] dbname
其中dbname是要在其中进行测试的预先创建好的数据库的名称(你可能还需要-h、-p或-U选项来指定如何连接到数据库服务器)。
Caution |
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接受下列命令行初始化参数:
要求调用初始化模式。
用给定的填充因子创建pgbench_accounts、 pgbench_tellers和 pgbench_branches表。默认值是 100。
初始化以后不执行清理。
把记录切换到安静模式,只是每 5 秒产生一个进度消息。默认的记录会每 100000 行打印一个消息,这经常会在每秒钟输出很多行(特别是在好的硬件上)。
将生成的行数乘以比例因子。例如,-s 100将在pgbench_accounts表中创建 10,000,000 行。默认为 1。当比例为 20,000 或更高时,用来保存账号标识符的列(aid列)将切换到使用更大的整数(bigint),这样才能足以保存账号标识符。
在标准的表之间创建外键约束。
在指定的表空间而不是默认表空间中创建索引。
在指定的表空间而不是默认表空间中创建表。
把所有的表创建为非日志记录表而不是永久表。
pgbench接受下列命令行基准参数:
模拟的客户端数量,也就是并发数据库会话数量。默认为 1。
为每一个事务建立一个新连接,而不是只为每个客户端会话建立一个连接。这对于度量连接开销有用。
打印调试输出。
定义一个由自定义脚本(见下文)使用的变量。允许多个-D选项。
从filename读取事务脚本。详见下文。 -N、-S和-f是互斥的。
pgbench中的工作者线程数量。在多 CPU 机器上使用多于一个线程会有用。客户端数量必须是线程数的一个倍数,因为每一个线程会被给予相同数量的客户端会话进行管理。默认为 1。
把每一个事务花费的时间写到一个日志文件中。详见下文。
要用来提交查询到服务器的协议:
simple:使用简单查询协议。
extended使用扩展查询协议。
prepared:使用带预备语句的扩展查询语句。
默认是简单查询协议(详见Chapter 48)。
在运行测试前不进行清理。如果你在运行一个不包括标准的表pgbench_accounts、 pgbench_branches、pgbench_history和 pgbench_tellers的自定义测试场景时,这个选项是必需的。
不要更新pgbench_tellers和 pgbench_branches。这将避免在这些表上的更新争夺,但是它会让测试案例更不像 TPC-B。
在基准结束后,报告平均的每个命令的每语句等待时间(从客户端的角度来说是执行时间)。详见下文。
在pgbench的输出中报告指定的比例因子。对于内建测试,这并非必需;正确的比例因子将通过对pgbench_branches表中的行计数来检测。不过,当测试自定义基准(-f选项)时,比例因子将被报告为 1(除非使用了这个选项)。
执行只选择的事务而不是类 TPC-B 测试。
每个客户端运行的事务数量。默认为 10。
运行测试这么多秒,而不是为每个客户端运行固定数量的事务。-t和-T是互斥的。
在运行测试前清理所有四个标准的表。在没有用-n以及-v时,pgbench 将清理pgbench_tellers和pgbench_branches表,并且截断pgbench_history。
聚集区间的长度(以秒计)。可以只与-l一起使用 - 通过这个选项,日志会包含每个区间的总结(事务数、最小/最大等待时间以及用于方差估计的两个额外域)。
当前在 Windows 上不支持这个选项。
采样率,在写入数据到日志时被用来减少日志产生的数量。如果给出这个选项,只有指定比例的事务被记录。1.0 表示所有事务都将被记录,0.05 表示只有 5% 的事务会被记录。
在处理日志文件时,记得要考虑这个采样率。例如,当计算 tps 值时,你需要相应地乘以这个数字(例如,采样率是 0.01,你将只能得到实际 tps 的 1/100)。
默认的事务脚本在每个事务中发出七个命令:
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;
如果你指定-N,步骤 4 和 5 不会被包括在事务中。如果你指定-S,只会发出SELECT。
pgbench支持通过从一个文件中(-f选项)读取事务脚本替换默认的事务脚本(如上文所述)来运行自定义的基准场景。在这种情况中,一个"事务"就是一个脚本文件的一次执行。你甚至可以指定多个脚本(多个-f选项),那样每次一个客户端会话开始一个新事务时会从中随机选取一个脚本。
脚本文件的格式是每行一个 SQL 命令,不支持跨多行的 SQL 命令。空行以及--开始的行会被忽略。脚本文件中的行也可以是由pgbench自己解释的"元命令",它们在下文中描述。
对脚本文件有一种简单的变量替换功能。如上所述,变量可以用命令行的-D选项设置,或者按下文所说的使用元命令设置。除了用-D命令行选项设置的任何变量之外,还预先设置了一个变量scale为当前的比例因子。一旦被设置,可以在 SQL 命令中写:variablename来插入一个变量的值。当运行多于一个客户端会话时,每一个会话拥有它自己的变量集合。
脚本文件元命令开始于一个反斜线(\)。一个元命令和它的参数用空白分隔。支持的元命令是:
将变量varname设置成一个计算出来的整数值。每一个operand要么是一个整数常量,要么是一个引用了具有整数值的变量的:variablename。operator可以是+\ -、*或者/。
例子:
\set ntellers 10 * :scale
将变量varname设置成界线min和max之间的一个随机整数值。每一个界线要么是一个整数常量,要么是一个引用了具有整数值的变量的:variablename。
例子:
\setrandom aid 1 :naccounts
导致脚本执行休眠指定的时间,时间的单位可以是微妙(us)、毫秒(ms)或者秒(s)。如果单位被忽略,则秒是默认值。number要么是一个整数常量,要么是一个引用了具有整数值的变量的:variablename。
例子:
\sleep 10 ms
设置变量varname为 shell 命令command的结果。该命令必须通过它的标准输出返回一个整数值。
argument要么是一个文本常量,要么是一个引用了任意类型变量的:variablename。如果你想要使用以冒号开始的argument,你需要在argument的开头增加一个额外的冒号。
例子:
\setshell variable_to_be_assigned command literal_argument :variable ::literal_starting_with_colon
与\setshell相同,但是结果被忽略。
例子:
\shell command literal_argument :variable ::literal_starting_with_colon
作为一个例子,内建的类 TPC-B 事务的全部定义是:
\set nbranches :scale \set ntellers 10 * :scale \set naccounts 100000 * :scale \setrandom aid 1 :naccounts \setrandom bid 1 :nbranches \setrandom tid 1 :ntellers \setrandom delta -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会把每一个事务花费的时间写入到一个日志文件。该日志文件被命名为pgbench_log.nnn,其中nnn是 pgbench 进程的 PID。如果-j选项是 2 或更高,会创建多个工作者进程,每一个将有自己的日志文件。第一个工作者将使用标准的单一工作者情形中相同的名称作为它的日志文件名。其他工作者的日志文件将被命名为pgbench_log.nnn.mmm,其中mmm是用于每一个工作者的序号,序号从 1 开始。
日志的格式是:
client_id transaction_no time file_no time_epoch time_us
其中time是以微秒计的总共用掉的事务时间,file_no标识了要使用哪个脚本文件(当用-f指定多个脚本时有用),而time_epoch/time_us是一个 UNIX 纪元格式的时间戳以及一个显示事务完成时间的以微秒计的偏移量(适合于创建一个带有分数秒的 ISO 8601 时间戳)。
这是一些输出的例子:
0 199 2241 0 1175850568 995598 0 200 2465 0 1175850568 998079 0 201 2513 0 1175850569 608 0 202 2038 0 1175850569 2663
在能够处理大量事务的硬件上运行一次长时间的测试时,日志文件可能变得非常大。--sampling-rate选项能被用来只记录事务的一个随机采样。
通过--aggregate-interval选项,日志可以使用一种不太一样的格式:
interval_start num_of_transactions latency_sum latency_2_sum min_latency max_latency
其中interval_start是区间的开始(UNIX 纪元格式时间戳),num_of_transactions是在区间内的事务数,latency_sum是延迟总量(这样你能很容易地计算平均延迟)。下面的两个域对于方差估计有用 - latency_sum是延迟总计而latency_2_sum是延迟的二次方的总计。最后两个域是min_latency - 区间中的最小延迟,以及max_latency - 区间中的最大延迟。一个事务会被计入它被提交时所在的那个区间。
这里是一些输出示例:
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
注意虽然纯(未聚合的)日志文件包含自定义脚本文件的索引,而聚合日志则不包含索引。因此如果你需要针对每个脚本的数据,你需要自行聚合数据。
通过-r选项,pgbench收集每一个客户端执行的每一个语句花费的事务时间。然后在基准完成后,它会报告这些值的平均值,作为每个语句的延迟。
对于默认脚本,输出看起来会像这样:
starting vacuum...end. transaction type: 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 tps = 618.764555 (including connections establishing) tps = 622.977698 (excluding connections establishing) statement latencies in milliseconds: 0.004386 \set nbranches 1 * :scale 0.001343 \set ntellers 10 * :scale 0.001212 \set naccounts 100000 * :scale 0.001310 \setrandom aid 1 :naccounts 0.001073 \setrandom bid 1 :nbranches 0.001005 \setrandom tid 1 :ntellers 0.001078 \setrandom delta -5000 5000 0.326152 BEGIN; 0.603376 UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid; 0.454643 SELECT abalance FROM pgbench_accounts WHERE aid = :aid; 5.528491 UPDATE pgbench_tellers SET tbalance = tbalance + :delta WHERE tid = :tid; 7.335435 UPDATE pgbench_branches SET bbalance = bbalance + :delta WHERE bid = :bid; 0.371851 INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (:tid, :bid, :aid, :delta, CURRENT_TIMESTAMP); 1.212976 END;
如果指定了多个脚本文件,会为每一个脚本文件单独报告平均值。
注意为每个语句的延迟计算收集额外的时间信息会增加一些负荷。这将拖慢平均执行速度并且降低计算出的 TPS。降低的总量会很显著地依赖于平台和硬件。对比使用和不适用延迟报告时的平均 TPS 值是评估时间开销是否明显的好方法。
很容易使用pgbench产生完全没有意义的数字。这里有一些指导可以帮你得到有用的结果。
排在第一位的是,永远不要相信任何只运行了几秒的测试。使用-t或-T选项让运行持续至少几分钟,这样可以用平均值去掉噪声。在一些情况中,你可能需要数小时来得到能重现的数字。多运行几次测试是一个好主意,这样可以看看你的数字是不是可以重现。
对于默认的类 TPC-B 测试场景,初始化的比例因子(-s)应该至少和你想要测试的最大客户端数量一样大(-c),否则你将主要在度量更新争夺。在pgbench_branches表中只有-s行,并且每个事务都想更新其中之一,因此-c值超过-s将毫无疑问地导致大量事务被阻塞来等待其他事务。
默认的测试场景也对表被初始化了多久非常敏感:表中死亡行和死亡空间的累积会改变结果。要理解结果,你必须跟踪更新的总数以及何时发生清理。如果开启了自动清理,它可能会在度量的性能上产生不可预估的改变。
pgbench的一个限制是在尝试测试大量客户端会话时,它自身可能成为瓶颈。这可以通过在数据库服务器之外的一台机器上运行pgbench来缓解,不过必须是具有低网络延迟的机器。甚至可以在多个客户端机器上针对同一个数据库服务器并发地运行多个pgbench实例。