It does seem to me that we should Poissonize the throttle time, then subtract the average overhead, rather than Poissonizing the difference.
After thinking again about Jeff's point and failing to sleep, I think that doing exactly that is better because:
- it is "right" - the code is simpler and shorter - my transaction stuck sequence issue is not that big an issue anyway Here is a patch to schedule transactions along Poisson-distributed events. This patch replaces my previous proposal.Note that there is no reference to the current time after the stochastic process is initiated. This is necessary, and mean that if transactions lag behind the throttle at some point they will try to catch up later. Neither a good nor a bad thing, mostly a feature.
-- Fabien
diff --git a/contrib/pgbench/pgbench.c b/contrib/pgbench/pgbench.c index bc01f07..0142ed0 100644 --- a/contrib/pgbench/pgbench.c +++ b/contrib/pgbench/pgbench.c @@ -137,6 +137,12 @@ int unlogged_tables = 0; double sample_rate = 0.0; /* + * whether clients are throttled to a given rate, expressed as a delay in us. + * 0, the default means no throttling. + */ +int64 throttle = 0; + +/* * tablespace selection */ char *tablespace = NULL; @@ -204,6 +210,8 @@ typedef struct int nvariables; instr_time txn_begin; /* used for measuring transaction latencies */ instr_time stmt_begin; /* used for measuring statement latencies */ + int64 trigger; /* previous/next throttling (us) */ + bool throttled; /* whether current transaction was throttled */ int use_file; /* index in sql_files for this client */ bool prepared[MAX_FILES]; } CState; @@ -361,6 +369,9 @@ usage(void) " -S perform SELECT-only transactions\n" " -t NUM number of transactions each client runs (default: 10)\n" " -T NUM duration of benchmark test in seconds\n" + " -H SPEC, --throttle SPEC\n" + " delay in second to throttle each client\n" + " sample specs: 0.025 40tps 25ms 25000us\n" " -v vacuum all four standard tables before tests\n" "\nCommon options:\n" " -d print debugging output\n" @@ -1027,7 +1038,7 @@ top: } } - if (commands[st->state]->type == SQL_COMMAND) + if (!st->throttled && commands[st->state]->type == SQL_COMMAND) { /* * Read and discard the query result; note this is not included in @@ -1049,26 +1060,54 @@ top: discard_response(st); } + /* some stuff done at the end */ if (commands[st->state + 1] == NULL) { - if (is_connect) + /* disconnect if required and needed */ + if (is_connect && st->con) { PQfinish(st->con); st->con = NULL; } - ++st->cnt; - if ((st->cnt >= nxacts && duration <= 0) || timer_exceeded) - return clientDone(st, true); /* exit success */ + /* update transaction counter once, and possibly end */ + if (!st->throttled) + { + ++st->cnt; + if ((st->cnt >= nxacts && duration <= 0) || timer_exceeded) + return clientDone(st, true); /* exit success */ + } + + /* handle throttling once, as the last post-transaction stuff */ + if (throttle && !st->throttled) + { + /* compute delay to approximate a Poisson distribution + * 1000000 => 13.8 .. 0 multiplier + * if transactions are too slow or a given wait shorter than + * a transaction, the next transaction will start right away. + */ + int64 wait = (int64) + throttle * -log(getrand(thread, 1, 1000000)/1000000.0); + st->trigger += wait; + st->sleeping = 1; + st->until = st->trigger; + st->throttled = true; + if (debug) + fprintf(stderr, "client %d throttling %d us\n", + st->id, (int) wait); + return true; + } } /* increment state counter */ st->state++; if (commands[st->state] == NULL) { + /* reset */ st->state = 0; st->use_file = (int) getrand(thread, 0, num_files - 1); commands = sql_files[st->use_file]; + st->throttled = false; } } @@ -2086,6 +2125,7 @@ main(int argc, char **argv) {"unlogged-tables", no_argument, &unlogged_tables, 1}, {"sampling-rate", required_argument, NULL, 4}, {"aggregate-interval", required_argument, NULL, 5}, + {"throttle", required_argument, NULL, 'H'}, {NULL, 0, NULL, 0} }; @@ -2152,7 +2192,7 @@ main(int argc, char **argv) state = (CState *) pg_malloc(sizeof(CState)); memset(state, 0, sizeof(CState)); - while ((c = getopt_long(argc, argv, "ih:nvp:dqSNc:j:Crs:t:T:U:lf:D:F:M:", long_options, &optindex)) != -1) + while ((c = getopt_long(argc, argv, "ih:nvp:dqSNc:j:Crs:t:T:U:lf:D:F:M:H:", long_options, &optindex)) != -1) { switch (c) { @@ -2307,6 +2347,26 @@ main(int argc, char **argv) exit(1); } break; + case 'H': + { + /* get a double from the beginning of option value */ + double throttle_value = atof(optarg); + if (throttle_value <= 0.0) + { + fprintf(stderr, "invalid throttle value: %s\n", optarg); + exit(1); + } + /* rough handling of possible units */ + if (strstr(optarg, "us")) + throttle = (int64) throttle_value; + else if (strstr(optarg, "ms")) + throttle = (int64) (1000.0 * throttle_value); + else if (strstr(optarg, "tps")) + throttle = (int64) (1000000.0 / throttle_value); + else /* assume that default is in second */ + throttle = (int64) (1000000.0 * throttle_value); + } + break; case 0: /* This covers long options which take no argument. */ break; @@ -2533,6 +2593,18 @@ main(int argc, char **argv) INSTR_TIME_SET_CURRENT(start_time); srandom((unsigned int) INSTR_TIME_GET_MICROSEC(start_time)); + /* initial throttling setup with regular increasing delays */ + if (throttle) + { + int delay = throttle / nclients; + for (i=0; i<nclients; i++) + { + state[i].trigger = INSTR_TIME_GET_MICROSEC(start_time) + i*delay; + state[i].sleeping = 1; + state[i].until = state[i].trigger; + } + } + /* process builtin SQL scripts */ switch (ttype) { diff --git a/doc/src/sgml/pgbench.sgml b/doc/src/sgml/pgbench.sgml index 79b4baf..b5c6c1c 100644 --- a/doc/src/sgml/pgbench.sgml +++ b/doc/src/sgml/pgbench.sgml @@ -310,6 +310,26 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</> </varlistentry> <varlistentry> + <term><option>-H</option> <replaceable>rate</></term> + <term><option>--throttle</option> <replaceable>rate</></term> + <listitem> + <para> + Do client transaction throttling at the specified rate instead of + maximizing the load. + Each client connection targets this rate by starting transactions + along a Poisson-distributed event time line. + Obviously, the targetted rate must be below the maximum possible rate + of the system. + Example equivalent <replaceable>rate</> specifications which aim at + 40 transactions-per-second, that is one transaction every 25 ms: + <litteral>0.025</>, <litteral>0.025s</>, <litteral>25ms</>, + <litteral>25000us</> and finally <litteral>40tps</>. + Default is no throttling. + </para> + </listitem> + </varlistentry> + + <varlistentry> <term><option>-j</option> <replaceable>threads</></term> <listitem> <para>
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers