Skip to content

Coalesce server messages into fewer writes - #20

Draft
nickamorim wants to merge 1 commit into
Shopify:sg/qe-wire-v020-20260928from
nickamorim:coalesce-server-writes
Draft

nickamorim wants to merge 1 commit into
Shopify:sg/qe-wire-v020-20260928from
nickamorim:coalesce-server-writes

Conversation

@nickamorim

Copy link
Copy Markdown

Adds opt-in WriteBufferSize(n), which buffers complete messages per connection instead of writing each one separately. Buffered output is written once n bytes are pending, after ReadyForQuery, on a client Flush, before the server reads from the client, when the connection ends, and at most WriteBufferMaxDelay (default 1ms) after buffering. The default is unchanged.

  • Shares the StartBatch buffer, so protocol messages and row batches stay in order.
  • Writer.Write goes through the buffer, keeping pipelined responses behind a buffered RowDescription.
  • The full suite passes under -race with buffering forced on for every server.

Loopback, pgx client:

rows writes/query time/query
16 19 → 1 318µs → 96µs
1024 1027 → 2 13.5ms → 0.39ms

Outside a StartBatch batch, every message written through buffer.Writer is
its own write on the connection: a result costs one write system call per
row, plus one for each protocol message around it, and with TCP_NODELAY each
write is also its own packet.

Add opt-in coalescing, modeled on the PostgreSQL backend's send buffer.
With WriteBufferSize(n), complete messages are buffered per connection and
written together when n bytes are pending, after ReadyForQuery, when the
client sends Flush, before the server reads from the client, and when the
connection ends. Reading only after flushing means a client can never wait
on output the server is holding, including during authentication and COPY.
Output is also written at most WriteBufferMaxDelay (default 1ms) after it is
buffered, so rows written by a handler that then stalls still arrive; a
negative delay gives the PostgreSQL behavior.

The buffer is the one used by StartBatch, so coalesced protocol messages and
materialized row batches leave in order, and batching behaves as before when
coalescing is off. Writer.Write, used for frames encoded elsewhere such as
pipelined responses, also goes through the buffer. Every write contains whole
messages, and with coalescing a write error is sticky. The default remains
one write per message.

On loopback with a pgx client, 16 rows go from 19 writes to 1 per query
(~318us -> ~96us) and 1024 rows from 1027 writes to 2 (~13.5ms -> ~0.39ms),
with unchanged allocations. The full suite passes under -race with buffering
forced on, and with it forced on without the delay bound.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant