Re: Avoid streaming zero-filled WAL switch padding

From: Sehrope Sarkuni <sehrope(at)jackdb(dot)com>
To: Andrey Borodin <x4mmm(at)yandex-team(dot)ru>
Cc: pgsql-hackers mailing list <pgsql-hackers(at)lists(dot)postgresql(dot)org>
Subject: Re: Avoid streaming zero-filled WAL switch padding
Date: 2026-09-05 15:03:03
Message-ID: CAH7T-apOPJxePszVUEjYjWTtUYkxsYLwkQaQ3_kA5tMnrTvwxA@mail.gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

On Thu, Aug 6, 2026 at 3:49 AM Andrey Borodin <x4mmm(at)yandex-team(dot)ru> wrote:
> The attached patch adds a compact replication message for zero-filled WAL
> padding.

This is neat. It's almost a custom compression handler for a common
situation.

I tried this atop 798bdcae89d. It applies cleanly and builds without
warnings. The suite passes and the numbers match yours for the
walsender traffic.

The reasoning behind the zero check looks sound to me. Messages start at
a record or page boundary, both of which begin with nonzero bytes, and
CopyXLogRecordToWAL zeroes the padding pages including their headers, so
an all-zero payload can only be switch padding. The sentPtr /
WalSndCaughtUp handling in XLogSendPhysical also looks right.

Also thought a bit about how this would interact with wire compression as
well. I think it'd still be useful together as even if the network byte
savings are less, you're still avoiding the disk writes.

A few things I noticed:

1. The new test is not added to src/test/recovery/meson.build, so meson
never runs it (I added it locally to test it out).

2. There is no negotiation. Every physical START_REPLICATION client gets
the 'z' message, so third-party physical stream consumers and older
pg_receivewal fail with "unrecognized streaming header". I think this
needs an opt-in from the client. Something like a START_REPLICATION
option or at least a protocol guard.

3. The sparse tail defeats wal_init_zero on the standby. Those segments
get recycled with holes, so later writes into them allocate blocks.
That would show up after a promotion. Perhaps only take the truncate
path when wal_init_zero is off, and pwrite the zeros otherwise? The
network saving is the same either way.

--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -59,6 +59,7 @@
#include "access/xlogwait.h"
#include "catalog/pg_authid.h"
+#include "common/file_utils.h"
#include "funcapi.h"
@@ -1048,7 +1049,21 @@ XLogWalRcvWriteZeros(Size nbytes, XLogRecPtr
recptr, TimeLineID tli)
errmsg_internal("zero WAL range
crosses a segment boundary")));

- if (recvFileZeroedFrom < 0 || recvFileZeroedFrom > startoff)
+ if (wal_init_zero)
+ {
+ /*
+ * XLogFileInit allocated the whole segment so that
writes into it,
+ * including after it is recycled, never have to allocate blocks.
+ */
+ pgstat_report_wait_start(WAIT_EVENT_WAL_WRITE);
+ if (pg_pwrite_zeros(recvFile, nbytes, (pgoff_t) startoff) < 0)
+ ereport(PANIC,
+ (errcode_for_file_access(),
+ errmsg("could not write
zero-filled WAL tail: %m")));
+ pgstat_report_wait_end();
+ }
+ else if (recvFileZeroedFrom < 0 || recvFileZeroedFrom > startoff)

4. If the wal_init_zero case is changed to write the zero range with
pg_pwrite_zeros(), it should use the same pgstat_...() accounting as
XLogWalRcvWrite(). Otherwise those writes will be missing stats.

5. A crash between the two ftruncate calls leaves a short segment. I
think replay is fine since the padding is never read, but anything
that expects to read whole segments would have a problem.

6. The walsender scan can use pg_memory_is_all_zeros() instead of the
byte loop. Early exit case would be the same and faster on long
zero runs.

Regards,
-- Sehrope Sarkuni
Founder & CEO | JackDB, Inc. | https://www.jackdb.com/

In response to

Browse pgsql-hackers by date

  From Date Subject
Next Message Andrey Borodin 2026-09-05 15:05:04 Re: Commitfest manager for September 2026
Previous Message Andrey Borodin 2026-09-05 14:16:22 Re: [PATCH] pg_surgery: check the page header and line pointers