Protocol Compression (fourth attempt)

Started by Anthonin Bonnefoy25 days ago7 messageshackers
Beta feature

Hackorum builds and tests every patch posted to the lists, not only commitfest submissions. This is Hackorum's own CI rather than the PostgreSQL project's, and it is still under testing - please report anything that looks wrong.

appliessuccessCI history

You can run a PostgreSQL built from this patch straight from Docker, with no checkout and no build:

docker run --rm -p 5432:5432 ghcr.io/hackorum-dev/postgres-patch:t253992
psql -h localhost -U postgres

Built from patchset v7 (message #7), October 23, 2026 at 01:43 PM.

Every patchset is also pushed to a branch of our PostgreSQL fork, so you can check out the same tree CI built. Without a PostgreSQL checkout:

git clone --branch t253992_7 https://github.com/hackorum-dev/postgres.git

In a checkout you already have, add the fork once:

git remote add hackorum https://github.com/hackorum-dev/postgres.git

then, for this patchset and every later one:

git fetch hackorum t253992_7 && git checkout t253992_7

Patchset v7 (message #7) is on t253992_7

Jump to latest
#1Anthonin Bonnefoy
anthonin.bonnefoy@datadoghq.com

Hi all,

Here's a fourth attempt at implementing protocol compression (past
attempts: [1]/messages/by-id/aad16e41-b3f9-e89d-fa57-fb4c694bec25@postgrespro.ru, [2]/messages/by-id/ABAA09C6-BB95-47A5-890D-90353533F9AC@yandex-team.ru, [3]/messages/by-id/CACzsqT4cJG0kaCbz24Sd=GAEgiQDpzU8yuD6vF25zo870+3M6g@mail.gmail.com). I've read through the past threads and tried
to address the concerns, however, the threads are long so it's very
likely I've missed or forgot about some details in the discussion.
I've also taken a different approach for the implementation, so I
haven't reused code from the previous attempts.

* Backend implementation (src/backend/libpq/pqcomm_compress{,_zstd,_lz4}) *

A new protocol message, CompressedMessages, is introduced and contains:
- The compression algorithm used (lz4 or zstd).
- The message types contained in the payload. For example, 'TDD' means
the CompressedMessages contains 1 RowDescription and 2 DataRows
- The compressed payload containing 0 to n messages

I've tried to fit the compression logic as much as possible in the
available interfaces, and PQcommMethods is a surprisingly good fit to
handle streaming compression. On a very high level, we have:
PqCommMethods->putmessage() adds the new message to the ongoing
compression buffer with 'ZSTD_compressStream2(..., ZSTD_e_continue)'
PqCommMethods->flush() calls 'ZSTD_compressStream2(...,
ZSTD_e_flush)' and sends the compressed payload in a new
CompressedMessages.

Enabling compression can be done by modifying PqCommMethods from:
PqCommMethods -> PqCommSocketMethods
To
PqCommMethods -> PqCompressMethods -> PqCommSocketMethods
The previous PqCommMethods (which is likely PqCommSocketMethods) is
saved and used to send the compressed (or uncompressed) messages.

* Backend memory footprint *

As streaming is used, the memory usage on the backend is capped.
- cctx: up to 1.3MB with default compression level
- outBuf: 131KB (set to ZSTD_CStreamOutSize())

When the output buffer is full, the content is sent in a new
CompressedMessages, allowing to clear the output buffer and continue
compression. In this case, the last message was only partially
compressed and will need additional payloads to be rebuilt.

* Compression Activation *

Compression is controlled through multiple GUCs:
protocol_backend_compression_allowed_algorithms: List of compression
algorithms allowed (and supported) by the server.
protocol_backend_compression: Compression algorithm to use
protocol_backend_compression_threshold: Minimum threshold needed to
trigger compression, currently set at 100 bytes
protocol_backend_compression_number_messages: Number of messages per
CompressedMessages before sending it. Currently 0 (CompressedMessages
is sent if the buffer is full or a flush is called).

It's possible for a client to freely enable/disable/switch compression
algorithms within a session. The server can forbid compression by
setting protocol_backend_compression_allowed_algorithms='none'. As
compression only starts after GUCs are processed, startup and
authentication packets are not compressed.

For example, a session can be started with compression enabled and
protocol tracing with:
PQTRACE="-" psql "options='-cprotocol_backend_compression=zstd
-cprotocol_backend_compression_threshold=0'" -c"select 1"
It will yield the following protocol trace:
F 14 Query "SELECT 1;"
B* 33 RowDescription 1 "?column?" 0 0 23 4 -1 0
B* 11 DataRow 1 1 '1'
B 63 CompressedMessages \x03 "TD" '\x28\xb5\x2f\xfd\x00...'
B 13 CommandComplete "SELECT 1"
B 5 ReadyForQuery I

Protocol tracing was modified to prefix messages nested in a
CompressedMessages with B*. Here, RowDescription and DataRow are both
part of the compressed payload. The CompressedMessages itself is
displayed and consumed only after all nested messages are processed.

* Proxy compatibility *

From checking pgbouncer's code, it only needs to inspect the content
of a subset of messages: ReadyForQuery, ErrorResponse,
ParameterStatus, CommandComplete. For other messages, just knowing
their types seems to be enough. Thus, the messages that need to be
inspected are never compressed (plus they don't benefit much from
compression given how small they are). With that, it should be
possible for pgbouncer to forward CompressedMessages without having to
decompress them (assuming the client supports the compression
algorithm).

This does require compression frames to be independent between
transactions, otherwise the client won't be able to decompress the
payload if some part of the compression frame was sent to a different
client. This can be achieved with
'protocol_backend_compression_transaction_frame'. With this GUC
enabled, the compression frame will be closed with ZSTD_e_end (or the
LZ4 equivalent) when a transaction ends.

With protocol_backend_compression_transaction_frame enabled, the
previous protocol trace becomes:
PQTRACE="-" psql "options='-cprotocol_backend_compression=zstd
-cprotocol_backend_compression_threshold=0
-cprotocol_backend_compression_transaction_frame=true'" -c"select 1"
F 13 Query "select 1"
B* 33 RowDescription 1 "?column?" 0 0 23 4 -1 0
B* 11 DataRow 1 1 '1'
B 63 CompressedMessages \x03 "TD" '\x28\xb5\x2f\xfd\x00...'
B 13 CommandComplete "SELECT 1"
B 9 CompressedMessages \x03 "" '\x01\x00\x00'
B 5 ReadyForQuery I

There's an additional CompressedMessages with no messages and a
'\x01\x00\x00' content: this is the end of a zstd frame. With this,
each transaction has a dedicated compression frame, making them
independent of each other.

* Frontend Implementation src/interfaces/libpq/fe-compress{,-zstd,-lz4}.c *

This time, I couldn't fit well in the available code as the existing
code consumes from PGConn's inBuffer in three different places:
pqParseInput3, getCopyMessageData, pqFunctionCall3 and all pqGet*
functions. To reduce the amount of direct access to inBuffer, I've
introduced a new msg_buffer struct that's passed to the pqGet*
functions.

To handle CompressedMessages in the frontend, a new decompressBuffer
msg_buffer was added to the connection which will be used for
decompression. pqParseInput3/getCopyMessageData/pqFunctionCall3 will
use decompressBuffer if there's at least one full message available,
otherwise, inBuffer is used.

* Protocol negotiation *

While the server can advertise the allowed and supported algorithms
through protocol_backend_compression_allowed_algorithms, the server
should only send compressed messages if the client actually supports
it (or at least claims to support it). This is done with the client
sending the supported algorithms through _pq_.supported_compressions
startup option. The server saves the information
port->supported_compress_{zstd,lz4} and will only accept the client
setting protocol_backend_compression to the algorithm it supports.
Given that this is more a protocol extension, the backend only sends
the new CompressedMessages if the client sent the
_pq_.supported_compressions list, I don't think a protocol version
bump is needed (though I'm not sure what are the rules here).

* Tests *

Protocol tracing provided by fe-trace is incredibly valuable to check
the new CompressedMessages. However, there's currently only one module
that relies on it, libpq_pipeline, using dedicated code to enable
protocol tracing. I've built a new trace test harness that relies on
an ongoing PQTRACE patch[4]/messages/by-id/CAO6_XqrOuYe8J1vOuErx0XarkF3e96fCO4fAHWOK-6amJ_UCew@mail.gmail.com which is very similar to regress: SQL
scripts are executed, and protocol trace is captured along the output,
and checked against the expected values. This allows tests to check
things like if a message is compressed as expected or if a compression
frame is correctly closed.

* Replication Compression *

Since WAL replication is sent through backend messages, protocol
compression can be applied to the WAL stream by modifying conninfo:
primary_conninfo = '... options=''-cprotocol_backend_compression=zstd'' ... '

* Some Benchmarks *

I've tested running 3 different queries while capturing the network
footprint with tcpdump:
- select_all: SELECT * FROM pgbench_accounts (100 times)
- select_100: SELECT * FROM pgbench_accounts LIMIT 100 (20000 times)
- select_random: SELECT gen_random_uuid() FROM generate_series(1,
50000) (100 times)
With different compression parameters:
- No compression/zstd/lz4
- tx frame enabled/disabled
- 1,2,3,2000 and default number messages per CompressedMessages

The results are available in benchmarks.csv. I've also attached graphs
showing the latency and network size specifically for the select_all
query.

All 3 queries have similar results, but looking specifically at select_all test:
- There's an increased latency (at least for zstd, 55ms -> 70ms) when
using the default options (CompressedMessages is only sent when the
output buffer is full or during a flush). Since the rows are heavily
compressible, everything is sent in a single CompressedMessage and
pcap shows the server sends the first TCP packet after 41ms. Meaning
the client is idle during those first 41ms and then has to process
everything.
- Limiting the number of messages per CompressedMessage can mitigate
this behaviour. At 2K messages/batch, zstd even has a similar latency
to uncompressed.
- Batching messages helps a lot with compression and latency. zstd
compresses from 1GB to 18MB with >2K messages/batch. Given that a
ZSTD_e_flush closes a zstd block, having only 1 msg/batch prevents
zstd from finding patterns between rows (like the same bid and
abalance values).
- It doesn't seem like there's much difference between enabling or
disabling Transaction Frame. So maybe it would be better to make this
the default and remove the option? Though there might be some use
cases of keeping the frame opened with a high level of compression and
long distance enabled.

With the random query, sending only 1 message per message leads to an
increase in wire size with the additional CompressedMessages bytes and
compression frame overhead. With >2K msgs/batch, we still manage to
reduce the size from 227MB to 105MB.

Current limitations:
- The patch only implements lz4 and zstd compression. gzip can be added later
- Only backend messages can be compressed for now. There's nothing
preventing the use of CompressedMessages for the frontend, but I
wanted to keep the patch focused, and limiting it to backend
compression sounded like a good stopping point.
- I haven't written the related doc yet
- protocol_backend_compression_allowed_algorithms only limit the
algorithms used. Maybe we want to also limit the compression level?

The patchset has the following files:

001 to 006: Those are from the PQTRACE[4]/messages/by-id/CAO6_XqrOuYe8J1vOuErx0XarkF3e96fCO4fAHWOK-6amJ_UCew@mail.gmail.com patch. I've joined them
since they are needed to run the tests.

007: A small change to libpq to hide notifications so LISTEN can be
used in regression tests

008: This creates a new trace test harness. It's very similar to the
regress harness, except it also captures and compare the protocol
traces using PQTRACE

009: Make parse_compress_options available to the backend. The goal is
to have the protocol_backend_compression GUC reuse the same
METHOD:DETAIL format.

010: Add backend support for CompressedMessages. This creates the new
PQcommMethods with the zstd and lz4 implementations.

011: Create a new msg_buffer struct to store PGconn's inBuffer and
pass it to pqGet* functions.

012: Frontend support for decompressing CompressedMessages.

013: Add CompressedMessages to protocol tracing and tag compressed
message with 'B*'

014: This adds the protocol negotiation with the client sending
_pq_.supported_compressions and the backend parsing it and storing the
zstd/lz4 support.

015 to 016: The regression and trace tests

[1]: /messages/by-id/aad16e41-b3f9-e89d-fa57-fb4c694bec25@postgrespro.ru
[2]: /messages/by-id/ABAA09C6-BB95-47A5-890D-90353533F9AC@yandex-team.ru
[3]: /messages/by-id/CACzsqT4cJG0kaCbz24Sd=GAEgiQDpzU8yuD6vF25zo870+3M6g@mail.gmail.com
[4]: /messages/by-id/CAO6_XqrOuYe8J1vOuErx0XarkF3e96fCO4fAHWOK-6amJ_UCew@mail.gmail.com

Attachments:

t253992_1
v1-0001-Print-cancel-key-as-a-hex-string-in-trace.patchapplication/octet-stream; name=v1-0001-Print-cancel-key-as-a-hex-string-in-trace.patchDownload+29-3
v1-0004-Make-pqtrace-write-lines-atomically.patchapplication/octet-stream; name=v1-0004-Make-pqtrace-write-lines-atomically.patchDownload+265-249
v1-0005-Add-PQTRACE-env-to-enable-protocol-tracing-in-lib.patchapplication/octet-stream; name=v1-0005-Add-PQTRACE-env-to-enable-protocol-tracing-in-lib.patchDownload+116-2
v1-0002-Add-more-regress-masks-in-pqtrace.patchapplication/octet-stream; name=v1-0002-Add-more-regress-masks-in-pqtrace.patchDownload+25-13
v1-0003-Add-mismatch-length-check-on-pqTraceOutputNoTypeB.patchapplication/octet-stream; name=v1-0003-Add-mismatch-length-check-on-pqTraceOutputNoTypeB.patchDownload+15-1
v1-0008-Add-a-protocol-trace-test-harness.patchapplication/octet-stream; name=v1-0008-Add-a-protocol-trace-test-harness.patchDownload+246-3
v1-0010-Add-backend-support-for-CompressedMessages.patchapplication/octet-stream; name=v1-0010-Add-backend-support-for-CompressedMessages.patchDownload+1361-8
v1-0006-Add-trace-of-startup-packets-in-libpq_pipeline-te.patchapplication/octet-stream; name=v1-0006-Add-trace-of-startup-packets-in-libpq_pipeline-te.patchDownload+225-33
v1-0009-Make-parse_compress_options-available-to-backend.patchapplication/octet-stream; name=v1-0009-Make-parse_compress_options-available-to-backend.patchDownload+2-6
v1-0013-Add-tracing-for-PqMsg_CompressedMessages.patchapplication/octet-stream; name=v1-0013-Add-tracing-for-PqMsg_CompressedMessages.patchDownload+40-19
v1-0011-Create-a-new-msg_buffer-struct-for-PGconn-s-inBuf.patchapplication/octet-stream; name=v1-0011-Create-a-new-msg_buffer-struct-for-PGconn-s-inBuf.patchDownload+280-267
v1-0012-Add-Frontend-support-for-CompressedMessages.patchapplication/octet-stream; name=v1-0012-Add-Frontend-support-for-CompressedMessages.patchDownload+695-14
v1-0014-Protocol-negotiation-for-compression-algorithms.patchapplication/octet-stream; name=v1-0014-Protocol-negotiation-for-compression-algorithms.patchDownload+77-4
v1-0016-Add-trace-tests-for-compression.patchapplication/octet-stream; name=v1-0016-Add-trace-tests-for-compression.patchDownload+1891-2
v1-0015-Add-regress-tests-for-compression.patchapplication/octet-stream; name=v1-0015-Add-regress-tests-for-compression.patchDownload+529-2
v1-0007-Add-HIDE_NOTIFICATIONS-parameter-to-psql-for-regr.patchapplication/octet-stream; name=v1-0007-Add-HIDE_NOTIFICATIONS-parameter-to-psql-for-regr.patchDownload+20-8
benchmarks.csvtext/csv; charset=US-ASCII; name=benchmarks.csvDownload
latency_select_all.pdfapplication/pdf; name=latency_select_all.pdfDownload
size_select_all.pdfapplication/pdf; name=size_select_all.pdfDownload
#2Michael Banck
michael.banck@credativ.de
In reply to: Anthonin Bonnefoy (#1)
Re: Protocol Compression (fourth attempt)

Hi,

On Tue, Sep 29, 2026 at 11:30:51AM +0200, Anthonin Bonnefoy wrote:

Here's a fourth attempt at implementing protocol compression (past
attempts: [1], [2], [3]).

I'm afraid your attempt is the fith - number 4 was:
/messages/by-id/CF7DB21D-F9CF-4FD1-8966-47C5B560471F@yandex-team.ru

Michael

#3Andrey Borodin
amborodin@acm.org
In reply to: Anthonin Bonnefoy (#1)
Re: Protocol Compression (fourth attempt)

Hi Anthonin,

On 29 Sep 2026, Anthonin Bonnefoy wrote:

I've also taken a different approach for the implementation, so I
haven't reused code from the previous attempts.

Could we combine our efforts on this?

Thanks, Michael, for linking my proposal. My goal is a minimal,
future-proof design: a small useful protocol and configuration
interface, with a clear way to negotiate extensions later. My takeaway
from the previous attempts is that expanding scope kept us from
agreeing on that core.

Your PQcommMethods layer looks like a good fit. The separate msg_buffer
avoids copying decompressed messages back into the input buffer, as my
prototype does. I would also like to keep your trace-based tests for
compressed messages and frame boundaries.

There are several choices I would simplify for v1:

- Multiple codecs, levels and long-distance matching. I would start
with Zstandard at its default level.
- Switching codecs within a session and identifying them in every
wrapper. Choosing once at startup avoids those state transitions.
- Compressing additional message types and listing their types in the
wrapper. Starting with DataRow and CopyData leaves other messages
visible to poolers without that extra metadata.
- GUCs for thresholds and batch sizes. We can improve the buffering
policy while keeping these choices internal.
- Configurable frame lifetime. I agree with your suggestion to remove
that option and settle on a mandatory reset rule for poolers.

I am open to using your patchset as the base and reducing its scope, or
taking your implementation ideas into mine. We can choose the base
once we agree on the common design.

Thank you!

Best regards, Andrey Borodin.

#4Anthonin Bonnefoy
anthonin.bonnefoy@datadoghq.com
In reply to: Andrey Borodin (#3)
Re: Protocol Compression (fourth attempt)

On Tue, Sep 29, 2026 at 4:06 PM Andrey Borodin <x4mmm@yandex-team.ru> wrote:

Could we combine our efforts on this?

Yeah definitely. Sorry I've missed your proposal.

Thanks, Michael, for linking my proposal. My goal is a minimal,
future-proof design: a small useful protocol and configuration
interface, with a clear way to negotiate extensions later. My takeaway
from the previous attempts is that expanding scope kept us from
agreeing on that core.

I had the same take.

The separate msg_buffer avoids copying decompressed messages
back into the input buffer, as my prototype does.

I had a similar approach initially, but that definitely required a lot
of memmove. I'm still not 100% happy about the frontend and feel like
there's still some possible simplification that could be done.
You do have something in your prototype that I'm not doing: shrinking
the buffer once a large result has been processed.

I would also like to keep your trace-based tests for compressed
messages and frame boundaries.

+1

There are several choices I would simplify for v1:

- Multiple codecs, levels and long-distance matching. I would start
with Zstandard at its default level.

Sounds reasonable. I've mostly added LZ4 to validate that the
interface works fine with another codec.

- Switching codecs within a session and identifying them in every
wrapper. Choosing once at startup avoids those state transitions.

Yeah, codec switching definitely adds a fair share of complexity and
tricky edge cases. I would still keep the capacity of enabling and
disabling compression freely in a session, and a session will be
locked to the codec used the first time. The use of the PqCommMethods
layer makes enabling and disabling compression straightforward.

- Compressing additional message types and listing their types in the
wrapper. Starting with DataRow and CopyData leaves other messages
visible to poolers without that extra metadata.

The message types were definitely for the poolers, I didn't use them
in libpq. If we only compress messages that can be ignored by poolers,
then they can definitely be removed.

- GUCs for thresholds and batch sizes. We can improve the buffering
policy while keeping these choices internal.

The GUCs were there as a way to be able to test different values since
I had no idea what would be a good value. Also, it's likely the
defaults may be inappropriate for some workloads, so I feel like
leaving the option to tune them would help.

- Configurable frame lifetime. I agree with your suggestion to remove
that option and settle on a mandatory reset rule for poolers.

+1

I am open to using your patchset as the base and reducing its scope, or
taking your implementation ideas into mine. We can choose the base
once we agree on the common design.

Given you sent your proposal earlier, you have precedence, so I leave
you the choice :). I could start by implementing the simplifications
you've mentioned to get a v2 on my patch to make it easier to agree
and merge the approach.

Thank you!
Anthonin Bonnefoy

#5Andrey Borodin
amborodin@acm.org
In reply to: Anthonin Bonnefoy (#4)
Re: Protocol Compression (fourth attempt)

Hi Anthonin,

On 29 Sep 2026, Anthonin Bonnefoy wrote:

I could start by implementing the simplifications
you've mentioned to get a v2 on my patch [...]

Would you mind if I prepared that revision of your patchset? It would
help me understand your implementation better and give us a concrete
starting point for combining our work. I'd focus on the simplifications
we agree on.

If you've already started on v2, let me know so we can split the work.

On enabling and disabling compression within a session, I agree that
PQcommMethods makes the switch simple. But a USERSET GUC must handle
SET LOCAL and rollback while output is buffered or a frame is open.
Could we keep the choice at connection startup for v1? Ordinary messages
would still be allowed on a compressed connection, so we could add
session-level control later without changing the wire format. Is there a
use case where choosing at startup would not be enough?

On thresholds and batch sizes, I agree that the knobs are useful for
experiments. Your first-packet timings show that the flush policy needs
work. I'd first try to address that internally, for example by bounding
the amount of uncompressed input processed before flushing output.
Publishing these thresholds as GUCs would mean supporting their
semantics as we change the buffering policy. Could we keep them in a
benchmarking patch for now, and add public controls if measurements
show a trade-off that users need to choose themselves?

Thank you!

Best regards, Andrey Borodin.

#6Anthonin Bonnefoy
anthonin.bonnefoy@datadoghq.com
In reply to: Andrey Borodin (#5)
Re: Protocol Compression (fourth attempt)

On Wed, Sep 30, 2026 at 9:53 AM Andrey Borodin <x4mmm@yandex-team.ru> wrote:

Would you mind if I prepared that revision of your patchset? It would
help me understand your implementation better and give us a concrete
starting point for combining our work. I'd focus on the simplifications
we agree on.

If you've already started on v2, let me know so we can split the work.

Sounds good to me, I haven't started the v2 yet.

On enabling and disabling compression within a session, I agree that
PQcommMethods makes the switch simple. But a USERSET GUC must handle
SET LOCAL and rollback while output is buffered or a frame is open.
Could we keep the choice at connection startup for v1? Ordinary messages
would still be allowed on a compressed connection, so we could add
session-level control later without changing the wire format. Is there a
use case where choosing at startup would not be enough?

That's currently handled by flushing + pq_send_messages which closes
the frame and sends the buffered messages. I imagine that on some
workloads (table with mostly random data), compression would have
mostly negative effects and users may want to disable it for specific
queries. For a v1, that's fine to leave this out.

On thresholds and batch sizes, I agree that the knobs are useful for
experiments. Your first-packet timings show that the flush policy needs
work. I'd first try to address that internally, for example by bounding
the amount of uncompressed input processed before flushing output.
Publishing these thresholds as GUCs would mean supporting their
semantics as we change the buffering policy. Could we keep them in a
benchmarking patch for now, and add public controls if measurements
show a trade-off that users need to choose themselves?

Sounds reasonable to me. Using the input size to trigger flush was
something I had in mind, but that wouldn't be useful if the messages
are incomplete as the client won't start processing the message until
it is fully available. Maybe a combination of both (limited input size
with at least x full messages) could work? Adding it and benchmarking
it would definitely help to see the impact.

Regards,
Anthonin Bonnefoy

#7Andrey Borodin
amborodin@acm.org
In reply to: Anthonin Bonnefoy (#6)
Re: Protocol Compression (fourth attempt)

Hi Anthonin,

Thanks! Attached is a revision based on your v1. I kept your msg_buffer
refactoring as a separate patch, and retained PQcommMethods and decoded
message tracing. The second patch brings over client-to-server COPY
compression and reusable buffers from my prototype. The trace
checks use the existing libpq testclient.

As discussed, this version negotiates Zstandard at startup and
compresses DataRow/CopyData. Backend frames end at every ReadyForQuery,
frontend frames at CopyDone/CopyFail. Other codecs and levels, session
control, public thresholds and pg_stat_* counters are left for later.

On 30 Sep 2026, Anthonin Bonnefoy wrote:

Maybe a combination of both (limited input size
with at least x full messages) could work?

I used a 32 KiB input limit without a message-count minimum. Large
messages can span wrappers. Existing flush points still apply, so a
small query result does not wait for the buffer to fill.

In a local TCP comparison of 100000 rows with 80-byte payloads, the
first row arrived in about 0.5 ms with this revision versus 13 ms with
your v1.

You are right that libpq cannot deliver an incomplete row. Partial
flushing bounds buffering and allows compression, transfer and
decompression to overlap. A single large row still has to be decoded
in full. I would keep the threshold internal while we compare more
workloads.

Does this look like a useful common base?

Thank you!

Best regards, Andrey Borodin.

Attachments:

t253992_7
v2-0001-Create-a-new-msg_buffer-struct-for-PGconn-s-inBuf.patchapplication/octet-stream; name=v2-0001-Create-a-new-msg_buffer-struct-for-PGconn-s-inBuf.patch; x-unix-mode=0644Download+281-267
v2-0002-Add-compression-to-the-frontend-backend-protocol.patchapplication/octet-stream; name=v2-0002-Add-compression-to-the-frontend-backend-protocol.patch; x-unix-mode=0644Download+2533-40