Re: SendRowDescriptionMessage() is slow for queries with a lot of columns

From: Thom Brown <thom(at)linux(dot)com>
To: Andres Freund <andres(at)anarazel(dot)de>
Cc: PostgreSQL-development <pgsql-hackers(at)postgresql(dot)org>
Subject: Re: SendRowDescriptionMessage() is slow for queries with a lot of columns
Date: 2017-09-15 11:16:22
Message-ID: CAA-aLv7UYu3L7gEeBjOH-7a1O4PmX9huR9dYm2m=NO9VNTP-YA@mail.gmail.com
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

On 14 September 2017 at 07:34, Andres Freund <andres(at)anarazel(dot)de> wrote:
> Hi,
>
> When running workloads that include fast queries with a lot of columns,
> SendRowDescriptionMessage(), and the routines it calls, becomes a
> bottleneck. Besides syscache lookups (see [1] and [2]) a major cost of
> that is manipulation of the StringBuffer and the version specific
> branches in the per-attribute loop. As so often, the performance
> differential of this patch gets bigger when the other performance
> patches are applied.
>
> The issues in SendRowDescriptionMessage() are the following:
>
> 1) All the pq_sendint calls, and also the pq_sendstring, are
> expensive. The amount of calculations done for a single 2/4 byte
> addition to the stringbuffer (particularly enlargeStringInfo()) is
> problematic, as are the reallocations themselves.
>
> I addressed this by adding pq_send*_pre() wrappers, implemented as
> inline functions, that require that memory is pre-allocated.
> Combining that with doing a enlargeStringInfo() in
> SendRowDescriptionMessage() that pre-allocates the maximum required
> memory, yields pretty good speedup.
>
> I'm not yet super sure about the implementation. For one, I'm not
> sure this shouldn't instead be stringinfo.h functions, with very very
> tiny pqformat.h wrappers. But conversely I think it'd make a lot of
> sense for the pqformat integer functions to get rid of the
> continually maintained trailing null-byte - I was hoping the compiler
> could optimize that away, but alas, no luck. As soon as a single
> integer is sent, you can't rely on 0 terminated strings anyway.
>
> 2) It creates a new StringInfo in every iteration. That causes
> noticeable memory management overhead, and restarts the size of the
> buffer every time. When the description is relatively large, that
> leads to a number of reallocations for every
> SendRowDescriptionMessage() call.
>
> My solution here was to create persistent StringInfo for
> SendRowDescriptionMessage() that never gets deallocated (just
> reset). That in combination with new versions of
> pq_beginmessage/endmessage that keep the buffer alive, yields a nice
> speedup.
>
> Currently I'm using a static variable to allocate a string buffer for
> the function. It'd probably better to manage that outside of a single
> function - I'm also wondering why we're allocating a good number of
> stringinfos in various places, rather than reuse them. Most of them
> can't be entered recursively, and even if that's a concern, we could
> have one reusable per portal or such.
>
> 3) The v2/v3 branches in the attribute loop are noticeable (others too,
> but well...). I solved that by splitting out the v2 and v3
> per-attribute loops into separate functions. Imo also a good chunk
> more readable.
>
> Comments?

I've run a fairly basic test with a table with 101 columns, selecting
a single row from the table and I get the following results:

Columns with 1-character names:

master (80 jobs, 80 connections, 60 seconds):

transaction type: /tmp/test.sql
scaling factor: 1
query mode: simple
number of clients: 80
number of threads: 80
duration: 60 s
number of transactions actually processed: 559275
latency average = 8.596 ms
tps = 9306.984058 (including connections establishing)
tps = 11144.622096 (excluding connections establishing)

patched:

transaction type: /tmp/test.sql
scaling factor: 1
query mode: simple
number of clients: 80
number of threads: 80
duration: 60 s
number of transactions actually processed: 585526
latency average = 8.210 ms
tps = 9744.240847 (including connections establishing)
tps = 11454.339301 (excluding connections establishing)

master (80 jobs, 80 connections, 120 seconds):

transaction type: /tmp/test.sql
scaling factor: 1
query mode: simple
number of clients: 80
number of threads: 80
duration: 120 s
number of transactions actually processed: 1108312
latency average = 8.668 ms
tps = 9229.356994 (including connections establishing)
tps = 9987.385281 (excluding connections establishing)

patched:

transaction type: /tmp/test.sql
scaling factor: 1
query mode: simple
number of clients: 80
number of threads: 80
duration: 120 s
number of transactions actually processed: 1130313
latency average = 8.499 ms
tps = 9412.904876 (including connections establishing)
tps = 10319.404302 (excluding connections establishing)

Columns with at least 42-character names:

master (80 jobs, 80 connections, 60 seconds):

transaction type: /tmp/test.sql
scaling factor: 1
query mode: simple
number of clients: 80
number of threads: 80
duration: 60 s
number of transactions actually processed: 197815
latency average = 24.308 ms
tps = 3291.157486 (including connections establishing)
tps = 3825.309489 (excluding connections establishing)

patched:

transaction type: /tmp/test.sql
scaling factor: 1
query mode: simple
number of clients: 80
number of threads: 80
duration: 60 s
number of transactions actually processed: 198549
latency average = 24.214 ms
tps = 3303.896651 (including connections establishing)
tps = 3895.738024 (excluding connections establishing)

master (80 jobs, 80 connections, 120 seconds):

transaction type: /tmp/test.sql
scaling factor: 1
query mode: simple
number of clients: 80
number of threads: 80
duration: 120 s
number of transactions actually processed: 391312
latency average = 24.551 ms
tps = 3258.493026 (including connections establishing)
tps = 3497.581844 (excluding connections establishing)

patched:

transaction type: /tmp/test.sql
scaling factor: 1
query mode: simple
number of clients: 80
number of threads: 80
duration: 120 s
number of transactions actually processed: 391733
latency average = 24.526 ms
tps = 3261.805060 (including connections establishing)
tps = 3552.604408 (excluding connections establishing)

This is just using the patches you posted on this thread. Does this
exercise the patch in the way you intended?

Regards

Thom

In response to

Responses

Browse pgsql-hackers by date

  From Date Subject
Next Message Amit Khandekar 2017-09-15 11:25:35 Re: UPDATE of partition key
Previous Message Daniel Gustafsson 2017-09-15 11:15:32 Re: Patch: add --if-exists to pg_recvlogical