V3 protocol is slower than V2
Hi,
V3 protocol seems to be slower than V2 if we don't use prepared statement
together. I measured performance of protocols on 8.3beta using pgbench -S
with modification to use PQsendQueryParams and PQsendQueryPrepared
instead of PQsendQuery. (I'll send the patch to -patches shortly.)
[V2] PQsendQuery (V2; original)
[V3] PQsendQueryParams (V3)
[V3P] PQsendQueryPrepared (V3 with prepared statements)
V3 was 12% slower than V2, and V3P was 40% faster than V2.
There seems to be some inefficient behaviors in the handling of
V3 protocol or in libpq. Are there any TODO items here?
----
pg_ctl start -o "-c shared_buffers=1GB"
./pgbench -n -S -s50 -c16 -t100000 -M [V2|V3|PREPARE]
transaction type: SELECT only
scaling factor: 50
number of clients: 16
number of transactions per client: 100000
number of transactions actually processed: 1600000/1600000
sql mode: V2
tps = 19025.932203 (including connections establishing)
tps = 19036.285885 (excluding connections establishing)
sql mode: V3
tps = 16699.173521 (including connections establishing)
tps = 16707.135700 (excluding connections establishing)
sql mode: PREPARE
tps = 26629.710398 (including connections establishing)
tps = 26649.901658 (excluding connections establishing)
Regards,
---
ITAGAKI Takahiro
NTT Open Source Software Center
ITAGAKI Takahiro <itagaki.takahiro@oss.ntt.co.jp> writes:
[V2] PQsendQuery (V2; original)
[V3] PQsendQueryParams (V3)
[V3P] PQsendQueryPrepared (V3 with prepared statements)
V3 was 12% slower than V2, and V3P was 40% faster than V2.
Those aren't really comparable, because the functionality is different.
Did you check plain PQSendQuery on both V2 and V3?
regards, tom lane
Tom Lane <tgl@sss.pgh.pa.us> wrote:
ITAGAKI Takahiro <itagaki.takahiro@oss.ntt.co.jp> writes:
[V2] PQsendQuery (V2; original)
[V3] PQsendQueryParams (V3)
[V3P] PQsendQueryPrepared (V3 with prepared statements)V3 was 12% slower than V2, and V3P was 40% faster than V2.
Those aren't really comparable, because the functionality is different.
Did you check plain PQSendQuery on both V2 and V3?
Oops, I called 'simple query' V2. All of my tests used V3 protocol.
The results need to be read as:
PQsendQuery = (simple query)
vs.
PQsendQueryParams = (parse + bind + describe + execute + sync)
If the latter is slower than the former, the another version of
PQsendQueryParams, that fills parameters into SQL and uses simple-query,
might be considerable for performance. For example in Java, using
PreparedStatement class with V2 protocol.
Regards,
---
ITAGAKI Takahiro
NTT Open Source Software Center
Here is a patch for testing query modes on pgbench. It is discussed on
http://archives.postgresql.org/pgsql-hackers/2007-10/msg00755.php
I think this is useful to measure performance of protocols and
prepared statements. I hope the extended protocol will be improved
as fast as the simple protocol. It is 10-20% slower now.
(for 8.4 item)
The querymode option is added:
-M querymode
Choose the query mode from the follows:
simple: using simple query protocol.
extended: using extended protocol.
prepared: using extended protocol with prepared statements.
default is simple.
Usage:
$ pgbench -M [simple|extended|prepared]
Regards,
---
ITAGAKI Takahiro
NTT Open Source Software Center
Attachments:
pgbench_querymode.patchapplication/octet-stream; name=pgbench_querymode.patchDownload
diff -cpr head/contrib/pgbench/README.pgbench pgbench_querymode/contrib/pgbench/README.pgbench
*** head/contrib/pgbench/README.pgbench Wed Oct 17 15:19:18 2007
--- pgbench_querymode/contrib/pgbench/README.pgbench Wed Oct 17 15:20:08 2007
*************** o options
*** 163,168 ****
--- 163,176 ----
0 201 2513 0 1175850569 608
0 202 2038 0 1175850569 2663
+ -M querymode
+
+ Choose the query mode from the follows:
+ simple: using simple query protocol.
+ extended: using extended protocol.
+ prepared: using extended protocol with prepared statements.
+ default is simple.
+
-F fillfactor
Create tables(accounts, tellers and branches) with the given
diff -cpr head/contrib/pgbench/pgbench.c pgbench_querymode/contrib/pgbench/pgbench.c
*** head/contrib/pgbench/pgbench.c Wed Oct 17 15:19:18 2007
--- pgbench_querymode/contrib/pgbench/pgbench.c Wed Oct 17 14:53:44 2007
*************** typedef struct
*** 102,107 ****
--- 102,109 ----
char *value; /* its value */
} Variable;
+ #define MAX_FILES 128 /* max number of SQL script files allowed */
+
/*
* structures used in custom query mode
*/
*************** typedef struct
*** 121,126 ****
--- 123,129 ----
int nvariables;
struct timeval txn_begin; /* used for measuring latencies */
int use_file; /* index in sql_files for this client */
+ bool prepared[MAX_FILES];
} CState;
/*
*************** typedef struct
*** 130,135 ****
--- 133,149 ----
#define META_COMMAND 2
#define MAX_ARGS 10
+ typedef enum QueryMode
+ {
+ QUERY_SIMPLE, /* simple query */
+ QUERY_EXTENDED, /* extended query */
+ QUERY_PREPARED, /* extended query with prepared statements */
+ NUM_SQLMODE
+ } QueryMode;
+
+ static QueryMode querymode = QUERY_SIMPLE;
+ static const char *QUERYMODE[] = { "simple", "extended", "prepared" };
+
typedef struct
{
int type; /* command type (SQL_COMMAND or META_COMMAND) */
*************** typedef struct
*** 137,144 ****
char *argv[MAX_ARGS]; /* command list */
} Command;
- #define MAX_FILES 128 /* max number of SQL script files allowed */
-
Command **sql_files[MAX_FILES]; /* SQL script files */
int num_files; /* its number */
--- 151,156 ----
*************** static char *select_only = {
*** 186,192 ****
static void
usage(void)
{
! fprintf(stderr, "usage: pgbench [-h hostname][-p port][-c nclients][-t ntransactions][-s scaling_factor][-D varname=value][-n][-C][-v][-S][-N][-f filename][-l][-U login][-P password][-d][dbname]\n");
fprintf(stderr, "(initialize mode): pgbench -i [-h hostname][-p port][-s scaling_factor] [-F fillfactor] [-U login][-P password][-d][dbname]\n");
}
--- 198,204 ----
static void
usage(void)
{
! fprintf(stderr, "usage: pgbench [-h hostname][-p port][-c nclients][-t ntransactions][-s scaling_factor][-D varname=value][-n][-C][-v][-S][-N][-f filename][-M querymode][-l][-U login][-P password][-d][dbname]\n");
fprintf(stderr, "(initialize mode): pgbench -i [-h hostname][-p port][-s scaling_factor] [-F fillfactor] [-U login][-P password][-d][dbname]\n");
}
*************** assignVariables(CState * st, char *sql)
*** 439,444 ****
--- 451,472 ----
}
static void
+ getQueryParams(CState *st, const Command *command, const char **params)
+ {
+ int i;
+
+ for (i = 0; i < command->argc - 1; i++)
+ params[i] = getVariable(st, command->argv[i+1]);
+ }
+
+ #define MAX_PREPARE_NAME 32
+ static void
+ preparedStatementName(char *buffer, int file, int state)
+ {
+ sprintf(buffer, "P%d_%d", file, state);
+ }
+
+ static void
doCustom(CState * state, int n, int debug)
{
PGresult *res;
*************** top:
*** 557,585 ****
if (commands[st->state]->type == SQL_COMMAND)
{
! char *sql;
! if ((sql = strdup(commands[st->state]->argv[0])) == NULL
! || (sql = assignVariables(st, sql)) == NULL)
{
! fprintf(stderr, "out of memory\n");
! st->ecnt++;
! return;
}
! if (debug)
! fprintf(stderr, "client %d sending %s\n", n, sql);
! if (PQsendQuery(st->con, sql) == 0)
{
if (debug)
! fprintf(stderr, "PQsendQuery(%s)failed\n", sql);
st->ecnt++;
}
else
- {
st->listen = 1; /* flags that should be listened */
- }
- free(sql);
}
else if (commands[st->state]->type == META_COMMAND)
{
--- 585,667 ----
if (commands[st->state]->type == SQL_COMMAND)
{
! const Command *command = commands[st->state];
! int r;
! if (querymode == QUERY_SIMPLE)
{
! char *sql;
!
! if ((sql = strdup(command->argv[0])) == NULL
! || (sql = assignVariables(st, sql)) == NULL)
! {
! fprintf(stderr, "out of memory\n");
! st->ecnt++;
! return;
! }
!
! if (debug)
! fprintf(stderr, "client %d sending %s\n", n, 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", n, 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", n, name);
! r = PQsendQueryPrepared(st->con, name, command->argc - 1,
! params, NULL, NULL, 0);
! }
! else /* unknown sql mode */
! r = 0;
!
! if (r == 0)
! {
! if (debug)
! fprintf(stderr, "client %d cannot send %s\n", n, command->argv[0]);
st->ecnt++;
}
else
st->listen = 1; /* flags that should be listened */
}
else if (commands[st->state]->type == META_COMMAND)
{
*************** init(void)
*** 902,907 ****
--- 984,1046 ----
PQfinish(con);
}
+ /*
+ * Parse the raw sql and replace :param to $n.
+ */
+ static bool
+ parseQuery(Command *cmd, const char *raw_sql)
+ {
+ int i,
+ j;
+ char *sql,
+ *p,
+ *name;
+
+ sql = strdup(raw_sql);
+ if (sql == NULL)
+ return false;
+ cmd->argc = 1;
+
+ i = 0;
+ while ((p = strchr(&sql[i], ':')) != NULL)
+ {
+ char param[12];
+ int paramln;
+
+ if (cmd->argc > MAX_ARGS)
+ {
+ fprintf(stderr, "too many args: %s\n", raw_sql);
+ return false;
+ }
+
+ i = j = p - sql;
+ do
+ {
+ i++;
+ } while (isalnum((unsigned char) sql[i]) || sql[i] == '_');
+ if (i == j + 1)
+ continue;
+
+ name = malloc(i - j);
+ if (name == NULL)
+ return false;
+ memcpy(name, &sql[j + 1], i - (j + 1));
+ name[i - (j + 1)] = '\0';
+
+ paramln = sprintf(param, "$%d", cmd->argc);
+ if (paramln != i - j)
+ memmove(&sql[j + paramln], &sql[i], strlen(&sql[i]) + 1);
+ strncpy(&sql[j], param, paramln);
+ cmd->argv[cmd->argc] = name;
+ cmd->argc++;
+
+ i = j + paramln;
+ }
+
+ cmd->argv[0] = sql;
+ return true;
+ }
+
static Command *
process_commands(char *buf)
{
*************** process_commands(char *buf)
*** 1008,1017 ****
{
my_commands->type = SQL_COMMAND;
! if ((my_commands->argv[0] = strdup(p)) == NULL)
! return NULL;
!
! my_commands->argc++;
}
return my_commands;
--- 1147,1167 ----
{
my_commands->type = SQL_COMMAND;
! switch (querymode)
! {
! case QUERY_SIMPLE:
! if ((my_commands->argv[0] = strdup(p)) == NULL)
! return NULL;
! my_commands->argc++;
! break;
! case QUERY_EXTENDED:
! case QUERY_PREPARED:
! if (!parseQuery(my_commands, p))
! return NULL;
! break;
! default:
! return NULL;
! }
}
return my_commands;
*************** printResults(
*** 1188,1193 ****
--- 1338,1344 ----
printf("transaction type: %s\n", s);
printf("scaling factor: %d\n", scale);
+ printf("query mode: %s\n", QUERYMODE[querymode]);
printf("number of clients: %d\n", nclients);
printf("number of transactions per client: %d\n", nxacts);
printf("number of transactions actually processed: %d/%d\n", normal_xacts, nxacts * nclients);
*************** main(int argc, char **argv)
*** 1255,1261 ****
memset(state, 0, sizeof(*state));
! while ((c = getopt(argc, argv, "ih:nvp:dc:t:s:U:P:CNSlf:D:F:")) != -1)
{
switch (c)
{
--- 1406,1412 ----
memset(state, 0, sizeof(*state));
! while ((c = getopt(argc, argv, "ih:nvp:dc:t:s:U:P:CNSlf:D:F:M:")) != -1)
{
switch (c)
{
*************** main(int argc, char **argv)
*** 1365,1370 ****
--- 1516,1531 ----
if ((fillfactor < 10) || (fillfactor > 100))
{
fprintf(stderr, "invalid fillfactor: %d\n", fillfactor);
+ exit(1);
+ }
+ break;
+ case 'M':
+ for (querymode = 0; querymode < NUM_SQLMODE; querymode++)
+ if (strcmp(optarg, QUERYMODE[querymode]) == 0)
+ break;
+ if (querymode >= NUM_SQLMODE)
+ {
+ fprintf(stderr, "invalid querymode: %s\n", optarg);
exit(1);
}
break;
This has been saved for the 8.4 release:
http://momjian.postgresql.org/cgi-bin/pgpatches_hold
---------------------------------------------------------------------------
ITAGAKI Takahiro wrote:
Here is a patch for testing query modes on pgbench. It is discussed on
http://archives.postgresql.org/pgsql-hackers/2007-10/msg00755.phpI think this is useful to measure performance of protocols and
prepared statements. I hope the extended protocol will be improved
as fast as the simple protocol. It is 10-20% slower now.
(for 8.4 item)The querymode option is added:
-M querymode
Choose the query mode from the follows:
simple: using simple query protocol.
extended: using extended protocol.
prepared: using extended protocol with prepared statements.
default is simple.Usage:
$ pgbench -M [simple|extended|prepared]Regards,
---
ITAGAKI Takahiro
NTT Open Source Software Center
[ Attachment, skipping... ]
---------------------------(end of broadcast)---------------------------
TIP 4: Have you searched our list archives?
--
Bruce Momjian <bruce@momjian.us> http://momjian.us
EnterpriseDB http://postgres.enterprisedb.com
+ If your life is a hard drive, Christ can be your backup. +