Add a pg_wal_preallocate() SQL function to eagerly create future WAL segments

Started by Ayush Tiwari12 days ago9 messageshackers
Jump to latest
#1Ayush Tiwari
ayushtiwari.slg01@gmail.com

Hi,

Creating a new WAL segment (open + zero-fill + fsync) is quite a bit more
expensive than recycling an existing one, and on the write path that cost
falls on foreground backends. The pool of future segments only fills in
lazily as WAL is produced, so right after initdb, or before a benchmark or
bulk load, it can be essentially empty and backends end up creating the
segments themselves at the worst possible time.

This is the pg_wal_preallocate() idea Andres raised in the "Pre-allocating
WAL files" thread [1]PostgreSQL: Pre-allocating WAL files </messages/by-id/20201225200953.jjkrytlrzojbndh5@alap3.anarazel.de&gt;; I finally sat down and tried it. The attached patch
adds a superuser-only function:

pg_wal_preallocate(bytes bigint DEFAULT NULL) returns bigint

It creates ceil(bytes / wal_segment_size) segments ahead of the current
insert location and returns how many it actually created; existing ones are
skipped. With no argument it uses min_wal_size, and the size can carry
units, e.g. pg_wal_preallocate(pg_size_bytes('1GB')). It reuses the
existing segment-creation code, is interruptible, and won't run during
recovery. It's best-effort, though: a later checkpoint may recycle or
remove anything past min_wal_size, and on copy-on-write filesystems (see
wal_recycle) there's little point to it.

On a fresh cluster (track_wal_io_timing on), a bulk INSERT creating ~13
segments paid the creation cost in the foreground (pg_stat_io, object=wal,
context=init): 13 creations and ~0.3 s of write + fsync without
preallocation, and 0 after it. The 0.3 s is storage-dependent, so take it
with a grain of salt.

Documentation and a TAP test are included.

Two things I'm unsure about and would welcome opinions on: whether a byte
count defaulting to min_wal_size is the right interface or a plain segment
count would be more honest; and whether there should be a cap, since nothing
currently stops a request large enough to fill the disk.

Thoughts?

[1]: PostgreSQL: Pre-allocating WAL files </messages/by-id/20201225200953.jjkrytlrzojbndh5@alap3.anarazel.de&gt;
</messages/by-id/20201225200953.jjkrytlrzojbndh5@alap3.anarazel.de&gt;

Regards,
Ayush

Attachments:

v1-0001-pg_wal_preallocate.patchapplication/octet-stream; name=v1-0001-pg_wal_preallocate.patchDownload+308-2
#2Ian Lawrence Barwick
barwick@gmail.com
In reply to: Ayush Tiwari (#1)
Re: Add a pg_wal_preallocate() SQL function to eagerly create future WAL segments

Hi

2026年7月23日(木) 0:07 Ayush Tiwari <ayushtiwari.slg01@gmail.com>:

Hi,

(...)

Two things I'm unsure about and would welcome opinions on: whether a byte
count defaulting to min_wal_size is the right interface or a plain segment
count would be more honest;

A plain segment count feels like the more intuitive value to provide, especially
as the function returns the number of segments created. OTOH min/max_wal_size
etc. are all specified by size, so maybe that's more consistent.

Regarding min_wal_size, if there is already more than that amount of WAL
present, the function is basically just adding an arbitrary number of
WAL segments.
Maybe the function could, if no value is provided, just create segments until
min_wal_size is reached?

and whether there should be a cap, since nothing
currently stops a request large enough to fill the disk.

Could max_wal_size be a reasonable default cap or soft upper limit?

Minor code nitpick:

-#define CATALOG_VERSION_NO    202607201
+#define CATALOG_VERSION_NO    202607220

I don't think the catalog version bump is needed in patches,
it's up to the committer to set the appropriate value at commit time.

Regards

Ian Barwick

#3Ayush Tiwari
ayushtiwari.slg01@gmail.com
In reply to: Ian Lawrence Barwick (#2)
Re: Add a pg_wal_preallocate() SQL function to eagerly create future WAL segments

Hi,

On Wed, 22 Jul 2026 at 21:30, Ian Lawrence Barwick <barwick@gmail.com>
wrote:

2026年7月23日(木) 0:07 Ayush Tiwari <ayushtiwari.slg01@gmail.com>:

Hi,

(...)

Two things I'm unsure about and would welcome opinions on: whether a byte
count defaulting to min_wal_size is the right interface or a plain

segment

count would be more honest;

A plain segment count feels like the more intuitive value to provide,
especially
as the function returns the number of segments created. OTOH
min/max_wal_size
etc. are all specified by size, so maybe that's more consistent.

Thanks for the review!

I went back and forth too. I'd keep bytes for now, for consistency with
min/max_wal_size and so it composes with pg_size_bytes('1GB'), but I'm
happy to
switch if the count reads better to people.

Regarding min_wal_size, if there is already more than that amount of WAL
present, the function is basically just adding an arbitrary number of
WAL segments.
Maybe the function could, if no value is provided, just create segments
until
min_wal_size is reached?

That's what it already does, just undocumented: existing segments are
skipped,
so the default only creates the missing ones in the min_wal_size window
ahead
of the insertion point (0 if they're already there). I'll make the docs say
so.

and whether there should be a cap, since nothing
currently stops a request large enough to fill the disk.

Could max_wal_size be a reasonable default cap or soft upper limit?

Possibly. I left it uncapped for now since it's a superuser-only explicit
action, but max_wal_size is a sensible ceiling if we want one. Happy to add
a
soft clamp if there's appetite for it.

Minor code nitpick:

-#define CATALOG_VERSION_NO    202607201
+#define CATALOG_VERSION_NO    202607220

I don't think the catalog version bump is needed in patches,
it's up to the committer to set the appropriate value at commit time.

Agreed, I'll drop it in the next rebase / v2.

Regards,
Ayush

#4Ayush Tiwari
ayushtiwari.slg01@gmail.com
In reply to: Ayush Tiwari (#3)
Re: Add a pg_wal_preallocate() SQL function to eagerly create future WAL segments

Hi,

On Thu, 23 Jul 2026 at 11:14, Ayush Tiwari <ayushtiwari.slg01@gmail.com>
wrote:

Hi,

On Wed, 22 Jul 2026 at 21:30, Ian Lawrence Barwick <barwick@gmail.com>
wrote:

2026年7月23日(木) 0:07 Ayush Tiwari <ayushtiwari.slg01@gmail.com>:

Hi,

(...)

Two things I'm unsure about and would welcome opinions on: whether a

byte

count defaulting to min_wal_size is the right interface or a plain

segment

count would be more honest;

A plain segment count feels like the more intuitive value to provide,
especially
as the function returns the number of segments created. OTOH
min/max_wal_size
etc. are all specified by size, so maybe that's more consistent.

Thanks for the review!

I went back and forth too. I'd keep bytes for now, for consistency with
min/max_wal_size and so it composes with pg_size_bytes('1GB'), but I'm
happy to
switch if the count reads better to people.

Regarding min_wal_size, if there is already more than that amount of WAL
present, the function is basically just adding an arbitrary number of
WAL segments.
Maybe the function could, if no value is provided, just create segments
until
min_wal_size is reached?

That's what it already does, just undocumented: existing segments are
skipped,
so the default only creates the missing ones in the min_wal_size window
ahead
of the insertion point (0 if they're already there). I'll make the docs
say so.

and whether there should be a cap, since nothing
currently stops a request large enough to fill the disk.

Could max_wal_size be a reasonable default cap or soft upper limit?

Possibly. I left it uncapped for now since it's a superuser-only explicit
action, but max_wal_size is a sensible ceiling if we want one. Happy to
add a
soft clamp if there's appetite for it.

Minor code nitpick:

-#define CATALOG_VERSION_NO    202607201
+#define CATALOG_VERSION_NO    202607220

I don't think the catalog version bump is needed in patches,
it's up to the committer to set the appropriate value at commit time.

Agreed, I'll drop it in the next rebase / v2.

Rebased with updated doc and reverted catalog_version_no.

Regards,
Ayush

Attachments:

v2-0001-pg_wal_preallocate.patchapplication/octet-stream; name=v2-0001-pg_wal_preallocate.patchDownload+309-1
#5solai v
solai.cdac@gmail.com
In reply to: Ayush Tiwari (#4)
Re: Add a pg_wal_preallocate() SQL function to eagerly create future WAL segments

Hi all,

On Fri, Jul 31, 2026 at 11:16 AM Ayush Tiwari
<ayushtiwari.slg01@gmail.com> wrote:

Hi,

On Thu, 23 Jul 2026 at 11:14, Ayush Tiwari <ayushtiwari.slg01@gmail.com> wrote:

Hi,

On Wed, 22 Jul 2026 at 21:30, Ian Lawrence Barwick <barwick@gmail.com> wrote:

2026年7月23日(木) 0:07 Ayush Tiwari <ayushtiwari.slg01@gmail.com>:

Hi,

(...)

Two things I'm unsure about and would welcome opinions on: whether a byte
count defaulting to min_wal_size is the right interface or a plain segment
count would be more honest;

A plain segment count feels like the more intuitive value to provide, especially
as the function returns the number of segments created. OTOH min/max_wal_size
etc. are all specified by size, so maybe that's more consistent.

Thanks for the review!

I went back and forth too. I'd keep bytes for now, for consistency with
min/max_wal_size and so it composes with pg_size_bytes('1GB'), but I'm happy to
switch if the count reads better to people.

Regarding min_wal_size, if there is already more than that amount of WAL
present, the function is basically just adding an arbitrary number of
WAL segments.
Maybe the function could, if no value is provided, just create segments until
min_wal_size is reached?

That's what it already does, just undocumented: existing segments are skipped,
so the default only creates the missing ones in the min_wal_size window ahead
of the insertion point (0 if they're already there). I'll make the docs say so.

and whether there should be a cap, since nothing
currently stops a request large enough to fill the disk.

Could max_wal_size be a reasonable default cap or soft upper limit?

Possibly. I left it uncapped for now since it's a superuser-only explicit
action, but max_wal_size is a sensible ceiling if we want one. Happy to add a
soft clamp if there's appetite for it.

Minor code nitpick:

-#define CATALOG_VERSION_NO    202607201
+#define CATALOG_VERSION_NO    202607220

I don't think the catalog version bump is needed in patches,
it's up to the committer to set the appropriate value at commit time.

Agreed, I'll drop it in the next rebase / v2.

Rebased with updated doc and reverted catalog_version_no.

Thanks for the updated patch. I tested this patch and was able to
verify the new pg_wal_preallocate() functionality on a freshly
initialized cluster. Before invoking the function, the pg_wal
directory contained a single WAL segment. Executing: SELECT
pg_wal_preallocate(), returned 5, and I confirmed that 5 additional
WAL segment files were created in the pg_wal directory, matching the
configured min_wal_size target. I also verified the updated default
behavior discussed in the thread. The default invocation only created
the missing segments needed to satisfy the min_wal_size window ahead
of the current insertion point, rather than creating an arbitrary
number of WAL segments. I then reset the WAL and I/O statistics and
executed a workload generating approximately 3.3 GB of WAL using a
5-million-row INSERT. Since the default invocation only preallocated
enough segments to satisfy min_wal_size (80 MB in my setup), the
workload still required additional WAL segment creation during
execution. This behavior seems consistent with the intended design and
the discussion in the thread. I did not encounter any functional
issues during testing.

Regards,
Solai

#6Ayush Tiwari
ayushtiwari.slg01@gmail.com
In reply to: solai v (#5)
Re: Add a pg_wal_preallocate() SQL function to eagerly create future WAL segments

Hi,

On Fri, 31 Jul 2026 at 15:31, solai v <solai.cdac@gmail.com> wrote:

Hi all,

Thanks for the updated patch. I tested this patch and was able to
verify the new pg_wal_preallocate() functionality on a freshly
initialized cluster. Before invoking the function, the pg_wal
directory contained a single WAL segment. Executing: SELECT
pg_wal_preallocate(), returned 5, and I confirmed that 5 additional
WAL segment files were created in the pg_wal directory, matching the
configured min_wal_size target. I also verified the updated default
behavior discussed in the thread. The default invocation only created
the missing segments needed to satisfy the min_wal_size window ahead
of the current insertion point, rather than creating an arbitrary
number of WAL segments. I then reset the WAL and I/O statistics and
executed a workload generating approximately 3.3 GB of WAL using a
5-million-row INSERT. Since the default invocation only preallocated
enough segments to satisfy min_wal_size (80 MB in my setup), the
workload still required additional WAL segment creation during
execution. This behavior seems consistent with the intended design and
the discussion in the thread. I did not encounter any functional
issues during testing.

Thanks for testing!

Rebased patch attached.

Regards,
Ayush

Attachments:

v3-0001-pg_wal_preallocate.patchapplication/octet-stream; name=v3-0001-pg_wal_preallocate.patchDownload+309-1
#7Nazir Bilal Yavuz
byavuz81@gmail.com
In reply to: Ayush Tiwari (#6)
Re: Add a pg_wal_preallocate() SQL function to eagerly create future WAL segments

Hi,

Thank you for working on this!

On Mon, 3 Aug 2026 at 08:13, Ayush Tiwari <ayushtiwari.slg01@gmail.com> wrote:

Hi,

Rebased patch attached.

Patch LGTM, it works as intended. My only concern is the unbounded max
limit; which can cause problems. I think 'max_wal_size' is a
reasonable limit. Perhaps we can add a 'force' boolean option to
function, then it can bypass the 'max_wal_size' limit; what do you
think?

--
Regards,
Nazir Bilal Yavuz
Microsoft

#8Ayush Tiwari
ayushtiwari.slg01@gmail.com
In reply to: Nazir Bilal Yavuz (#7)
Re: Add a pg_wal_preallocate() SQL function to eagerly create future WAL segments

Hi,

On Mon, 3 Aug 2026 at 16:38, Nazir Bilal Yavuz <byavuz81@gmail.com> wrote:

Hi,

Thank you for working on this!

On Mon, 3 Aug 2026 at 08:13, Ayush Tiwari <ayushtiwari.slg01@gmail.com>
wrote:

Hi,

Rebased patch attached.

Patch LGTM, it works as intended. My only concern is the unbounded max
limit; which can cause problems. I think 'max_wal_size' is a
reasonable limit. Perhaps we can add a 'force' boolean option to
function, then it can bypass the 'max_wal_size' limit; what do you
think?

Thanks for the review! I like the idea you suggested.

Let me try implementing it that way.

Regards,
Ayush

#9Ayush Tiwari
ayushtiwari.slg01@gmail.com
In reply to: Ayush Tiwari (#8)
Re: Add a pg_wal_preallocate() SQL function to eagerly create future WAL segments

Hi,

On Mon, 3 Aug 2026 at 17:30, Ayush Tiwari <ayushtiwari.slg01@gmail.com>
wrote:

Hi,

On Mon, 3 Aug 2026 at 16:38, Nazir Bilal Yavuz <byavuz81@gmail.com> wrote:

Patch LGTM, it works as intended. My only concern is the unbounded max
limit; which can cause problems. I think 'max_wal_size' is a
reasonable limit. Perhaps we can add a 'force' boolean option to
function, then it can bypass the 'max_wal_size' limit; what do you
think?

Thanks for the review! I like the idea you suggested.

Let me try implementing it that way.

v4 attached.

By default the request is now limited to the whole segments that fit within
max_wal_size, and force => true bypasses that when you really do want a
bigger
warm-up. A NOTICE is issued only when an explicit request is reduced; a
plain
no-argument call stays quiet.

Regards,
Ayush

Attachments:

v4-0001-pg_wal_preallocate.patchapplication/octet-stream; name=v4-0001-pg_wal_preallocate.patchDownload+353-1