文章目录
  1. 1. pgbench的参数
  2. 2. pgbench的异步接口实现
    1. 2.1. 简单调用PQsendQuery(-M simple)
    2. 2.2. 扩展调用PQsendQueryParams(-M extended)
    3. 2.3. 绑定变量调用PQsendPrepare和PQsendQueryPrepared(-M prepared)
  3. 3. pgbench的安装和测试准备
  4. 4. 初始化命令
  5. 5. pgbench进行postgres的压力测试

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 /* unknown sql mode */
r = 0;

pgbench的安装和测试准备

pgbench的源码在postgres源码目录的contrib/pgbench目录下,进入该路径下进行makemake 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 pgbench;
CREATE DATABASE
postgres=# \q
[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的压力测试,本文参考了1

文章目录
  1. 1. pgbench的参数
  2. 2. pgbench的异步接口实现
    1. 2.1. 简单调用PQsendQuery(-M simple)
    2. 2.2. 扩展调用PQsendQueryParams(-M extended)
    3. 2.3. 绑定变量调用PQsendPrepare和PQsendQueryPrepared(-M prepared)
  3. 3. pgbench的安装和测试准备
  4. 4. 初始化命令
  5. 5. pgbench进行postgres的压力测试

欢迎来到Valleylord的博客!

本博的文章尽量原创。