On 6/1/13 5:00 AM, Fabien COELHO wrote:
Question 1: should it report the maximum lang encountered?

I haven't found the lag measurement to be very useful yet, outside of debugging the feature itself. Accordingly I don't see a reason to add even more statistics about the number outside of testing the code. I'm seeing some weird lag problems that this will be useful for though right now, more on that a few places below.

Question 2: the next step would be to have the current lag shown under
option --progress, but that would mean having a combined --throttle
--progress patch submission, or maybe dependencies between patches.

This is getting too far ahead. Let's get the throttle part nailed down before introducing even more moving parts into this. I've attached an updated patch that changes a few things around already. I'm not done with this yet and it needs some more review before commit, but it's not too far away from being ready.

This feature works quite well. On a system that will run at 25K TPS without any limit, I did a run with 25 clients and a rate of 400/second, aiming at 10,000 TPS, and that's what I got:

number of clients: 25
number of threads: 1
duration: 60 s
number of transactions actually processed: 599620
average transaction lag: 0.307 ms
tps = 9954.779317 (including connections establishing)
tps = 9964.947522 (excluding connections establishing)

I never thought of implementing the throttle like this before, but it seems to work out well so far. Check out tps.png to see the smoothness of the TPS curve (the graphs came out of pgbench-tools. There's a little more play outside of the target than ideal for this case. Maybe it's worth tightening the Poisson curve a bit around its center?

The main implementation issue I haven't looked into yet is why things can get weird at the end of the run. See the latency.png graph attached and you can see what I mean.

I didn't like the naming on this option or all of the ways you could specify the delay. None of those really added anything, since you can get every other behavior by specifying a non-integer TPS. And using the word "throttle" inside the code is fine, but I didn't like exposing that implementation detail more than it had to be.

What I did instead was think of this as a transaction rate target, which makes the help a whole lot simpler:

  -R SPEC, --rate SPEC
               target rate per client in transactions per second

Made the documentation easier to write too. I'm not quite done with that yet, the docs wording in this updated patch could still be better.

I personally would like this better if --rate specified a *total* rate across all clients. However, there are examples of both types of settings in the program already, so there's no one precedent for which is right here. -t is per-client and now -R is too; I'd prefer it to be like -T instead. It's not that important though, and the code is cleaner as it's written right now. Maybe this is better; I'm not sure.

I did some basic error handling checks on this and they seemed good, the program rejects target rates of <=0.

On the topic of this weird latency spike issue, I did see that show up in some of the results too. Here's one where I tried to specify a rate higher than the system can actually handle, 80000 TPS total on a SELECT-only test

$ pgbench -S -T 30 -c 8 -j 4 -R10000tps pgbench
starting vacuum...end.
transaction type: SELECT only
scaling factor: 100
query mode: simple
number of clients: 8
number of threads: 4
duration: 30 s
number of transactions actually processed: 761779
average transaction lag: 10298.380 ms
tps = 25392.312544 (including connections establishing)
tps = 25397.294583 (excluding connections establishing)

It was actually limited by the capabilities of the hardware, 25K TPS. 10298 ms of lag per transaction can't be right though.

Some general patch submission suggestions for you as a new contributor:

-When re-submitting something with improvements, it's a good idea to add a version number to the patch so reviewers can tell them apart easily. But there is no reason to change the subject line of the e-mail each time. I followed that standard here. If you updated this again I would name the file pgbench-throttle-v9.patch but keep the same e-mail subject.

-There were some extra carriage return characters in your last submission. Wasn't a problem this time, but if you can get rid of those that makes for a better patch.

--
Greg Smith   2ndQuadrant US    g...@2ndquadrant.com   Baltimore, MD
PostgreSQL Training, Services, and 24x7 Support www.2ndQuadrant.com

<<attachment: tps.png>>

<<attachment: latency.png>>

diff --git a/contrib/pgbench/pgbench.c b/contrib/pgbench/pgbench.c
index 8c202bf..799dfcd 100644
--- a/contrib/pgbench/pgbench.c
+++ b/contrib/pgbench/pgbench.c
@@ -137,6 +137,12 @@ int                        unlogged_tables = 0;
 double         sample_rate = 0.0;
 
 /*
+ * When clients are throttled to a given rate limit, this is the target delay
+ * to reach that rate in usec.  0 is the default and means no throttling.
+ */
+int64          throttle_delay = 0;
+
+/*
  * tablespace selection
  */
 char      *tablespace = NULL;
@@ -205,6 +211,9 @@ 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 */
+       int64           throttle_lag;   /* transaction lag behind throttling */
        int                     use_file;               /* index in sql_files 
for this client */
        bool            prepared[MAX_FILES];
 } CState;
@@ -348,6 +357,8 @@ usage(void)
                   "  -D VARNAME=VALUE\n"
                   "               define variable for use by custom script\n"
                   "  -f FILENAME  read transaction script from FILENAME\n"
+                  "  -R SPEC, --rate SPEC\n"
+                  "               target rate per client in transactions per 
second\n"
                   "  -j NUM       number of threads (default: 1)\n"
                   "  -l           write transaction times to log file\n"
                   "  -M simple|extended|prepared\n"
@@ -902,13 +913,41 @@ doCustom(TState *thread, CState *st, instr_time 
*conn_time, FILE *logfile, AggVa
 top:
        commands = sql_files[st->use_file];
 
+       /* handle throttling once per transaction by inserting a sleep.
+        * this is simpler than doing it at the end.
+        */
+       if (throttle_delay && ! 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_delay * -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 "INT64_FORMAT" 
us\n",
+                                       st->id, wait);
+       }
+
        if (st->sleeping)
        {                                                       /* are we 
sleeping? */
                instr_time      now;
+               int64 now_us;
 
                INSTR_TIME_SET_CURRENT(now);
-               if (st->until <= INSTR_TIME_GET_MICROSEC(now))
+               now_us = INSTR_TIME_GET_MICROSEC(now);
+               if (st->until <= now_us)
+               {
                        st->sleeping = 0;       /* Done sleeping, go ahead with 
next command */
+                       if (throttle_delay && st->state==0)
+                               /* measure lag of throttled transaction */
+                               st->throttle_lag += (now_us - st->until);
+               }
                else
                        return true;            /* Still sleeping, nothing to 
do here */
        }
@@ -1095,6 +1134,7 @@ top:
                        st->state = 0;
                        st->use_file = (int) getrand(thread, 0, num_files - 1);
                        commands = sql_files[st->use_file];
+                       st->throttled = false;
                }
        }
 
@@ -2015,7 +2055,8 @@ process_builtin(char *tb)
 
 /* print out results */
 static void
-printResults(int ttype, int normal_xacts, int nclients,
+printResults(int ttype, int normal_xacts,
+                        CState *clients, int nclients,
                         TState *threads, int nthreads,
                         instr_time total_time, instr_time conn_total_time)
 {
@@ -2055,6 +2096,23 @@ printResults(int ttype, int normal_xacts, int nclients,
                printf("number of transactions actually processed: %d\n",
                           normal_xacts);
        }
+
+       if (throttle_delay)
+       {
+               /* Report average transaction lag under throttling, i.e. the 
delay
+                  between scheduled and actual start times for the transaction.
+                  The measured lag may be linked to the thread/client load,
+                  the database load, or the Poisson throttling process.
+                  should it report the maximum encountered lag?
+                */
+               int64 throttle_lag = 0;
+               int c;
+               for (c = 0; c < nclients; c++)
+                       throttle_lag += clients[c].throttle_lag;
+               printf("average transaction lag: %.3f ms\n",
+                          0.001 * throttle_lag / normal_xacts);
+       }
+
        printf("tps = %f (including connections establishing)\n", tps_include);
        printf("tps = %f (excluding connections establishing)\n", tps_exclude);
 
@@ -2115,6 +2173,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},
+               {"rate", required_argument, NULL, 'R'},
                {NULL, 0, NULL, 0}
        };
 
@@ -2181,7 +2240,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:R:", long_options, &optindex)) != -1)
        {
                switch (c)
                {
@@ -2336,6 +2395,19 @@ main(int argc, char **argv)
                                        exit(1);
                                }
                                break;
+                       case 'R':
+                       {
+                               /* get a double from the beginning of option 
value */
+                               double throttle_value = atof(optarg);
+                               if (throttle_value <= 0.0)
+                               {
+                                       fprintf(stderr, "invalid rate limit: 
%s\n", optarg);
+                                       exit(1);
+                               }
+                               /* Invert rate limit into a time offset */
+                               throttle_delay = (int64) (1000000.0 / 
throttle_value);
+                       }
+                               break;
                        case 0:
                                /* This covers long options which take no 
argument. */
                                break;
@@ -2626,6 +2698,14 @@ main(int argc, char **argv)
        /* get start up time */
        INSTR_TIME_SET_CURRENT(start_time);
 
+       /* set initial client throttling trigger */
+       if (throttle_delay)
+       {
+               state[0].trigger = INSTR_TIME_GET_MICROSEC(start_time);
+               for (i = 1; i < nclients; i++)
+                       state[i].trigger = state[0].trigger;
+       }
+
        /* set alarm if duration is specified. */
        if (duration > 0)
                setalarm(duration);
@@ -2680,7 +2760,7 @@ main(int argc, char **argv)
        /* get end time */
        INSTR_TIME_SET_CURRENT(total_time);
        INSTR_TIME_SUBTRACT(total_time, start_time);
-       printResults(ttype, total_xacts, nclients, threads, nthreads,
+       printResults(ttype, total_xacts, state, nclients, threads, nthreads,
                                 total_time, conn_total_time);
 
        return 0;
diff --git a/doc/src/sgml/pgbench.sgml b/doc/src/sgml/pgbench.sgml
index e9900d3..ee7e216 100644
--- a/doc/src/sgml/pgbench.sgml
+++ b/doc/src/sgml/pgbench.sgml
@@ -310,6 +310,25 @@ pgbench <optional> <replaceable>options</> </optional> 
<replaceable>dbname</>
      </varlistentry>
 
      <varlistentry>
+      <term><option>-R</option> <replaceable>rate</></term>
+      <term><option>--rate</option> <replaceable>rate</></term>
+      <listitem>
+       <para>
+       Execute client transactions targeting the specified rate instead of
+       running as fast as possible (the default).  The rate is given in
+        transactions per second and is per client.  If the targeted rate is
+        above the maximum possible rate these transactions can execute at,
+        the rate limit won't have any impact on results.
+
+       Each client connection targets this rate by starting transactions
+       along a Poisson-distributed event time line.  When a rate limit is
+        active, the average transaction lag time (the delay between
+       the scheduled and actual transaction start times) is reported in ms.
+       </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

Reply via email to