Protocol compression: a fourth design

From: Andrey Borodin <x4mmm(at)yandex-team(dot)ru>
To: pgsql-hackers mailing list <pgsql-hackers(at)lists(dot)postgresql(dot)org>
Subject: Protocol compression: a fourth design
Date: 2026-08-31 12:26:38
Message-ID: CF7DB21D-F9CF-4FD1-8966-47C5B560471F@yandex-team.ru
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Hi,

There have been three substantial attempts to add protocol compression
to PostgreSQL [0][1][2]. They demonstrated useful reductions in network
traffic, but also accumulated questions about negotiation, I/O layering,
codec and direction selection, poolers, and compression before
encryption. Thanks to Konstantin, Daniil, Jacob, and everyone who worked
on and reviewed them.

I have been experimenting with a different design and decided that it's
time to discuss the topic again.

The proposal is:

- Zstandard only.
- An explicitly negotiated _pq_.compression=zstd protocol extension [3].
- Compression starts only after authentication and the first
ReadyForQuery.
- Server to client: DataRow and CopyData.
- Client to server: CopyData during COPY FROM.
- Query, Parse, Bind, parameters, and all control messages remain
ordinary protocol messages.
- One persistent stream per active direction, carried in bounded
CompressedData messages.
- The backend stream ends before ReadyForQuery. The frontend stream ends
before CopyDone or CopyFail.

The wrapper contains only a flushed segment of the compressed stream.
The uncompressed bytes from successive wrappers form an ordinary
protocol message stream, so a large DataRow or CopyData message may span
multiple wrappers. The receiver bounds both the wrapper and the output
produced by each segment, and validates the inner message types and
lengths as they become available.

This is deliberately asymmetric. DataRow and backend CopyData cover
query results, COPY TO, base backup, and WAL streaming. Frontend
CopyData covers COPY FROM and pg_restore. Leaving queries and parameters
visible means that a pooler can still inspect and route queries or
rewrite prepared statement names without decompressing them.
ReadyForQuery also remains an explicit transaction-pooling boundary. A
pooler that passes the negotiation and length-prefixed wrappers through
can relay CompressedData without understanding Zstandard.

The server has protocol_compression = off | zstd, with off as the
default. libpq has compression=off, prefer, and zstd.
Prefer continues without compression when the server rejects the
extension; zstd requires it. A server build without Zstandard does not
expose the zstd GUC value.

I also tested the new libpq against an unmodified PostgreSQL 14
server. prefer fell back and connected normally, while zstd failed with
the expected unsupported-method error.

The server switch is important because compression happens before SSL
or GSS encryption. Compressed lengths may reveal information when
attacker-controlled and secret values share history. Excluding
authentication, queries, and bind parameters reduces the scope but does
not eliminate that property. The administrator can prohibit the
feature, and it is off by default.

Compression state is allocated lazily, and small results can remain
uncompressed.

I deliberately left reporting through pg_stat_* out of the first patch
to keep it small. A follow-up can expose whether a connection uses
compression and its compressed and uncompressed byte counts in each
direction. Other follow-up features I have in mind are additional
codecs and compression levels, and per-direction control. None of these
requires changing the basic CompressedData framing.

Earlier end-to-end prototype measurements reduced mixed DataRow and
COPY TO workloads to about 10% and 14% of their original wire size.
MD5-heavy DataRow and COPY FROM workloads were reduced to about 51%.
I reran this implementation with the default Zstandard level. In a
realistic pg_restore test, client-to-server traffic fell from 264 MB to
21 MB; median local restore times were 3.00 seconds without compression
and 3.04 seconds with it. One-row pgbench medians were 7034 and
7018 TPS. Pseudorandom 32 MB DataRow and COPY TO results were reduced
only to 78% and 81%, and took about 0.5 seconds more CPU time on
loopback. The byte savings on compressible data translated into
corresponding speedups through a bandwidth-limited proxy.

The memory test also found a useful bound. An earlier prototype with an
8 MiB Zstandard window retained about 8.6 MiB per active backend.
Limiting history to 64 KiB reduced the additional retained private
anonymous memory to about 1 MiB in a same-backend test, at a cost of
about 1.8% more compressed bytes for COPY TO and no loss in the other
two measured results.

I would like feedback on three design choices before turning this into a
patch series:

1. Message scope. v1 compresses only bulk-data messages with the clearest
benefit. It leaves authentication, query, parameter, and control
messages ordinary where the benefit is less clear or the
confidentiality concern is more direct. Is this the right future-proof
scope?
2. Stream lifetime. Ending streams before ReadyForQuery and CopyDone or
CopyFail keeps transaction and COPY boundaries explicit, so a pooler
can remap connections without decompressing and recompressing data. Are
these the right boundaries?
3. Policy. The server GUC uses the familiar off | compression-method
pattern. The client combines that with PostgreSQL's opportunistic or
required connection policy: off | prefer | zstd, where naming the
method requires it. Is this interface sufficient?

Thank you!

Best regards, Andrey Borodin.

[0] https://www.postgresql.org/message-id/flat/aad16e41-b3f9-e89d-fa57-fb4c694bec25%40postgrespro.ru
[1] https://www.postgresql.org/message-id/flat/ABAA09C6-BB95-47A5-890D-90353533F9AC%40yandex-team.ru
[2] https://www.postgresql.org/message-id/flat/CACzsqT4cJG0kaCbz24Sd%3DGAEgiQDpzU8yuD6vF25zo870%2B3M6g%40mail.gmail.com
[3] https://www.postgresql.org/docs/devel/protocol-flow.html#PROTOCOL-EXTENSIONS

Attachment Content-Type Size
v1-0001-Add-protocol-compression.patch application/octet-stream 88.2 KB

Browse pgsql-hackers by date

  From Date Subject
Next Message Andrew Dunstan 2026-08-31 12:27:15 Re: Allow table AMs to define their own reloptions
Previous Message Sami Imseih 2026-08-31 12:20:21 Re: WAIT FOR command should do some query jumbling