Re: [Patch] Batch fsyncs when recycling WAL segments - Mailing list pgsql-hackers

From Ayush Tiwari
Subject Re: [Patch] Batch fsyncs when recycling WAL segments
Date
Msg-id CAJTYsWW5nRTuPkU+mwM2ohN3L4SxN4xwca-5a7s99dOr49Roug@mail.gmail.com
Whole thread
List pgsql-hackers
Hi,

On Sun, 2 Aug 2026 at 16:18, Ayush Tiwari <ayushtiwari.slg01@gmail.com> wrote:
Hi,

While working on [1], I read on the "Pre-allocating WAL files" thread [2]
that the fsyncs we do when recycling WAL segments are a real bottleneck,
and Andres suggested batching them: rename a batch first, then fsync the
files and pg_wal once, instead of the current per-segment
durable_rename() in InstallXLogFileSegment().  The attached patch does
that.  (Andres and Nathan, who were on that thread, are cc'd.)

The tricky part was durability.  As soon as a segment is renamed it can
be handed to the WAL write path, but the rename isn't on disk yet, and
issue_xlog_fsync() only fsyncs the segment file, never the directory.
So if a backend writes a commit into a just-recycled segment and we
crash before the batched fsync, the rename can be lost.

What I ended up with is a small "durability frontier" in XLogCtl (a
timeline and the highest segment whose rename is known durable).  The
checkpointer advances it after the batched fsync, and XLogFileInit()
checks it before letting anyone write into a segment; if the segment
isn't covered yet it just fsyncs the file and pg_wal itself.  Normally
the checkpointer stays ahead, so that fallback basically never fires(?) it's
only there to close the crash window.  An injection point and a TAP test
force that window open and confirm a committed row survives an immediate
crash.

Rough numbers: a WAL-heavy workload recycling ~44 segments per
checkpoint (min_wal_size=1GB, max_wal_size=2GB, wal_recycle=on,
fsync=on), ext4 on an Azure disk, 14 checkpoints with the first dropped:

              median    mean     min      max
  master     138.6 ms  143 ms   123 ms   171 ms
  patch       27.3 ms   28 ms    25 ms    39 ms

~5x here, and a lot steadier: master pays ~44 flushes every checkpoint,
the patch pays one.
 
Is the frontier + write-path check the right shape for this, or is there a
simpler invariant that keeps the write path from ever reaching a
not-yet-durable recycled segment?

Thoughts?

Regards,
Ayush

[1] https://www.postgresql.org/message-id/CAJTYsWXAPabSWBeT%3DbqJD%2B6w0D4-ozvoVoH7KiFUncM88bsQOg%40mail.gmail.com
[2] https://www.postgresql.org/message-id/745zvagaf6tfn2zbvtmqvxsr6kmybjbzcw6rsm55jnql4233je%40tudeipoewruo

While testing v1 I noticed that the TAP test still passed if I removed the
EnsureXLogSegDirDurable() call.  An immediate server stop does not discard the
kernel's filesystem state, so that did not prove that the write-path fallback
had run.  v2 adds an injection point in the fallback and waits for the WAL
writer to reach it while the checkpointer is stopped before the batch fsync.
With the fallback removed, the test now fails at that wait.  

Added the test to the Meson list as well. It was missing earlier.

I also checked the segment-size question.  On ext4, with fsync enabled and
512 MiB recycled per checkpoint, the median of five checkpoints was:

  segment size    files    master    patched
          1 MiB      512    1900 ms      80 ms
         16 MiB       32     156 ms      49 ms
         64 MiB        8      46 ms      21 ms 

Regards,
Ayush
Attachment

pgsql-hackers by date:

Previous
From: Pavel Stehule
Date:
Subject: Re: missing possibility to use alternative translated month names in to_char function
Next
From: Bharath Rupireddy
Date:
Subject: Re: Introduce XID age based replication slot invalidation