pgbench是基于tpc-b模型的postgresql测试工具。它属于开源软件,主要为对 PostgreSQL 进行压力测试的一款简单程序, SQL命令可以在一个连接中顺序地执行,通常会开多个数据库 Session, 并且在测试最后形成测试报告,得出每秒平均事务数,pgbench可以测试 select,update,insert,delete 命令,用户可以编写自己的脚本进行测试。
pgbench的参数
pgbench的参数如下表。
初始化
-i
invokes initialization mode//调用初始化模式
-F NUM
fill factor(指定建表的fill_factor(heap page的保留空间,对于更新频繁的表,可以产生HOT,有利于降低索引膨胀及索引更新的可能性)
-n
do not run VACUUM after initialization//数据初始化后不执行vacuum
-q
quiet logging (one message each 5 seconds)//每隔5秒钟输出一次消息
-s NUM
scaling的值为倍数
--foreign-keys
create foreign key constraints between tables//表间创建主外键
--index-tablespace=TABLESPACE
create tables in the specified tablespace//在特定的表空间创建表
--unlogged-tables
create tables as unlogged tables//创建无日志表
测试执行参数
-c NUM
number of concurrent database clients (default: 1)//测试时模拟的客户端个数,默认为1
-C
establish new connection for each transaction//是否使用短连接(每个请求一个连接,用于没有连接池环境,tps峰值,使用-C后 -M不能使用prepared,使用extend模式)
-D VARNAME=VALUE
define variable for use by custom script//设置变量,在自定义脚本中使用varname引用,可使用多个-D设置多个变量
-f FILENAME
read transaction script from FILENAME//指定自定义的测试文件(由元命令和SQL组成),可使用多个-f指定多个文件,每个文件作为一个事务,每次执行事务时随机选择一个文件执行
-j NUM
number of threads (default: 1)//pgbench的工作线程,-l和-c之间是倍数,c是l的倍数
-l
write transaction times to log file//开启事务统计,输出文件名格式pgbench_log.$PID.$threadID(当-j>=2时,threadID从1开始)
-M simple, extended, prepared
protocol for submitting queries to server (default: simple) //libpg接口
-n
do not run VACUUM before tests//测试之前不执行vacuum
-N
do not update tables "pgbench_tellers" and "pgbench_branches"//TPC-B非默认测试模式,少两个表的更新
-r
report average latency per command//报告测试文件中每条(包括元命令和SQL)的平均执行延迟
-s NUM
report this scale factor in output//使用自定义脚本测试时,指定scale的输出,没有实质意义
-S
perform SELECT-only transactions//TPC-B非默认测试模式,只查询
-t NUM
number of transactions each client runs (default: 10)//指定每个连接的执行事务数
-T NUM
duration of benchmark test in seconds//指定总的压力测试时间,与-t不能同时使用
-v
vacuum all four standard tables before tests//测试前先vacuum4个和tpc-b相关的表
--aggregate-interval=NUM
aggregate data over NUM seconds//输出聚合后的事务统计信息
--sampling-rate=NUM
fraction of transactions to log (e.g. 0.01 for 1% sample)//指定采样百分比,得出的TPC将只有正常tps*rate | | 通用参数 | -d | print debugging output | | | -h HOSTNAME | database server host or socket directory | | | -p PORT | database server port number | | | -U USERNAME | connect as specified database user | | | -V, --version | output version information, then exit | | | -?, --help | show this help, then exit |
pgbench的异步接口实现
pgbench支持3种异步接口:简单调用、扩展调用、绑定变量调用,参考Postgres官方文档。
简单调用PQsendQuery(-M simple
)
向服务器提交一个命令而不等待结果。如果查询成功发送则返回 1,否则返回 0。 (此时,可以用PQerrorMessage获取关于失败的信息)。
在成功调用PQsendQuery后,调用PQgetResult一次或者多次获取结果 。在PQsendQuery返回 NULL 指针,表明命令完成之前, 我们不能再调用PQsendQuery(在同一次连接里)。
注:每次调用会带来比较大的开销,每次执行都需要sql解析,因为没有没有执行计划缓存。
扩展调用PQsendQueryParams(-M extended
)
给服务器提交一个命令和(命令需要的)分隔的参数,而不等待结果。
这个等效于 PQsendQuery,只是查询参数可以和查询字串分开声明。 函数的参数处理和 PQexecParams 一样。和 PQexecParams 类似, 它不能在 2.0 版本的协议连接上工作,并且它只允许在查询字串里出现一条命令。
注:也有和简单调用一样的问题,没有执行计划缓存
绑定变量调用PQsendPrepare和PQsendQueryPrepared(-M prepared
)
PQsendPrepare发送一个请求,创建一个给定参数的准备好语句,而不等待结束。
这是 PQprepare 的异步版本:如果它能发送这个请求,则返回 1, 如果不能,则返回 0。在成功调用之后,调用 PQgetResult 判断服务器是否成功创建了准备好语句。 这个函数的参数的处理和 PQprepare 一样。 类似 PQprepare,它不能在 2.0 版本协议的连接上运转。
PQsendQueryPrepared发送一个执行带有给出参数的准备好的语句的请求,不等待结果。
这个函数类似 PQsendQueryParams,但是要执行的命令是通过给一个前面准备好的语句命名来声明的, 而不是给出一个查询字串。函数的参数处理和 PQexecPrepared 一样。类似 PQexecPrepared, 它也不能在 2.0 版本的协议连接上跑。
注:绑定变量可以重用执行计划,相似的SQL但是参数不同,只需解析SQL一次即可。
以下代码段摘选自pgbench.c的代码,显示了pgbench根据不同的SQL接口模式,调用不同的postgres函数接口,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
if (querymode == QUERY_SIMPLE)
{
char *sql;
sql = pg_strdup(command->argv[0 ]);
sql = assignVariables(st, sql);
if (debug)
fprintf (stderr, "client %d sending %s\n" , st->id, sql);
r = PQsendQuery(st->con, sql);
free (sql);
}
else if (querymode == QUERY_EXTENDED)
{
const char *sql = command->argv[0 ];
const char *params[MAX_ARGS];
getQueryParams(st, command, params);
if (debug)
fprintf (stderr, "client %d sending %s\n" , st->id, sql);
r = PQsendQueryParams(st->con, sql, command->argc - 1 ,
NULL, params, NULL, NULL, 0 );
}
else if (querymode == QUERY_PREPARED)
{
char name[MAX_PREPARE_NAME];
const char *params[MAX_ARGS];
if (!st->prepared[st->use_file])
{
int j;
for (j = 0 ; commands[j] != NULL; j++)
{
PGresult *res;
char name[MAX_PREPARE_NAME];
if (commands[j]->type != SQL_COMMAND)
continue ;
preparedStatementName(name, st->use_file, j);
res = PQprepare(st->con, name,
commands[j]->argv[0 ], commands[j]->argc - 1 , NULL);
if (PQresultStatus(res) != PGRES_COMMAND_OK)
fprintf (stderr, "%s" , PQerrorMessage(st->con));
PQclear(res);
}
st->prepared[st->use_file] = true ;
}
getQueryParams(st, command, params);
preparedStatementName(name, st->use_file, st->state);
if (debug)
fprintf (stderr, "client %d sending %s\n" , st->id, name);
r = PQsendQueryPrepared(st->con, name, command->argc - 1 ,
params, NULL, NULL, 0 );
}
else
r = 0 ;
pgbench的安装和测试准备
pgbench的源码在postgres源码目录的contrib/pgbench目录下,进入该路径下进行make
,make install
即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
[postgres@anzhy contrib]$ cd pgbench/
[postgres@anzhy pgbench]$ ls
Makefile pgbench.c
[postgres@anzhy pgbench]$ make all
gcc -O2 -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Wendif-labels -Wmissing-format-attribute -Wformat-security
-fno-strict-aliasing -fwrapv -g -pthread -D_REENTRANT -D_THREAD_SAFE -D_POSIX_PTHREAD_SEMANTICS -I../../src/interfaces/libpq
-I. -I. -I../../src/include -D_GNU_SOURCE -c -o pgbench.o pgbench.c -MMD -MP -MF .deps/pgbench.Po
gcc -O2 -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Wendif-labels -Wmissing-format-attribute -Wformat-security
-fno-strict-aliasing -fwrapv -g -pthread -D_REENTRANT -D_THREAD_SAFE -D_POSIX_PTHREAD_SEMANTICS pgbench.o -L../../src/port
-lpgport -L../../src/common -lpgcommon -L../../src/interfaces/libpq -lpq -lpthread -L../../src/port -L../../src/common
-Wl,--as-needed -Wl,-rpath,'/home/postgres/postgres/lib' ,--enable-new-dtags -lpgport -lpgcommon -lz -lreadline -lcrypt -ldl -lm -o pgbench
[postgres@anzhy pgbench]$ make install
/bin/mkdir -p '/home/postgres/postgres/bin'
/usr/bin/install -c pgbench '/home/postgres/postgres/bin'
测试之前当然还要建立一个测试数据库,这里就使用pgbench
这个数据库名。
1
2
3
4
5
6
7
8
[postgres@anzhy ~]$ psql -U postgres -p 5432
psql (9.3 .4 )
Type "help" for help.
postgres=
CREATE DATABASE
postgres=
[postgres@anzhy ~]$
初始化命令
初始化数据库中的测试数据,使用pgbench -i
进入初始化模式。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
[postgres@anzhy ~]$ pgbench -i --unlogged-tables -s 2 -U postgres -p 5432 -d pgbench
Connection to database "pgbench" failed:
FATAL: database "pgbench" does not exist
[postgres@anzhy ~]$ pgbench -i --unlogged-tables -s 2 -U postgres -p 5432 -d pgbench
NOTICE: table "pgbench_history" does not exist, skipping
NOTICE: table "pgbench_tellers" does not exist, skipping
NOTICE: table "pgbench_accounts" does not exist, skipping
NOTICE: table "pgbench_branches" does not exist, skipping
creating tables...
100000 of 200000 tuples (50 %) done (elapsed 0.24 s, remaining 0.24 s).
200000 of 200000 tuples (100 %) done (elapsed 0.52 s, remaining 0.00 s).
vacuum...
set primary keys...
done.
[postgres@anzhy ~]$ pgbench -i --unlogged-tables -s 16 -U postgres -p 5432 -d pgbench
creating tables...
100000 of 1600000 tuples (6 %) done (elapsed 0.16 s, remaining 2.46 s).
200000 of 1600000 tuples (12 %) done (elapsed 0.35 s, remaining 2.43 s).
300000 of 1600000 tuples (18 %) done (elapsed 0.51 s, remaining 2.23 s).
400000 of 1600000 tuples (25 %) done (elapsed 0.70 s, remaining 2.09 s).
500000 of 1600000 tuples (31 %) done (elapsed 0.91 s, remaining 2.01 s).
600000 of 1600000 tuples (37 %) done (elapsed 1.16 s, remaining 1.94 s).
700000 of 1600000 tuples (43 %) done (elapsed 1.36 s, remaining 1.75 s).
800000 of 1600000 tuples (50 %) done (elapsed 1.60 s, remaining 1.60 s).
900000 of 1600000 tuples (56 %) done (elapsed 2.13 s, remaining 1.66 s).
1000000 of 1600000 tuples (62 %) done (elapsed 2.39 s, remaining 1.44 s).
1100000 of 1600000 tuples (68 %) done (elapsed 2.64 s, remaining 1.20 s).
1200000 of 1600000 tuples (75 %) done (elapsed 2.87 s, remaining 0.96 s).
1300000 of 1600000 tuples (81 %) done (elapsed 3.62 s, remaining 0.84 s).
1400000 of 1600000 tuples (87 %) done (elapsed 3.95 s, remaining 0.56 s).
1500000 of 1600000 tuples (93 %) done (elapsed 4.20 s, remaining 0.28 s).
1600000 of 1600000 tuples (100 %) done (elapsed 4.54 s, remaining 0.00 s).
vacuum...
set primary keys...
done.
[postgres@anzhy ~]$
以上参数中,-i
表示初始化模式,--unlogged-tables
表示创建没有log的表,-s 16
和-s 2
表示默认的数据的几倍,默认是100000条数据,其他是postgres连接的参数。每次测试默认会清除之前的表,创建完之后可以发现,pgbench_accounts表中有了1600000条数据。
pgbench进行postgres的压力测试
在不使用-i
参数的时候,pgbench都是表示在进行压力测试,如下命令表示一个压力测试的案例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
pgbench -M prepared -r -c 8 -j 2 -T 10 -U postgres -p 5432 -d pgbench -l
......
transaction type : TPC-B (sort of)
scaling factor: 16
query mode: prepared
number of clients: 8
number of threads: 2
duration: 10 s
number of transactions actually processed: 4508
tps = 449.584997 (including connections establishing)
tps = 450.698796 (excluding connections establishing)
statement latencies in milliseconds:
0.049447 \set nbranches 1 * :scale
0.047364 \set ntellers 10 * :scale
0.045153 \set naccounts 100000 * :scale
0.036945 \setrandom aid 1 :naccounts
0.040092 \setrandom bid 1 :nbranches
0.039480 \setrandom tid 1 :ntellers
0.036813 \setrandom delta -5000 5000
2.554601 BEGIN;
2.468227 UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid;
2.355124 SELECT abalance FROM pgbench_accounts WHERE aid = :aid;
2.481090 UPDATE pgbench_tellers SET tbalance = tbalance + :delta WHERE tid = :tid;
2.758949 UPDATE pgbench_branches SET bbalance = bbalance + :delta WHERE bid = :bid;
2.271135 INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (:tid, :bid, :aid, :delta, CURRENT_TIMESTAMP);
2.370568 END;
以上参数中,-M prepared
表示绑定变量形式的调用SQL,-r
表示报告测试文件中每条SQL的平均执行延迟,-c 8
表示模拟8个客户端,-j 2
表示pgbench的工作线程是2个,-T 10
表示压力测试的时间是10秒,-l
表示把事务统计写入log,其余的是postgres连接相关的参数。
执行上述命令后,屏幕上开始显示执行过程,最后给出测试总结,可以看出在这个testcase下,tps是450。如果觉得不想看执行的时候输出,可以将这些输出重定向到文件,使用nohup
和>
。如下,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
[postgres@anzhy ~]$ nohup pgbench -M prepared -r -c 8 -j 2 -T 10 -U postgres -p 5432 -d pgbench -l > pgbench.log 2 >&1
[postgres@anzhy ~]$ tail -27 pgbench.log
client 5 receiving
client 3 receiving
pghost: pgport: 5432 nclients: 8 duration: 10 dbName: pgbench
transaction type : TPC-B (sort of)
scaling factor: 16
query mode: prepared
number of clients: 8
number of threads: 2
duration: 10 s
number of transactions actually processed: 7287
tps = 727.437968 (including connections establishing)
tps = 729.205960 (excluding connections establishing)
statement latencies in milliseconds:
0.061104 \set nbranches 1 * :scale
0.060691 \set ntellers 10 * :scale
0.063393 \set naccounts 100000 * :scale
0.058017 \setrandom aid 1 :naccounts
0.065122 \setrandom bid 1 :nbranches
0.064820 \setrandom tid 1 :ntellers
0.063816 \setrandom delta -5000 5000
1.449164 BEGIN;
1.518156 UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid;
1.456869 SELECT abalance FROM pgbench_accounts WHERE aid = :aid;
1.509500 UPDATE pgbench_tellers SET tbalance = tbalance + :delta WHERE tid = :tid;
1.620091 UPDATE pgbench_branches SET bbalance = bbalance + :delta WHERE bid = :bid;
1.371588 INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (:tid, :bid, :aid, :delta, CURRENT_TIMESTAMP);
1.498616 END;
[postgres@anzhy ~]$
将输出重定向到pgbench.log中,然后用tail查看即可。
以上是使用pgbench做PostgreSQL的压力测试,本文参考了。