public inbox for [email protected]
help / color / mirror / Atom feedPre-allocating WAL files
19+ messages / 9 participants
[nested] [flat]
* Pre-allocating WAL files
@ 2020-12-25 20:09 Andres Freund <[email protected]>
0 siblings, 1 reply; 19+ messages in thread
From: Andres Freund @ 2020-12-25 20:09 UTC (permalink / raw)
To: pgsql-hackers
Hi,
When running write heavy transactional workloads I've many times
observed that one needs to run the benchmarks for quite a while till
they get to their steady state performance. The most significant reason
for that is that initially WAL files will not get recycled, but need to
be freshly initialized. That's 16MB of writes that need to synchronously
finish before a small write transaction can even start to be written
out...
I think there's two useful things we could do:
1) Add pg_wal_preallocate(uint64 bytes) that ensures (bytes +
segment_size - 1) / segment_size WAL segments exist from the current
point in the WAL. Perhaps with the number of bytes defaulting to
min_wal_size if not explicitly specified?
2) Have checkpointer (we want walwriter to run with low latency to flush
out async commits etc) occasionally check if WAL files need to be
pre-allocated.
Checkpointer already tracks the amount of WAL that's expected to be
generated till the end of the checkpoint, so it seems like it's a
pretty good candidate to do so.
To keep checkpointer pre-allocating when idle we could signal it
whenever a record has crossed a segment boundary.
With a plain pgbench run I see a 2.5x reduction in throughput in the
periods where we initialize WAL files.
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 19+ messages in thread
* Re: Pre-allocating WAL files
@ 2021-06-07 15:18 Bossart, Nathan <[email protected]>
parent: Andres Freund <[email protected]>
0 siblings, 1 reply; 19+ messages in thread
From: Bossart, Nathan @ 2021-06-07 15:18 UTC (permalink / raw)
To: Andres Freund <[email protected]>; pgsql-hackers
On 12/25/20, 12:09 PM, "Andres Freund" <[email protected]> wrote:
> When running write heavy transactional workloads I've many times
> observed that one needs to run the benchmarks for quite a while till
> they get to their steady state performance. The most significant reason
> for that is that initially WAL files will not get recycled, but need to
> be freshly initialized. That's 16MB of writes that need to synchronously
> finish before a small write transaction can even start to be written
> out...
>
> I think there's two useful things we could do:
>
> 1) Add pg_wal_preallocate(uint64 bytes) that ensures (bytes +
> segment_size - 1) / segment_size WAL segments exist from the current
> point in the WAL. Perhaps with the number of bytes defaulting to
> min_wal_size if not explicitly specified?
>
> 2) Have checkpointer (we want walwriter to run with low latency to flush
> out async commits etc) occasionally check if WAL files need to be
> pre-allocated.
>
> Checkpointer already tracks the amount of WAL that's expected to be
> generated till the end of the checkpoint, so it seems like it's a
> pretty good candidate to do so.
>
> To keep checkpointer pre-allocating when idle we could signal it
> whenever a record has crossed a segment boundary.
>
>
> With a plain pgbench run I see a 2.5x reduction in throughput in the
> periods where we initialize WAL files.
I've been exploring this independently a bit and noticed this message.
Attached is a proof-of-concept patch for a separate "WAL allocator"
process that maintains a pool of WAL-segment-sized files that can be
claimed whenever a new segment file is needed. An early version of
this patch attempted to spread the I/O like non-immediate checkpoints
do, but I couldn't point to any real benefit from doing so, and it
complicated things quite a bit.
I like the idea of trying to bake this into an existing process such
as the checkpointer. I'll admit that creating a new process just for
WAL pre-allocation feels a bit heavy-handed, but it was a nice way to
keep this stuff modularized. I can look into moving this
functionality into the checkpointer process if this is something that
folks are interested in.
Nathan
Attachments:
[application/octet-stream] v1-0001-wal-segment-pre-allocation.patch (42.9K, ../../[email protected]/2-v1-0001-wal-segment-pre-allocation.patch)
download | inline diff:
From 79a42dcc29592d25d84aa2d20e72994d238bf9fe Mon Sep 17 00:00:00 2001
From: Nathan Bossart <[email protected]>
Date: Mon, 7 Jun 2021 14:51:09 +0000
Subject: [PATCH v1 1/1] wal segment pre-allocation
---
doc/src/sgml/config.sgml | 23 ++
src/backend/access/transam/xlog.c | 228 ++++++++++---------
src/backend/bootstrap/bootstrap.c | 8 +
src/backend/postmaster/Makefile | 1 +
src/backend/postmaster/postmaster.c | 44 ++++
src/backend/postmaster/wal_allocator.c | 315 ++++++++++++++++++++++++++
src/backend/replication/basebackup.c | 6 +-
src/backend/storage/file/fd.c | 115 ++++++++++
src/backend/storage/lmgr/lwlocknames.txt | 1 +
src/backend/storage/lmgr/proc.c | 1 +
src/backend/utils/activity/wait_event.c | 3 +
src/backend/utils/init/miscinit.c | 3 +
src/backend/utils/misc/guc.c | 12 +
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/bin/initdb/initdb.c | 1 +
src/bin/pg_basebackup/pg_basebackup.c | 19 +-
src/bin/pg_basebackup/t/010_pg_basebackup.pl | 4 +-
src/bin/pg_resetwal/pg_resetwal.c | 48 ++++
src/include/access/xlog.h | 5 +
src/include/miscadmin.h | 3 +
src/include/postmaster/wal_allocator.h | 21 ++
src/include/storage/fd.h | 1 +
src/include/storage/proc.h | 2 +
src/include/utils/wait_event.h | 3 +-
24 files changed, 761 insertions(+), 107 deletions(-)
create mode 100644 src/backend/postmaster/wal_allocator.c
create mode 100644 src/include/postmaster/wal_allocator.h
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index d8c0fd3315..df30d9411f 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -3186,6 +3186,29 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-wal-allocator-max-size" xreflabel="wal_allocator_max_size">
+ <term><varname>wal_allocator_max_size</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>wal_allocator_max_size</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ The maximum amount of WAL to pre-allocate for use when a new WAL segment
+ must be initialized. Setting this parameter to a size less than the WAL
+ segment size disables this behavior. The default values is 64 megabytes
+ (<literal>64MB</literal>). This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command line.
+ </para>
+
+ <para>
+ WAL pre-allocation may improve performance in scenarios where new WAL
+ segments must be created (e.g., during checkpoints when WAL segments
+ cannot be recycled).
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-wal-buffers" xreflabel="wal_buffers">
<term><varname>wal_buffers</varname> (<type>integer</type>)
<indexterm>
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 441a9124cd..b8fa237412 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -51,6 +51,7 @@
#include "port/pg_iovec.h"
#include "postmaster/bgwriter.h"
#include "postmaster/startup.h"
+#include "postmaster/wal_allocator.h"
#include "postmaster/walwriter.h"
#include "replication/basebackup.h"
#include "replication/logical.h"
@@ -732,6 +733,13 @@ typedef struct XLogCtlData
*/
XLogRecPtr lastFpwDisableRecPtr;
+ /*
+ * number of pre-allocated WAL segments in pg_wal/preallocated_segments
+ *
+ * Protected by WALPreallocationLock.
+ */
+ int num_prealloc_segs;
+
slock_t info_lck; /* locks shared variables shown above */
} XLogCtlData;
@@ -3283,11 +3291,10 @@ XLogFileInit(XLogSegNo logsegno, bool *use_existent, bool use_lock)
{
char path[MAXPGPATH];
char tmppath[MAXPGPATH];
- PGAlignedXLogBlock zbuffer;
XLogSegNo installed_segno;
XLogSegNo max_segno;
int fd;
- int save_errno;
+ bool found = false;
XLogFilePath(path, ThisTimeLineID, logsegno, wal_segment_size);
@@ -3309,114 +3316,44 @@ XLogFileInit(XLogSegNo logsegno, bool *use_existent, bool use_lock)
}
/*
- * Initialize an empty (all zeroes) segment. NOTE: it is possible that
- * another process is doing the same thing. If so, we will end up
- * pre-creating an extra log segment. That seems OK, and better than
- * holding the lock throughout this lengthy process.
+ * Try to use a pre-allocated segment, if one exists. If none are
+ * available, we fall back to creating a new segment on our own.
+ *
+ * Note that we still look for a pre-allocated segment even if the pre-
+ * allocation functionality is disabled via the GUCs. This ensures that any
+ * pre-allocated segments left over after turning off the pre-allocation
+ * functionality are eventually used up.
*/
- elog(DEBUG2, "creating and filling new WAL file");
-
- snprintf(tmppath, MAXPGPATH, XLOGDIR "/xlogtemp.%d", (int) getpid());
-
- unlink(tmppath);
-
- /* do not use get_sync_bit() here --- want to fsync only at end of fill */
- fd = BasicOpenFile(tmppath, O_RDWR | O_CREAT | O_EXCL | PG_BINARY);
- if (fd < 0)
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not create file \"%s\": %m", tmppath)));
-
- memset(zbuffer.data, 0, XLOG_BLCKSZ);
-
- pgstat_report_wait_start(WAIT_EVENT_WAL_INIT_WRITE);
- save_errno = 0;
- if (wal_init_zero)
+ LWLockAcquire(WALPreallocationLock, LW_EXCLUSIVE);
+ if (XLogCtl->num_prealloc_segs > 0)
{
- struct iovec iov[PG_IOV_MAX];
- int blocks;
-
- /*
- * Zero-fill the file. With this setting, we do this the hard way to
- * ensure that all the file space has really been allocated. On
- * platforms that allow "holes" in files, just seeking to the end
- * doesn't allocate intermediate space. This way, we know that we
- * have all the space and (after the fsync below) that all the
- * indirect blocks are down on disk. Therefore, fdatasync(2) or
- * O_DSYNC will be sufficient to sync future writes to the log file.
- */
-
- /* Prepare to write out a lot of copies of our zero buffer at once. */
- for (int i = 0; i < lengthof(iov); ++i)
- {
- iov[i].iov_base = zbuffer.data;
- iov[i].iov_len = XLOG_BLCKSZ;
- }
-
- /* Loop, writing as many blocks as we can for each system call. */
- blocks = wal_segment_size / XLOG_BLCKSZ;
- for (int i = 0; i < blocks;)
- {
- int iovcnt = Min(blocks - i, lengthof(iov));
- off_t offset = i * XLOG_BLCKSZ;
-
- if (pg_pwritev_with_retry(fd, iov, iovcnt, offset) < 0)
- {
- save_errno = errno;
- break;
- }
+ elog(DEBUG2, "using pre-allocated WAL file");
- i += iovcnt;
- }
+ found = true;
+ XLogCtl->num_prealloc_segs--;
+ snprintf(tmppath, MAXPGPATH, "%s/preallocated_segments/xlogtemp.%d",
+ XLOGDIR, XLogCtl->num_prealloc_segs);
}
- else
+
+ if (!found)
{
/*
- * Otherwise, seeking to the end and writing a solitary byte is
- * enough.
+ * We're not using a pre-allocated segment, so there's no need to keep
+ * holding the WALPreallocationLock.
*/
- errno = 0;
- if (pg_pwrite(fd, zbuffer.data, 1, wal_segment_size - 1) != 1)
- {
- /* if write didn't set errno, assume no disk space */
- save_errno = errno ? errno : ENOSPC;
- }
- }
- pgstat_report_wait_end();
+ LWLockRelease(WALPreallocationLock);
- if (save_errno)
- {
/*
- * If we fail to make the file, delete it to release disk space
+ * Initialize an empty (all zeroes) segment. NOTE: it is possible that
+ * another process is doing the same thing. If so, we will end up
+ * pre-creating an extra log segment. That seems OK, and better than
+ * holding the lock throughout this lengthy process.
*/
- unlink(tmppath);
-
- close(fd);
+ elog(DEBUG2, "creating and filling new WAL file");
- errno = save_errno;
-
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not write to file \"%s\": %m", tmppath)));
- }
-
- pgstat_report_wait_start(WAIT_EVENT_WAL_INIT_SYNC);
- if (pg_fsync(fd) != 0)
- {
- int save_errno = errno;
-
- close(fd);
- errno = save_errno;
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not fsync file \"%s\": %m", tmppath)));
+ snprintf(tmppath, MAXPGPATH, XLOGDIR "/xlogtemp.%d", (int) getpid());
+ CreateEmptyWalSegment(tmppath);
}
- pgstat_report_wait_end();
-
- if (close(fd) != 0)
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not close file \"%s\": %m", tmppath)));
/*
* Now move the segment into place with its final name.
@@ -3450,6 +3387,20 @@ XLogFileInit(XLogSegNo logsegno, bool *use_existent, bool use_lock)
unlink(tmppath);
}
+ /*
+ * If we are using a pre-allocated segment, we've been holding onto the
+ * WALPreallocationLock all this time so that the WAL Allocator process
+ * can't overwrite the file before we've installed it.
+ *
+ * While we're at it, also try to wake up the WAL allocator early so that it
+ * can get a jump start on allocating new segments if possible.
+ *
+ * XXX: Check that this works for all the failure scenarios.
+ */
+ if (found)
+ LWLockRelease(WALPreallocationLock);
+ RequestWalPreallocation();
+
/* Set flag to tell caller there was no existent file */
*use_existent = false;
@@ -3460,7 +3411,7 @@ XLogFileInit(XLogSegNo logsegno, bool *use_existent, bool use_lock)
(errcode_for_file_access(),
errmsg("could not open file \"%s\": %m", path)));
- elog(DEBUG2, "done creating and filling new WAL file");
+ elog(DEBUG2, "done installing new WAL file");
return fd;
}
@@ -4280,8 +4231,8 @@ RemoveXlogFile(const char *segname, XLogSegNo recycleSegNo,
}
/*
- * Verify whether pg_wal and pg_wal/archive_status exist.
- * If the latter does not exist, recreate it.
+ * Verify whether pg_wal, pg_wal/archive_status and pg_wal/preallocated_segments
+ * exist. If either of the latter two do not exist, recreate it.
*
* It is not the goal of this function to verify the contents of these
* directories, but to help in cases where someone has performed a cluster
@@ -4324,6 +4275,26 @@ ValidateXLOGDirectoryStructure(void)
(errmsg("could not create missing directory \"%s\": %m",
path)));
}
+
+ /* Check for preallocated_segments */
+ snprintf(path, MAXPGPATH, XLOGDIR "/preallocated_segments");
+ if (stat(path, &stat_buf) == 0)
+ {
+ /* Check for weird cases where it exists but isn't a directory */
+ if (!S_ISDIR(stat_buf.st_mode))
+ ereport(FATAL,
+ (errmsg("required WAL directory \"%s\" does not exist",
+ path)));
+ }
+ else
+ {
+ ereport(LOG,
+ (errmsg("creating missing WAL directory \"%s\"", path)));
+ if (MakePGDirectory(path) < 0)
+ ereport(FATAL,
+ (errmsg("could not create missing directory \"%s\": %m",
+ path)));
+ }
}
/*
@@ -5230,6 +5201,9 @@ XLOGShmemInit(void)
SpinLockInit(&XLogCtl->ulsn_lck);
InitSharedLatch(&XLogCtl->recoveryWakeupLatch);
ConditionVariableInit(&XLogCtl->recoveryNotPausedCV);
+
+ /* protected by WALPreallocationLock */
+ XLogCtl->num_prealloc_segs = 0;
}
/*
@@ -12929,3 +12903,55 @@ XLogRequestWalReceiverReply(void)
{
doRequestWalReceiverReply = true;
}
+
+int
+GetNumPreallocatedWalSegs(void)
+{
+ int ret;
+
+ LWLockAcquire(WALPreallocationLock, LW_SHARED);
+ ret = XLogCtl->num_prealloc_segs;
+ LWLockRelease(WALPreallocationLock);
+
+ return ret;
+}
+
+void
+SetNumPreallocatedWalSegs(int i)
+{
+ LWLockAcquire(WALPreallocationLock, LW_EXCLUSIVE);
+ XLogCtl->num_prealloc_segs = i;
+ LWLockRelease(WALPreallocationLock);
+}
+
+/*
+ * InstallPreallocatedWalSeg
+ *
+ * Renames the file at "path" to the next open pre-allocated segment slot and
+ * bumps up XLogCtl->num_prealloc_segs.
+ */
+void
+InstallPreallocatedWalSeg(const char *path)
+{
+ char newpath[MAXPGPATH];
+
+ LWLockAcquire(WALPreallocationLock, LW_EXCLUSIVE);
+
+ snprintf(newpath, MAXPGPATH, "%s/preallocated_segments/xlogtemp.%d",
+ XLOGDIR, XLogCtl->num_prealloc_segs);
+ (void) durable_rename(path, newpath, ERROR);
+ XLogCtl->num_prealloc_segs++;
+
+ LWLockRelease(WALPreallocationLock);
+}
+
+/*
+ * Request that the WAL allocator wake up early.
+ */
+void
+RequestWalPreallocation(void)
+{
+ if (ProcGlobal->walAllocatorLatch &&
+ WalPreallocationEnabled())
+ SetLatch(ProcGlobal->walAllocatorLatch);
+}
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index 94ab5ca095..56132337b0 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -36,6 +36,7 @@
#include "pgstat.h"
#include "postmaster/bgwriter.h"
#include "postmaster/startup.h"
+#include "postmaster/wal_allocator.h"
#include "postmaster/walwriter.h"
#include "replication/walreceiver.h"
#include "storage/bufmgr.h"
@@ -333,6 +334,9 @@ AuxiliaryProcessMain(int argc, char *argv[])
case WalReceiverProcess:
MyBackendType = B_WAL_RECEIVER;
break;
+ case WalAllocatorProcess:
+ MyBackendType = B_WAL_ALLOCATOR;
+ break;
default:
MyBackendType = B_INVALID;
}
@@ -468,6 +472,10 @@ AuxiliaryProcessMain(int argc, char *argv[])
WalReceiverMain();
proc_exit(1);
+ case WalAllocatorProcess:
+ WalAllocatorMain();
+ proc_exit(1);
+
default:
elog(PANIC, "unrecognized process type: %d", (int) MyAuxProcType);
proc_exit(1);
diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile
index bfdf6a833d..4bac1e640f 100644
--- a/src/backend/postmaster/Makefile
+++ b/src/backend/postmaster/Makefile
@@ -24,6 +24,7 @@ OBJS = \
postmaster.o \
startup.o \
syslogger.o \
+ wal_allocator.o \
walwriter.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 5a050898fe..d67de4b4a9 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -251,6 +251,7 @@ static pid_t StartupPID = 0,
CheckpointerPID = 0,
WalWriterPID = 0,
WalReceiverPID = 0,
+ WalAllocatorPID = 0,
AutoVacPID = 0,
PgArchPID = 0,
PgStatPID = 0,
@@ -557,6 +558,7 @@ static void ShmemBackendArrayRemove(Backend *bn);
#define StartCheckpointer() StartChildProcess(CheckpointerProcess)
#define StartWalWriter() StartChildProcess(WalWriterProcess)
#define StartWalReceiver() StartChildProcess(WalReceiverProcess)
+#define StartWalAllocator() StartChildProcess(WalAllocatorProcess)
/* Macros to check exit status of a child process */
#define EXIT_STATUS_0(st) ((st) == 0)
@@ -1781,6 +1783,12 @@ ServerLoop(void)
if (WalWriterPID == 0 && pmState == PM_RUN)
WalWriterPID = StartWalWriter();
+ /*
+ * If we have lost the WAL allocator process, try to start a new one.
+ */
+ if (WalAllocatorPID == 0 && pmState == PM_RUN)
+ WalAllocatorPID = StartWalAllocator();
+
/*
* If we have lost the autovacuum launcher, try to start a new one. We
* don't want autovacuum to run in binary upgrade mode because
@@ -2696,6 +2704,8 @@ SIGHUP_handler(SIGNAL_ARGS)
signal_child(WalWriterPID, SIGHUP);
if (WalReceiverPID != 0)
signal_child(WalReceiverPID, SIGHUP);
+ if (WalAllocatorPID != 0)
+ signal_child(WalAllocatorPID, SIGHUP);
if (AutoVacPID != 0)
signal_child(AutoVacPID, SIGHUP);
if (PgArchPID != 0)
@@ -3023,6 +3033,8 @@ reaper(SIGNAL_ARGS)
BgWriterPID = StartBackgroundWriter();
if (WalWriterPID == 0)
WalWriterPID = StartWalWriter();
+ if (WalAllocatorPID == 0)
+ WalAllocatorPID = StartWalAllocator();
/*
* Likewise, start other special children as needed. In a restart
@@ -3150,6 +3162,20 @@ reaper(SIGNAL_ARGS)
continue;
}
+ /*
+ * Was it the WAL allocator? Normal exit can be ignored; we'll start a
+ * new one at the next iteration of the postmaster's main loop, if
+ * necessary. Any other exit condition is treated as a crash.
+ */
+ if (pid == WalAllocatorPID)
+ {
+ WalAllocatorPID = 0;
+ if (!EXIT_STATUS_0(exitstatus))
+ HandleChildCrash(pid, exitstatus,
+ _("WAL Allocator process"));
+ continue;
+ }
+
/*
* Was it the autovacuum launcher? Normal exit can be ignored; we'll
* start a new one at the next iteration of the postmaster's main
@@ -3623,6 +3649,18 @@ HandleChildCrash(int pid, int exitstatus, const char *procname)
signal_child(WalReceiverPID, (SendStop ? SIGSTOP : SIGQUIT));
}
+ /* Take care of the WAL allocator too */
+ if (pid == WalAllocatorPID)
+ WalAllocatorPID = 0;
+ else if (WalAllocatorPID != 0 && take_action)
+ {
+ ereport(DEBUG2,
+ (errmsg_internal("sending %s to process %d",
+ (SendStop ? "SIGSTOP" : "SIGQUIT"),
+ (int) WalAllocatorPID)));
+ signal_child(WalAllocatorPID, (SendStop ? SIGSTOP : SIGQUIT));
+ }
+
/* Take care of the autovacuum launcher too */
if (pid == AutoVacPID)
AutoVacPID = 0;
@@ -3810,6 +3848,8 @@ PostmasterStateMachine(void)
signal_child(StartupPID, SIGTERM);
if (WalReceiverPID != 0)
signal_child(WalReceiverPID, SIGTERM);
+ if (WalAllocatorPID != 0)
+ signal_child(WalAllocatorPID, SIGTERM);
/* checkpointer, archiver, stats, and syslogger may continue for now */
/* Now transition to PM_WAIT_BACKENDS state to wait for them to die */
@@ -3840,6 +3880,7 @@ PostmasterStateMachine(void)
(CheckpointerPID == 0 ||
(!FatalError && Shutdown < ImmediateShutdown)) &&
WalWriterPID == 0 &&
+ WalAllocatorPID == 0 &&
AutoVacPID == 0)
{
if (Shutdown >= ImmediateShutdown || FatalError)
@@ -3933,6 +3974,7 @@ PostmasterStateMachine(void)
Assert(BgWriterPID == 0);
Assert(CheckpointerPID == 0);
Assert(WalWriterPID == 0);
+ Assert(WalAllocatorPID == 0);
Assert(AutoVacPID == 0);
Assert(PgArchPID == 0);
/* syslogger is not considered here */
@@ -4142,6 +4184,8 @@ TerminateChildren(int signal)
signal_child(WalWriterPID, signal);
if (WalReceiverPID != 0)
signal_child(WalReceiverPID, signal);
+ if (WalAllocatorPID != 0)
+ signal_child(WalAllocatorPID, signal);
if (AutoVacPID != 0)
signal_child(AutoVacPID, signal);
if (PgArchPID != 0)
diff --git a/src/backend/postmaster/wal_allocator.c b/src/backend/postmaster/wal_allocator.c
new file mode 100644
index 0000000000..38cebf7953
--- /dev/null
+++ b/src/backend/postmaster/wal_allocator.c
@@ -0,0 +1,315 @@
+/*-------------------------------------------------------------------------
+ *
+ * wal_allocator.c
+ *
+ * The WAL allocator is new as of Postgres 15. It attempts to keep regular
+ * backends from having to allocate new WAL segments. Even when
+ * wal_recycle is enabled, pre-allocating WAL segments can yield
+ * significant performance improvements in certain scenarios. Note that
+ * regular backends are still empowered to create new WAL segments if the
+ * WAL allocator fails to generate enough new segments.
+ *
+ * The WAL allocator is controlled by one parameter: wal_allocator_max_size.
+ * wal_allocator_max_size specifies the maximum amount of WAL to pre-
+ * allocate. If this value is not divisible by the WAL segment size, fewer
+ * WAL segments will be pre-allocated.
+ *
+ * The WAL allocator is started by the postmaster as soon as the startup
+ * subprocess finishes, or as soon as recovery begins if we are doing
+ * archive recovery. It remains alive until the postmaster commands it to
+ * terminate. Normal termination is by SIGTERM, which instructs the
+ * WAL allocator to exit(0). Emergency termination is by SIGQUIT; like any
+ * backend, the WAL allocator will simply abort and exit on SIGQUIT.
+ *
+ * If the WAL allocator exits unexpectedly, the postmaster treats that the
+ * same as a backend crash: shared memory may be corrupted, so remaining
+ * backends should be killed by SIGQUIT and then a recovery cycle started.
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/postmaster/wal_allocator.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <sys/time.h>
+
+#include "access/xlog.h"
+#include "access/xlog_internal.h"
+#include "libpq/pqsignal.h"
+#include "pgstat.h"
+#include "postmaster/interrupt.h"
+#include "postmaster/wal_allocator.h"
+#include "storage/bufmgr.h"
+#include "storage/condition_variable.h"
+#include "storage/fd.h"
+#include "storage/ipc.h"
+#include "storage/lwlock.h"
+#include "storage/proc.h"
+#include "storage/procsignal.h"
+#include "storage/shmem.h"
+#include "storage/smgr.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+#define WAL_ALLOC_TIMEOUT_S (60)
+
+/*
+ * GUC parameters
+ */
+int wal_alloc_max_size_mb = 64;
+
+static void DoWalPreAllocation(void);
+static void ScanForExistingPreallocatedSegments(void);
+
+/*
+ * Main entry point for WAL allocator process
+ *
+ * This is invoked from AuxiliaryProcessMain, which has already created the
+ * basic execution environment, but not enabled signals yet.
+ */
+void
+WalAllocatorMain(void)
+{
+ sigjmp_buf local_sigjmp_buf;
+ MemoryContext wal_alloc_context;
+
+ /*
+ * Properly accept or ignore signals that might be sent to us.
+ */
+ pqsignal(SIGHUP, SignalHandlerForConfigReload);
+ pqsignal(SIGINT, SIG_IGN);
+ pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
+ /* SIGQUIT handler was already set up by InitPostmasterChild */
+ pqsignal(SIGALRM, SIG_IGN);
+ pqsignal(SIGPIPE, SIG_IGN);
+ pqsignal(SIGUSR1, procsignal_sigusr1_handler);
+ pqsignal(SIGUSR2, SIG_IGN);
+
+ /*
+ * Reset some signals that are accepted by postmaster but not here
+ */
+ pqsignal(SIGCHLD, SIG_DFL);
+
+ /*
+ * Create a memory context that we will do all our work in. We do this so
+ * that we can reset the context during error recovery and thereby avoid
+ * possible memory leaks.
+ */
+ wal_alloc_context = AllocSetContextCreate(TopMemoryContext,
+ "WAL Allocator",
+ ALLOCSET_DEFAULT_SIZES);
+ MemoryContextSwitchTo(wal_alloc_context);
+
+ /*
+ * If an exception is encountered, processing resumes here.
+ *
+ * You might wonder why this isn't coded as an infinite loop around a
+ * PG_TRY construct. The reason is that this is the bottom of the
+ * exception stack, and so with PG_TRY there would be no exception handler
+ * in force at all during the CATCH part. By leaving the outermost setjmp
+ * always active, we have at least some chance of recovering from an error
+ * during error recovery. (If we get into an infinite loop thereby, it
+ * will soon be stopped by overflow of elog.c's internal state stack.)
+ *
+ * Note that we use sigsetjmp(..., 1), so that the prevailing signal mask
+ * (to wit, BlockSig) will be restored when longjmp'ing to here. Thus,
+ * signals other than SIGQUIT will be blocked until we complete error
+ * recovery. It might seem that this policy makes the HOLD_INTERRUPTS()
+ * call redundant, but it is not since InterruptPending might be set
+ * already.
+ */
+ if (sigsetjmp(local_sigjmp_buf, 1) != 0)
+ {
+ /* Since not using PG_TRY, must reset error stack by hand */
+ error_context_stack = NULL;
+
+ /* Prevent interrupts while cleaning up */
+ HOLD_INTERRUPTS();
+
+ /* Report the error to the server log */
+ EmitErrorReport();
+
+ /*
+ * These operations are really just a minimal subset of
+ * AbortTransaction(). We don't have very many resources to worry
+ * about.
+ */
+ LWLockReleaseAll();
+ ConditionVariableCancelSleep();
+ pgstat_report_wait_end();
+ AbortBufferIO();
+ UnlockBuffers();
+ ReleaseAuxProcessResources(false);
+ AtEOXact_Buffers(false);
+ AtEOXact_SMgr();
+ AtEOXact_Files(false);
+ AtEOXact_HashTables(false);
+
+ /*
+ * Now return to normal top-level context and clear ErrorContext for
+ * next time.
+ */
+ MemoryContextSwitchTo(wal_alloc_context);
+ FlushErrorState();
+
+ /* Flush any leaked data in the top-level context */
+ MemoryContextResetAndDeleteChildren(wal_alloc_context);
+
+ /* Now we can allow interrupts again */
+ RESUME_INTERRUPTS();
+
+ /*
+ * Sleep at least 1 second after any error. A write error is likely
+ * to be repeated, and we don't want to be filling the error logs as
+ * fast as we can.
+ */
+ pg_usleep(1000000L);
+
+ /*
+ * Close all open files after any error. This is helpful on Windows,
+ * where holding deleted files open causes various strange errors.
+ * It's not clear we need it elsewhere, but shouldn't hurt.
+ */
+ smgrcloseall();
+
+ /* Report wait end here, when there is no further possibility of wait */
+ pgstat_report_wait_end();
+ }
+
+ /* We can now handle ereport(ERROR) */
+ PG_exception_stack = &local_sigjmp_buf;
+
+ /*
+ * Unblock signals (they were blocked when the postmaster forked us)
+ */
+ PG_SETMASK(&UnBlockSig);
+
+ /*
+ * Advertise our latch that backends can use to wake us up while we're
+ * sleeping.
+ */
+ ProcGlobal->walAllocatorLatch = &MyProc->procLatch;
+
+ /*
+ * Before we go into the main loop, scan the pre-allocated segments
+ * directory and look for anything that was left over from the last time.
+ */
+ ScanForExistingPreallocatedSegments();
+
+ /*
+ * Loop forever
+ */
+ for (;;)
+ {
+ /* Clear any already-pending wakeups */
+ ResetLatch(MyLatch);
+
+ HandleMainLoopInterrupts();
+
+ DoWalPreAllocation();
+
+ /* XXX: Send activity statistics to the stats collector */
+
+ /* Sleep until we are signaled or WAL_ALLOC_TIMEOUT_S has elapsed. */
+ (void) WaitLatch(MyLatch,
+ WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+ WAL_ALLOC_TIMEOUT_S * 1000L /* convert to ms */,
+ WAIT_EVENT_WAL_ALLOCATOR_MAIN);
+ }
+
+ pg_unreachable();
+}
+
+/*
+ * DoWalPreAllocation
+ *
+ * Tries to allocate up to wal_allocator_max_size worth of WAL.
+ */
+static void
+DoWalPreAllocation(void)
+{
+ int max_prealloc_segs;
+
+ max_prealloc_segs = wal_alloc_max_size_mb / (wal_segment_size / (1024 * 1024));
+
+ while (!ShutdownRequestPending &&
+ GetNumPreallocatedWalSegs() < max_prealloc_segs)
+ {
+ char tmppath[MAXPGPATH];
+
+ snprintf(tmppath, MAXPGPATH, "%s/preallocated_segments/xlogtemp", XLOGDIR);
+ CreateEmptyWalSegment(tmppath);
+ InstallPreallocatedWalSeg(tmppath);
+ elog(DEBUG2, "pre-allocated WAL segment");
+
+ if (ConfigReloadPending)
+ {
+ ConfigReloadPending = false;
+ ProcessConfigFile(PGC_SIGHUP);
+
+ max_prealloc_segs = wal_alloc_max_size_mb / (wal_segment_size / (1024 * 1024));
+ }
+ }
+}
+
+/*
+ * ScanForExistingPreallocatedSegments
+ *
+ * This function searches through pg_wal/preallocated_segments for any segments
+ * that were left over from a previous WAL allocator process and sets the
+ * tracking variable in shared memory accordingly.
+ */
+static void
+ScanForExistingPreallocatedSegments(void)
+{
+ int i = 0;
+
+ /*
+ * fsync the preallocated_segments directory in case any renames have yet to
+ * be flushed to disk. This is probably not really necessary, but it seems
+ * nice to know that all the segments we find are really where we think they
+ * are.
+ */
+ fsync_fname_ext(XLOGDIR "/preallocated_segments", true, false, FATAL);
+
+ /*
+ * Gather all the preallocated segments we can find.
+ */
+ while (true)
+ {
+ FILE *fd;
+ char path[MAXPGPATH];
+
+ snprintf(path, MAXPGPATH, "%s/preallocated_segments/xlogtemp.%d",
+ XLOGDIR, i);
+
+ fd = AllocateFile(path, "r");
+ if (fd != NULL)
+ {
+ FreeFile(fd);
+ i++;
+ }
+ else
+ {
+ if (errno != ENOENT)
+ ereport(FATAL,
+ (errcode_for_file_access(),
+ errmsg("could not open file \"%s\": %m", path)));
+ break;
+ }
+ }
+
+ SetNumPreallocatedWalSegs(i);
+ elog(DEBUG2, "found %d preallocated segments during startup", i);
+}
+
+bool
+WalPreallocationEnabled(void)
+{
+ return wal_alloc_max_size_mb >= wal_segment_size / (1024 * 1024);
+}
diff --git a/src/backend/replication/basebackup.c b/src/backend/replication/basebackup.c
index e09108d0ec..c9a91dc938 100644
--- a/src/backend/replication/basebackup.c
+++ b/src/backend/replication/basebackup.c
@@ -1399,11 +1399,13 @@ sendDir(const char *path, int basepathlen, bool sizeonly, List *tablespaces,
size += _tarWriteDir(pathbuf, basepathlen, &statbuf, sizeonly);
/*
- * Also send archive_status directory (by hackishly reusing
- * statbuf from above ...).
+ * Also send archive_status and preallocated_segments (by hackishly
+ * reusing statbuf from above ...).
*/
size += _tarWriteHeader("./pg_wal/archive_status", NULL, &statbuf,
sizeonly);
+ size += _tarWriteHeader("./pg_wal/preallocated_segments", NULL,
+ &statbuf, sizeonly);
continue; /* don't recurse into pg_wal */
}
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index e8cd7ef088..705c360a27 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -76,6 +76,7 @@
#include <sys/file.h>
#include <sys/param.h>
#include <sys/stat.h>
+#include <sys/time.h>
#include <sys/types.h>
#ifndef WIN32
#include <sys/mman.h>
@@ -3779,3 +3780,117 @@ pg_pwritev_with_retry(int fd, const struct iovec *iov, int iovcnt, off_t offset)
return sum;
}
+
+/*
+ * CreateEmptyWalSegment
+ *
+ * Create a new file that can be used as a new WAL segment. The caller is
+ * responsible for installing the new file in pg_wal.
+ */
+void
+CreateEmptyWalSegment(const char *path)
+{
+ PGAlignedXLogBlock zbuffer;
+ int fd;
+ int save_errno;
+
+ unlink(path);
+
+ /* do not use get_sync_bit() here --- want to fsync only at end of fill */
+ fd = BasicOpenFile(path, O_RDWR | O_CREAT | O_EXCL | PG_BINARY);
+ if (fd < 0)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not create file \"%s\": %m", path)));
+
+ memset(zbuffer.data, 0, XLOG_BLCKSZ);
+
+ pgstat_report_wait_start(WAIT_EVENT_WAL_INIT_WRITE);
+ save_errno = 0;
+ if (wal_init_zero)
+ {
+ struct iovec iov[PG_IOV_MAX];
+ int blocks;
+
+ /*
+ * Zero-fill the file. With this setting, we do this the hard way to
+ * ensure that all the file space has really been allocated. On
+ * platforms that allow "holes" in files, just seeking to the end
+ * doesn't allocate intermediate space. This way, we know that we
+ * have all the space and (after the fsync below) that all the
+ * indirect blocks are down on disk. Therefore, fdatasync(2) or
+ * O_DSYNC will be sufficient to sync future writes to the log file.
+ */
+
+ /* Prepare to write out a lot of copies of our zero buffer at once. */
+ for (int i = 0; i < lengthof(iov); ++i)
+ {
+ iov[i].iov_base = zbuffer.data;
+ iov[i].iov_len = XLOG_BLCKSZ;
+ }
+
+ /* Loop, writing as many blocks as we can for each system call. */
+ blocks = wal_segment_size / XLOG_BLCKSZ;
+ for (int i = 0; i < blocks;)
+ {
+ int iovcnt = Min(blocks - i, lengthof(iov));
+ off_t offset = i * XLOG_BLCKSZ;
+
+ if (pg_pwritev_with_retry(fd, iov, iovcnt, offset) < 0)
+ {
+ save_errno = errno;
+ break;
+ }
+
+ i += iovcnt;
+ }
+ }
+ else
+ {
+ /*
+ * Otherwise, seeking to the end and writing a solitary byte is
+ * enough.
+ */
+ errno = 0;
+ if (pg_pwrite(fd, zbuffer.data, 1, wal_segment_size - 1) != 1)
+ {
+ /* if write didn't set errno, assume no disk space */
+ save_errno = errno ? errno : ENOSPC;
+ }
+ }
+ pgstat_report_wait_end();
+
+ if (save_errno)
+ {
+ /*
+ * If we fail to make the file, delete it to release disk space
+ */
+ unlink(path);
+
+ close(fd);
+
+ errno = save_errno;
+
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not write to file \"%s\": %m", path)));
+ }
+
+ pgstat_report_wait_start(WAIT_EVENT_WAL_INIT_SYNC);
+ if (pg_fsync(fd) != 0)
+ {
+ int save_errno = errno;
+
+ close(fd);
+ errno = save_errno;
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not fsync file \"%s\": %m", path)));
+ }
+ pgstat_report_wait_end();
+
+ if (close(fd) != 0)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not close file \"%s\": %m", path)));
+}
diff --git a/src/backend/storage/lmgr/lwlocknames.txt b/src/backend/storage/lmgr/lwlocknames.txt
index 6c7cf6c295..212bda8e7d 100644
--- a/src/backend/storage/lmgr/lwlocknames.txt
+++ b/src/backend/storage/lmgr/lwlocknames.txt
@@ -53,3 +53,4 @@ XactTruncationLock 44
# 45 was XactTruncationLock until removal of BackendRandomLock
WrapLimitsVacuumLock 46
NotifyQueueTailLock 47
+WALPreallocationLock 48
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 2575ea1ca0..555f4ef53e 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -181,6 +181,7 @@ InitProcGlobal(void)
ProcGlobal->startupProcPid = 0;
ProcGlobal->startupBufferPinWaitBufId = -1;
ProcGlobal->walwriterLatch = NULL;
+ ProcGlobal->walAllocatorLatch = NULL;
ProcGlobal->checkpointerLatch = NULL;
pg_atomic_init_u32(&ProcGlobal->procArrayGroupFirst, INVALID_PGPROCNO);
pg_atomic_init_u32(&ProcGlobal->clogGroupFirst, INVALID_PGPROCNO);
diff --git a/src/backend/utils/activity/wait_event.c b/src/backend/utils/activity/wait_event.c
index 6baf67740c..bb598b5154 100644
--- a/src/backend/utils/activity/wait_event.c
+++ b/src/backend/utils/activity/wait_event.c
@@ -248,6 +248,9 @@ pgstat_get_wait_activity(WaitEventActivity w)
case WAIT_EVENT_WAL_WRITER_MAIN:
event_name = "WalWriterMain";
break;
+ case WAIT_EVENT_WAL_ALLOCATOR_MAIN:
+ event_name = "WalAllocatorMain";
+ break;
/* no default case, so that compiler will warn */
}
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 8b73850d0d..b671dc6fac 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -268,6 +268,9 @@ GetBackendTypeDesc(BackendType backendType)
case B_WAL_WRITER:
backendDesc = "walwriter";
break;
+ case B_WAL_ALLOCATOR:
+ backendDesc = "wal allocator";
+ break;
case B_ARCHIVER:
backendDesc = "archiver";
break;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 68b62d523d..5e2dbd7c56 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -72,6 +72,7 @@
#include "postmaster/bgwriter.h"
#include "postmaster/postmaster.h"
#include "postmaster/syslogger.h"
+#include "postmaster/wal_allocator.h"
#include "postmaster/walwriter.h"
#include "replication/logicallauncher.h"
#include "replication/reorderbuffer.h"
@@ -2809,6 +2810,17 @@ static struct config_int ConfigureNamesInt[] =
NULL, NULL, NULL
},
+ {
+ {"wal_allocator_max_size", PGC_SIGHUP, WAL_SETTINGS,
+ gettext_noop("Sets the maximum amount of WAL to pre-allocate."),
+ NULL,
+ GUC_UNIT_MB
+ },
+ &wal_alloc_max_size_mb,
+ 64, 0, 102400,
+ NULL, NULL, NULL
+ },
+
{
{"wal_buffers", PGC_POSTMASTER, WAL_SETTINGS,
gettext_noop("Sets the number of disk-page buffers in shared memory for WAL."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index ddbb6dc2be..a1250cf67c 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -221,6 +221,7 @@
#wal_compression = off # enable compression of full-page writes
#wal_init_zero = on # zero-fill new WAL files
#wal_recycle = on # recycle WAL files
+#wal_allocator_max_size = 64MB # amount of WAL to pre-allocate
#wal_buffers = -1 # min 32kB, -1 sets based on shared_buffers
# (change requires restart)
#wal_writer_delay = 200ms # 1-10000 milliseconds
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 152d21e88b..d24c9cbc38 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -205,6 +205,7 @@ static const char *backend_options = "--single -F -O -j -c search_path=pg_catalo
static const char *const subdirs[] = {
"global",
"pg_wal/archive_status",
+ "pg_wal/preallocated_segments",
"pg_commit_ts",
"pg_dynshmem",
"pg_notify",
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index 16d8929b23..a81d92f8de 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -678,6 +678,22 @@ StartLogStreamer(char *startpos, uint32 timeline, char *sysidentifier)
pg_log_error("could not create directory \"%s\": %m", statusdir);
exit(1);
}
+
+ /*
+ * Also create pg_wal/preallocated_segments if necessary.
+ */
+ if (PQserverVersion(conn) >= 150000)
+ {
+ char prealloc_dir[MAXPGPATH];
+
+ snprintf(prealloc_dir, sizeof(prealloc_dir), "%s/pg_wal/preallocated_segments",
+ basedir);
+ if (pg_mkdir_p(prealloc_dir, pg_dir_create_mode) != 0 && errno != EEXIST)
+ {
+ pg_log_error("could not create directory \"%s\": %m", prealloc_dir);
+ exit(1);
+ }
+ }
}
/*
@@ -1614,7 +1630,8 @@ ReceiveTarAndUnpackCopyChunk(size_t r, char *copybuf, void *callback_data)
*/
if (!((pg_str_endswith(state->filename, "/pg_wal") ||
pg_str_endswith(state->filename, "/pg_xlog") ||
- pg_str_endswith(state->filename, "/archive_status")) &&
+ pg_str_endswith(state->filename, "/archive_status") ||
+ pg_str_endswith(state->filename, "/preallocated_segments")) &&
errno == EEXIST))
{
pg_log_error("could not create directory \"%s\": %m",
diff --git a/src/bin/pg_basebackup/t/010_pg_basebackup.pl b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
index 74f8c2c739..61ceda2c6e 100644
--- a/src/bin/pg_basebackup/t/010_pg_basebackup.pl
+++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
@@ -120,10 +120,10 @@ SKIP:
"check backup dir permissions");
}
-# Only archive_status directory should be copied in pg_wal/.
+# Only archive_status and preallocated_segments directories should be copied in pg_wal/.
is_deeply(
[ sort(slurp_dir("$tempdir/backup/pg_wal/")) ],
- [ sort qw(. .. archive_status) ],
+ [ sort qw(. .. archive_status preallocated_segments) ],
'no WAL files copied');
# Contents of these directories should not be copied.
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 805dafef07..f5a4d77064 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -84,6 +84,7 @@ static void RewriteControlFile(void);
static void FindEndOfXLOG(void);
static void KillExistingXLOG(void);
static void KillExistingArchiveStatus(void);
+static void KillExistingPreallocatedSegments(void);
static void WriteEmptyXLOG(void);
static void usage(void);
@@ -513,6 +514,7 @@ main(int argc, char *argv[])
RewriteControlFile();
KillExistingXLOG();
KillExistingArchiveStatus();
+ KillExistingPreallocatedSegments();
WriteEmptyXLOG();
printf(_("Write-ahead log reset\n"));
@@ -1053,6 +1055,52 @@ KillExistingXLOG(void)
}
+/*
+ * Remove existing preallocated segments
+ */
+static void
+KillExistingPreallocatedSegments(void)
+{
+#define PREALLOCSEGDIR XLOGDIR "/preallocated_segments"
+
+ DIR *xldir;
+ struct dirent *xlde;
+ char path[MAXPGPATH + sizeof(PREALLOCSEGDIR)];
+
+ xldir = opendir(PREALLOCSEGDIR);
+ if (xldir == NULL)
+ {
+ pg_log_error("could not open directory \"%s\": %m", PREALLOCSEGDIR);
+ exit(1);
+ }
+
+ while (errno = 0, (xlde = readdir(xldir)) != NULL)
+ {
+ if (strncmp(xlde->d_name, "xlogtemp", strlen("xlogtemp")) == 0)
+ {
+ snprintf(path, sizeof(path), "%s/%s", PREALLOCSEGDIR, xlde->d_name);
+ if (unlink(path) < 0)
+ {
+ pg_log_error("could not delete file \"%s\": %m", path);
+ exit(1);
+ }
+ }
+ }
+
+ if (errno)
+ {
+ pg_log_error("could not read directory \"%s\": %m", PREALLOCSEGDIR);
+ exit(1);
+ }
+
+ if (closedir(xldir))
+ {
+ pg_log_error("could not close directory \"%s\": %m", PREALLOCSEGDIR);
+ exit(1);
+ }
+}
+
+
/*
* Remove existing archive status files
*/
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index 77187c12be..608ec4e74b 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -361,6 +361,11 @@ extern void XLogRequestWalReceiverReply(void);
extern void assign_max_wal_size(int newval, void *extra);
extern void assign_checkpoint_completion_target(double newval, void *extra);
+extern int GetNumPreallocatedWalSegs(void);
+extern void InstallPreallocatedWalSeg(const char *path);
+extern void SetNumPreallocatedWalSegs(int i);
+extern void RequestWalPreallocation(void);
+
/*
* Routines to start, stop, and get status of a base backup.
*/
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 4dc343cbc5..a4599df7ff 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -333,6 +333,7 @@ typedef enum BackendType
B_WAL_RECEIVER,
B_WAL_SENDER,
B_WAL_WRITER,
+ B_WAL_ALLOCATOR,
B_ARCHIVER,
B_STATS_COLLECTOR,
B_LOGGER,
@@ -435,6 +436,7 @@ typedef enum
CheckpointerProcess,
WalWriterProcess,
WalReceiverProcess,
+ WalAllocatorProcess,
NUM_AUXPROCTYPES /* Must be last! */
} AuxProcType;
@@ -448,6 +450,7 @@ extern AuxProcType MyAuxProcType;
#define AmCheckpointerProcess() (MyAuxProcType == CheckpointerProcess)
#define AmWalWriterProcess() (MyAuxProcType == WalWriterProcess)
#define AmWalReceiverProcess() (MyAuxProcType == WalReceiverProcess)
+#define AmWalAllocatorProcess() (MyAuxProcType == WalAllocatorProcess)
/*****************************************************************************
diff --git a/src/include/postmaster/wal_allocator.h b/src/include/postmaster/wal_allocator.h
new file mode 100644
index 0000000000..c7e139226b
--- /dev/null
+++ b/src/include/postmaster/wal_allocator.h
@@ -0,0 +1,21 @@
+/*-------------------------------------------------------------------------
+ *
+ * wal_allocator.h
+ * Exports from postmaster/wal_allocator.c.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ *
+ * src/include/postmaster/wal_allocator.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _WAL_ALLOCATOR_H
+#define _WAL_ALLOCATOR_H
+
+/* GUC options */
+extern int wal_alloc_max_size_mb;
+
+extern void WalAllocatorMain(void) pg_attribute_noreturn();
+extern bool WalPreallocationEnabled(void);
+
+#endif /* _WAL_ALLOCATOR_H */
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index 5b3c280dd7..008a54f2ee 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -173,6 +173,7 @@ extern int durable_unlink(const char *fname, int loglevel);
extern int durable_rename_excl(const char *oldfile, const char *newfile, int loglevel);
extern void SyncDataDirectory(void);
extern int data_sync_elevel(int elevel);
+extern void CreateEmptyWalSegment(const char *path);
/* Filename components */
#define PG_TEMP_FILES_DIR "pgsql_tmp"
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index be67d8a861..9b3acfb68e 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -348,6 +348,8 @@ typedef struct PROC_HDR
pg_atomic_uint32 clogGroupFirst;
/* WALWriter process's latch */
Latch *walwriterLatch;
+ /* WAL Allocator process's latch */
+ Latch *walAllocatorLatch;
/* Checkpointer process's latch */
Latch *checkpointerLatch;
/* Current shared estimate of appropriate spins_per_delay value */
diff --git a/src/include/utils/wait_event.h b/src/include/utils/wait_event.h
index 6c6ec2e711..36324b16c7 100644
--- a/src/include/utils/wait_event.h
+++ b/src/include/utils/wait_event.h
@@ -47,7 +47,8 @@ typedef enum
WAIT_EVENT_SYSLOGGER_MAIN,
WAIT_EVENT_WAL_RECEIVER_MAIN,
WAIT_EVENT_WAL_SENDER_MAIN,
- WAIT_EVENT_WAL_WRITER_MAIN
+ WAIT_EVENT_WAL_WRITER_MAIN,
+ WAIT_EVENT_WAL_ALLOCATOR_MAIN
} WaitEventActivity;
/* ----------
--
2.16.6
^ permalink raw reply [nested|flat] 19+ messages in thread
* Re: Pre-allocating WAL files
@ 2021-07-05 16:52 vignesh C <[email protected]>
parent: Bossart, Nathan <[email protected]>
0 siblings, 4 replies; 19+ messages in thread
From: vignesh C @ 2021-07-05 16:52 UTC (permalink / raw)
To: Bossart, Nathan <[email protected]>; +Cc: Andres Freund <[email protected]>; pgsql-hackers
On Mon, Jun 7, 2021 at 8:48 PM Bossart, Nathan <[email protected]> wrote:
>
> On 12/25/20, 12:09 PM, "Andres Freund" <[email protected]> wrote:
> > When running write heavy transactional workloads I've many times
> > observed that one needs to run the benchmarks for quite a while till
> > they get to their steady state performance. The most significant reason
> > for that is that initially WAL files will not get recycled, but need to
> > be freshly initialized. That's 16MB of writes that need to synchronously
> > finish before a small write transaction can even start to be written
> > out...
> >
> > I think there's two useful things we could do:
> >
> > 1) Add pg_wal_preallocate(uint64 bytes) that ensures (bytes +
> > segment_size - 1) / segment_size WAL segments exist from the current
> > point in the WAL. Perhaps with the number of bytes defaulting to
> > min_wal_size if not explicitly specified?
> >
> > 2) Have checkpointer (we want walwriter to run with low latency to flush
> > out async commits etc) occasionally check if WAL files need to be
> > pre-allocated.
> >
> > Checkpointer already tracks the amount of WAL that's expected to be
> > generated till the end of the checkpoint, so it seems like it's a
> > pretty good candidate to do so.
> >
> > To keep checkpointer pre-allocating when idle we could signal it
> > whenever a record has crossed a segment boundary.
> >
> >
> > With a plain pgbench run I see a 2.5x reduction in throughput in the
> > periods where we initialize WAL files.
>
> I've been exploring this independently a bit and noticed this message.
> Attached is a proof-of-concept patch for a separate "WAL allocator"
> process that maintains a pool of WAL-segment-sized files that can be
> claimed whenever a new segment file is needed. An early version of
> this patch attempted to spread the I/O like non-immediate checkpoints
> do, but I couldn't point to any real benefit from doing so, and it
> complicated things quite a bit.
>
> I like the idea of trying to bake this into an existing process such
> as the checkpointer. I'll admit that creating a new process just for
> WAL pre-allocation feels a bit heavy-handed, but it was a nice way to
> keep this stuff modularized. I can look into moving this
> functionality into the checkpointer process if this is something that
> folks are interested in.
Thanks for posting the patch, the patch no more applies on Head:
Applying: wal segment pre-allocation
error: patch failed: src/backend/access/transam/xlog.c:3283
error: src/backend/access/transam/xlog.c: patch does not apply
Can you rebase the patch and post, it might help if someone is picking
it up for review.
Regards,
Vignesh
^ permalink raw reply [nested|flat] 19+ messages in thread
* Re: Pre-allocating WAL files
@ 2021-07-09 21:08 Bossart, Nathan <[email protected]>
parent: vignesh C <[email protected]>
3 siblings, 0 replies; 19+ messages in thread
From: Bossart, Nathan @ 2021-07-09 21:08 UTC (permalink / raw)
To: vignesh C <[email protected]>; +Cc: Andres Freund <[email protected]>; pgsql-hackers
On 7/5/21, 9:52 AM, "vignesh C" <[email protected]> wrote:
> Thanks for posting the patch, the patch no more applies on Head:
> Applying: wal segment pre-allocation
> error: patch failed: src/backend/access/transam/xlog.c:3283
> error: src/backend/access/transam/xlog.c: patch does not apply
>
> Can you rebase the patch and post, it might help if someone is picking
> it up for review.
I've attached a rebased version of the patch.
Nathan
Attachments:
[application/octet-stream] v2-0001-wal-segment-pre-allocation.patch (43.7K, ../../[email protected]/2-v2-0001-wal-segment-pre-allocation.patch)
download | inline diff:
From f14c235dfb23056c146e39126da9cd53967ad912 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <[email protected]>
Date: Fri, 9 Jul 2021 20:47:14 +0000
Subject: [PATCH v2 1/1] wal segment pre-allocation
---
doc/src/sgml/config.sgml | 23 ++
src/backend/access/transam/xlog.c | 226 +++++++++---------
src/backend/bootstrap/bootstrap.c | 8 +
src/backend/postmaster/Makefile | 1 +
src/backend/postmaster/postmaster.c | 45 ++++
src/backend/postmaster/wal_allocator.c | 315 ++++++++++++++++++++++++++
src/backend/replication/basebackup.c | 6 +-
src/backend/storage/file/fd.c | 115 ++++++++++
src/backend/storage/lmgr/lwlocknames.txt | 1 +
src/backend/storage/lmgr/proc.c | 1 +
src/backend/utils/activity/wait_event.c | 3 +
src/backend/utils/init/miscinit.c | 3 +
src/backend/utils/misc/guc.c | 12 +
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/bin/initdb/initdb.c | 1 +
src/bin/pg_basebackup/pg_basebackup.c | 19 +-
src/bin/pg_basebackup/t/010_pg_basebackup.pl | 4 +-
src/bin/pg_resetwal/pg_resetwal.c | 48 ++++
src/include/access/xlog.h | 5 +
src/include/miscadmin.h | 3 +
src/include/postmaster/wal_allocator.h | 21 ++
src/include/storage/fd.h | 1 +
src/include/storage/proc.h | 11 +-
src/include/utils/wait_event.h | 3 +-
24 files changed, 765 insertions(+), 111 deletions(-)
create mode 100644 src/backend/postmaster/wal_allocator.c
create mode 100644 src/include/postmaster/wal_allocator.h
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 381d8636ab..f3b1eba340 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -3197,6 +3197,29 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-wal-allocator-max-size" xreflabel="wal_allocator_max_size">
+ <term><varname>wal_allocator_max_size</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>wal_allocator_max_size</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ The maximum amount of WAL to pre-allocate for use when a new WAL segment
+ must be initialized. Setting this parameter to a size less than the WAL
+ segment size disables this behavior. The default values is 64 megabytes
+ (<literal>64MB</literal>). This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command line.
+ </para>
+
+ <para>
+ WAL pre-allocation may improve performance in scenarios where new WAL
+ segments must be created (e.g., during checkpoints when WAL segments
+ cannot be recycled).
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-wal-buffers" xreflabel="wal_buffers">
<term><varname>wal_buffers</varname> (<type>integer</type>)
<indexterm>
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index c7c928f50b..16e063cae1 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -51,6 +51,7 @@
#include "port/pg_iovec.h"
#include "postmaster/bgwriter.h"
#include "postmaster/startup.h"
+#include "postmaster/wal_allocator.h"
#include "postmaster/walwriter.h"
#include "replication/basebackup.h"
#include "replication/logical.h"
@@ -742,6 +743,13 @@ typedef struct XLogCtlData
*/
XLogRecPtr lastFpwDisableRecPtr;
+ /*
+ * number of pre-allocated WAL segments in pg_wal/preallocated_segments
+ *
+ * Protected by WALPreallocationLock.
+ */
+ int num_prealloc_segs;
+
slock_t info_lck; /* locks shared variables shown above */
} XLogCtlData;
@@ -3281,11 +3289,10 @@ static int
XLogFileInitInternal(XLogSegNo logsegno, bool *added, char *path)
{
char tmppath[MAXPGPATH];
- PGAlignedXLogBlock zbuffer;
XLogSegNo installed_segno;
XLogSegNo max_segno;
int fd;
- int save_errno;
+ bool found = false;
XLogFilePath(path, ThisTimeLineID, logsegno, wal_segment_size);
@@ -3305,115 +3312,45 @@ XLogFileInitInternal(XLogSegNo logsegno, bool *added, char *path)
return fd;
/*
- * Initialize an empty (all zeroes) segment. NOTE: it is possible that
- * another process is doing the same thing. If so, we will end up
- * pre-creating an extra log segment. That seems OK, and better than
- * holding the lock throughout this lengthy process.
+ * Try to use a pre-allocated segment, if one exists. If none are
+ * available, we fall back to creating a new segment on our own.
+ *
+ * Note that we still look for a pre-allocated segment even if the pre-
+ * allocation functionality is disabled via the GUCs. This ensures that any
+ * pre-allocated segments left over after turning off the pre-allocation
+ * functionality are eventually used up.
*/
- elog(DEBUG2, "creating and filling new WAL file");
-
- snprintf(tmppath, MAXPGPATH, XLOGDIR "/xlogtemp.%d", (int) getpid());
-
- unlink(tmppath);
-
- /* do not use get_sync_bit() here --- want to fsync only at end of fill */
- fd = BasicOpenFile(tmppath, O_RDWR | O_CREAT | O_EXCL | PG_BINARY);
- if (fd < 0)
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not create file \"%s\": %m", tmppath)));
-
- memset(zbuffer.data, 0, XLOG_BLCKSZ);
-
- pgstat_report_wait_start(WAIT_EVENT_WAL_INIT_WRITE);
- save_errno = 0;
- if (wal_init_zero)
+ LWLockAcquire(WALPreallocationLock, LW_EXCLUSIVE);
+ if (XLogCtl->num_prealloc_segs > 0)
{
- struct iovec iov[PG_IOV_MAX];
- int blocks;
-
- /*
- * Zero-fill the file. With this setting, we do this the hard way to
- * ensure that all the file space has really been allocated. On
- * platforms that allow "holes" in files, just seeking to the end
- * doesn't allocate intermediate space. This way, we know that we
- * have all the space and (after the fsync below) that all the
- * indirect blocks are down on disk. Therefore, fdatasync(2) or
- * O_DSYNC will be sufficient to sync future writes to the log file.
- */
-
- /* Prepare to write out a lot of copies of our zero buffer at once. */
- for (int i = 0; i < lengthof(iov); ++i)
- {
- iov[i].iov_base = zbuffer.data;
- iov[i].iov_len = XLOG_BLCKSZ;
- }
-
- /* Loop, writing as many blocks as we can for each system call. */
- blocks = wal_segment_size / XLOG_BLCKSZ;
- for (int i = 0; i < blocks;)
- {
- int iovcnt = Min(blocks - i, lengthof(iov));
- off_t offset = i * XLOG_BLCKSZ;
-
- if (pg_pwritev_with_retry(fd, iov, iovcnt, offset) < 0)
- {
- save_errno = errno;
- break;
- }
+ elog(DEBUG2, "using pre-allocated WAL file");
- i += iovcnt;
- }
+ found = true;
+ XLogCtl->num_prealloc_segs--;
+ snprintf(tmppath, MAXPGPATH, "%s/preallocated_segments/xlogtemp.%d",
+ XLOGDIR, XLogCtl->num_prealloc_segs);
}
- else
+
+ if (!found)
{
/*
- * Otherwise, seeking to the end and writing a solitary byte is
- * enough.
+ * We're not using a pre-allocated segment, so there's no need to keep
+ * holding the WALPreallocationLock.
*/
- errno = 0;
- if (pg_pwrite(fd, zbuffer.data, 1, wal_segment_size - 1) != 1)
- {
- /* if write didn't set errno, assume no disk space */
- save_errno = errno ? errno : ENOSPC;
- }
- }
- pgstat_report_wait_end();
+ LWLockRelease(WALPreallocationLock);
- if (save_errno)
- {
/*
- * If we fail to make the file, delete it to release disk space
+ * Initialize an empty (all zeroes) segment. NOTE: it is possible that
+ * another process is doing the same thing. If so, we will end up
+ * pre-creating an extra log segment. That seems OK, and better than
+ * holding the lock throughout this lengthy process.
*/
- unlink(tmppath);
-
- close(fd);
+ elog(DEBUG2, "creating and filling new WAL file");
- errno = save_errno;
-
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not write to file \"%s\": %m", tmppath)));
+ snprintf(tmppath, MAXPGPATH, XLOGDIR "/xlogtemp.%d", (int) getpid());
+ CreateEmptyWalSegment(tmppath);
}
- pgstat_report_wait_start(WAIT_EVENT_WAL_INIT_SYNC);
- if (pg_fsync(fd) != 0)
- {
- int save_errno = errno;
-
- close(fd);
- errno = save_errno;
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not fsync file \"%s\": %m", tmppath)));
- }
- pgstat_report_wait_end();
-
- if (close(fd) != 0)
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not close file \"%s\": %m", tmppath)));
-
/*
* Now move the segment into place with its final name. Cope with
* possibility that someone else has created the file while we were
@@ -3434,7 +3371,7 @@ XLogFileInitInternal(XLogSegNo logsegno, bool *added, char *path)
if (InstallXLogFileSegment(&installed_segno, tmppath, true, max_segno))
{
*added = true;
- elog(DEBUG2, "done creating and filling new WAL file");
+ elog(DEBUG2, "done installing new WAL file");
}
else
{
@@ -3447,6 +3384,18 @@ XLogFileInitInternal(XLogSegNo logsegno, bool *added, char *path)
elog(DEBUG2, "abandoned new WAL file");
}
+ /*
+ * If we are using a pre-allocated segment, we've been holding onto the
+ * WALPreallocationLock all this time so that the WAL Allocator process
+ * can't overwrite the file before we've installed it.
+ *
+ * While we're at it, also try to wake up the WAL allocator early so that it
+ * can get a jump start on allocating new segments if possible.
+ */
+ if (found)
+ LWLockRelease(WALPreallocationLock);
+ RequestWalPreallocation();
+
return -1;
}
@@ -4305,8 +4254,8 @@ RemoveXlogFile(const char *segname, XLogSegNo recycleSegNo,
}
/*
- * Verify whether pg_wal and pg_wal/archive_status exist.
- * If the latter does not exist, recreate it.
+ * Verify whether pg_wal, pg_wal/archive_status and pg_wal/preallocated_segments
+ * exist. If either of the latter two do not exist, recreate it.
*
* It is not the goal of this function to verify the contents of these
* directories, but to help in cases where someone has performed a cluster
@@ -4349,6 +4298,26 @@ ValidateXLOGDirectoryStructure(void)
(errmsg("could not create missing directory \"%s\": %m",
path)));
}
+
+ /* Check for preallocated_segments */
+ snprintf(path, MAXPGPATH, XLOGDIR "/preallocated_segments");
+ if (stat(path, &stat_buf) == 0)
+ {
+ /* Check for weird cases where it exists but isn't a directory */
+ if (!S_ISDIR(stat_buf.st_mode))
+ ereport(FATAL,
+ (errmsg("required WAL directory \"%s\" does not exist",
+ path)));
+ }
+ else
+ {
+ ereport(LOG,
+ (errmsg("creating missing WAL directory \"%s\"", path)));
+ if (MakePGDirectory(path) < 0)
+ ereport(FATAL,
+ (errmsg("could not create missing directory \"%s\": %m",
+ path)));
+ }
}
/*
@@ -5256,6 +5225,9 @@ XLOGShmemInit(void)
SpinLockInit(&XLogCtl->ulsn_lck);
InitSharedLatch(&XLogCtl->recoveryWakeupLatch);
ConditionVariableInit(&XLogCtl->recoveryNotPausedCV);
+
+ /* protected by WALPreallocationLock */
+ XLogCtl->num_prealloc_segs = 0;
}
/*
@@ -12989,3 +12961,55 @@ XLogRequestWalReceiverReply(void)
{
doRequestWalReceiverReply = true;
}
+
+int
+GetNumPreallocatedWalSegs(void)
+{
+ int ret;
+
+ LWLockAcquire(WALPreallocationLock, LW_SHARED);
+ ret = XLogCtl->num_prealloc_segs;
+ LWLockRelease(WALPreallocationLock);
+
+ return ret;
+}
+
+void
+SetNumPreallocatedWalSegs(int i)
+{
+ LWLockAcquire(WALPreallocationLock, LW_EXCLUSIVE);
+ XLogCtl->num_prealloc_segs = i;
+ LWLockRelease(WALPreallocationLock);
+}
+
+/*
+ * InstallPreallocatedWalSeg
+ *
+ * Renames the file at "path" to the next open pre-allocated segment slot and
+ * bumps up XLogCtl->num_prealloc_segs.
+ */
+void
+InstallPreallocatedWalSeg(const char *path)
+{
+ char newpath[MAXPGPATH];
+
+ LWLockAcquire(WALPreallocationLock, LW_EXCLUSIVE);
+
+ snprintf(newpath, MAXPGPATH, "%s/preallocated_segments/xlogtemp.%d",
+ XLOGDIR, XLogCtl->num_prealloc_segs);
+ (void) durable_rename(path, newpath, ERROR);
+ XLogCtl->num_prealloc_segs++;
+
+ LWLockRelease(WALPreallocationLock);
+}
+
+/*
+ * Request that the WAL allocator wake up early.
+ */
+void
+RequestWalPreallocation(void)
+{
+ if (ProcGlobal->walAllocatorLatch &&
+ WalPreallocationEnabled())
+ SetLatch(ProcGlobal->walAllocatorLatch);
+}
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index 94ab5ca095..56132337b0 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -36,6 +36,7 @@
#include "pgstat.h"
#include "postmaster/bgwriter.h"
#include "postmaster/startup.h"
+#include "postmaster/wal_allocator.h"
#include "postmaster/walwriter.h"
#include "replication/walreceiver.h"
#include "storage/bufmgr.h"
@@ -333,6 +334,9 @@ AuxiliaryProcessMain(int argc, char *argv[])
case WalReceiverProcess:
MyBackendType = B_WAL_RECEIVER;
break;
+ case WalAllocatorProcess:
+ MyBackendType = B_WAL_ALLOCATOR;
+ break;
default:
MyBackendType = B_INVALID;
}
@@ -468,6 +472,10 @@ AuxiliaryProcessMain(int argc, char *argv[])
WalReceiverMain();
proc_exit(1);
+ case WalAllocatorProcess:
+ WalAllocatorMain();
+ proc_exit(1);
+
default:
elog(PANIC, "unrecognized process type: %d", (int) MyAuxProcType);
proc_exit(1);
diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile
index bfdf6a833d..4bac1e640f 100644
--- a/src/backend/postmaster/Makefile
+++ b/src/backend/postmaster/Makefile
@@ -24,6 +24,7 @@ OBJS = \
postmaster.o \
startup.o \
syslogger.o \
+ wal_allocator.o \
walwriter.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 5a050898fe..82939a5b62 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -251,6 +251,7 @@ static pid_t StartupPID = 0,
CheckpointerPID = 0,
WalWriterPID = 0,
WalReceiverPID = 0,
+ WalAllocatorPID = 0,
AutoVacPID = 0,
PgArchPID = 0,
PgStatPID = 0,
@@ -557,6 +558,7 @@ static void ShmemBackendArrayRemove(Backend *bn);
#define StartCheckpointer() StartChildProcess(CheckpointerProcess)
#define StartWalWriter() StartChildProcess(WalWriterProcess)
#define StartWalReceiver() StartChildProcess(WalReceiverProcess)
+#define StartWalAllocator() StartChildProcess(WalAllocatorProcess)
/* Macros to check exit status of a child process */
#define EXIT_STATUS_0(st) ((st) == 0)
@@ -1781,6 +1783,13 @@ ServerLoop(void)
if (WalWriterPID == 0 && pmState == PM_RUN)
WalWriterPID = StartWalWriter();
+ /*
+ * If we have lost the WAL allocator process, try to start a new one.
+ */
+ if (WalAllocatorPID == 0 &&
+ (pmState == PM_RUN || pmState == PM_HOT_STANDBY))
+ WalAllocatorPID = StartWalAllocator();
+
/*
* If we have lost the autovacuum launcher, try to start a new one. We
* don't want autovacuum to run in binary upgrade mode because
@@ -2696,6 +2705,8 @@ SIGHUP_handler(SIGNAL_ARGS)
signal_child(WalWriterPID, SIGHUP);
if (WalReceiverPID != 0)
signal_child(WalReceiverPID, SIGHUP);
+ if (WalAllocatorPID != 0)
+ signal_child(WalAllocatorPID, SIGHUP);
if (AutoVacPID != 0)
signal_child(AutoVacPID, SIGHUP);
if (PgArchPID != 0)
@@ -3023,6 +3034,8 @@ reaper(SIGNAL_ARGS)
BgWriterPID = StartBackgroundWriter();
if (WalWriterPID == 0)
WalWriterPID = StartWalWriter();
+ if (WalAllocatorPID == 0)
+ WalAllocatorPID = StartWalAllocator();
/*
* Likewise, start other special children as needed. In a restart
@@ -3150,6 +3163,20 @@ reaper(SIGNAL_ARGS)
continue;
}
+ /*
+ * Was it the WAL allocator? Normal exit can be ignored; we'll start a
+ * new one at the next iteration of the postmaster's main loop, if
+ * necessary. Any other exit condition is treated as a crash.
+ */
+ if (pid == WalAllocatorPID)
+ {
+ WalAllocatorPID = 0;
+ if (!EXIT_STATUS_0(exitstatus))
+ HandleChildCrash(pid, exitstatus,
+ _("WAL Allocator process"));
+ continue;
+ }
+
/*
* Was it the autovacuum launcher? Normal exit can be ignored; we'll
* start a new one at the next iteration of the postmaster's main
@@ -3623,6 +3650,18 @@ HandleChildCrash(int pid, int exitstatus, const char *procname)
signal_child(WalReceiverPID, (SendStop ? SIGSTOP : SIGQUIT));
}
+ /* Take care of the WAL allocator too */
+ if (pid == WalAllocatorPID)
+ WalAllocatorPID = 0;
+ else if (WalAllocatorPID != 0 && take_action)
+ {
+ ereport(DEBUG2,
+ (errmsg_internal("sending %s to process %d",
+ (SendStop ? "SIGSTOP" : "SIGQUIT"),
+ (int) WalAllocatorPID)));
+ signal_child(WalAllocatorPID, (SendStop ? SIGSTOP : SIGQUIT));
+ }
+
/* Take care of the autovacuum launcher too */
if (pid == AutoVacPID)
AutoVacPID = 0;
@@ -3810,6 +3849,8 @@ PostmasterStateMachine(void)
signal_child(StartupPID, SIGTERM);
if (WalReceiverPID != 0)
signal_child(WalReceiverPID, SIGTERM);
+ if (WalAllocatorPID != 0)
+ signal_child(WalAllocatorPID, SIGTERM);
/* checkpointer, archiver, stats, and syslogger may continue for now */
/* Now transition to PM_WAIT_BACKENDS state to wait for them to die */
@@ -3840,6 +3881,7 @@ PostmasterStateMachine(void)
(CheckpointerPID == 0 ||
(!FatalError && Shutdown < ImmediateShutdown)) &&
WalWriterPID == 0 &&
+ WalAllocatorPID == 0 &&
AutoVacPID == 0)
{
if (Shutdown >= ImmediateShutdown || FatalError)
@@ -3933,6 +3975,7 @@ PostmasterStateMachine(void)
Assert(BgWriterPID == 0);
Assert(CheckpointerPID == 0);
Assert(WalWriterPID == 0);
+ Assert(WalAllocatorPID == 0);
Assert(AutoVacPID == 0);
Assert(PgArchPID == 0);
/* syslogger is not considered here */
@@ -4142,6 +4185,8 @@ TerminateChildren(int signal)
signal_child(WalWriterPID, signal);
if (WalReceiverPID != 0)
signal_child(WalReceiverPID, signal);
+ if (WalAllocatorPID != 0)
+ signal_child(WalAllocatorPID, signal);
if (AutoVacPID != 0)
signal_child(AutoVacPID, signal);
if (PgArchPID != 0)
diff --git a/src/backend/postmaster/wal_allocator.c b/src/backend/postmaster/wal_allocator.c
new file mode 100644
index 0000000000..38cebf7953
--- /dev/null
+++ b/src/backend/postmaster/wal_allocator.c
@@ -0,0 +1,315 @@
+/*-------------------------------------------------------------------------
+ *
+ * wal_allocator.c
+ *
+ * The WAL allocator is new as of Postgres 15. It attempts to keep regular
+ * backends from having to allocate new WAL segments. Even when
+ * wal_recycle is enabled, pre-allocating WAL segments can yield
+ * significant performance improvements in certain scenarios. Note that
+ * regular backends are still empowered to create new WAL segments if the
+ * WAL allocator fails to generate enough new segments.
+ *
+ * The WAL allocator is controlled by one parameter: wal_allocator_max_size.
+ * wal_allocator_max_size specifies the maximum amount of WAL to pre-
+ * allocate. If this value is not divisible by the WAL segment size, fewer
+ * WAL segments will be pre-allocated.
+ *
+ * The WAL allocator is started by the postmaster as soon as the startup
+ * subprocess finishes, or as soon as recovery begins if we are doing
+ * archive recovery. It remains alive until the postmaster commands it to
+ * terminate. Normal termination is by SIGTERM, which instructs the
+ * WAL allocator to exit(0). Emergency termination is by SIGQUIT; like any
+ * backend, the WAL allocator will simply abort and exit on SIGQUIT.
+ *
+ * If the WAL allocator exits unexpectedly, the postmaster treats that the
+ * same as a backend crash: shared memory may be corrupted, so remaining
+ * backends should be killed by SIGQUIT and then a recovery cycle started.
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/postmaster/wal_allocator.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <sys/time.h>
+
+#include "access/xlog.h"
+#include "access/xlog_internal.h"
+#include "libpq/pqsignal.h"
+#include "pgstat.h"
+#include "postmaster/interrupt.h"
+#include "postmaster/wal_allocator.h"
+#include "storage/bufmgr.h"
+#include "storage/condition_variable.h"
+#include "storage/fd.h"
+#include "storage/ipc.h"
+#include "storage/lwlock.h"
+#include "storage/proc.h"
+#include "storage/procsignal.h"
+#include "storage/shmem.h"
+#include "storage/smgr.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+#define WAL_ALLOC_TIMEOUT_S (60)
+
+/*
+ * GUC parameters
+ */
+int wal_alloc_max_size_mb = 64;
+
+static void DoWalPreAllocation(void);
+static void ScanForExistingPreallocatedSegments(void);
+
+/*
+ * Main entry point for WAL allocator process
+ *
+ * This is invoked from AuxiliaryProcessMain, which has already created the
+ * basic execution environment, but not enabled signals yet.
+ */
+void
+WalAllocatorMain(void)
+{
+ sigjmp_buf local_sigjmp_buf;
+ MemoryContext wal_alloc_context;
+
+ /*
+ * Properly accept or ignore signals that might be sent to us.
+ */
+ pqsignal(SIGHUP, SignalHandlerForConfigReload);
+ pqsignal(SIGINT, SIG_IGN);
+ pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
+ /* SIGQUIT handler was already set up by InitPostmasterChild */
+ pqsignal(SIGALRM, SIG_IGN);
+ pqsignal(SIGPIPE, SIG_IGN);
+ pqsignal(SIGUSR1, procsignal_sigusr1_handler);
+ pqsignal(SIGUSR2, SIG_IGN);
+
+ /*
+ * Reset some signals that are accepted by postmaster but not here
+ */
+ pqsignal(SIGCHLD, SIG_DFL);
+
+ /*
+ * Create a memory context that we will do all our work in. We do this so
+ * that we can reset the context during error recovery and thereby avoid
+ * possible memory leaks.
+ */
+ wal_alloc_context = AllocSetContextCreate(TopMemoryContext,
+ "WAL Allocator",
+ ALLOCSET_DEFAULT_SIZES);
+ MemoryContextSwitchTo(wal_alloc_context);
+
+ /*
+ * If an exception is encountered, processing resumes here.
+ *
+ * You might wonder why this isn't coded as an infinite loop around a
+ * PG_TRY construct. The reason is that this is the bottom of the
+ * exception stack, and so with PG_TRY there would be no exception handler
+ * in force at all during the CATCH part. By leaving the outermost setjmp
+ * always active, we have at least some chance of recovering from an error
+ * during error recovery. (If we get into an infinite loop thereby, it
+ * will soon be stopped by overflow of elog.c's internal state stack.)
+ *
+ * Note that we use sigsetjmp(..., 1), so that the prevailing signal mask
+ * (to wit, BlockSig) will be restored when longjmp'ing to here. Thus,
+ * signals other than SIGQUIT will be blocked until we complete error
+ * recovery. It might seem that this policy makes the HOLD_INTERRUPTS()
+ * call redundant, but it is not since InterruptPending might be set
+ * already.
+ */
+ if (sigsetjmp(local_sigjmp_buf, 1) != 0)
+ {
+ /* Since not using PG_TRY, must reset error stack by hand */
+ error_context_stack = NULL;
+
+ /* Prevent interrupts while cleaning up */
+ HOLD_INTERRUPTS();
+
+ /* Report the error to the server log */
+ EmitErrorReport();
+
+ /*
+ * These operations are really just a minimal subset of
+ * AbortTransaction(). We don't have very many resources to worry
+ * about.
+ */
+ LWLockReleaseAll();
+ ConditionVariableCancelSleep();
+ pgstat_report_wait_end();
+ AbortBufferIO();
+ UnlockBuffers();
+ ReleaseAuxProcessResources(false);
+ AtEOXact_Buffers(false);
+ AtEOXact_SMgr();
+ AtEOXact_Files(false);
+ AtEOXact_HashTables(false);
+
+ /*
+ * Now return to normal top-level context and clear ErrorContext for
+ * next time.
+ */
+ MemoryContextSwitchTo(wal_alloc_context);
+ FlushErrorState();
+
+ /* Flush any leaked data in the top-level context */
+ MemoryContextResetAndDeleteChildren(wal_alloc_context);
+
+ /* Now we can allow interrupts again */
+ RESUME_INTERRUPTS();
+
+ /*
+ * Sleep at least 1 second after any error. A write error is likely
+ * to be repeated, and we don't want to be filling the error logs as
+ * fast as we can.
+ */
+ pg_usleep(1000000L);
+
+ /*
+ * Close all open files after any error. This is helpful on Windows,
+ * where holding deleted files open causes various strange errors.
+ * It's not clear we need it elsewhere, but shouldn't hurt.
+ */
+ smgrcloseall();
+
+ /* Report wait end here, when there is no further possibility of wait */
+ pgstat_report_wait_end();
+ }
+
+ /* We can now handle ereport(ERROR) */
+ PG_exception_stack = &local_sigjmp_buf;
+
+ /*
+ * Unblock signals (they were blocked when the postmaster forked us)
+ */
+ PG_SETMASK(&UnBlockSig);
+
+ /*
+ * Advertise our latch that backends can use to wake us up while we're
+ * sleeping.
+ */
+ ProcGlobal->walAllocatorLatch = &MyProc->procLatch;
+
+ /*
+ * Before we go into the main loop, scan the pre-allocated segments
+ * directory and look for anything that was left over from the last time.
+ */
+ ScanForExistingPreallocatedSegments();
+
+ /*
+ * Loop forever
+ */
+ for (;;)
+ {
+ /* Clear any already-pending wakeups */
+ ResetLatch(MyLatch);
+
+ HandleMainLoopInterrupts();
+
+ DoWalPreAllocation();
+
+ /* XXX: Send activity statistics to the stats collector */
+
+ /* Sleep until we are signaled or WAL_ALLOC_TIMEOUT_S has elapsed. */
+ (void) WaitLatch(MyLatch,
+ WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+ WAL_ALLOC_TIMEOUT_S * 1000L /* convert to ms */,
+ WAIT_EVENT_WAL_ALLOCATOR_MAIN);
+ }
+
+ pg_unreachable();
+}
+
+/*
+ * DoWalPreAllocation
+ *
+ * Tries to allocate up to wal_allocator_max_size worth of WAL.
+ */
+static void
+DoWalPreAllocation(void)
+{
+ int max_prealloc_segs;
+
+ max_prealloc_segs = wal_alloc_max_size_mb / (wal_segment_size / (1024 * 1024));
+
+ while (!ShutdownRequestPending &&
+ GetNumPreallocatedWalSegs() < max_prealloc_segs)
+ {
+ char tmppath[MAXPGPATH];
+
+ snprintf(tmppath, MAXPGPATH, "%s/preallocated_segments/xlogtemp", XLOGDIR);
+ CreateEmptyWalSegment(tmppath);
+ InstallPreallocatedWalSeg(tmppath);
+ elog(DEBUG2, "pre-allocated WAL segment");
+
+ if (ConfigReloadPending)
+ {
+ ConfigReloadPending = false;
+ ProcessConfigFile(PGC_SIGHUP);
+
+ max_prealloc_segs = wal_alloc_max_size_mb / (wal_segment_size / (1024 * 1024));
+ }
+ }
+}
+
+/*
+ * ScanForExistingPreallocatedSegments
+ *
+ * This function searches through pg_wal/preallocated_segments for any segments
+ * that were left over from a previous WAL allocator process and sets the
+ * tracking variable in shared memory accordingly.
+ */
+static void
+ScanForExistingPreallocatedSegments(void)
+{
+ int i = 0;
+
+ /*
+ * fsync the preallocated_segments directory in case any renames have yet to
+ * be flushed to disk. This is probably not really necessary, but it seems
+ * nice to know that all the segments we find are really where we think they
+ * are.
+ */
+ fsync_fname_ext(XLOGDIR "/preallocated_segments", true, false, FATAL);
+
+ /*
+ * Gather all the preallocated segments we can find.
+ */
+ while (true)
+ {
+ FILE *fd;
+ char path[MAXPGPATH];
+
+ snprintf(path, MAXPGPATH, "%s/preallocated_segments/xlogtemp.%d",
+ XLOGDIR, i);
+
+ fd = AllocateFile(path, "r");
+ if (fd != NULL)
+ {
+ FreeFile(fd);
+ i++;
+ }
+ else
+ {
+ if (errno != ENOENT)
+ ereport(FATAL,
+ (errcode_for_file_access(),
+ errmsg("could not open file \"%s\": %m", path)));
+ break;
+ }
+ }
+
+ SetNumPreallocatedWalSegs(i);
+ elog(DEBUG2, "found %d preallocated segments during startup", i);
+}
+
+bool
+WalPreallocationEnabled(void)
+{
+ return wal_alloc_max_size_mb >= wal_segment_size / (1024 * 1024);
+}
diff --git a/src/backend/replication/basebackup.c b/src/backend/replication/basebackup.c
index e09108d0ec..c9a91dc938 100644
--- a/src/backend/replication/basebackup.c
+++ b/src/backend/replication/basebackup.c
@@ -1399,11 +1399,13 @@ sendDir(const char *path, int basepathlen, bool sizeonly, List *tablespaces,
size += _tarWriteDir(pathbuf, basepathlen, &statbuf, sizeonly);
/*
- * Also send archive_status directory (by hackishly reusing
- * statbuf from above ...).
+ * Also send archive_status and preallocated_segments (by hackishly
+ * reusing statbuf from above ...).
*/
size += _tarWriteHeader("./pg_wal/archive_status", NULL, &statbuf,
sizeonly);
+ size += _tarWriteHeader("./pg_wal/preallocated_segments", NULL,
+ &statbuf, sizeonly);
continue; /* don't recurse into pg_wal */
}
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index a340a5f6af..7a2dddb59a 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -76,6 +76,7 @@
#include <sys/file.h>
#include <sys/param.h>
#include <sys/stat.h>
+#include <sys/time.h>
#include <sys/types.h>
#ifndef WIN32
#include <sys/mman.h>
@@ -3779,3 +3780,117 @@ pg_pwritev_with_retry(int fd, const struct iovec *iov, int iovcnt, off_t offset)
return sum;
}
+
+/*
+ * CreateEmptyWalSegment
+ *
+ * Create a new file that can be used as a new WAL segment. The caller is
+ * responsible for installing the new file in pg_wal.
+ */
+void
+CreateEmptyWalSegment(const char *path)
+{
+ PGAlignedXLogBlock zbuffer;
+ int fd;
+ int save_errno;
+
+ unlink(path);
+
+ /* do not use get_sync_bit() here --- want to fsync only at end of fill */
+ fd = BasicOpenFile(path, O_RDWR | O_CREAT | O_EXCL | PG_BINARY);
+ if (fd < 0)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not create file \"%s\": %m", path)));
+
+ memset(zbuffer.data, 0, XLOG_BLCKSZ);
+
+ pgstat_report_wait_start(WAIT_EVENT_WAL_INIT_WRITE);
+ save_errno = 0;
+ if (wal_init_zero)
+ {
+ struct iovec iov[PG_IOV_MAX];
+ int blocks;
+
+ /*
+ * Zero-fill the file. With this setting, we do this the hard way to
+ * ensure that all the file space has really been allocated. On
+ * platforms that allow "holes" in files, just seeking to the end
+ * doesn't allocate intermediate space. This way, we know that we
+ * have all the space and (after the fsync below) that all the
+ * indirect blocks are down on disk. Therefore, fdatasync(2) or
+ * O_DSYNC will be sufficient to sync future writes to the log file.
+ */
+
+ /* Prepare to write out a lot of copies of our zero buffer at once. */
+ for (int i = 0; i < lengthof(iov); ++i)
+ {
+ iov[i].iov_base = zbuffer.data;
+ iov[i].iov_len = XLOG_BLCKSZ;
+ }
+
+ /* Loop, writing as many blocks as we can for each system call. */
+ blocks = wal_segment_size / XLOG_BLCKSZ;
+ for (int i = 0; i < blocks;)
+ {
+ int iovcnt = Min(blocks - i, lengthof(iov));
+ off_t offset = i * XLOG_BLCKSZ;
+
+ if (pg_pwritev_with_retry(fd, iov, iovcnt, offset) < 0)
+ {
+ save_errno = errno;
+ break;
+ }
+
+ i += iovcnt;
+ }
+ }
+ else
+ {
+ /*
+ * Otherwise, seeking to the end and writing a solitary byte is
+ * enough.
+ */
+ errno = 0;
+ if (pg_pwrite(fd, zbuffer.data, 1, wal_segment_size - 1) != 1)
+ {
+ /* if write didn't set errno, assume no disk space */
+ save_errno = errno ? errno : ENOSPC;
+ }
+ }
+ pgstat_report_wait_end();
+
+ if (save_errno)
+ {
+ /*
+ * If we fail to make the file, delete it to release disk space
+ */
+ unlink(path);
+
+ close(fd);
+
+ errno = save_errno;
+
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not write to file \"%s\": %m", path)));
+ }
+
+ pgstat_report_wait_start(WAIT_EVENT_WAL_INIT_SYNC);
+ if (pg_fsync(fd) != 0)
+ {
+ int save_errno = errno;
+
+ close(fd);
+ errno = save_errno;
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not fsync file \"%s\": %m", path)));
+ }
+ pgstat_report_wait_end();
+
+ if (close(fd) != 0)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not close file \"%s\": %m", path)));
+}
diff --git a/src/backend/storage/lmgr/lwlocknames.txt b/src/backend/storage/lmgr/lwlocknames.txt
index 6c7cf6c295..212bda8e7d 100644
--- a/src/backend/storage/lmgr/lwlocknames.txt
+++ b/src/backend/storage/lmgr/lwlocknames.txt
@@ -53,3 +53,4 @@ XactTruncationLock 44
# 45 was XactTruncationLock until removal of BackendRandomLock
WrapLimitsVacuumLock 46
NotifyQueueTailLock 47
+WALPreallocationLock 48
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 2575ea1ca0..555f4ef53e 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -181,6 +181,7 @@ InitProcGlobal(void)
ProcGlobal->startupProcPid = 0;
ProcGlobal->startupBufferPinWaitBufId = -1;
ProcGlobal->walwriterLatch = NULL;
+ ProcGlobal->walAllocatorLatch = NULL;
ProcGlobal->checkpointerLatch = NULL;
pg_atomic_init_u32(&ProcGlobal->procArrayGroupFirst, INVALID_PGPROCNO);
pg_atomic_init_u32(&ProcGlobal->clogGroupFirst, INVALID_PGPROCNO);
diff --git a/src/backend/utils/activity/wait_event.c b/src/backend/utils/activity/wait_event.c
index ef7e6bfb77..a3c4324c92 100644
--- a/src/backend/utils/activity/wait_event.c
+++ b/src/backend/utils/activity/wait_event.c
@@ -248,6 +248,9 @@ pgstat_get_wait_activity(WaitEventActivity w)
case WAIT_EVENT_WAL_WRITER_MAIN:
event_name = "WalWriterMain";
break;
+ case WAIT_EVENT_WAL_ALLOCATOR_MAIN:
+ event_name = "WalAllocatorMain";
+ break;
/* no default case, so that compiler will warn */
}
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 8b73850d0d..b671dc6fac 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -268,6 +268,9 @@ GetBackendTypeDesc(BackendType backendType)
case B_WAL_WRITER:
backendDesc = "walwriter";
break;
+ case B_WAL_ALLOCATOR:
+ backendDesc = "wal allocator";
+ break;
case B_ARCHIVER:
backendDesc = "archiver";
break;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 480e8cd199..3cc5848d95 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -72,6 +72,7 @@
#include "postmaster/bgwriter.h"
#include "postmaster/postmaster.h"
#include "postmaster/syslogger.h"
+#include "postmaster/wal_allocator.h"
#include "postmaster/walwriter.h"
#include "replication/logicallauncher.h"
#include "replication/reorderbuffer.h"
@@ -2816,6 +2817,17 @@ static struct config_int ConfigureNamesInt[] =
NULL, NULL, NULL
},
+ {
+ {"wal_allocator_max_size", PGC_SIGHUP, WAL_SETTINGS,
+ gettext_noop("Sets the maximum amount of WAL to pre-allocate."),
+ NULL,
+ GUC_UNIT_MB
+ },
+ &wal_alloc_max_size_mb,
+ 64, 0, 102400,
+ NULL, NULL, NULL
+ },
+
{
{"wal_buffers", PGC_POSTMASTER, WAL_SETTINGS,
gettext_noop("Sets the number of disk-page buffers in shared memory for WAL."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index b696abfe54..f122aacbc8 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -222,6 +222,7 @@
# off, pglz, lz4, or on
#wal_init_zero = on # zero-fill new WAL files
#wal_recycle = on # recycle WAL files
+#wal_allocator_max_size = 64MB # amount of WAL to pre-allocate
#wal_buffers = -1 # min 32kB, -1 sets based on shared_buffers
# (change requires restart)
#wal_writer_delay = 200ms # 1-10000 milliseconds
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 0945d70061..d9c3f301ff 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -208,6 +208,7 @@ static char *extra_options = "";
static const char *const subdirs[] = {
"global",
"pg_wal/archive_status",
+ "pg_wal/preallocated_segments",
"pg_commit_ts",
"pg_dynshmem",
"pg_notify",
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index 8bb0acf498..141db22bd5 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -678,6 +678,22 @@ StartLogStreamer(char *startpos, uint32 timeline, char *sysidentifier)
pg_log_error("could not create directory \"%s\": %m", statusdir);
exit(1);
}
+
+ /*
+ * Also create pg_wal/preallocated_segments if necessary.
+ */
+ if (PQserverVersion(conn) >= 150000)
+ {
+ char prealloc_dir[MAXPGPATH];
+
+ snprintf(prealloc_dir, sizeof(prealloc_dir), "%s/pg_wal/preallocated_segments",
+ basedir);
+ if (pg_mkdir_p(prealloc_dir, pg_dir_create_mode) != 0 && errno != EEXIST)
+ {
+ pg_log_error("could not create directory \"%s\": %m", prealloc_dir);
+ exit(1);
+ }
+ }
}
/*
@@ -1614,7 +1630,8 @@ ReceiveTarAndUnpackCopyChunk(size_t r, char *copybuf, void *callback_data)
*/
if (!((pg_str_endswith(state->filename, "/pg_wal") ||
pg_str_endswith(state->filename, "/pg_xlog") ||
- pg_str_endswith(state->filename, "/archive_status")) &&
+ pg_str_endswith(state->filename, "/archive_status") ||
+ pg_str_endswith(state->filename, "/preallocated_segments")) &&
errno == EEXIST))
{
pg_log_error("could not create directory \"%s\": %m",
diff --git a/src/bin/pg_basebackup/t/010_pg_basebackup.pl b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
index 74f8c2c739..61ceda2c6e 100644
--- a/src/bin/pg_basebackup/t/010_pg_basebackup.pl
+++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
@@ -120,10 +120,10 @@ SKIP:
"check backup dir permissions");
}
-# Only archive_status directory should be copied in pg_wal/.
+# Only archive_status and preallocated_segments directories should be copied in pg_wal/.
is_deeply(
[ sort(slurp_dir("$tempdir/backup/pg_wal/")) ],
- [ sort qw(. .. archive_status) ],
+ [ sort qw(. .. archive_status preallocated_segments) ],
'no WAL files copied');
# Contents of these directories should not be copied.
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 805dafef07..f5a4d77064 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -84,6 +84,7 @@ static void RewriteControlFile(void);
static void FindEndOfXLOG(void);
static void KillExistingXLOG(void);
static void KillExistingArchiveStatus(void);
+static void KillExistingPreallocatedSegments(void);
static void WriteEmptyXLOG(void);
static void usage(void);
@@ -513,6 +514,7 @@ main(int argc, char *argv[])
RewriteControlFile();
KillExistingXLOG();
KillExistingArchiveStatus();
+ KillExistingPreallocatedSegments();
WriteEmptyXLOG();
printf(_("Write-ahead log reset\n"));
@@ -1053,6 +1055,52 @@ KillExistingXLOG(void)
}
+/*
+ * Remove existing preallocated segments
+ */
+static void
+KillExistingPreallocatedSegments(void)
+{
+#define PREALLOCSEGDIR XLOGDIR "/preallocated_segments"
+
+ DIR *xldir;
+ struct dirent *xlde;
+ char path[MAXPGPATH + sizeof(PREALLOCSEGDIR)];
+
+ xldir = opendir(PREALLOCSEGDIR);
+ if (xldir == NULL)
+ {
+ pg_log_error("could not open directory \"%s\": %m", PREALLOCSEGDIR);
+ exit(1);
+ }
+
+ while (errno = 0, (xlde = readdir(xldir)) != NULL)
+ {
+ if (strncmp(xlde->d_name, "xlogtemp", strlen("xlogtemp")) == 0)
+ {
+ snprintf(path, sizeof(path), "%s/%s", PREALLOCSEGDIR, xlde->d_name);
+ if (unlink(path) < 0)
+ {
+ pg_log_error("could not delete file \"%s\": %m", path);
+ exit(1);
+ }
+ }
+ }
+
+ if (errno)
+ {
+ pg_log_error("could not read directory \"%s\": %m", PREALLOCSEGDIR);
+ exit(1);
+ }
+
+ if (closedir(xldir))
+ {
+ pg_log_error("could not close directory \"%s\": %m", PREALLOCSEGDIR);
+ exit(1);
+ }
+}
+
+
/*
* Remove existing archive status files
*/
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index ccfcf43d62..59a1780975 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -369,6 +369,11 @@ extern void XLogRequestWalReceiverReply(void);
extern void assign_max_wal_size(int newval, void *extra);
extern void assign_checkpoint_completion_target(double newval, void *extra);
+extern int GetNumPreallocatedWalSegs(void);
+extern void InstallPreallocatedWalSeg(const char *path);
+extern void SetNumPreallocatedWalSegs(int i);
+extern void RequestWalPreallocation(void);
+
/*
* Routines to start, stop, and get status of a base backup.
*/
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 4dc343cbc5..a4599df7ff 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -333,6 +333,7 @@ typedef enum BackendType
B_WAL_RECEIVER,
B_WAL_SENDER,
B_WAL_WRITER,
+ B_WAL_ALLOCATOR,
B_ARCHIVER,
B_STATS_COLLECTOR,
B_LOGGER,
@@ -435,6 +436,7 @@ typedef enum
CheckpointerProcess,
WalWriterProcess,
WalReceiverProcess,
+ WalAllocatorProcess,
NUM_AUXPROCTYPES /* Must be last! */
} AuxProcType;
@@ -448,6 +450,7 @@ extern AuxProcType MyAuxProcType;
#define AmCheckpointerProcess() (MyAuxProcType == CheckpointerProcess)
#define AmWalWriterProcess() (MyAuxProcType == WalWriterProcess)
#define AmWalReceiverProcess() (MyAuxProcType == WalReceiverProcess)
+#define AmWalAllocatorProcess() (MyAuxProcType == WalAllocatorProcess)
/*****************************************************************************
diff --git a/src/include/postmaster/wal_allocator.h b/src/include/postmaster/wal_allocator.h
new file mode 100644
index 0000000000..c7e139226b
--- /dev/null
+++ b/src/include/postmaster/wal_allocator.h
@@ -0,0 +1,21 @@
+/*-------------------------------------------------------------------------
+ *
+ * wal_allocator.h
+ * Exports from postmaster/wal_allocator.c.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ *
+ * src/include/postmaster/wal_allocator.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _WAL_ALLOCATOR_H
+#define _WAL_ALLOCATOR_H
+
+/* GUC options */
+extern int wal_alloc_max_size_mb;
+
+extern void WalAllocatorMain(void) pg_attribute_noreturn();
+extern bool WalPreallocationEnabled(void);
+
+#endif /* _WAL_ALLOCATOR_H */
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index 5b3c280dd7..008a54f2ee 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -173,6 +173,7 @@ extern int durable_unlink(const char *fname, int loglevel);
extern int durable_rename_excl(const char *oldfile, const char *newfile, int loglevel);
extern void SyncDataDirectory(void);
extern int data_sync_elevel(int elevel);
+extern void CreateEmptyWalSegment(const char *path);
/* Filename components */
#define PG_TEMP_FILES_DIR "pgsql_tmp"
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index be67d8a861..8d0cae37b2 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -348,6 +348,8 @@ typedef struct PROC_HDR
pg_atomic_uint32 clogGroupFirst;
/* WALWriter process's latch */
Latch *walwriterLatch;
+ /* WAL Allocator process's latch */
+ Latch *walAllocatorLatch;
/* Checkpointer process's latch */
Latch *checkpointerLatch;
/* Current shared estimate of appropriate spins_per_delay value */
@@ -370,11 +372,12 @@ extern PGPROC *PreparedXactProcs;
* We set aside some extra PGPROC structures for auxiliary processes,
* ie things that aren't full-fledged backends but need shmem access.
*
- * Background writer, checkpointer, WAL writer and archiver run during normal
- * operation. Startup process and WAL receiver also consume 2 slots, but WAL
- * writer is launched only after startup has exited, so we only need 5 slots.
+ * Background writer, checkpointer, WAL writer, archiver, and WAL allocator run
+ * during normal operation. Startup process and WAL receiver also consume 2
+ * slots, but WAL writer is launched only after startup has exited, so we only
+ * need 6 slots.
*/
-#define NUM_AUXILIARY_PROCS 5
+#define NUM_AUXILIARY_PROCS 6
/* configurable options */
extern PGDLLIMPORT int DeadlockTimeout;
diff --git a/src/include/utils/wait_event.h b/src/include/utils/wait_event.h
index 6007827b44..18a05f8d59 100644
--- a/src/include/utils/wait_event.h
+++ b/src/include/utils/wait_event.h
@@ -47,7 +47,8 @@ typedef enum
WAIT_EVENT_SYSLOGGER_MAIN,
WAIT_EVENT_WAL_RECEIVER_MAIN,
WAIT_EVENT_WAL_SENDER_MAIN,
- WAIT_EVENT_WAL_WRITER_MAIN
+ WAIT_EVENT_WAL_WRITER_MAIN,
+ WAIT_EVENT_WAL_ALLOCATOR_MAIN
} WaitEventActivity;
/* ----------
--
2.16.6
^ permalink raw reply [nested|flat] 19+ messages in thread
* Re: Pre-allocating WAL files
@ 2021-08-06 20:25 Bossart, Nathan <[email protected]>
parent: vignesh C <[email protected]>
3 siblings, 0 replies; 19+ messages in thread
From: Bossart, Nathan @ 2021-08-06 20:25 UTC (permalink / raw)
To: vignesh C <[email protected]>; +Cc: Andres Freund <[email protected]>; pgsql-hackers
On 7/9/21, 2:10 PM, "Bossart, Nathan" <[email protected]> wrote:
> I've attached a rebased version of the patch.
Here's a newer rebased version of the patch.
Nathan
Attachments:
[application/octet-stream] v3-0001-wal-segment-pre-allocation.patch (43.7K, ../../[email protected]/2-v3-0001-wal-segment-pre-allocation.patch)
download | inline diff:
From c7a7aa10cdc2b7e8531a788819b59fd1bb1805f9 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <[email protected]>
Date: Fri, 6 Aug 2021 20:20:42 +0000
Subject: [PATCH v3 1/1] wal segment pre-allocation
---
doc/src/sgml/config.sgml | 23 ++
src/backend/access/transam/xlog.c | 226 +++++++++---------
src/backend/postmaster/Makefile | 1 +
src/backend/postmaster/auxprocess.c | 8 +
src/backend/postmaster/postmaster.c | 45 ++++
src/backend/postmaster/wal_allocator.c | 315 ++++++++++++++++++++++++++
src/backend/replication/basebackup.c | 6 +-
src/backend/storage/file/fd.c | 115 ++++++++++
src/backend/storage/lmgr/lwlocknames.txt | 1 +
src/backend/storage/lmgr/proc.c | 1 +
src/backend/utils/activity/wait_event.c | 3 +
src/backend/utils/init/miscinit.c | 3 +
src/backend/utils/misc/guc.c | 12 +
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/bin/initdb/initdb.c | 1 +
src/bin/pg_basebackup/pg_basebackup.c | 19 +-
src/bin/pg_basebackup/t/010_pg_basebackup.pl | 4 +-
src/bin/pg_resetwal/pg_resetwal.c | 48 ++++
src/include/access/xlog.h | 5 +
src/include/miscadmin.h | 3 +
src/include/postmaster/wal_allocator.h | 21 ++
src/include/storage/fd.h | 1 +
src/include/storage/proc.h | 11 +-
src/include/utils/wait_event.h | 3 +-
24 files changed, 765 insertions(+), 111 deletions(-)
create mode 100644 src/backend/postmaster/wal_allocator.c
create mode 100644 src/include/postmaster/wal_allocator.h
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 610372b78a..b8a0a37aab 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -3197,6 +3197,29 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-wal-allocator-max-size" xreflabel="wal_allocator_max_size">
+ <term><varname>wal_allocator_max_size</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>wal_allocator_max_size</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ The maximum amount of WAL to pre-allocate for use when a new WAL segment
+ must be initialized. Setting this parameter to a size less than the WAL
+ segment size disables this behavior. The default values is 64 megabytes
+ (<literal>64MB</literal>). This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command line.
+ </para>
+
+ <para>
+ WAL pre-allocation may improve performance in scenarios where new WAL
+ segments must be created (e.g., during checkpoints when WAL segments
+ cannot be recycled).
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-wal-buffers" xreflabel="wal_buffers">
<term><varname>wal_buffers</varname> (<type>integer</type>)
<indexterm>
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index d0ec6a834b..f9caddc8eb 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -51,6 +51,7 @@
#include "port/pg_iovec.h"
#include "postmaster/bgwriter.h"
#include "postmaster/startup.h"
+#include "postmaster/wal_allocator.h"
#include "postmaster/walwriter.h"
#include "replication/basebackup.h"
#include "replication/logical.h"
@@ -723,6 +724,13 @@ typedef struct XLogCtlData
*/
XLogRecPtr lastFpwDisableRecPtr;
+ /*
+ * number of pre-allocated WAL segments in pg_wal/preallocated_segments
+ *
+ * Protected by WALPreallocationLock.
+ */
+ int num_prealloc_segs;
+
slock_t info_lck; /* locks shared variables shown above */
} XLogCtlData;
@@ -3260,11 +3268,10 @@ static int
XLogFileInitInternal(XLogSegNo logsegno, bool *added, char *path)
{
char tmppath[MAXPGPATH];
- PGAlignedXLogBlock zbuffer;
XLogSegNo installed_segno;
XLogSegNo max_segno;
int fd;
- int save_errno;
+ bool found = false;
XLogFilePath(path, ThisTimeLineID, logsegno, wal_segment_size);
@@ -3284,115 +3291,45 @@ XLogFileInitInternal(XLogSegNo logsegno, bool *added, char *path)
return fd;
/*
- * Initialize an empty (all zeroes) segment. NOTE: it is possible that
- * another process is doing the same thing. If so, we will end up
- * pre-creating an extra log segment. That seems OK, and better than
- * holding the lock throughout this lengthy process.
+ * Try to use a pre-allocated segment, if one exists. If none are
+ * available, we fall back to creating a new segment on our own.
+ *
+ * Note that we still look for a pre-allocated segment even if the pre-
+ * allocation functionality is disabled via the GUCs. This ensures that any
+ * pre-allocated segments left over after turning off the pre-allocation
+ * functionality are eventually used up.
*/
- elog(DEBUG2, "creating and filling new WAL file");
-
- snprintf(tmppath, MAXPGPATH, XLOGDIR "/xlogtemp.%d", (int) getpid());
-
- unlink(tmppath);
-
- /* do not use get_sync_bit() here --- want to fsync only at end of fill */
- fd = BasicOpenFile(tmppath, O_RDWR | O_CREAT | O_EXCL | PG_BINARY);
- if (fd < 0)
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not create file \"%s\": %m", tmppath)));
-
- memset(zbuffer.data, 0, XLOG_BLCKSZ);
-
- pgstat_report_wait_start(WAIT_EVENT_WAL_INIT_WRITE);
- save_errno = 0;
- if (wal_init_zero)
+ LWLockAcquire(WALPreallocationLock, LW_EXCLUSIVE);
+ if (XLogCtl->num_prealloc_segs > 0)
{
- struct iovec iov[PG_IOV_MAX];
- int blocks;
-
- /*
- * Zero-fill the file. With this setting, we do this the hard way to
- * ensure that all the file space has really been allocated. On
- * platforms that allow "holes" in files, just seeking to the end
- * doesn't allocate intermediate space. This way, we know that we
- * have all the space and (after the fsync below) that all the
- * indirect blocks are down on disk. Therefore, fdatasync(2) or
- * O_DSYNC will be sufficient to sync future writes to the log file.
- */
-
- /* Prepare to write out a lot of copies of our zero buffer at once. */
- for (int i = 0; i < lengthof(iov); ++i)
- {
- iov[i].iov_base = zbuffer.data;
- iov[i].iov_len = XLOG_BLCKSZ;
- }
-
- /* Loop, writing as many blocks as we can for each system call. */
- blocks = wal_segment_size / XLOG_BLCKSZ;
- for (int i = 0; i < blocks;)
- {
- int iovcnt = Min(blocks - i, lengthof(iov));
- off_t offset = i * XLOG_BLCKSZ;
-
- if (pg_pwritev_with_retry(fd, iov, iovcnt, offset) < 0)
- {
- save_errno = errno;
- break;
- }
+ elog(DEBUG2, "using pre-allocated WAL file");
- i += iovcnt;
- }
+ found = true;
+ XLogCtl->num_prealloc_segs--;
+ snprintf(tmppath, MAXPGPATH, "%s/preallocated_segments/xlogtemp.%d",
+ XLOGDIR, XLogCtl->num_prealloc_segs);
}
- else
+
+ if (!found)
{
/*
- * Otherwise, seeking to the end and writing a solitary byte is
- * enough.
+ * We're not using a pre-allocated segment, so there's no need to keep
+ * holding the WALPreallocationLock.
*/
- errno = 0;
- if (pg_pwrite(fd, zbuffer.data, 1, wal_segment_size - 1) != 1)
- {
- /* if write didn't set errno, assume no disk space */
- save_errno = errno ? errno : ENOSPC;
- }
- }
- pgstat_report_wait_end();
+ LWLockRelease(WALPreallocationLock);
- if (save_errno)
- {
/*
- * If we fail to make the file, delete it to release disk space
+ * Initialize an empty (all zeroes) segment. NOTE: it is possible that
+ * another process is doing the same thing. If so, we will end up
+ * pre-creating an extra log segment. That seems OK, and better than
+ * holding the lock throughout this lengthy process.
*/
- unlink(tmppath);
-
- close(fd);
+ elog(DEBUG2, "creating and filling new WAL file");
- errno = save_errno;
-
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not write to file \"%s\": %m", tmppath)));
+ snprintf(tmppath, MAXPGPATH, XLOGDIR "/xlogtemp.%d", (int) getpid());
+ CreateEmptyWalSegment(tmppath);
}
- pgstat_report_wait_start(WAIT_EVENT_WAL_INIT_SYNC);
- if (pg_fsync(fd) != 0)
- {
- int save_errno = errno;
-
- close(fd);
- errno = save_errno;
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not fsync file \"%s\": %m", tmppath)));
- }
- pgstat_report_wait_end();
-
- if (close(fd) != 0)
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not close file \"%s\": %m", tmppath)));
-
/*
* Now move the segment into place with its final name. Cope with
* possibility that someone else has created the file while we were
@@ -3413,7 +3350,7 @@ XLogFileInitInternal(XLogSegNo logsegno, bool *added, char *path)
if (InstallXLogFileSegment(&installed_segno, tmppath, true, max_segno))
{
*added = true;
- elog(DEBUG2, "done creating and filling new WAL file");
+ elog(DEBUG2, "done installing new WAL file");
}
else
{
@@ -3426,6 +3363,18 @@ XLogFileInitInternal(XLogSegNo logsegno, bool *added, char *path)
elog(DEBUG2, "abandoned new WAL file");
}
+ /*
+ * If we are using a pre-allocated segment, we've been holding onto the
+ * WALPreallocationLock all this time so that the WAL Allocator process
+ * can't overwrite the file before we've installed it.
+ *
+ * While we're at it, also try to wake up the WAL allocator early so that it
+ * can get a jump start on allocating new segments if possible.
+ */
+ if (found)
+ LWLockRelease(WALPreallocationLock);
+ RequestWalPreallocation();
+
return -1;
}
@@ -4282,8 +4231,8 @@ RemoveXlogFile(const char *segname, XLogSegNo recycleSegNo,
}
/*
- * Verify whether pg_wal and pg_wal/archive_status exist.
- * If the latter does not exist, recreate it.
+ * Verify whether pg_wal, pg_wal/archive_status and pg_wal/preallocated_segments
+ * exist. If either of the latter two do not exist, recreate it.
*
* It is not the goal of this function to verify the contents of these
* directories, but to help in cases where someone has performed a cluster
@@ -4326,6 +4275,26 @@ ValidateXLOGDirectoryStructure(void)
(errmsg("could not create missing directory \"%s\": %m",
path)));
}
+
+ /* Check for preallocated_segments */
+ snprintf(path, MAXPGPATH, XLOGDIR "/preallocated_segments");
+ if (stat(path, &stat_buf) == 0)
+ {
+ /* Check for weird cases where it exists but isn't a directory */
+ if (!S_ISDIR(stat_buf.st_mode))
+ ereport(FATAL,
+ (errmsg("required WAL directory \"%s\" does not exist",
+ path)));
+ }
+ else
+ {
+ ereport(LOG,
+ (errmsg("creating missing WAL directory \"%s\"", path)));
+ if (MakePGDirectory(path) < 0)
+ ereport(FATAL,
+ (errmsg("could not create missing directory \"%s\": %m",
+ path)));
+ }
}
/*
@@ -5233,6 +5202,9 @@ XLOGShmemInit(void)
SpinLockInit(&XLogCtl->ulsn_lck);
InitSharedLatch(&XLogCtl->recoveryWakeupLatch);
ConditionVariableInit(&XLogCtl->recoveryNotPausedCV);
+
+ /* protected by WALPreallocationLock */
+ XLogCtl->num_prealloc_segs = 0;
}
/*
@@ -12984,3 +12956,55 @@ XLogRequestWalReceiverReply(void)
{
doRequestWalReceiverReply = true;
}
+
+int
+GetNumPreallocatedWalSegs(void)
+{
+ int ret;
+
+ LWLockAcquire(WALPreallocationLock, LW_SHARED);
+ ret = XLogCtl->num_prealloc_segs;
+ LWLockRelease(WALPreallocationLock);
+
+ return ret;
+}
+
+void
+SetNumPreallocatedWalSegs(int i)
+{
+ LWLockAcquire(WALPreallocationLock, LW_EXCLUSIVE);
+ XLogCtl->num_prealloc_segs = i;
+ LWLockRelease(WALPreallocationLock);
+}
+
+/*
+ * InstallPreallocatedWalSeg
+ *
+ * Renames the file at "path" to the next open pre-allocated segment slot and
+ * bumps up XLogCtl->num_prealloc_segs.
+ */
+void
+InstallPreallocatedWalSeg(const char *path)
+{
+ char newpath[MAXPGPATH];
+
+ LWLockAcquire(WALPreallocationLock, LW_EXCLUSIVE);
+
+ snprintf(newpath, MAXPGPATH, "%s/preallocated_segments/xlogtemp.%d",
+ XLOGDIR, XLogCtl->num_prealloc_segs);
+ (void) durable_rename(path, newpath, ERROR);
+ XLogCtl->num_prealloc_segs++;
+
+ LWLockRelease(WALPreallocationLock);
+}
+
+/*
+ * Request that the WAL allocator wake up early.
+ */
+void
+RequestWalPreallocation(void)
+{
+ if (ProcGlobal->walAllocatorLatch &&
+ WalPreallocationEnabled())
+ SetLatch(ProcGlobal->walAllocatorLatch);
+}
diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile
index 787c6a2c3b..d69ada0c4e 100644
--- a/src/backend/postmaster/Makefile
+++ b/src/backend/postmaster/Makefile
@@ -25,6 +25,7 @@ OBJS = \
postmaster.o \
startup.o \
syslogger.o \
+ wal_allocator.o \
walwriter.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/postmaster/auxprocess.c b/src/backend/postmaster/auxprocess.c
index 16d87e9140..d7f920afba 100644
--- a/src/backend/postmaster/auxprocess.c
+++ b/src/backend/postmaster/auxprocess.c
@@ -21,6 +21,7 @@
#include "postmaster/auxprocess.h"
#include "postmaster/bgwriter.h"
#include "postmaster/startup.h"
+#include "postmaster/wal_allocator.h"
#include "postmaster/walwriter.h"
#include "replication/walreceiver.h"
#include "storage/bufmgr.h"
@@ -80,6 +81,9 @@ AuxiliaryProcessMain(AuxProcType auxtype)
case WalReceiverProcess:
MyBackendType = B_WAL_RECEIVER;
break;
+ case WalAllocatorProcess:
+ MyBackendType = B_WAL_ALLOCATOR;
+ break;
default:
elog(ERROR, "something has gone wrong");
MyBackendType = B_INVALID;
@@ -164,6 +168,10 @@ AuxiliaryProcessMain(AuxProcType auxtype)
WalReceiverMain();
proc_exit(1);
+ case WalAllocatorProcess:
+ WalAllocatorMain();
+ proc_exit(1);
+
default:
elog(PANIC, "unrecognized process type: %d", (int) MyAuxProcType);
proc_exit(1);
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index fc0bc8d99e..3f0114795f 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -251,6 +251,7 @@ static pid_t StartupPID = 0,
CheckpointerPID = 0,
WalWriterPID = 0,
WalReceiverPID = 0,
+ WalAllocatorPID = 0,
AutoVacPID = 0,
PgArchPID = 0,
PgStatPID = 0,
@@ -557,6 +558,7 @@ static void ShmemBackendArrayRemove(Backend *bn);
#define StartCheckpointer() StartChildProcess(CheckpointerProcess)
#define StartWalWriter() StartChildProcess(WalWriterProcess)
#define StartWalReceiver() StartChildProcess(WalReceiverProcess)
+#define StartWalAllocator() StartChildProcess(WalAllocatorProcess)
/* Macros to check exit status of a child process */
#define EXIT_STATUS_0(st) ((st) == 0)
@@ -1794,6 +1796,13 @@ ServerLoop(void)
if (WalWriterPID == 0 && pmState == PM_RUN)
WalWriterPID = StartWalWriter();
+ /*
+ * If we have lost the WAL allocator process, try to start a new one.
+ */
+ if (WalAllocatorPID == 0 &&
+ (pmState == PM_RUN || pmState == PM_HOT_STANDBY))
+ WalAllocatorPID = StartWalAllocator();
+
/*
* If we have lost the autovacuum launcher, try to start a new one. We
* don't want autovacuum to run in binary upgrade mode because
@@ -2709,6 +2718,8 @@ SIGHUP_handler(SIGNAL_ARGS)
signal_child(WalWriterPID, SIGHUP);
if (WalReceiverPID != 0)
signal_child(WalReceiverPID, SIGHUP);
+ if (WalAllocatorPID != 0)
+ signal_child(WalAllocatorPID, SIGHUP);
if (AutoVacPID != 0)
signal_child(AutoVacPID, SIGHUP);
if (PgArchPID != 0)
@@ -3036,6 +3047,8 @@ reaper(SIGNAL_ARGS)
BgWriterPID = StartBackgroundWriter();
if (WalWriterPID == 0)
WalWriterPID = StartWalWriter();
+ if (WalAllocatorPID == 0)
+ WalAllocatorPID = StartWalAllocator();
/*
* Likewise, start other special children as needed. In a restart
@@ -3163,6 +3176,20 @@ reaper(SIGNAL_ARGS)
continue;
}
+ /*
+ * Was it the WAL allocator? Normal exit can be ignored; we'll start a
+ * new one at the next iteration of the postmaster's main loop, if
+ * necessary. Any other exit condition is treated as a crash.
+ */
+ if (pid == WalAllocatorPID)
+ {
+ WalAllocatorPID = 0;
+ if (!EXIT_STATUS_0(exitstatus))
+ HandleChildCrash(pid, exitstatus,
+ _("WAL Allocator process"));
+ continue;
+ }
+
/*
* Was it the autovacuum launcher? Normal exit can be ignored; we'll
* start a new one at the next iteration of the postmaster's main
@@ -3636,6 +3663,18 @@ HandleChildCrash(int pid, int exitstatus, const char *procname)
signal_child(WalReceiverPID, (SendStop ? SIGSTOP : SIGQUIT));
}
+ /* Take care of the WAL allocator too */
+ if (pid == WalAllocatorPID)
+ WalAllocatorPID = 0;
+ else if (WalAllocatorPID != 0 && take_action)
+ {
+ ereport(DEBUG2,
+ (errmsg_internal("sending %s to process %d",
+ (SendStop ? "SIGSTOP" : "SIGQUIT"),
+ (int) WalAllocatorPID)));
+ signal_child(WalAllocatorPID, (SendStop ? SIGSTOP : SIGQUIT));
+ }
+
/* Take care of the autovacuum launcher too */
if (pid == AutoVacPID)
AutoVacPID = 0;
@@ -3823,6 +3862,8 @@ PostmasterStateMachine(void)
signal_child(StartupPID, SIGTERM);
if (WalReceiverPID != 0)
signal_child(WalReceiverPID, SIGTERM);
+ if (WalAllocatorPID != 0)
+ signal_child(WalAllocatorPID, SIGTERM);
/* checkpointer, archiver, stats, and syslogger may continue for now */
/* Now transition to PM_WAIT_BACKENDS state to wait for them to die */
@@ -3853,6 +3894,7 @@ PostmasterStateMachine(void)
(CheckpointerPID == 0 ||
(!FatalError && Shutdown < ImmediateShutdown)) &&
WalWriterPID == 0 &&
+ WalAllocatorPID == 0 &&
AutoVacPID == 0)
{
if (Shutdown >= ImmediateShutdown || FatalError)
@@ -3946,6 +3988,7 @@ PostmasterStateMachine(void)
Assert(BgWriterPID == 0);
Assert(CheckpointerPID == 0);
Assert(WalWriterPID == 0);
+ Assert(WalAllocatorPID == 0);
Assert(AutoVacPID == 0);
/* syslogger is not considered here */
pmState = PM_NO_CHILDREN;
@@ -4154,6 +4197,8 @@ TerminateChildren(int signal)
signal_child(WalWriterPID, signal);
if (WalReceiverPID != 0)
signal_child(WalReceiverPID, signal);
+ if (WalAllocatorPID != 0)
+ signal_child(WalAllocatorPID, signal);
if (AutoVacPID != 0)
signal_child(AutoVacPID, signal);
if (PgArchPID != 0)
diff --git a/src/backend/postmaster/wal_allocator.c b/src/backend/postmaster/wal_allocator.c
new file mode 100644
index 0000000000..38cebf7953
--- /dev/null
+++ b/src/backend/postmaster/wal_allocator.c
@@ -0,0 +1,315 @@
+/*-------------------------------------------------------------------------
+ *
+ * wal_allocator.c
+ *
+ * The WAL allocator is new as of Postgres 15. It attempts to keep regular
+ * backends from having to allocate new WAL segments. Even when
+ * wal_recycle is enabled, pre-allocating WAL segments can yield
+ * significant performance improvements in certain scenarios. Note that
+ * regular backends are still empowered to create new WAL segments if the
+ * WAL allocator fails to generate enough new segments.
+ *
+ * The WAL allocator is controlled by one parameter: wal_allocator_max_size.
+ * wal_allocator_max_size specifies the maximum amount of WAL to pre-
+ * allocate. If this value is not divisible by the WAL segment size, fewer
+ * WAL segments will be pre-allocated.
+ *
+ * The WAL allocator is started by the postmaster as soon as the startup
+ * subprocess finishes, or as soon as recovery begins if we are doing
+ * archive recovery. It remains alive until the postmaster commands it to
+ * terminate. Normal termination is by SIGTERM, which instructs the
+ * WAL allocator to exit(0). Emergency termination is by SIGQUIT; like any
+ * backend, the WAL allocator will simply abort and exit on SIGQUIT.
+ *
+ * If the WAL allocator exits unexpectedly, the postmaster treats that the
+ * same as a backend crash: shared memory may be corrupted, so remaining
+ * backends should be killed by SIGQUIT and then a recovery cycle started.
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/postmaster/wal_allocator.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <sys/time.h>
+
+#include "access/xlog.h"
+#include "access/xlog_internal.h"
+#include "libpq/pqsignal.h"
+#include "pgstat.h"
+#include "postmaster/interrupt.h"
+#include "postmaster/wal_allocator.h"
+#include "storage/bufmgr.h"
+#include "storage/condition_variable.h"
+#include "storage/fd.h"
+#include "storage/ipc.h"
+#include "storage/lwlock.h"
+#include "storage/proc.h"
+#include "storage/procsignal.h"
+#include "storage/shmem.h"
+#include "storage/smgr.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+#define WAL_ALLOC_TIMEOUT_S (60)
+
+/*
+ * GUC parameters
+ */
+int wal_alloc_max_size_mb = 64;
+
+static void DoWalPreAllocation(void);
+static void ScanForExistingPreallocatedSegments(void);
+
+/*
+ * Main entry point for WAL allocator process
+ *
+ * This is invoked from AuxiliaryProcessMain, which has already created the
+ * basic execution environment, but not enabled signals yet.
+ */
+void
+WalAllocatorMain(void)
+{
+ sigjmp_buf local_sigjmp_buf;
+ MemoryContext wal_alloc_context;
+
+ /*
+ * Properly accept or ignore signals that might be sent to us.
+ */
+ pqsignal(SIGHUP, SignalHandlerForConfigReload);
+ pqsignal(SIGINT, SIG_IGN);
+ pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
+ /* SIGQUIT handler was already set up by InitPostmasterChild */
+ pqsignal(SIGALRM, SIG_IGN);
+ pqsignal(SIGPIPE, SIG_IGN);
+ pqsignal(SIGUSR1, procsignal_sigusr1_handler);
+ pqsignal(SIGUSR2, SIG_IGN);
+
+ /*
+ * Reset some signals that are accepted by postmaster but not here
+ */
+ pqsignal(SIGCHLD, SIG_DFL);
+
+ /*
+ * Create a memory context that we will do all our work in. We do this so
+ * that we can reset the context during error recovery and thereby avoid
+ * possible memory leaks.
+ */
+ wal_alloc_context = AllocSetContextCreate(TopMemoryContext,
+ "WAL Allocator",
+ ALLOCSET_DEFAULT_SIZES);
+ MemoryContextSwitchTo(wal_alloc_context);
+
+ /*
+ * If an exception is encountered, processing resumes here.
+ *
+ * You might wonder why this isn't coded as an infinite loop around a
+ * PG_TRY construct. The reason is that this is the bottom of the
+ * exception stack, and so with PG_TRY there would be no exception handler
+ * in force at all during the CATCH part. By leaving the outermost setjmp
+ * always active, we have at least some chance of recovering from an error
+ * during error recovery. (If we get into an infinite loop thereby, it
+ * will soon be stopped by overflow of elog.c's internal state stack.)
+ *
+ * Note that we use sigsetjmp(..., 1), so that the prevailing signal mask
+ * (to wit, BlockSig) will be restored when longjmp'ing to here. Thus,
+ * signals other than SIGQUIT will be blocked until we complete error
+ * recovery. It might seem that this policy makes the HOLD_INTERRUPTS()
+ * call redundant, but it is not since InterruptPending might be set
+ * already.
+ */
+ if (sigsetjmp(local_sigjmp_buf, 1) != 0)
+ {
+ /* Since not using PG_TRY, must reset error stack by hand */
+ error_context_stack = NULL;
+
+ /* Prevent interrupts while cleaning up */
+ HOLD_INTERRUPTS();
+
+ /* Report the error to the server log */
+ EmitErrorReport();
+
+ /*
+ * These operations are really just a minimal subset of
+ * AbortTransaction(). We don't have very many resources to worry
+ * about.
+ */
+ LWLockReleaseAll();
+ ConditionVariableCancelSleep();
+ pgstat_report_wait_end();
+ AbortBufferIO();
+ UnlockBuffers();
+ ReleaseAuxProcessResources(false);
+ AtEOXact_Buffers(false);
+ AtEOXact_SMgr();
+ AtEOXact_Files(false);
+ AtEOXact_HashTables(false);
+
+ /*
+ * Now return to normal top-level context and clear ErrorContext for
+ * next time.
+ */
+ MemoryContextSwitchTo(wal_alloc_context);
+ FlushErrorState();
+
+ /* Flush any leaked data in the top-level context */
+ MemoryContextResetAndDeleteChildren(wal_alloc_context);
+
+ /* Now we can allow interrupts again */
+ RESUME_INTERRUPTS();
+
+ /*
+ * Sleep at least 1 second after any error. A write error is likely
+ * to be repeated, and we don't want to be filling the error logs as
+ * fast as we can.
+ */
+ pg_usleep(1000000L);
+
+ /*
+ * Close all open files after any error. This is helpful on Windows,
+ * where holding deleted files open causes various strange errors.
+ * It's not clear we need it elsewhere, but shouldn't hurt.
+ */
+ smgrcloseall();
+
+ /* Report wait end here, when there is no further possibility of wait */
+ pgstat_report_wait_end();
+ }
+
+ /* We can now handle ereport(ERROR) */
+ PG_exception_stack = &local_sigjmp_buf;
+
+ /*
+ * Unblock signals (they were blocked when the postmaster forked us)
+ */
+ PG_SETMASK(&UnBlockSig);
+
+ /*
+ * Advertise our latch that backends can use to wake us up while we're
+ * sleeping.
+ */
+ ProcGlobal->walAllocatorLatch = &MyProc->procLatch;
+
+ /*
+ * Before we go into the main loop, scan the pre-allocated segments
+ * directory and look for anything that was left over from the last time.
+ */
+ ScanForExistingPreallocatedSegments();
+
+ /*
+ * Loop forever
+ */
+ for (;;)
+ {
+ /* Clear any already-pending wakeups */
+ ResetLatch(MyLatch);
+
+ HandleMainLoopInterrupts();
+
+ DoWalPreAllocation();
+
+ /* XXX: Send activity statistics to the stats collector */
+
+ /* Sleep until we are signaled or WAL_ALLOC_TIMEOUT_S has elapsed. */
+ (void) WaitLatch(MyLatch,
+ WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+ WAL_ALLOC_TIMEOUT_S * 1000L /* convert to ms */,
+ WAIT_EVENT_WAL_ALLOCATOR_MAIN);
+ }
+
+ pg_unreachable();
+}
+
+/*
+ * DoWalPreAllocation
+ *
+ * Tries to allocate up to wal_allocator_max_size worth of WAL.
+ */
+static void
+DoWalPreAllocation(void)
+{
+ int max_prealloc_segs;
+
+ max_prealloc_segs = wal_alloc_max_size_mb / (wal_segment_size / (1024 * 1024));
+
+ while (!ShutdownRequestPending &&
+ GetNumPreallocatedWalSegs() < max_prealloc_segs)
+ {
+ char tmppath[MAXPGPATH];
+
+ snprintf(tmppath, MAXPGPATH, "%s/preallocated_segments/xlogtemp", XLOGDIR);
+ CreateEmptyWalSegment(tmppath);
+ InstallPreallocatedWalSeg(tmppath);
+ elog(DEBUG2, "pre-allocated WAL segment");
+
+ if (ConfigReloadPending)
+ {
+ ConfigReloadPending = false;
+ ProcessConfigFile(PGC_SIGHUP);
+
+ max_prealloc_segs = wal_alloc_max_size_mb / (wal_segment_size / (1024 * 1024));
+ }
+ }
+}
+
+/*
+ * ScanForExistingPreallocatedSegments
+ *
+ * This function searches through pg_wal/preallocated_segments for any segments
+ * that were left over from a previous WAL allocator process and sets the
+ * tracking variable in shared memory accordingly.
+ */
+static void
+ScanForExistingPreallocatedSegments(void)
+{
+ int i = 0;
+
+ /*
+ * fsync the preallocated_segments directory in case any renames have yet to
+ * be flushed to disk. This is probably not really necessary, but it seems
+ * nice to know that all the segments we find are really where we think they
+ * are.
+ */
+ fsync_fname_ext(XLOGDIR "/preallocated_segments", true, false, FATAL);
+
+ /*
+ * Gather all the preallocated segments we can find.
+ */
+ while (true)
+ {
+ FILE *fd;
+ char path[MAXPGPATH];
+
+ snprintf(path, MAXPGPATH, "%s/preallocated_segments/xlogtemp.%d",
+ XLOGDIR, i);
+
+ fd = AllocateFile(path, "r");
+ if (fd != NULL)
+ {
+ FreeFile(fd);
+ i++;
+ }
+ else
+ {
+ if (errno != ENOENT)
+ ereport(FATAL,
+ (errcode_for_file_access(),
+ errmsg("could not open file \"%s\": %m", path)));
+ break;
+ }
+ }
+
+ SetNumPreallocatedWalSegs(i);
+ elog(DEBUG2, "found %d preallocated segments during startup", i);
+}
+
+bool
+WalPreallocationEnabled(void)
+{
+ return wal_alloc_max_size_mb >= wal_segment_size / (1024 * 1024);
+}
diff --git a/src/backend/replication/basebackup.c b/src/backend/replication/basebackup.c
index e09108d0ec..c9a91dc938 100644
--- a/src/backend/replication/basebackup.c
+++ b/src/backend/replication/basebackup.c
@@ -1399,11 +1399,13 @@ sendDir(const char *path, int basepathlen, bool sizeonly, List *tablespaces,
size += _tarWriteDir(pathbuf, basepathlen, &statbuf, sizeonly);
/*
- * Also send archive_status directory (by hackishly reusing
- * statbuf from above ...).
+ * Also send archive_status and preallocated_segments (by hackishly
+ * reusing statbuf from above ...).
*/
size += _tarWriteHeader("./pg_wal/archive_status", NULL, &statbuf,
sizeonly);
+ size += _tarWriteHeader("./pg_wal/preallocated_segments", NULL,
+ &statbuf, sizeonly);
continue; /* don't recurse into pg_wal */
}
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index 5d5e8ae94e..ef9485cf12 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -76,6 +76,7 @@
#include <sys/file.h>
#include <sys/param.h>
#include <sys/stat.h>
+#include <sys/time.h>
#include <sys/types.h>
#ifndef WIN32
#include <sys/mman.h>
@@ -3823,3 +3824,117 @@ pg_pwritev_with_retry(int fd, const struct iovec *iov, int iovcnt, off_t offset)
return sum;
}
+
+/*
+ * CreateEmptyWalSegment
+ *
+ * Create a new file that can be used as a new WAL segment. The caller is
+ * responsible for installing the new file in pg_wal.
+ */
+void
+CreateEmptyWalSegment(const char *path)
+{
+ PGAlignedXLogBlock zbuffer;
+ int fd;
+ int save_errno;
+
+ unlink(path);
+
+ /* do not use get_sync_bit() here --- want to fsync only at end of fill */
+ fd = BasicOpenFile(path, O_RDWR | O_CREAT | O_EXCL | PG_BINARY);
+ if (fd < 0)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not create file \"%s\": %m", path)));
+
+ memset(zbuffer.data, 0, XLOG_BLCKSZ);
+
+ pgstat_report_wait_start(WAIT_EVENT_WAL_INIT_WRITE);
+ save_errno = 0;
+ if (wal_init_zero)
+ {
+ struct iovec iov[PG_IOV_MAX];
+ int blocks;
+
+ /*
+ * Zero-fill the file. With this setting, we do this the hard way to
+ * ensure that all the file space has really been allocated. On
+ * platforms that allow "holes" in files, just seeking to the end
+ * doesn't allocate intermediate space. This way, we know that we
+ * have all the space and (after the fsync below) that all the
+ * indirect blocks are down on disk. Therefore, fdatasync(2) or
+ * O_DSYNC will be sufficient to sync future writes to the log file.
+ */
+
+ /* Prepare to write out a lot of copies of our zero buffer at once. */
+ for (int i = 0; i < lengthof(iov); ++i)
+ {
+ iov[i].iov_base = zbuffer.data;
+ iov[i].iov_len = XLOG_BLCKSZ;
+ }
+
+ /* Loop, writing as many blocks as we can for each system call. */
+ blocks = wal_segment_size / XLOG_BLCKSZ;
+ for (int i = 0; i < blocks;)
+ {
+ int iovcnt = Min(blocks - i, lengthof(iov));
+ off_t offset = i * XLOG_BLCKSZ;
+
+ if (pg_pwritev_with_retry(fd, iov, iovcnt, offset) < 0)
+ {
+ save_errno = errno;
+ break;
+ }
+
+ i += iovcnt;
+ }
+ }
+ else
+ {
+ /*
+ * Otherwise, seeking to the end and writing a solitary byte is
+ * enough.
+ */
+ errno = 0;
+ if (pg_pwrite(fd, zbuffer.data, 1, wal_segment_size - 1) != 1)
+ {
+ /* if write didn't set errno, assume no disk space */
+ save_errno = errno ? errno : ENOSPC;
+ }
+ }
+ pgstat_report_wait_end();
+
+ if (save_errno)
+ {
+ /*
+ * If we fail to make the file, delete it to release disk space
+ */
+ unlink(path);
+
+ close(fd);
+
+ errno = save_errno;
+
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not write to file \"%s\": %m", path)));
+ }
+
+ pgstat_report_wait_start(WAIT_EVENT_WAL_INIT_SYNC);
+ if (pg_fsync(fd) != 0)
+ {
+ int save_errno = errno;
+
+ close(fd);
+ errno = save_errno;
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not fsync file \"%s\": %m", path)));
+ }
+ pgstat_report_wait_end();
+
+ if (close(fd) != 0)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not close file \"%s\": %m", path)));
+}
diff --git a/src/backend/storage/lmgr/lwlocknames.txt b/src/backend/storage/lmgr/lwlocknames.txt
index 6c7cf6c295..212bda8e7d 100644
--- a/src/backend/storage/lmgr/lwlocknames.txt
+++ b/src/backend/storage/lmgr/lwlocknames.txt
@@ -53,3 +53,4 @@ XactTruncationLock 44
# 45 was XactTruncationLock until removal of BackendRandomLock
WrapLimitsVacuumLock 46
NotifyQueueTailLock 47
+WALPreallocationLock 48
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index b7d9da0aa9..5790d586ef 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -181,6 +181,7 @@ InitProcGlobal(void)
ProcGlobal->startupProcPid = 0;
ProcGlobal->startupBufferPinWaitBufId = -1;
ProcGlobal->walwriterLatch = NULL;
+ ProcGlobal->walAllocatorLatch = NULL;
ProcGlobal->checkpointerLatch = NULL;
pg_atomic_init_u32(&ProcGlobal->procArrayGroupFirst, INVALID_PGPROCNO);
pg_atomic_init_u32(&ProcGlobal->clogGroupFirst, INVALID_PGPROCNO);
diff --git a/src/backend/utils/activity/wait_event.c b/src/backend/utils/activity/wait_event.c
index ef7e6bfb77..a3c4324c92 100644
--- a/src/backend/utils/activity/wait_event.c
+++ b/src/backend/utils/activity/wait_event.c
@@ -248,6 +248,9 @@ pgstat_get_wait_activity(WaitEventActivity w)
case WAIT_EVENT_WAL_WRITER_MAIN:
event_name = "WalWriterMain";
break;
+ case WAIT_EVENT_WAL_ALLOCATOR_MAIN:
+ event_name = "WalAllocatorMain";
+ break;
/* no default case, so that compiler will warn */
}
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 88801374b5..9f12e18b66 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -285,6 +285,9 @@ GetBackendTypeDesc(BackendType backendType)
case B_WAL_WRITER:
backendDesc = "walwriter";
break;
+ case B_WAL_ALLOCATOR:
+ backendDesc = "wal allocator";
+ break;
case B_ARCHIVER:
backendDesc = "archiver";
break;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index a2e0f8de7e..b220311687 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -72,6 +72,7 @@
#include "postmaster/bgwriter.h"
#include "postmaster/postmaster.h"
#include "postmaster/syslogger.h"
+#include "postmaster/wal_allocator.h"
#include "postmaster/walwriter.h"
#include "replication/logicallauncher.h"
#include "replication/reorderbuffer.h"
@@ -2816,6 +2817,17 @@ static struct config_int ConfigureNamesInt[] =
NULL, NULL, NULL
},
+ {
+ {"wal_allocator_max_size", PGC_SIGHUP, WAL_SETTINGS,
+ gettext_noop("Sets the maximum amount of WAL to pre-allocate."),
+ NULL,
+ GUC_UNIT_MB
+ },
+ &wal_alloc_max_size_mb,
+ 64, 0, 102400,
+ NULL, NULL, NULL
+ },
+
{
{"wal_buffers", PGC_POSTMASTER, WAL_SETTINGS,
gettext_noop("Sets the number of disk-page buffers in shared memory for WAL."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index b242a7fc8b..c2476a4bca 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -223,6 +223,7 @@
# off, pglz, lz4, or on
#wal_init_zero = on # zero-fill new WAL files
#wal_recycle = on # recycle WAL files
+#wal_allocator_max_size = 64MB # amount of WAL to pre-allocate
#wal_buffers = -1 # min 32kB, -1 sets based on shared_buffers
# (change requires restart)
#wal_writer_delay = 200ms # 1-10000 milliseconds
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 5e84c7bb20..74b9a39ad6 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -208,6 +208,7 @@ static char *extra_options = "";
static const char *const subdirs[] = {
"global",
"pg_wal/archive_status",
+ "pg_wal/preallocated_segments",
"pg_commit_ts",
"pg_dynshmem",
"pg_notify",
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index 7296eb97d0..1778c9c91b 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -680,6 +680,22 @@ StartLogStreamer(char *startpos, uint32 timeline, char *sysidentifier)
pg_log_error("could not create directory \"%s\": %m", statusdir);
exit(1);
}
+
+ /*
+ * Also create pg_wal/preallocated_segments if necessary.
+ */
+ if (PQserverVersion(conn) >= 150000)
+ {
+ char prealloc_dir[MAXPGPATH];
+
+ snprintf(prealloc_dir, sizeof(prealloc_dir), "%s/pg_wal/preallocated_segments",
+ basedir);
+ if (pg_mkdir_p(prealloc_dir, pg_dir_create_mode) != 0 && errno != EEXIST)
+ {
+ pg_log_error("could not create directory \"%s\": %m", prealloc_dir);
+ exit(1);
+ }
+ }
}
/*
@@ -1616,7 +1632,8 @@ ReceiveTarAndUnpackCopyChunk(size_t r, char *copybuf, void *callback_data)
*/
if (!((pg_str_endswith(state->filename, "/pg_wal") ||
pg_str_endswith(state->filename, "/pg_xlog") ||
- pg_str_endswith(state->filename, "/archive_status")) &&
+ pg_str_endswith(state->filename, "/archive_status") ||
+ pg_str_endswith(state->filename, "/preallocated_segments")) &&
errno == EEXIST))
{
pg_log_error("could not create directory \"%s\": %m",
diff --git a/src/bin/pg_basebackup/t/010_pg_basebackup.pl b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
index a2cb2a7679..43e1e3e89d 100644
--- a/src/bin/pg_basebackup/t/010_pg_basebackup.pl
+++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
@@ -120,10 +120,10 @@ SKIP:
"check backup dir permissions");
}
-# Only archive_status directory should be copied in pg_wal/.
+# Only archive_status and preallocated_segments directories should be copied in pg_wal/.
is_deeply(
[ sort(slurp_dir("$tempdir/backup/pg_wal/")) ],
- [ sort qw(. .. archive_status) ],
+ [ sort qw(. .. archive_status preallocated_segments) ],
'no WAL files copied');
# Contents of these directories should not be copied.
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index b79f70a60d..80ee28c404 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -85,6 +85,7 @@ static void RewriteControlFile(void);
static void FindEndOfXLOG(void);
static void KillExistingXLOG(void);
static void KillExistingArchiveStatus(void);
+static void KillExistingPreallocatedSegments(void);
static void WriteEmptyXLOG(void);
static void usage(void);
@@ -522,6 +523,7 @@ main(int argc, char *argv[])
RewriteControlFile();
KillExistingXLOG();
KillExistingArchiveStatus();
+ KillExistingPreallocatedSegments();
WriteEmptyXLOG();
printf(_("Write-ahead log reset\n"));
@@ -1062,6 +1064,52 @@ KillExistingXLOG(void)
}
+/*
+ * Remove existing preallocated segments
+ */
+static void
+KillExistingPreallocatedSegments(void)
+{
+#define PREALLOCSEGDIR XLOGDIR "/preallocated_segments"
+
+ DIR *xldir;
+ struct dirent *xlde;
+ char path[MAXPGPATH + sizeof(PREALLOCSEGDIR)];
+
+ xldir = opendir(PREALLOCSEGDIR);
+ if (xldir == NULL)
+ {
+ pg_log_error("could not open directory \"%s\": %m", PREALLOCSEGDIR);
+ exit(1);
+ }
+
+ while (errno = 0, (xlde = readdir(xldir)) != NULL)
+ {
+ if (strncmp(xlde->d_name, "xlogtemp", strlen("xlogtemp")) == 0)
+ {
+ snprintf(path, sizeof(path), "%s/%s", PREALLOCSEGDIR, xlde->d_name);
+ if (unlink(path) < 0)
+ {
+ pg_log_error("could not delete file \"%s\": %m", path);
+ exit(1);
+ }
+ }
+ }
+
+ if (errno)
+ {
+ pg_log_error("could not read directory \"%s\": %m", PREALLOCSEGDIR);
+ exit(1);
+ }
+
+ if (closedir(xldir))
+ {
+ pg_log_error("could not close directory \"%s\": %m", PREALLOCSEGDIR);
+ exit(1);
+ }
+}
+
+
/*
* Remove existing archive status files
*/
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index 0a8ede700d..7d99d90244 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -327,6 +327,11 @@ extern void XLogRequestWalReceiverReply(void);
extern void assign_max_wal_size(int newval, void *extra);
extern void assign_checkpoint_completion_target(double newval, void *extra);
+extern int GetNumPreallocatedWalSegs(void);
+extern void InstallPreallocatedWalSeg(const char *path);
+extern void SetNumPreallocatedWalSegs(int i);
+extern void RequestWalPreallocation(void);
+
/*
* Routines to start, stop, and get status of a base backup.
*/
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 2e2e9a364a..e10d37bc00 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -333,6 +333,7 @@ typedef enum BackendType
B_WAL_RECEIVER,
B_WAL_SENDER,
B_WAL_WRITER,
+ B_WAL_ALLOCATOR,
B_ARCHIVER,
B_STATS_COLLECTOR,
B_LOGGER,
@@ -433,6 +434,7 @@ typedef enum
CheckpointerProcess,
WalWriterProcess,
WalReceiverProcess,
+ WalAllocatorProcess,
NUM_AUXPROCTYPES /* Must be last! */
} AuxProcType;
@@ -445,6 +447,7 @@ extern AuxProcType MyAuxProcType;
#define AmCheckpointerProcess() (MyAuxProcType == CheckpointerProcess)
#define AmWalWriterProcess() (MyAuxProcType == WalWriterProcess)
#define AmWalReceiverProcess() (MyAuxProcType == WalReceiverProcess)
+#define AmWalAllocatorProcess() (MyAuxProcType == WalAllocatorProcess)
/*****************************************************************************
diff --git a/src/include/postmaster/wal_allocator.h b/src/include/postmaster/wal_allocator.h
new file mode 100644
index 0000000000..c7e139226b
--- /dev/null
+++ b/src/include/postmaster/wal_allocator.h
@@ -0,0 +1,21 @@
+/*-------------------------------------------------------------------------
+ *
+ * wal_allocator.h
+ * Exports from postmaster/wal_allocator.c.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ *
+ * src/include/postmaster/wal_allocator.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _WAL_ALLOCATOR_H
+#define _WAL_ALLOCATOR_H
+
+/* GUC options */
+extern int wal_alloc_max_size_mb;
+
+extern void WalAllocatorMain(void) pg_attribute_noreturn();
+extern bool WalPreallocationEnabled(void);
+
+#endif /* _WAL_ALLOCATOR_H */
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index 2d843eb992..a21d692a36 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -189,6 +189,7 @@ extern int durable_unlink(const char *fname, int loglevel);
extern int durable_rename_excl(const char *oldfile, const char *newfile, int loglevel);
extern void SyncDataDirectory(void);
extern int data_sync_elevel(int elevel);
+extern void CreateEmptyWalSegment(const char *path);
/* Filename components */
#define PG_TEMP_FILES_DIR "pgsql_tmp"
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index be67d8a861..8d0cae37b2 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -348,6 +348,8 @@ typedef struct PROC_HDR
pg_atomic_uint32 clogGroupFirst;
/* WALWriter process's latch */
Latch *walwriterLatch;
+ /* WAL Allocator process's latch */
+ Latch *walAllocatorLatch;
/* Checkpointer process's latch */
Latch *checkpointerLatch;
/* Current shared estimate of appropriate spins_per_delay value */
@@ -370,11 +372,12 @@ extern PGPROC *PreparedXactProcs;
* We set aside some extra PGPROC structures for auxiliary processes,
* ie things that aren't full-fledged backends but need shmem access.
*
- * Background writer, checkpointer, WAL writer and archiver run during normal
- * operation. Startup process and WAL receiver also consume 2 slots, but WAL
- * writer is launched only after startup has exited, so we only need 5 slots.
+ * Background writer, checkpointer, WAL writer, archiver, and WAL allocator run
+ * during normal operation. Startup process and WAL receiver also consume 2
+ * slots, but WAL writer is launched only after startup has exited, so we only
+ * need 6 slots.
*/
-#define NUM_AUXILIARY_PROCS 5
+#define NUM_AUXILIARY_PROCS 6
/* configurable options */
extern PGDLLIMPORT int DeadlockTimeout;
diff --git a/src/include/utils/wait_event.h b/src/include/utils/wait_event.h
index 6007827b44..18a05f8d59 100644
--- a/src/include/utils/wait_event.h
+++ b/src/include/utils/wait_event.h
@@ -47,7 +47,8 @@ typedef enum
WAIT_EVENT_SYSLOGGER_MAIN,
WAIT_EVENT_WAL_RECEIVER_MAIN,
WAIT_EVENT_WAL_SENDER_MAIN,
- WAIT_EVENT_WAL_WRITER_MAIN
+ WAIT_EVENT_WAL_WRITER_MAIN,
+ WAIT_EVENT_WAL_ALLOCATOR_MAIN
} WaitEventActivity;
/* ----------
--
2.16.6
^ permalink raw reply [nested|flat] 19+ messages in thread
* Re: Pre-allocating WAL files
@ 2021-08-31 17:24 Bossart, Nathan <[email protected]>
parent: vignesh C <[email protected]>
3 siblings, 0 replies; 19+ messages in thread
From: Bossart, Nathan @ 2021-08-31 17:24 UTC (permalink / raw)
To: vignesh C <[email protected]>; +Cc: Andres Freund <[email protected]>; pgsql-hackers
On 8/6/21, 1:27 PM, "Bossart, Nathan" <[email protected]> wrote:
> Here's a newer rebased version of the patch.
Rebasing again to keep http://commitfest.cputube.org/ happy.
Nathan
Attachments:
[application/octet-stream] v4-0001-wal-segment-pre-allocation.patch (43.7K, ../../[email protected]/2-v4-0001-wal-segment-pre-allocation.patch)
download | inline diff:
From e650d130980499be829edd507275a25e7f8fbe8e Mon Sep 17 00:00:00 2001
From: Nathan Bossart <[email protected]>
Date: Tue, 31 Aug 2021 17:07:04 +0000
Subject: [PATCH v4 1/1] wal segment pre-allocation
---
doc/src/sgml/config.sgml | 23 ++
src/backend/access/transam/xlog.c | 226 +++++++++---------
src/backend/postmaster/Makefile | 1 +
src/backend/postmaster/auxprocess.c | 8 +
src/backend/postmaster/postmaster.c | 45 ++++
src/backend/postmaster/wal_allocator.c | 315 ++++++++++++++++++++++++++
src/backend/replication/basebackup.c | 6 +-
src/backend/storage/file/fd.c | 115 ++++++++++
src/backend/storage/lmgr/lwlocknames.txt | 1 +
src/backend/storage/lmgr/proc.c | 1 +
src/backend/utils/activity/wait_event.c | 3 +
src/backend/utils/init/miscinit.c | 3 +
src/backend/utils/misc/guc.c | 12 +
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/bin/initdb/initdb.c | 1 +
src/bin/pg_basebackup/pg_basebackup.c | 19 +-
src/bin/pg_basebackup/t/010_pg_basebackup.pl | 4 +-
src/bin/pg_resetwal/pg_resetwal.c | 48 ++++
src/include/access/xlog.h | 5 +
src/include/miscadmin.h | 3 +
src/include/postmaster/wal_allocator.h | 21 ++
src/include/storage/fd.h | 1 +
src/include/storage/proc.h | 11 +-
src/include/utils/wait_event.h | 3 +-
24 files changed, 765 insertions(+), 111 deletions(-)
create mode 100644 src/backend/postmaster/wal_allocator.c
create mode 100644 src/include/postmaster/wal_allocator.h
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 2c31c35a6b..92680b73a7 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -3203,6 +3203,29 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-wal-allocator-max-size" xreflabel="wal_allocator_max_size">
+ <term><varname>wal_allocator_max_size</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>wal_allocator_max_size</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ The maximum amount of WAL to pre-allocate for use when a new WAL segment
+ must be initialized. Setting this parameter to a size less than the WAL
+ segment size disables this behavior. The default values is 64 megabytes
+ (<literal>64MB</literal>). This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command line.
+ </para>
+
+ <para>
+ WAL pre-allocation may improve performance in scenarios where new WAL
+ segments must be created (e.g., during checkpoints when WAL segments
+ cannot be recycled).
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-wal-buffers" xreflabel="wal_buffers">
<term><varname>wal_buffers</varname> (<type>integer</type>)
<indexterm>
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 24165ab03e..947d27582d 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -51,6 +51,7 @@
#include "port/pg_iovec.h"
#include "postmaster/bgwriter.h"
#include "postmaster/startup.h"
+#include "postmaster/wal_allocator.h"
#include "postmaster/walwriter.h"
#include "replication/basebackup.h"
#include "replication/logical.h"
@@ -723,6 +724,13 @@ typedef struct XLogCtlData
*/
XLogRecPtr lastFpwDisableRecPtr;
+ /*
+ * number of pre-allocated WAL segments in pg_wal/preallocated_segments
+ *
+ * Protected by WALPreallocationLock.
+ */
+ int num_prealloc_segs;
+
slock_t info_lck; /* locks shared variables shown above */
/*
@@ -3311,11 +3319,10 @@ static int
XLogFileInitInternal(XLogSegNo logsegno, bool *added, char *path)
{
char tmppath[MAXPGPATH];
- PGAlignedXLogBlock zbuffer;
XLogSegNo installed_segno;
XLogSegNo max_segno;
int fd;
- int save_errno;
+ bool found = false;
XLogFilePath(path, ThisTimeLineID, logsegno, wal_segment_size);
@@ -3335,115 +3342,45 @@ XLogFileInitInternal(XLogSegNo logsegno, bool *added, char *path)
return fd;
/*
- * Initialize an empty (all zeroes) segment. NOTE: it is possible that
- * another process is doing the same thing. If so, we will end up
- * pre-creating an extra log segment. That seems OK, and better than
- * holding the lock throughout this lengthy process.
+ * Try to use a pre-allocated segment, if one exists. If none are
+ * available, we fall back to creating a new segment on our own.
+ *
+ * Note that we still look for a pre-allocated segment even if the pre-
+ * allocation functionality is disabled via the GUCs. This ensures that any
+ * pre-allocated segments left over after turning off the pre-allocation
+ * functionality are eventually used up.
*/
- elog(DEBUG2, "creating and filling new WAL file");
-
- snprintf(tmppath, MAXPGPATH, XLOGDIR "/xlogtemp.%d", (int) getpid());
-
- unlink(tmppath);
-
- /* do not use get_sync_bit() here --- want to fsync only at end of fill */
- fd = BasicOpenFile(tmppath, O_RDWR | O_CREAT | O_EXCL | PG_BINARY);
- if (fd < 0)
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not create file \"%s\": %m", tmppath)));
-
- memset(zbuffer.data, 0, XLOG_BLCKSZ);
-
- pgstat_report_wait_start(WAIT_EVENT_WAL_INIT_WRITE);
- save_errno = 0;
- if (wal_init_zero)
+ LWLockAcquire(WALPreallocationLock, LW_EXCLUSIVE);
+ if (XLogCtl->num_prealloc_segs > 0)
{
- struct iovec iov[PG_IOV_MAX];
- int blocks;
-
- /*
- * Zero-fill the file. With this setting, we do this the hard way to
- * ensure that all the file space has really been allocated. On
- * platforms that allow "holes" in files, just seeking to the end
- * doesn't allocate intermediate space. This way, we know that we
- * have all the space and (after the fsync below) that all the
- * indirect blocks are down on disk. Therefore, fdatasync(2) or
- * O_DSYNC will be sufficient to sync future writes to the log file.
- */
-
- /* Prepare to write out a lot of copies of our zero buffer at once. */
- for (int i = 0; i < lengthof(iov); ++i)
- {
- iov[i].iov_base = zbuffer.data;
- iov[i].iov_len = XLOG_BLCKSZ;
- }
-
- /* Loop, writing as many blocks as we can for each system call. */
- blocks = wal_segment_size / XLOG_BLCKSZ;
- for (int i = 0; i < blocks;)
- {
- int iovcnt = Min(blocks - i, lengthof(iov));
- off_t offset = i * XLOG_BLCKSZ;
-
- if (pg_pwritev_with_retry(fd, iov, iovcnt, offset) < 0)
- {
- save_errno = errno;
- break;
- }
+ elog(DEBUG2, "using pre-allocated WAL file");
- i += iovcnt;
- }
+ found = true;
+ XLogCtl->num_prealloc_segs--;
+ snprintf(tmppath, MAXPGPATH, "%s/preallocated_segments/xlogtemp.%d",
+ XLOGDIR, XLogCtl->num_prealloc_segs);
}
- else
+
+ if (!found)
{
/*
- * Otherwise, seeking to the end and writing a solitary byte is
- * enough.
+ * We're not using a pre-allocated segment, so there's no need to keep
+ * holding the WALPreallocationLock.
*/
- errno = 0;
- if (pg_pwrite(fd, zbuffer.data, 1, wal_segment_size - 1) != 1)
- {
- /* if write didn't set errno, assume no disk space */
- save_errno = errno ? errno : ENOSPC;
- }
- }
- pgstat_report_wait_end();
+ LWLockRelease(WALPreallocationLock);
- if (save_errno)
- {
/*
- * If we fail to make the file, delete it to release disk space
+ * Initialize an empty (all zeroes) segment. NOTE: it is possible that
+ * another process is doing the same thing. If so, we will end up
+ * pre-creating an extra log segment. That seems OK, and better than
+ * holding the lock throughout this lengthy process.
*/
- unlink(tmppath);
-
- close(fd);
+ elog(DEBUG2, "creating and filling new WAL file");
- errno = save_errno;
-
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not write to file \"%s\": %m", tmppath)));
+ snprintf(tmppath, MAXPGPATH, XLOGDIR "/xlogtemp.%d", (int) getpid());
+ CreateEmptyWalSegment(tmppath);
}
- pgstat_report_wait_start(WAIT_EVENT_WAL_INIT_SYNC);
- if (pg_fsync(fd) != 0)
- {
- int save_errno = errno;
-
- close(fd);
- errno = save_errno;
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not fsync file \"%s\": %m", tmppath)));
- }
- pgstat_report_wait_end();
-
- if (close(fd) != 0)
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not close file \"%s\": %m", tmppath)));
-
/*
* Now move the segment into place with its final name. Cope with
* possibility that someone else has created the file while we were
@@ -3464,7 +3401,7 @@ XLogFileInitInternal(XLogSegNo logsegno, bool *added, char *path)
if (InstallXLogFileSegment(&installed_segno, tmppath, true, max_segno))
{
*added = true;
- elog(DEBUG2, "done creating and filling new WAL file");
+ elog(DEBUG2, "done installing new WAL file");
}
else
{
@@ -3477,6 +3414,18 @@ XLogFileInitInternal(XLogSegNo logsegno, bool *added, char *path)
elog(DEBUG2, "abandoned new WAL file");
}
+ /*
+ * If we are using a pre-allocated segment, we've been holding onto the
+ * WALPreallocationLock all this time so that the WAL Allocator process
+ * can't overwrite the file before we've installed it.
+ *
+ * While we're at it, also try to wake up the WAL allocator early so that it
+ * can get a jump start on allocating new segments if possible.
+ */
+ if (found)
+ LWLockRelease(WALPreallocationLock);
+ RequestWalPreallocation();
+
return -1;
}
@@ -4333,8 +4282,8 @@ RemoveXlogFile(const char *segname, XLogSegNo recycleSegNo,
}
/*
- * Verify whether pg_wal and pg_wal/archive_status exist.
- * If the latter does not exist, recreate it.
+ * Verify whether pg_wal, pg_wal/archive_status and pg_wal/preallocated_segments
+ * exist. If either of the latter two do not exist, recreate it.
*
* It is not the goal of this function to verify the contents of these
* directories, but to help in cases where someone has performed a cluster
@@ -4377,6 +4326,26 @@ ValidateXLOGDirectoryStructure(void)
(errmsg("could not create missing directory \"%s\": %m",
path)));
}
+
+ /* Check for preallocated_segments */
+ snprintf(path, MAXPGPATH, XLOGDIR "/preallocated_segments");
+ if (stat(path, &stat_buf) == 0)
+ {
+ /* Check for weird cases where it exists but isn't a directory */
+ if (!S_ISDIR(stat_buf.st_mode))
+ ereport(FATAL,
+ (errmsg("required WAL directory \"%s\" does not exist",
+ path)));
+ }
+ else
+ {
+ ereport(LOG,
+ (errmsg("creating missing WAL directory \"%s\"", path)));
+ if (MakePGDirectory(path) < 0)
+ ereport(FATAL,
+ (errmsg("could not create missing directory \"%s\": %m",
+ path)));
+ }
}
/*
@@ -5417,6 +5386,9 @@ XLOGShmemInit(void)
XLogCtl->earliestSegBoundaryEndPtr = InvalidXLogRecPtr;
XLogCtl->latestSegBoundary = MaxXLogSegNo;
XLogCtl->latestSegBoundaryEndPtr = InvalidXLogRecPtr;
+
+ /* protected by WALPreallocationLock */
+ XLogCtl->num_prealloc_segs = 0;
}
/*
@@ -13191,3 +13163,55 @@ XLogRequestWalReceiverReply(void)
{
doRequestWalReceiverReply = true;
}
+
+int
+GetNumPreallocatedWalSegs(void)
+{
+ int ret;
+
+ LWLockAcquire(WALPreallocationLock, LW_SHARED);
+ ret = XLogCtl->num_prealloc_segs;
+ LWLockRelease(WALPreallocationLock);
+
+ return ret;
+}
+
+void
+SetNumPreallocatedWalSegs(int i)
+{
+ LWLockAcquire(WALPreallocationLock, LW_EXCLUSIVE);
+ XLogCtl->num_prealloc_segs = i;
+ LWLockRelease(WALPreallocationLock);
+}
+
+/*
+ * InstallPreallocatedWalSeg
+ *
+ * Renames the file at "path" to the next open pre-allocated segment slot and
+ * bumps up XLogCtl->num_prealloc_segs.
+ */
+void
+InstallPreallocatedWalSeg(const char *path)
+{
+ char newpath[MAXPGPATH];
+
+ LWLockAcquire(WALPreallocationLock, LW_EXCLUSIVE);
+
+ snprintf(newpath, MAXPGPATH, "%s/preallocated_segments/xlogtemp.%d",
+ XLOGDIR, XLogCtl->num_prealloc_segs);
+ (void) durable_rename(path, newpath, ERROR);
+ XLogCtl->num_prealloc_segs++;
+
+ LWLockRelease(WALPreallocationLock);
+}
+
+/*
+ * Request that the WAL allocator wake up early.
+ */
+void
+RequestWalPreallocation(void)
+{
+ if (ProcGlobal->walAllocatorLatch &&
+ WalPreallocationEnabled())
+ SetLatch(ProcGlobal->walAllocatorLatch);
+}
diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile
index 787c6a2c3b..d69ada0c4e 100644
--- a/src/backend/postmaster/Makefile
+++ b/src/backend/postmaster/Makefile
@@ -25,6 +25,7 @@ OBJS = \
postmaster.o \
startup.o \
syslogger.o \
+ wal_allocator.o \
walwriter.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/postmaster/auxprocess.c b/src/backend/postmaster/auxprocess.c
index 7452f908b2..83fbebc4d8 100644
--- a/src/backend/postmaster/auxprocess.c
+++ b/src/backend/postmaster/auxprocess.c
@@ -21,6 +21,7 @@
#include "postmaster/auxprocess.h"
#include "postmaster/bgwriter.h"
#include "postmaster/startup.h"
+#include "postmaster/wal_allocator.h"
#include "postmaster/walwriter.h"
#include "replication/walreceiver.h"
#include "storage/bufmgr.h"
@@ -80,6 +81,9 @@ AuxiliaryProcessMain(AuxProcType auxtype)
case WalReceiverProcess:
MyBackendType = B_WAL_RECEIVER;
break;
+ case WalAllocatorProcess:
+ MyBackendType = B_WAL_ALLOCATOR;
+ break;
default:
elog(ERROR, "something has gone wrong");
MyBackendType = B_INVALID;
@@ -162,6 +166,10 @@ AuxiliaryProcessMain(AuxProcType auxtype)
WalReceiverMain();
proc_exit(1);
+ case WalAllocatorProcess:
+ WalAllocatorMain();
+ proc_exit(1);
+
default:
elog(PANIC, "unrecognized process type: %d", (int) MyAuxProcType);
proc_exit(1);
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 9c2c98614a..5ed6561b66 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -251,6 +251,7 @@ static pid_t StartupPID = 0,
CheckpointerPID = 0,
WalWriterPID = 0,
WalReceiverPID = 0,
+ WalAllocatorPID = 0,
AutoVacPID = 0,
PgArchPID = 0,
PgStatPID = 0,
@@ -557,6 +558,7 @@ static void ShmemBackendArrayRemove(Backend *bn);
#define StartCheckpointer() StartChildProcess(CheckpointerProcess)
#define StartWalWriter() StartChildProcess(WalWriterProcess)
#define StartWalReceiver() StartChildProcess(WalReceiverProcess)
+#define StartWalAllocator() StartChildProcess(WalAllocatorProcess)
/* Macros to check exit status of a child process */
#define EXIT_STATUS_0(st) ((st) == 0)
@@ -1794,6 +1796,13 @@ ServerLoop(void)
if (WalWriterPID == 0 && pmState == PM_RUN)
WalWriterPID = StartWalWriter();
+ /*
+ * If we have lost the WAL allocator process, try to start a new one.
+ */
+ if (WalAllocatorPID == 0 &&
+ (pmState == PM_RUN || pmState == PM_HOT_STANDBY))
+ WalAllocatorPID = StartWalAllocator();
+
/*
* If we have lost the autovacuum launcher, try to start a new one. We
* don't want autovacuum to run in binary upgrade mode because
@@ -2709,6 +2718,8 @@ SIGHUP_handler(SIGNAL_ARGS)
signal_child(WalWriterPID, SIGHUP);
if (WalReceiverPID != 0)
signal_child(WalReceiverPID, SIGHUP);
+ if (WalAllocatorPID != 0)
+ signal_child(WalAllocatorPID, SIGHUP);
if (AutoVacPID != 0)
signal_child(AutoVacPID, SIGHUP);
if (PgArchPID != 0)
@@ -3036,6 +3047,8 @@ reaper(SIGNAL_ARGS)
BgWriterPID = StartBackgroundWriter();
if (WalWriterPID == 0)
WalWriterPID = StartWalWriter();
+ if (WalAllocatorPID == 0)
+ WalAllocatorPID = StartWalAllocator();
/*
* Likewise, start other special children as needed. In a restart
@@ -3163,6 +3176,20 @@ reaper(SIGNAL_ARGS)
continue;
}
+ /*
+ * Was it the WAL allocator? Normal exit can be ignored; we'll start a
+ * new one at the next iteration of the postmaster's main loop, if
+ * necessary. Any other exit condition is treated as a crash.
+ */
+ if (pid == WalAllocatorPID)
+ {
+ WalAllocatorPID = 0;
+ if (!EXIT_STATUS_0(exitstatus))
+ HandleChildCrash(pid, exitstatus,
+ _("WAL Allocator process"));
+ continue;
+ }
+
/*
* Was it the autovacuum launcher? Normal exit can be ignored; we'll
* start a new one at the next iteration of the postmaster's main
@@ -3631,6 +3658,18 @@ HandleChildCrash(int pid, int exitstatus, const char *procname)
signal_child(WalReceiverPID, (SendStop ? SIGSTOP : SIGQUIT));
}
+ /* Take care of the WAL allocator too */
+ if (pid == WalAllocatorPID)
+ WalAllocatorPID = 0;
+ else if (WalAllocatorPID != 0 && take_action)
+ {
+ ereport(DEBUG2,
+ (errmsg_internal("sending %s to process %d",
+ (SendStop ? "SIGSTOP" : "SIGQUIT"),
+ (int) WalAllocatorPID)));
+ signal_child(WalAllocatorPID, (SendStop ? SIGSTOP : SIGQUIT));
+ }
+
/* Take care of the autovacuum launcher too */
if (pid == AutoVacPID)
AutoVacPID = 0;
@@ -3818,6 +3857,8 @@ PostmasterStateMachine(void)
signal_child(StartupPID, SIGTERM);
if (WalReceiverPID != 0)
signal_child(WalReceiverPID, SIGTERM);
+ if (WalAllocatorPID != 0)
+ signal_child(WalAllocatorPID, SIGTERM);
/* checkpointer, archiver, stats, and syslogger may continue for now */
/* Now transition to PM_WAIT_BACKENDS state to wait for them to die */
@@ -3848,6 +3889,7 @@ PostmasterStateMachine(void)
(CheckpointerPID == 0 ||
(!FatalError && Shutdown < ImmediateShutdown)) &&
WalWriterPID == 0 &&
+ WalAllocatorPID == 0 &&
AutoVacPID == 0)
{
if (Shutdown >= ImmediateShutdown || FatalError)
@@ -3941,6 +3983,7 @@ PostmasterStateMachine(void)
Assert(BgWriterPID == 0);
Assert(CheckpointerPID == 0);
Assert(WalWriterPID == 0);
+ Assert(WalAllocatorPID == 0);
Assert(AutoVacPID == 0);
/* syslogger is not considered here */
pmState = PM_NO_CHILDREN;
@@ -4149,6 +4192,8 @@ TerminateChildren(int signal)
signal_child(WalWriterPID, signal);
if (WalReceiverPID != 0)
signal_child(WalReceiverPID, signal);
+ if (WalAllocatorPID != 0)
+ signal_child(WalAllocatorPID, signal);
if (AutoVacPID != 0)
signal_child(AutoVacPID, signal);
if (PgArchPID != 0)
diff --git a/src/backend/postmaster/wal_allocator.c b/src/backend/postmaster/wal_allocator.c
new file mode 100644
index 0000000000..38cebf7953
--- /dev/null
+++ b/src/backend/postmaster/wal_allocator.c
@@ -0,0 +1,315 @@
+/*-------------------------------------------------------------------------
+ *
+ * wal_allocator.c
+ *
+ * The WAL allocator is new as of Postgres 15. It attempts to keep regular
+ * backends from having to allocate new WAL segments. Even when
+ * wal_recycle is enabled, pre-allocating WAL segments can yield
+ * significant performance improvements in certain scenarios. Note that
+ * regular backends are still empowered to create new WAL segments if the
+ * WAL allocator fails to generate enough new segments.
+ *
+ * The WAL allocator is controlled by one parameter: wal_allocator_max_size.
+ * wal_allocator_max_size specifies the maximum amount of WAL to pre-
+ * allocate. If this value is not divisible by the WAL segment size, fewer
+ * WAL segments will be pre-allocated.
+ *
+ * The WAL allocator is started by the postmaster as soon as the startup
+ * subprocess finishes, or as soon as recovery begins if we are doing
+ * archive recovery. It remains alive until the postmaster commands it to
+ * terminate. Normal termination is by SIGTERM, which instructs the
+ * WAL allocator to exit(0). Emergency termination is by SIGQUIT; like any
+ * backend, the WAL allocator will simply abort and exit on SIGQUIT.
+ *
+ * If the WAL allocator exits unexpectedly, the postmaster treats that the
+ * same as a backend crash: shared memory may be corrupted, so remaining
+ * backends should be killed by SIGQUIT and then a recovery cycle started.
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/postmaster/wal_allocator.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <sys/time.h>
+
+#include "access/xlog.h"
+#include "access/xlog_internal.h"
+#include "libpq/pqsignal.h"
+#include "pgstat.h"
+#include "postmaster/interrupt.h"
+#include "postmaster/wal_allocator.h"
+#include "storage/bufmgr.h"
+#include "storage/condition_variable.h"
+#include "storage/fd.h"
+#include "storage/ipc.h"
+#include "storage/lwlock.h"
+#include "storage/proc.h"
+#include "storage/procsignal.h"
+#include "storage/shmem.h"
+#include "storage/smgr.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+#define WAL_ALLOC_TIMEOUT_S (60)
+
+/*
+ * GUC parameters
+ */
+int wal_alloc_max_size_mb = 64;
+
+static void DoWalPreAllocation(void);
+static void ScanForExistingPreallocatedSegments(void);
+
+/*
+ * Main entry point for WAL allocator process
+ *
+ * This is invoked from AuxiliaryProcessMain, which has already created the
+ * basic execution environment, but not enabled signals yet.
+ */
+void
+WalAllocatorMain(void)
+{
+ sigjmp_buf local_sigjmp_buf;
+ MemoryContext wal_alloc_context;
+
+ /*
+ * Properly accept or ignore signals that might be sent to us.
+ */
+ pqsignal(SIGHUP, SignalHandlerForConfigReload);
+ pqsignal(SIGINT, SIG_IGN);
+ pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
+ /* SIGQUIT handler was already set up by InitPostmasterChild */
+ pqsignal(SIGALRM, SIG_IGN);
+ pqsignal(SIGPIPE, SIG_IGN);
+ pqsignal(SIGUSR1, procsignal_sigusr1_handler);
+ pqsignal(SIGUSR2, SIG_IGN);
+
+ /*
+ * Reset some signals that are accepted by postmaster but not here
+ */
+ pqsignal(SIGCHLD, SIG_DFL);
+
+ /*
+ * Create a memory context that we will do all our work in. We do this so
+ * that we can reset the context during error recovery and thereby avoid
+ * possible memory leaks.
+ */
+ wal_alloc_context = AllocSetContextCreate(TopMemoryContext,
+ "WAL Allocator",
+ ALLOCSET_DEFAULT_SIZES);
+ MemoryContextSwitchTo(wal_alloc_context);
+
+ /*
+ * If an exception is encountered, processing resumes here.
+ *
+ * You might wonder why this isn't coded as an infinite loop around a
+ * PG_TRY construct. The reason is that this is the bottom of the
+ * exception stack, and so with PG_TRY there would be no exception handler
+ * in force at all during the CATCH part. By leaving the outermost setjmp
+ * always active, we have at least some chance of recovering from an error
+ * during error recovery. (If we get into an infinite loop thereby, it
+ * will soon be stopped by overflow of elog.c's internal state stack.)
+ *
+ * Note that we use sigsetjmp(..., 1), so that the prevailing signal mask
+ * (to wit, BlockSig) will be restored when longjmp'ing to here. Thus,
+ * signals other than SIGQUIT will be blocked until we complete error
+ * recovery. It might seem that this policy makes the HOLD_INTERRUPTS()
+ * call redundant, but it is not since InterruptPending might be set
+ * already.
+ */
+ if (sigsetjmp(local_sigjmp_buf, 1) != 0)
+ {
+ /* Since not using PG_TRY, must reset error stack by hand */
+ error_context_stack = NULL;
+
+ /* Prevent interrupts while cleaning up */
+ HOLD_INTERRUPTS();
+
+ /* Report the error to the server log */
+ EmitErrorReport();
+
+ /*
+ * These operations are really just a minimal subset of
+ * AbortTransaction(). We don't have very many resources to worry
+ * about.
+ */
+ LWLockReleaseAll();
+ ConditionVariableCancelSleep();
+ pgstat_report_wait_end();
+ AbortBufferIO();
+ UnlockBuffers();
+ ReleaseAuxProcessResources(false);
+ AtEOXact_Buffers(false);
+ AtEOXact_SMgr();
+ AtEOXact_Files(false);
+ AtEOXact_HashTables(false);
+
+ /*
+ * Now return to normal top-level context and clear ErrorContext for
+ * next time.
+ */
+ MemoryContextSwitchTo(wal_alloc_context);
+ FlushErrorState();
+
+ /* Flush any leaked data in the top-level context */
+ MemoryContextResetAndDeleteChildren(wal_alloc_context);
+
+ /* Now we can allow interrupts again */
+ RESUME_INTERRUPTS();
+
+ /*
+ * Sleep at least 1 second after any error. A write error is likely
+ * to be repeated, and we don't want to be filling the error logs as
+ * fast as we can.
+ */
+ pg_usleep(1000000L);
+
+ /*
+ * Close all open files after any error. This is helpful on Windows,
+ * where holding deleted files open causes various strange errors.
+ * It's not clear we need it elsewhere, but shouldn't hurt.
+ */
+ smgrcloseall();
+
+ /* Report wait end here, when there is no further possibility of wait */
+ pgstat_report_wait_end();
+ }
+
+ /* We can now handle ereport(ERROR) */
+ PG_exception_stack = &local_sigjmp_buf;
+
+ /*
+ * Unblock signals (they were blocked when the postmaster forked us)
+ */
+ PG_SETMASK(&UnBlockSig);
+
+ /*
+ * Advertise our latch that backends can use to wake us up while we're
+ * sleeping.
+ */
+ ProcGlobal->walAllocatorLatch = &MyProc->procLatch;
+
+ /*
+ * Before we go into the main loop, scan the pre-allocated segments
+ * directory and look for anything that was left over from the last time.
+ */
+ ScanForExistingPreallocatedSegments();
+
+ /*
+ * Loop forever
+ */
+ for (;;)
+ {
+ /* Clear any already-pending wakeups */
+ ResetLatch(MyLatch);
+
+ HandleMainLoopInterrupts();
+
+ DoWalPreAllocation();
+
+ /* XXX: Send activity statistics to the stats collector */
+
+ /* Sleep until we are signaled or WAL_ALLOC_TIMEOUT_S has elapsed. */
+ (void) WaitLatch(MyLatch,
+ WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+ WAL_ALLOC_TIMEOUT_S * 1000L /* convert to ms */,
+ WAIT_EVENT_WAL_ALLOCATOR_MAIN);
+ }
+
+ pg_unreachable();
+}
+
+/*
+ * DoWalPreAllocation
+ *
+ * Tries to allocate up to wal_allocator_max_size worth of WAL.
+ */
+static void
+DoWalPreAllocation(void)
+{
+ int max_prealloc_segs;
+
+ max_prealloc_segs = wal_alloc_max_size_mb / (wal_segment_size / (1024 * 1024));
+
+ while (!ShutdownRequestPending &&
+ GetNumPreallocatedWalSegs() < max_prealloc_segs)
+ {
+ char tmppath[MAXPGPATH];
+
+ snprintf(tmppath, MAXPGPATH, "%s/preallocated_segments/xlogtemp", XLOGDIR);
+ CreateEmptyWalSegment(tmppath);
+ InstallPreallocatedWalSeg(tmppath);
+ elog(DEBUG2, "pre-allocated WAL segment");
+
+ if (ConfigReloadPending)
+ {
+ ConfigReloadPending = false;
+ ProcessConfigFile(PGC_SIGHUP);
+
+ max_prealloc_segs = wal_alloc_max_size_mb / (wal_segment_size / (1024 * 1024));
+ }
+ }
+}
+
+/*
+ * ScanForExistingPreallocatedSegments
+ *
+ * This function searches through pg_wal/preallocated_segments for any segments
+ * that were left over from a previous WAL allocator process and sets the
+ * tracking variable in shared memory accordingly.
+ */
+static void
+ScanForExistingPreallocatedSegments(void)
+{
+ int i = 0;
+
+ /*
+ * fsync the preallocated_segments directory in case any renames have yet to
+ * be flushed to disk. This is probably not really necessary, but it seems
+ * nice to know that all the segments we find are really where we think they
+ * are.
+ */
+ fsync_fname_ext(XLOGDIR "/preallocated_segments", true, false, FATAL);
+
+ /*
+ * Gather all the preallocated segments we can find.
+ */
+ while (true)
+ {
+ FILE *fd;
+ char path[MAXPGPATH];
+
+ snprintf(path, MAXPGPATH, "%s/preallocated_segments/xlogtemp.%d",
+ XLOGDIR, i);
+
+ fd = AllocateFile(path, "r");
+ if (fd != NULL)
+ {
+ FreeFile(fd);
+ i++;
+ }
+ else
+ {
+ if (errno != ENOENT)
+ ereport(FATAL,
+ (errcode_for_file_access(),
+ errmsg("could not open file \"%s\": %m", path)));
+ break;
+ }
+ }
+
+ SetNumPreallocatedWalSegs(i);
+ elog(DEBUG2, "found %d preallocated segments during startup", i);
+}
+
+bool
+WalPreallocationEnabled(void)
+{
+ return wal_alloc_max_size_mb >= wal_segment_size / (1024 * 1024);
+}
diff --git a/src/backend/replication/basebackup.c b/src/backend/replication/basebackup.c
index e09108d0ec..c9a91dc938 100644
--- a/src/backend/replication/basebackup.c
+++ b/src/backend/replication/basebackup.c
@@ -1399,11 +1399,13 @@ sendDir(const char *path, int basepathlen, bool sizeonly, List *tablespaces,
size += _tarWriteDir(pathbuf, basepathlen, &statbuf, sizeonly);
/*
- * Also send archive_status directory (by hackishly reusing
- * statbuf from above ...).
+ * Also send archive_status and preallocated_segments (by hackishly
+ * reusing statbuf from above ...).
*/
size += _tarWriteHeader("./pg_wal/archive_status", NULL, &statbuf,
sizeonly);
+ size += _tarWriteHeader("./pg_wal/preallocated_segments", NULL,
+ &statbuf, sizeonly);
continue; /* don't recurse into pg_wal */
}
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index 433e2832a5..0e8b6eaf15 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -76,6 +76,7 @@
#include <sys/file.h>
#include <sys/param.h>
#include <sys/stat.h>
+#include <sys/time.h>
#include <sys/types.h>
#ifndef WIN32
#include <sys/mman.h>
@@ -3868,3 +3869,117 @@ pg_pwritev_with_retry(int fd, const struct iovec *iov, int iovcnt, off_t offset)
return sum;
}
+
+/*
+ * CreateEmptyWalSegment
+ *
+ * Create a new file that can be used as a new WAL segment. The caller is
+ * responsible for installing the new file in pg_wal.
+ */
+void
+CreateEmptyWalSegment(const char *path)
+{
+ PGAlignedXLogBlock zbuffer;
+ int fd;
+ int save_errno;
+
+ unlink(path);
+
+ /* do not use get_sync_bit() here --- want to fsync only at end of fill */
+ fd = BasicOpenFile(path, O_RDWR | O_CREAT | O_EXCL | PG_BINARY);
+ if (fd < 0)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not create file \"%s\": %m", path)));
+
+ memset(zbuffer.data, 0, XLOG_BLCKSZ);
+
+ pgstat_report_wait_start(WAIT_EVENT_WAL_INIT_WRITE);
+ save_errno = 0;
+ if (wal_init_zero)
+ {
+ struct iovec iov[PG_IOV_MAX];
+ int blocks;
+
+ /*
+ * Zero-fill the file. With this setting, we do this the hard way to
+ * ensure that all the file space has really been allocated. On
+ * platforms that allow "holes" in files, just seeking to the end
+ * doesn't allocate intermediate space. This way, we know that we
+ * have all the space and (after the fsync below) that all the
+ * indirect blocks are down on disk. Therefore, fdatasync(2) or
+ * O_DSYNC will be sufficient to sync future writes to the log file.
+ */
+
+ /* Prepare to write out a lot of copies of our zero buffer at once. */
+ for (int i = 0; i < lengthof(iov); ++i)
+ {
+ iov[i].iov_base = zbuffer.data;
+ iov[i].iov_len = XLOG_BLCKSZ;
+ }
+
+ /* Loop, writing as many blocks as we can for each system call. */
+ blocks = wal_segment_size / XLOG_BLCKSZ;
+ for (int i = 0; i < blocks;)
+ {
+ int iovcnt = Min(blocks - i, lengthof(iov));
+ off_t offset = i * XLOG_BLCKSZ;
+
+ if (pg_pwritev_with_retry(fd, iov, iovcnt, offset) < 0)
+ {
+ save_errno = errno;
+ break;
+ }
+
+ i += iovcnt;
+ }
+ }
+ else
+ {
+ /*
+ * Otherwise, seeking to the end and writing a solitary byte is
+ * enough.
+ */
+ errno = 0;
+ if (pg_pwrite(fd, zbuffer.data, 1, wal_segment_size - 1) != 1)
+ {
+ /* if write didn't set errno, assume no disk space */
+ save_errno = errno ? errno : ENOSPC;
+ }
+ }
+ pgstat_report_wait_end();
+
+ if (save_errno)
+ {
+ /*
+ * If we fail to make the file, delete it to release disk space
+ */
+ unlink(path);
+
+ close(fd);
+
+ errno = save_errno;
+
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not write to file \"%s\": %m", path)));
+ }
+
+ pgstat_report_wait_start(WAIT_EVENT_WAL_INIT_SYNC);
+ if (pg_fsync(fd) != 0)
+ {
+ int save_errno = errno;
+
+ close(fd);
+ errno = save_errno;
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not fsync file \"%s\": %m", path)));
+ }
+ pgstat_report_wait_end();
+
+ if (close(fd) != 0)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not close file \"%s\": %m", path)));
+}
diff --git a/src/backend/storage/lmgr/lwlocknames.txt b/src/backend/storage/lmgr/lwlocknames.txt
index 6c7cf6c295..212bda8e7d 100644
--- a/src/backend/storage/lmgr/lwlocknames.txt
+++ b/src/backend/storage/lmgr/lwlocknames.txt
@@ -53,3 +53,4 @@ XactTruncationLock 44
# 45 was XactTruncationLock until removal of BackendRandomLock
WrapLimitsVacuumLock 46
NotifyQueueTailLock 47
+WALPreallocationLock 48
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index b7d9da0aa9..5790d586ef 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -181,6 +181,7 @@ InitProcGlobal(void)
ProcGlobal->startupProcPid = 0;
ProcGlobal->startupBufferPinWaitBufId = -1;
ProcGlobal->walwriterLatch = NULL;
+ ProcGlobal->walAllocatorLatch = NULL;
ProcGlobal->checkpointerLatch = NULL;
pg_atomic_init_u32(&ProcGlobal->procArrayGroupFirst, INVALID_PGPROCNO);
pg_atomic_init_u32(&ProcGlobal->clogGroupFirst, INVALID_PGPROCNO);
diff --git a/src/backend/utils/activity/wait_event.c b/src/backend/utils/activity/wait_event.c
index ef7e6bfb77..a3c4324c92 100644
--- a/src/backend/utils/activity/wait_event.c
+++ b/src/backend/utils/activity/wait_event.c
@@ -248,6 +248,9 @@ pgstat_get_wait_activity(WaitEventActivity w)
case WAIT_EVENT_WAL_WRITER_MAIN:
event_name = "WalWriterMain";
break;
+ case WAIT_EVENT_WAL_ALLOCATOR_MAIN:
+ event_name = "WalAllocatorMain";
+ break;
/* no default case, so that compiler will warn */
}
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 88801374b5..9f12e18b66 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -285,6 +285,9 @@ GetBackendTypeDesc(BackendType backendType)
case B_WAL_WRITER:
backendDesc = "walwriter";
break;
+ case B_WAL_ALLOCATOR:
+ backendDesc = "wal allocator";
+ break;
case B_ARCHIVER:
backendDesc = "archiver";
break;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 467b0fd6fe..656e26fcd6 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -72,6 +72,7 @@
#include "postmaster/bgwriter.h"
#include "postmaster/postmaster.h"
#include "postmaster/syslogger.h"
+#include "postmaster/wal_allocator.h"
#include "postmaster/walwriter.h"
#include "replication/logicallauncher.h"
#include "replication/reorderbuffer.h"
@@ -2816,6 +2817,17 @@ static struct config_int ConfigureNamesInt[] =
NULL, NULL, NULL
},
+ {
+ {"wal_allocator_max_size", PGC_SIGHUP, WAL_SETTINGS,
+ gettext_noop("Sets the maximum amount of WAL to pre-allocate."),
+ NULL,
+ GUC_UNIT_MB
+ },
+ &wal_alloc_max_size_mb,
+ 64, 0, 102400,
+ NULL, NULL, NULL
+ },
+
{
{"wal_buffers", PGC_POSTMASTER, WAL_SETTINGS,
gettext_noop("Sets the number of disk-page buffers in shared memory for WAL."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 3fe9a53cb3..a517dfee6d 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -223,6 +223,7 @@
# off, pglz, lz4, or on
#wal_init_zero = on # zero-fill new WAL files
#wal_recycle = on # recycle WAL files
+#wal_allocator_max_size = 64MB # amount of WAL to pre-allocate
#wal_buffers = -1 # min 32kB, -1 sets based on shared_buffers
# (change requires restart)
#wal_writer_delay = 200ms # 1-10000 milliseconds
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index a16ad026f3..2e1c4d1e79 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -208,6 +208,7 @@ static char *extra_options = "";
static const char *const subdirs[] = {
"global",
"pg_wal/archive_status",
+ "pg_wal/preallocated_segments",
"pg_commit_ts",
"pg_dynshmem",
"pg_notify",
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index 7296eb97d0..1778c9c91b 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -680,6 +680,22 @@ StartLogStreamer(char *startpos, uint32 timeline, char *sysidentifier)
pg_log_error("could not create directory \"%s\": %m", statusdir);
exit(1);
}
+
+ /*
+ * Also create pg_wal/preallocated_segments if necessary.
+ */
+ if (PQserverVersion(conn) >= 150000)
+ {
+ char prealloc_dir[MAXPGPATH];
+
+ snprintf(prealloc_dir, sizeof(prealloc_dir), "%s/pg_wal/preallocated_segments",
+ basedir);
+ if (pg_mkdir_p(prealloc_dir, pg_dir_create_mode) != 0 && errno != EEXIST)
+ {
+ pg_log_error("could not create directory \"%s\": %m", prealloc_dir);
+ exit(1);
+ }
+ }
}
/*
@@ -1616,7 +1632,8 @@ ReceiveTarAndUnpackCopyChunk(size_t r, char *copybuf, void *callback_data)
*/
if (!((pg_str_endswith(state->filename, "/pg_wal") ||
pg_str_endswith(state->filename, "/pg_xlog") ||
- pg_str_endswith(state->filename, "/archive_status")) &&
+ pg_str_endswith(state->filename, "/archive_status") ||
+ pg_str_endswith(state->filename, "/preallocated_segments")) &&
errno == EEXIST))
{
pg_log_error("could not create directory \"%s\": %m",
diff --git a/src/bin/pg_basebackup/t/010_pg_basebackup.pl b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
index a2cb2a7679..43e1e3e89d 100644
--- a/src/bin/pg_basebackup/t/010_pg_basebackup.pl
+++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
@@ -120,10 +120,10 @@ SKIP:
"check backup dir permissions");
}
-# Only archive_status directory should be copied in pg_wal/.
+# Only archive_status and preallocated_segments directories should be copied in pg_wal/.
is_deeply(
[ sort(slurp_dir("$tempdir/backup/pg_wal/")) ],
- [ sort qw(. .. archive_status) ],
+ [ sort qw(. .. archive_status preallocated_segments) ],
'no WAL files copied');
# Contents of these directories should not be copied.
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 84b6f70ad6..234a867895 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -85,6 +85,7 @@ static void RewriteControlFile(void);
static void FindEndOfXLOG(void);
static void KillExistingXLOG(void);
static void KillExistingArchiveStatus(void);
+static void KillExistingPreallocatedSegments(void);
static void WriteEmptyXLOG(void);
static void usage(void);
@@ -530,6 +531,7 @@ main(int argc, char *argv[])
RewriteControlFile();
KillExistingXLOG();
KillExistingArchiveStatus();
+ KillExistingPreallocatedSegments();
WriteEmptyXLOG();
printf(_("Write-ahead log reset\n"));
@@ -1070,6 +1072,52 @@ KillExistingXLOG(void)
}
+/*
+ * Remove existing preallocated segments
+ */
+static void
+KillExistingPreallocatedSegments(void)
+{
+#define PREALLOCSEGDIR XLOGDIR "/preallocated_segments"
+
+ DIR *xldir;
+ struct dirent *xlde;
+ char path[MAXPGPATH + sizeof(PREALLOCSEGDIR)];
+
+ xldir = opendir(PREALLOCSEGDIR);
+ if (xldir == NULL)
+ {
+ pg_log_error("could not open directory \"%s\": %m", PREALLOCSEGDIR);
+ exit(1);
+ }
+
+ while (errno = 0, (xlde = readdir(xldir)) != NULL)
+ {
+ if (strncmp(xlde->d_name, "xlogtemp", strlen("xlogtemp")) == 0)
+ {
+ snprintf(path, sizeof(path), "%s/%s", PREALLOCSEGDIR, xlde->d_name);
+ if (unlink(path) < 0)
+ {
+ pg_log_error("could not delete file \"%s\": %m", path);
+ exit(1);
+ }
+ }
+ }
+
+ if (errno)
+ {
+ pg_log_error("could not read directory \"%s\": %m", PREALLOCSEGDIR);
+ exit(1);
+ }
+
+ if (closedir(xldir))
+ {
+ pg_log_error("could not close directory \"%s\": %m", PREALLOCSEGDIR);
+ exit(1);
+ }
+}
+
+
/*
* Remove existing archive status files
*/
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index 6b6ae81c2d..e23cdb5d6b 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -328,6 +328,11 @@ extern void XLogRequestWalReceiverReply(void);
extern void assign_max_wal_size(int newval, void *extra);
extern void assign_checkpoint_completion_target(double newval, void *extra);
+extern int GetNumPreallocatedWalSegs(void);
+extern void InstallPreallocatedWalSeg(const char *path);
+extern void SetNumPreallocatedWalSegs(int i);
+extern void RequestWalPreallocation(void);
+
/*
* Routines to start, stop, and get status of a base backup.
*/
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 2e2e9a364a..e10d37bc00 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -333,6 +333,7 @@ typedef enum BackendType
B_WAL_RECEIVER,
B_WAL_SENDER,
B_WAL_WRITER,
+ B_WAL_ALLOCATOR,
B_ARCHIVER,
B_STATS_COLLECTOR,
B_LOGGER,
@@ -433,6 +434,7 @@ typedef enum
CheckpointerProcess,
WalWriterProcess,
WalReceiverProcess,
+ WalAllocatorProcess,
NUM_AUXPROCTYPES /* Must be last! */
} AuxProcType;
@@ -445,6 +447,7 @@ extern AuxProcType MyAuxProcType;
#define AmCheckpointerProcess() (MyAuxProcType == CheckpointerProcess)
#define AmWalWriterProcess() (MyAuxProcType == WalWriterProcess)
#define AmWalReceiverProcess() (MyAuxProcType == WalReceiverProcess)
+#define AmWalAllocatorProcess() (MyAuxProcType == WalAllocatorProcess)
/*****************************************************************************
diff --git a/src/include/postmaster/wal_allocator.h b/src/include/postmaster/wal_allocator.h
new file mode 100644
index 0000000000..c7e139226b
--- /dev/null
+++ b/src/include/postmaster/wal_allocator.h
@@ -0,0 +1,21 @@
+/*-------------------------------------------------------------------------
+ *
+ * wal_allocator.h
+ * Exports from postmaster/wal_allocator.c.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ *
+ * src/include/postmaster/wal_allocator.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _WAL_ALLOCATOR_H
+#define _WAL_ALLOCATOR_H
+
+/* GUC options */
+extern int wal_alloc_max_size_mb;
+
+extern void WalAllocatorMain(void) pg_attribute_noreturn();
+extern bool WalPreallocationEnabled(void);
+
+#endif /* _WAL_ALLOCATOR_H */
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index 34602ae006..abcd74bd51 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -190,6 +190,7 @@ extern int durable_unlink(const char *fname, int loglevel);
extern int durable_rename_excl(const char *oldfile, const char *newfile, int loglevel);
extern void SyncDataDirectory(void);
extern int data_sync_elevel(int elevel);
+extern void CreateEmptyWalSegment(const char *path);
/* Filename components */
#define PG_TEMP_FILES_DIR "pgsql_tmp"
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index be67d8a861..8d0cae37b2 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -348,6 +348,8 @@ typedef struct PROC_HDR
pg_atomic_uint32 clogGroupFirst;
/* WALWriter process's latch */
Latch *walwriterLatch;
+ /* WAL Allocator process's latch */
+ Latch *walAllocatorLatch;
/* Checkpointer process's latch */
Latch *checkpointerLatch;
/* Current shared estimate of appropriate spins_per_delay value */
@@ -370,11 +372,12 @@ extern PGPROC *PreparedXactProcs;
* We set aside some extra PGPROC structures for auxiliary processes,
* ie things that aren't full-fledged backends but need shmem access.
*
- * Background writer, checkpointer, WAL writer and archiver run during normal
- * operation. Startup process and WAL receiver also consume 2 slots, but WAL
- * writer is launched only after startup has exited, so we only need 5 slots.
+ * Background writer, checkpointer, WAL writer, archiver, and WAL allocator run
+ * during normal operation. Startup process and WAL receiver also consume 2
+ * slots, but WAL writer is launched only after startup has exited, so we only
+ * need 6 slots.
*/
-#define NUM_AUXILIARY_PROCS 5
+#define NUM_AUXILIARY_PROCS 6
/* configurable options */
extern PGDLLIMPORT int DeadlockTimeout;
diff --git a/src/include/utils/wait_event.h b/src/include/utils/wait_event.h
index 6007827b44..18a05f8d59 100644
--- a/src/include/utils/wait_event.h
+++ b/src/include/utils/wait_event.h
@@ -47,7 +47,8 @@ typedef enum
WAIT_EVENT_SYSLOGGER_MAIN,
WAIT_EVENT_WAL_RECEIVER_MAIN,
WAIT_EVENT_WAL_SENDER_MAIN,
- WAIT_EVENT_WAL_WRITER_MAIN
+ WAIT_EVENT_WAL_WRITER_MAIN,
+ WAIT_EVENT_WAL_ALLOCATOR_MAIN
} WaitEventActivity;
/* ----------
--
2.16.6
^ permalink raw reply [nested|flat] 19+ messages in thread
* Re: Pre-allocating WAL files
@ 2021-09-14 20:06 Bossart, Nathan <[email protected]>
parent: vignesh C <[email protected]>
3 siblings, 1 reply; 19+ messages in thread
From: Bossart, Nathan @ 2021-09-14 20:06 UTC (permalink / raw)
To: vignesh C <[email protected]>; +Cc: Andres Freund <[email protected]>; pgsql-hackers
On 8/31/21, 10:27 AM, "Bossart, Nathan" <[email protected]> wrote:
> Rebasing again to keep http://commitfest.cputube.org/ happy.
Another rebase.
Nathan
Attachments:
[application/octet-stream] v5-0001-wal-segment-pre-allocation.patch (43.7K, ../../[email protected]/2-v5-0001-wal-segment-pre-allocation.patch)
download | inline diff:
From 8dc93ba06907c4e59fccf2db5fe5c20f1ca9ded5 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <[email protected]>
Date: Tue, 14 Sep 2021 18:44:15 +0000
Subject: [PATCH v5 1/1] wal segment pre-allocation
---
doc/src/sgml/config.sgml | 23 ++
src/backend/access/transam/xlog.c | 226 +++++++++---------
src/backend/postmaster/Makefile | 1 +
src/backend/postmaster/auxprocess.c | 8 +
src/backend/postmaster/postmaster.c | 45 ++++
src/backend/postmaster/wal_allocator.c | 315 ++++++++++++++++++++++++++
src/backend/replication/basebackup.c | 6 +-
src/backend/storage/file/fd.c | 115 ++++++++++
src/backend/storage/lmgr/lwlocknames.txt | 1 +
src/backend/storage/lmgr/proc.c | 1 +
src/backend/utils/activity/wait_event.c | 3 +
src/backend/utils/init/miscinit.c | 3 +
src/backend/utils/misc/guc.c | 12 +
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/bin/initdb/initdb.c | 1 +
src/bin/pg_basebackup/pg_basebackup.c | 19 +-
src/bin/pg_basebackup/t/010_pg_basebackup.pl | 4 +-
src/bin/pg_resetwal/pg_resetwal.c | 48 ++++
src/include/access/xlog.h | 5 +
src/include/miscadmin.h | 3 +
src/include/postmaster/wal_allocator.h | 21 ++
src/include/storage/fd.h | 1 +
src/include/storage/proc.h | 11 +-
src/include/utils/wait_event.h | 3 +-
24 files changed, 765 insertions(+), 111 deletions(-)
create mode 100644 src/backend/postmaster/wal_allocator.c
create mode 100644 src/include/postmaster/wal_allocator.h
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index ef0e2a7746..1165288987 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -3203,6 +3203,29 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-wal-allocator-max-size" xreflabel="wal_allocator_max_size">
+ <term><varname>wal_allocator_max_size</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>wal_allocator_max_size</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ The maximum amount of WAL to pre-allocate for use when a new WAL segment
+ must be initialized. Setting this parameter to a size less than the WAL
+ segment size disables this behavior. The default values is 64 megabytes
+ (<literal>64MB</literal>). This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command line.
+ </para>
+
+ <para>
+ WAL pre-allocation may improve performance in scenarios where new WAL
+ segments must be created (e.g., during checkpoints when WAL segments
+ cannot be recycled).
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-wal-buffers" xreflabel="wal_buffers">
<term><varname>wal_buffers</varname> (<type>integer</type>)
<indexterm>
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index e51a7a749d..0c38826ba6 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -51,6 +51,7 @@
#include "port/pg_iovec.h"
#include "postmaster/bgwriter.h"
#include "postmaster/startup.h"
+#include "postmaster/wal_allocator.h"
#include "postmaster/walwriter.h"
#include "replication/basebackup.h"
#include "replication/logical.h"
@@ -723,6 +724,13 @@ typedef struct XLogCtlData
*/
XLogRecPtr lastFpwDisableRecPtr;
+ /*
+ * number of pre-allocated WAL segments in pg_wal/preallocated_segments
+ *
+ * Protected by WALPreallocationLock.
+ */
+ int num_prealloc_segs;
+
slock_t info_lck; /* locks shared variables shown above */
} XLogCtlData;
@@ -3260,11 +3268,10 @@ static int
XLogFileInitInternal(XLogSegNo logsegno, bool *added, char *path)
{
char tmppath[MAXPGPATH];
- PGAlignedXLogBlock zbuffer;
XLogSegNo installed_segno;
XLogSegNo max_segno;
int fd;
- int save_errno;
+ bool found = false;
XLogFilePath(path, ThisTimeLineID, logsegno, wal_segment_size);
@@ -3284,115 +3291,45 @@ XLogFileInitInternal(XLogSegNo logsegno, bool *added, char *path)
return fd;
/*
- * Initialize an empty (all zeroes) segment. NOTE: it is possible that
- * another process is doing the same thing. If so, we will end up
- * pre-creating an extra log segment. That seems OK, and better than
- * holding the lock throughout this lengthy process.
+ * Try to use a pre-allocated segment, if one exists. If none are
+ * available, we fall back to creating a new segment on our own.
+ *
+ * Note that we still look for a pre-allocated segment even if the pre-
+ * allocation functionality is disabled via the GUCs. This ensures that any
+ * pre-allocated segments left over after turning off the pre-allocation
+ * functionality are eventually used up.
*/
- elog(DEBUG2, "creating and filling new WAL file");
-
- snprintf(tmppath, MAXPGPATH, XLOGDIR "/xlogtemp.%d", (int) getpid());
-
- unlink(tmppath);
-
- /* do not use get_sync_bit() here --- want to fsync only at end of fill */
- fd = BasicOpenFile(tmppath, O_RDWR | O_CREAT | O_EXCL | PG_BINARY);
- if (fd < 0)
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not create file \"%s\": %m", tmppath)));
-
- memset(zbuffer.data, 0, XLOG_BLCKSZ);
-
- pgstat_report_wait_start(WAIT_EVENT_WAL_INIT_WRITE);
- save_errno = 0;
- if (wal_init_zero)
+ LWLockAcquire(WALPreallocationLock, LW_EXCLUSIVE);
+ if (XLogCtl->num_prealloc_segs > 0)
{
- struct iovec iov[PG_IOV_MAX];
- int blocks;
-
- /*
- * Zero-fill the file. With this setting, we do this the hard way to
- * ensure that all the file space has really been allocated. On
- * platforms that allow "holes" in files, just seeking to the end
- * doesn't allocate intermediate space. This way, we know that we
- * have all the space and (after the fsync below) that all the
- * indirect blocks are down on disk. Therefore, fdatasync(2) or
- * O_DSYNC will be sufficient to sync future writes to the log file.
- */
-
- /* Prepare to write out a lot of copies of our zero buffer at once. */
- for (int i = 0; i < lengthof(iov); ++i)
- {
- iov[i].iov_base = zbuffer.data;
- iov[i].iov_len = XLOG_BLCKSZ;
- }
-
- /* Loop, writing as many blocks as we can for each system call. */
- blocks = wal_segment_size / XLOG_BLCKSZ;
- for (int i = 0; i < blocks;)
- {
- int iovcnt = Min(blocks - i, lengthof(iov));
- off_t offset = i * XLOG_BLCKSZ;
-
- if (pg_pwritev_with_retry(fd, iov, iovcnt, offset) < 0)
- {
- save_errno = errno;
- break;
- }
+ elog(DEBUG2, "using pre-allocated WAL file");
- i += iovcnt;
- }
+ found = true;
+ XLogCtl->num_prealloc_segs--;
+ snprintf(tmppath, MAXPGPATH, "%s/preallocated_segments/xlogtemp.%d",
+ XLOGDIR, XLogCtl->num_prealloc_segs);
}
- else
+
+ if (!found)
{
/*
- * Otherwise, seeking to the end and writing a solitary byte is
- * enough.
+ * We're not using a pre-allocated segment, so there's no need to keep
+ * holding the WALPreallocationLock.
*/
- errno = 0;
- if (pg_pwrite(fd, zbuffer.data, 1, wal_segment_size - 1) != 1)
- {
- /* if write didn't set errno, assume no disk space */
- save_errno = errno ? errno : ENOSPC;
- }
- }
- pgstat_report_wait_end();
+ LWLockRelease(WALPreallocationLock);
- if (save_errno)
- {
/*
- * If we fail to make the file, delete it to release disk space
+ * Initialize an empty (all zeroes) segment. NOTE: it is possible that
+ * another process is doing the same thing. If so, we will end up
+ * pre-creating an extra log segment. That seems OK, and better than
+ * holding the lock throughout this lengthy process.
*/
- unlink(tmppath);
-
- close(fd);
+ elog(DEBUG2, "creating and filling new WAL file");
- errno = save_errno;
-
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not write to file \"%s\": %m", tmppath)));
+ snprintf(tmppath, MAXPGPATH, XLOGDIR "/xlogtemp.%d", (int) getpid());
+ CreateEmptyWalSegment(tmppath);
}
- pgstat_report_wait_start(WAIT_EVENT_WAL_INIT_SYNC);
- if (pg_fsync(fd) != 0)
- {
- int save_errno = errno;
-
- close(fd);
- errno = save_errno;
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not fsync file \"%s\": %m", tmppath)));
- }
- pgstat_report_wait_end();
-
- if (close(fd) != 0)
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not close file \"%s\": %m", tmppath)));
-
/*
* Now move the segment into place with its final name. Cope with
* possibility that someone else has created the file while we were
@@ -3413,7 +3350,7 @@ XLogFileInitInternal(XLogSegNo logsegno, bool *added, char *path)
if (InstallXLogFileSegment(&installed_segno, tmppath, true, max_segno))
{
*added = true;
- elog(DEBUG2, "done creating and filling new WAL file");
+ elog(DEBUG2, "done installing new WAL file");
}
else
{
@@ -3426,6 +3363,18 @@ XLogFileInitInternal(XLogSegNo logsegno, bool *added, char *path)
elog(DEBUG2, "abandoned new WAL file");
}
+ /*
+ * If we are using a pre-allocated segment, we've been holding onto the
+ * WALPreallocationLock all this time so that the WAL Allocator process
+ * can't overwrite the file before we've installed it.
+ *
+ * While we're at it, also try to wake up the WAL allocator early so that it
+ * can get a jump start on allocating new segments if possible.
+ */
+ if (found)
+ LWLockRelease(WALPreallocationLock);
+ RequestWalPreallocation();
+
return -1;
}
@@ -4282,8 +4231,8 @@ RemoveXlogFile(const char *segname, XLogSegNo recycleSegNo,
}
/*
- * Verify whether pg_wal and pg_wal/archive_status exist.
- * If the latter does not exist, recreate it.
+ * Verify whether pg_wal, pg_wal/archive_status and pg_wal/preallocated_segments
+ * exist. If either of the latter two do not exist, recreate it.
*
* It is not the goal of this function to verify the contents of these
* directories, but to help in cases where someone has performed a cluster
@@ -4326,6 +4275,26 @@ ValidateXLOGDirectoryStructure(void)
(errmsg("could not create missing directory \"%s\": %m",
path)));
}
+
+ /* Check for preallocated_segments */
+ snprintf(path, MAXPGPATH, XLOGDIR "/preallocated_segments");
+ if (stat(path, &stat_buf) == 0)
+ {
+ /* Check for weird cases where it exists but isn't a directory */
+ if (!S_ISDIR(stat_buf.st_mode))
+ ereport(FATAL,
+ (errmsg("required WAL directory \"%s\" does not exist",
+ path)));
+ }
+ else
+ {
+ ereport(LOG,
+ (errmsg("creating missing WAL directory \"%s\"", path)));
+ if (MakePGDirectory(path) < 0)
+ ereport(FATAL,
+ (errmsg("could not create missing directory \"%s\": %m",
+ path)));
+ }
}
/*
@@ -5233,6 +5202,9 @@ XLOGShmemInit(void)
SpinLockInit(&XLogCtl->ulsn_lck);
InitSharedLatch(&XLogCtl->recoveryWakeupLatch);
ConditionVariableInit(&XLogCtl->recoveryNotPausedCV);
+
+ /* protected by WALPreallocationLock */
+ XLogCtl->num_prealloc_segs = 0;
}
/*
@@ -12993,3 +12965,55 @@ XLogRequestWalReceiverReply(void)
{
doRequestWalReceiverReply = true;
}
+
+int
+GetNumPreallocatedWalSegs(void)
+{
+ int ret;
+
+ LWLockAcquire(WALPreallocationLock, LW_SHARED);
+ ret = XLogCtl->num_prealloc_segs;
+ LWLockRelease(WALPreallocationLock);
+
+ return ret;
+}
+
+void
+SetNumPreallocatedWalSegs(int i)
+{
+ LWLockAcquire(WALPreallocationLock, LW_EXCLUSIVE);
+ XLogCtl->num_prealloc_segs = i;
+ LWLockRelease(WALPreallocationLock);
+}
+
+/*
+ * InstallPreallocatedWalSeg
+ *
+ * Renames the file at "path" to the next open pre-allocated segment slot and
+ * bumps up XLogCtl->num_prealloc_segs.
+ */
+void
+InstallPreallocatedWalSeg(const char *path)
+{
+ char newpath[MAXPGPATH];
+
+ LWLockAcquire(WALPreallocationLock, LW_EXCLUSIVE);
+
+ snprintf(newpath, MAXPGPATH, "%s/preallocated_segments/xlogtemp.%d",
+ XLOGDIR, XLogCtl->num_prealloc_segs);
+ (void) durable_rename(path, newpath, ERROR);
+ XLogCtl->num_prealloc_segs++;
+
+ LWLockRelease(WALPreallocationLock);
+}
+
+/*
+ * Request that the WAL allocator wake up early.
+ */
+void
+RequestWalPreallocation(void)
+{
+ if (ProcGlobal->walAllocatorLatch &&
+ WalPreallocationEnabled())
+ SetLatch(ProcGlobal->walAllocatorLatch);
+}
diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile
index 787c6a2c3b..d69ada0c4e 100644
--- a/src/backend/postmaster/Makefile
+++ b/src/backend/postmaster/Makefile
@@ -25,6 +25,7 @@ OBJS = \
postmaster.o \
startup.o \
syslogger.o \
+ wal_allocator.o \
walwriter.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/postmaster/auxprocess.c b/src/backend/postmaster/auxprocess.c
index 7452f908b2..83fbebc4d8 100644
--- a/src/backend/postmaster/auxprocess.c
+++ b/src/backend/postmaster/auxprocess.c
@@ -21,6 +21,7 @@
#include "postmaster/auxprocess.h"
#include "postmaster/bgwriter.h"
#include "postmaster/startup.h"
+#include "postmaster/wal_allocator.h"
#include "postmaster/walwriter.h"
#include "replication/walreceiver.h"
#include "storage/bufmgr.h"
@@ -80,6 +81,9 @@ AuxiliaryProcessMain(AuxProcType auxtype)
case WalReceiverProcess:
MyBackendType = B_WAL_RECEIVER;
break;
+ case WalAllocatorProcess:
+ MyBackendType = B_WAL_ALLOCATOR;
+ break;
default:
elog(ERROR, "something has gone wrong");
MyBackendType = B_INVALID;
@@ -162,6 +166,10 @@ AuxiliaryProcessMain(AuxProcType auxtype)
WalReceiverMain();
proc_exit(1);
+ case WalAllocatorProcess:
+ WalAllocatorMain();
+ proc_exit(1);
+
default:
elog(PANIC, "unrecognized process type: %d", (int) MyAuxProcType);
proc_exit(1);
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index b2fe420c3c..70ea42c65c 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -251,6 +251,7 @@ static pid_t StartupPID = 0,
CheckpointerPID = 0,
WalWriterPID = 0,
WalReceiverPID = 0,
+ WalAllocatorPID = 0,
AutoVacPID = 0,
PgArchPID = 0,
PgStatPID = 0,
@@ -557,6 +558,7 @@ static void ShmemBackendArrayRemove(Backend *bn);
#define StartCheckpointer() StartChildProcess(CheckpointerProcess)
#define StartWalWriter() StartChildProcess(WalWriterProcess)
#define StartWalReceiver() StartChildProcess(WalReceiverProcess)
+#define StartWalAllocator() StartChildProcess(WalAllocatorProcess)
/* Macros to check exit status of a child process */
#define EXIT_STATUS_0(st) ((st) == 0)
@@ -1801,6 +1803,13 @@ ServerLoop(void)
if (WalWriterPID == 0 && pmState == PM_RUN)
WalWriterPID = StartWalWriter();
+ /*
+ * If we have lost the WAL allocator process, try to start a new one.
+ */
+ if (WalAllocatorPID == 0 &&
+ (pmState == PM_RUN || pmState == PM_HOT_STANDBY))
+ WalAllocatorPID = StartWalAllocator();
+
/*
* If we have lost the autovacuum launcher, try to start a new one. We
* don't want autovacuum to run in binary upgrade mode because
@@ -2716,6 +2725,8 @@ SIGHUP_handler(SIGNAL_ARGS)
signal_child(WalWriterPID, SIGHUP);
if (WalReceiverPID != 0)
signal_child(WalReceiverPID, SIGHUP);
+ if (WalAllocatorPID != 0)
+ signal_child(WalAllocatorPID, SIGHUP);
if (AutoVacPID != 0)
signal_child(AutoVacPID, SIGHUP);
if (PgArchPID != 0)
@@ -3043,6 +3054,8 @@ reaper(SIGNAL_ARGS)
BgWriterPID = StartBackgroundWriter();
if (WalWriterPID == 0)
WalWriterPID = StartWalWriter();
+ if (WalAllocatorPID == 0)
+ WalAllocatorPID = StartWalAllocator();
/*
* Likewise, start other special children as needed. In a restart
@@ -3170,6 +3183,20 @@ reaper(SIGNAL_ARGS)
continue;
}
+ /*
+ * Was it the WAL allocator? Normal exit can be ignored; we'll start a
+ * new one at the next iteration of the postmaster's main loop, if
+ * necessary. Any other exit condition is treated as a crash.
+ */
+ if (pid == WalAllocatorPID)
+ {
+ WalAllocatorPID = 0;
+ if (!EXIT_STATUS_0(exitstatus))
+ HandleChildCrash(pid, exitstatus,
+ _("WAL Allocator process"));
+ continue;
+ }
+
/*
* Was it the autovacuum launcher? Normal exit can be ignored; we'll
* start a new one at the next iteration of the postmaster's main
@@ -3638,6 +3665,18 @@ HandleChildCrash(int pid, int exitstatus, const char *procname)
signal_child(WalReceiverPID, (SendStop ? SIGSTOP : SIGQUIT));
}
+ /* Take care of the WAL allocator too */
+ if (pid == WalAllocatorPID)
+ WalAllocatorPID = 0;
+ else if (WalAllocatorPID != 0 && take_action)
+ {
+ ereport(DEBUG2,
+ (errmsg_internal("sending %s to process %d",
+ (SendStop ? "SIGSTOP" : "SIGQUIT"),
+ (int) WalAllocatorPID)));
+ signal_child(WalAllocatorPID, (SendStop ? SIGSTOP : SIGQUIT));
+ }
+
/* Take care of the autovacuum launcher too */
if (pid == AutoVacPID)
AutoVacPID = 0;
@@ -3825,6 +3864,8 @@ PostmasterStateMachine(void)
signal_child(StartupPID, SIGTERM);
if (WalReceiverPID != 0)
signal_child(WalReceiverPID, SIGTERM);
+ if (WalAllocatorPID != 0)
+ signal_child(WalAllocatorPID, SIGTERM);
/* checkpointer, archiver, stats, and syslogger may continue for now */
/* Now transition to PM_WAIT_BACKENDS state to wait for them to die */
@@ -3855,6 +3896,7 @@ PostmasterStateMachine(void)
(CheckpointerPID == 0 ||
(!FatalError && Shutdown < ImmediateShutdown)) &&
WalWriterPID == 0 &&
+ WalAllocatorPID == 0 &&
AutoVacPID == 0)
{
if (Shutdown >= ImmediateShutdown || FatalError)
@@ -3948,6 +3990,7 @@ PostmasterStateMachine(void)
Assert(BgWriterPID == 0);
Assert(CheckpointerPID == 0);
Assert(WalWriterPID == 0);
+ Assert(WalAllocatorPID == 0);
Assert(AutoVacPID == 0);
/* syslogger is not considered here */
pmState = PM_NO_CHILDREN;
@@ -4156,6 +4199,8 @@ TerminateChildren(int signal)
signal_child(WalWriterPID, signal);
if (WalReceiverPID != 0)
signal_child(WalReceiverPID, signal);
+ if (WalAllocatorPID != 0)
+ signal_child(WalAllocatorPID, signal);
if (AutoVacPID != 0)
signal_child(AutoVacPID, signal);
if (PgArchPID != 0)
diff --git a/src/backend/postmaster/wal_allocator.c b/src/backend/postmaster/wal_allocator.c
new file mode 100644
index 0000000000..38cebf7953
--- /dev/null
+++ b/src/backend/postmaster/wal_allocator.c
@@ -0,0 +1,315 @@
+/*-------------------------------------------------------------------------
+ *
+ * wal_allocator.c
+ *
+ * The WAL allocator is new as of Postgres 15. It attempts to keep regular
+ * backends from having to allocate new WAL segments. Even when
+ * wal_recycle is enabled, pre-allocating WAL segments can yield
+ * significant performance improvements in certain scenarios. Note that
+ * regular backends are still empowered to create new WAL segments if the
+ * WAL allocator fails to generate enough new segments.
+ *
+ * The WAL allocator is controlled by one parameter: wal_allocator_max_size.
+ * wal_allocator_max_size specifies the maximum amount of WAL to pre-
+ * allocate. If this value is not divisible by the WAL segment size, fewer
+ * WAL segments will be pre-allocated.
+ *
+ * The WAL allocator is started by the postmaster as soon as the startup
+ * subprocess finishes, or as soon as recovery begins if we are doing
+ * archive recovery. It remains alive until the postmaster commands it to
+ * terminate. Normal termination is by SIGTERM, which instructs the
+ * WAL allocator to exit(0). Emergency termination is by SIGQUIT; like any
+ * backend, the WAL allocator will simply abort and exit on SIGQUIT.
+ *
+ * If the WAL allocator exits unexpectedly, the postmaster treats that the
+ * same as a backend crash: shared memory may be corrupted, so remaining
+ * backends should be killed by SIGQUIT and then a recovery cycle started.
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/postmaster/wal_allocator.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <sys/time.h>
+
+#include "access/xlog.h"
+#include "access/xlog_internal.h"
+#include "libpq/pqsignal.h"
+#include "pgstat.h"
+#include "postmaster/interrupt.h"
+#include "postmaster/wal_allocator.h"
+#include "storage/bufmgr.h"
+#include "storage/condition_variable.h"
+#include "storage/fd.h"
+#include "storage/ipc.h"
+#include "storage/lwlock.h"
+#include "storage/proc.h"
+#include "storage/procsignal.h"
+#include "storage/shmem.h"
+#include "storage/smgr.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+#define WAL_ALLOC_TIMEOUT_S (60)
+
+/*
+ * GUC parameters
+ */
+int wal_alloc_max_size_mb = 64;
+
+static void DoWalPreAllocation(void);
+static void ScanForExistingPreallocatedSegments(void);
+
+/*
+ * Main entry point for WAL allocator process
+ *
+ * This is invoked from AuxiliaryProcessMain, which has already created the
+ * basic execution environment, but not enabled signals yet.
+ */
+void
+WalAllocatorMain(void)
+{
+ sigjmp_buf local_sigjmp_buf;
+ MemoryContext wal_alloc_context;
+
+ /*
+ * Properly accept or ignore signals that might be sent to us.
+ */
+ pqsignal(SIGHUP, SignalHandlerForConfigReload);
+ pqsignal(SIGINT, SIG_IGN);
+ pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
+ /* SIGQUIT handler was already set up by InitPostmasterChild */
+ pqsignal(SIGALRM, SIG_IGN);
+ pqsignal(SIGPIPE, SIG_IGN);
+ pqsignal(SIGUSR1, procsignal_sigusr1_handler);
+ pqsignal(SIGUSR2, SIG_IGN);
+
+ /*
+ * Reset some signals that are accepted by postmaster but not here
+ */
+ pqsignal(SIGCHLD, SIG_DFL);
+
+ /*
+ * Create a memory context that we will do all our work in. We do this so
+ * that we can reset the context during error recovery and thereby avoid
+ * possible memory leaks.
+ */
+ wal_alloc_context = AllocSetContextCreate(TopMemoryContext,
+ "WAL Allocator",
+ ALLOCSET_DEFAULT_SIZES);
+ MemoryContextSwitchTo(wal_alloc_context);
+
+ /*
+ * If an exception is encountered, processing resumes here.
+ *
+ * You might wonder why this isn't coded as an infinite loop around a
+ * PG_TRY construct. The reason is that this is the bottom of the
+ * exception stack, and so with PG_TRY there would be no exception handler
+ * in force at all during the CATCH part. By leaving the outermost setjmp
+ * always active, we have at least some chance of recovering from an error
+ * during error recovery. (If we get into an infinite loop thereby, it
+ * will soon be stopped by overflow of elog.c's internal state stack.)
+ *
+ * Note that we use sigsetjmp(..., 1), so that the prevailing signal mask
+ * (to wit, BlockSig) will be restored when longjmp'ing to here. Thus,
+ * signals other than SIGQUIT will be blocked until we complete error
+ * recovery. It might seem that this policy makes the HOLD_INTERRUPTS()
+ * call redundant, but it is not since InterruptPending might be set
+ * already.
+ */
+ if (sigsetjmp(local_sigjmp_buf, 1) != 0)
+ {
+ /* Since not using PG_TRY, must reset error stack by hand */
+ error_context_stack = NULL;
+
+ /* Prevent interrupts while cleaning up */
+ HOLD_INTERRUPTS();
+
+ /* Report the error to the server log */
+ EmitErrorReport();
+
+ /*
+ * These operations are really just a minimal subset of
+ * AbortTransaction(). We don't have very many resources to worry
+ * about.
+ */
+ LWLockReleaseAll();
+ ConditionVariableCancelSleep();
+ pgstat_report_wait_end();
+ AbortBufferIO();
+ UnlockBuffers();
+ ReleaseAuxProcessResources(false);
+ AtEOXact_Buffers(false);
+ AtEOXact_SMgr();
+ AtEOXact_Files(false);
+ AtEOXact_HashTables(false);
+
+ /*
+ * Now return to normal top-level context and clear ErrorContext for
+ * next time.
+ */
+ MemoryContextSwitchTo(wal_alloc_context);
+ FlushErrorState();
+
+ /* Flush any leaked data in the top-level context */
+ MemoryContextResetAndDeleteChildren(wal_alloc_context);
+
+ /* Now we can allow interrupts again */
+ RESUME_INTERRUPTS();
+
+ /*
+ * Sleep at least 1 second after any error. A write error is likely
+ * to be repeated, and we don't want to be filling the error logs as
+ * fast as we can.
+ */
+ pg_usleep(1000000L);
+
+ /*
+ * Close all open files after any error. This is helpful on Windows,
+ * where holding deleted files open causes various strange errors.
+ * It's not clear we need it elsewhere, but shouldn't hurt.
+ */
+ smgrcloseall();
+
+ /* Report wait end here, when there is no further possibility of wait */
+ pgstat_report_wait_end();
+ }
+
+ /* We can now handle ereport(ERROR) */
+ PG_exception_stack = &local_sigjmp_buf;
+
+ /*
+ * Unblock signals (they were blocked when the postmaster forked us)
+ */
+ PG_SETMASK(&UnBlockSig);
+
+ /*
+ * Advertise our latch that backends can use to wake us up while we're
+ * sleeping.
+ */
+ ProcGlobal->walAllocatorLatch = &MyProc->procLatch;
+
+ /*
+ * Before we go into the main loop, scan the pre-allocated segments
+ * directory and look for anything that was left over from the last time.
+ */
+ ScanForExistingPreallocatedSegments();
+
+ /*
+ * Loop forever
+ */
+ for (;;)
+ {
+ /* Clear any already-pending wakeups */
+ ResetLatch(MyLatch);
+
+ HandleMainLoopInterrupts();
+
+ DoWalPreAllocation();
+
+ /* XXX: Send activity statistics to the stats collector */
+
+ /* Sleep until we are signaled or WAL_ALLOC_TIMEOUT_S has elapsed. */
+ (void) WaitLatch(MyLatch,
+ WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+ WAL_ALLOC_TIMEOUT_S * 1000L /* convert to ms */,
+ WAIT_EVENT_WAL_ALLOCATOR_MAIN);
+ }
+
+ pg_unreachable();
+}
+
+/*
+ * DoWalPreAllocation
+ *
+ * Tries to allocate up to wal_allocator_max_size worth of WAL.
+ */
+static void
+DoWalPreAllocation(void)
+{
+ int max_prealloc_segs;
+
+ max_prealloc_segs = wal_alloc_max_size_mb / (wal_segment_size / (1024 * 1024));
+
+ while (!ShutdownRequestPending &&
+ GetNumPreallocatedWalSegs() < max_prealloc_segs)
+ {
+ char tmppath[MAXPGPATH];
+
+ snprintf(tmppath, MAXPGPATH, "%s/preallocated_segments/xlogtemp", XLOGDIR);
+ CreateEmptyWalSegment(tmppath);
+ InstallPreallocatedWalSeg(tmppath);
+ elog(DEBUG2, "pre-allocated WAL segment");
+
+ if (ConfigReloadPending)
+ {
+ ConfigReloadPending = false;
+ ProcessConfigFile(PGC_SIGHUP);
+
+ max_prealloc_segs = wal_alloc_max_size_mb / (wal_segment_size / (1024 * 1024));
+ }
+ }
+}
+
+/*
+ * ScanForExistingPreallocatedSegments
+ *
+ * This function searches through pg_wal/preallocated_segments for any segments
+ * that were left over from a previous WAL allocator process and sets the
+ * tracking variable in shared memory accordingly.
+ */
+static void
+ScanForExistingPreallocatedSegments(void)
+{
+ int i = 0;
+
+ /*
+ * fsync the preallocated_segments directory in case any renames have yet to
+ * be flushed to disk. This is probably not really necessary, but it seems
+ * nice to know that all the segments we find are really where we think they
+ * are.
+ */
+ fsync_fname_ext(XLOGDIR "/preallocated_segments", true, false, FATAL);
+
+ /*
+ * Gather all the preallocated segments we can find.
+ */
+ while (true)
+ {
+ FILE *fd;
+ char path[MAXPGPATH];
+
+ snprintf(path, MAXPGPATH, "%s/preallocated_segments/xlogtemp.%d",
+ XLOGDIR, i);
+
+ fd = AllocateFile(path, "r");
+ if (fd != NULL)
+ {
+ FreeFile(fd);
+ i++;
+ }
+ else
+ {
+ if (errno != ENOENT)
+ ereport(FATAL,
+ (errcode_for_file_access(),
+ errmsg("could not open file \"%s\": %m", path)));
+ break;
+ }
+ }
+
+ SetNumPreallocatedWalSegs(i);
+ elog(DEBUG2, "found %d preallocated segments during startup", i);
+}
+
+bool
+WalPreallocationEnabled(void)
+{
+ return wal_alloc_max_size_mb >= wal_segment_size / (1024 * 1024);
+}
diff --git a/src/backend/replication/basebackup.c b/src/backend/replication/basebackup.c
index e09108d0ec..c9a91dc938 100644
--- a/src/backend/replication/basebackup.c
+++ b/src/backend/replication/basebackup.c
@@ -1399,11 +1399,13 @@ sendDir(const char *path, int basepathlen, bool sizeonly, List *tablespaces,
size += _tarWriteDir(pathbuf, basepathlen, &statbuf, sizeonly);
/*
- * Also send archive_status directory (by hackishly reusing
- * statbuf from above ...).
+ * Also send archive_status and preallocated_segments (by hackishly
+ * reusing statbuf from above ...).
*/
size += _tarWriteHeader("./pg_wal/archive_status", NULL, &statbuf,
sizeonly);
+ size += _tarWriteHeader("./pg_wal/preallocated_segments", NULL,
+ &statbuf, sizeonly);
continue; /* don't recurse into pg_wal */
}
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index f9cda6906d..e5ac34a539 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -76,6 +76,7 @@
#include <sys/file.h>
#include <sys/param.h>
#include <sys/stat.h>
+#include <sys/time.h>
#include <sys/types.h>
#ifndef WIN32
#include <sys/mman.h>
@@ -3870,3 +3871,117 @@ pg_pwritev_with_retry(int fd, const struct iovec *iov, int iovcnt, off_t offset)
return sum;
}
+
+/*
+ * CreateEmptyWalSegment
+ *
+ * Create a new file that can be used as a new WAL segment. The caller is
+ * responsible for installing the new file in pg_wal.
+ */
+void
+CreateEmptyWalSegment(const char *path)
+{
+ PGAlignedXLogBlock zbuffer;
+ int fd;
+ int save_errno;
+
+ unlink(path);
+
+ /* do not use get_sync_bit() here --- want to fsync only at end of fill */
+ fd = BasicOpenFile(path, O_RDWR | O_CREAT | O_EXCL | PG_BINARY);
+ if (fd < 0)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not create file \"%s\": %m", path)));
+
+ memset(zbuffer.data, 0, XLOG_BLCKSZ);
+
+ pgstat_report_wait_start(WAIT_EVENT_WAL_INIT_WRITE);
+ save_errno = 0;
+ if (wal_init_zero)
+ {
+ struct iovec iov[PG_IOV_MAX];
+ int blocks;
+
+ /*
+ * Zero-fill the file. With this setting, we do this the hard way to
+ * ensure that all the file space has really been allocated. On
+ * platforms that allow "holes" in files, just seeking to the end
+ * doesn't allocate intermediate space. This way, we know that we
+ * have all the space and (after the fsync below) that all the
+ * indirect blocks are down on disk. Therefore, fdatasync(2) or
+ * O_DSYNC will be sufficient to sync future writes to the log file.
+ */
+
+ /* Prepare to write out a lot of copies of our zero buffer at once. */
+ for (int i = 0; i < lengthof(iov); ++i)
+ {
+ iov[i].iov_base = zbuffer.data;
+ iov[i].iov_len = XLOG_BLCKSZ;
+ }
+
+ /* Loop, writing as many blocks as we can for each system call. */
+ blocks = wal_segment_size / XLOG_BLCKSZ;
+ for (int i = 0; i < blocks;)
+ {
+ int iovcnt = Min(blocks - i, lengthof(iov));
+ off_t offset = i * XLOG_BLCKSZ;
+
+ if (pg_pwritev_with_retry(fd, iov, iovcnt, offset) < 0)
+ {
+ save_errno = errno;
+ break;
+ }
+
+ i += iovcnt;
+ }
+ }
+ else
+ {
+ /*
+ * Otherwise, seeking to the end and writing a solitary byte is
+ * enough.
+ */
+ errno = 0;
+ if (pg_pwrite(fd, zbuffer.data, 1, wal_segment_size - 1) != 1)
+ {
+ /* if write didn't set errno, assume no disk space */
+ save_errno = errno ? errno : ENOSPC;
+ }
+ }
+ pgstat_report_wait_end();
+
+ if (save_errno)
+ {
+ /*
+ * If we fail to make the file, delete it to release disk space
+ */
+ unlink(path);
+
+ close(fd);
+
+ errno = save_errno;
+
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not write to file \"%s\": %m", path)));
+ }
+
+ pgstat_report_wait_start(WAIT_EVENT_WAL_INIT_SYNC);
+ if (pg_fsync(fd) != 0)
+ {
+ int save_errno = errno;
+
+ close(fd);
+ errno = save_errno;
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not fsync file \"%s\": %m", path)));
+ }
+ pgstat_report_wait_end();
+
+ if (close(fd) != 0)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not close file \"%s\": %m", path)));
+}
diff --git a/src/backend/storage/lmgr/lwlocknames.txt b/src/backend/storage/lmgr/lwlocknames.txt
index 6c7cf6c295..212bda8e7d 100644
--- a/src/backend/storage/lmgr/lwlocknames.txt
+++ b/src/backend/storage/lmgr/lwlocknames.txt
@@ -53,3 +53,4 @@ XactTruncationLock 44
# 45 was XactTruncationLock until removal of BackendRandomLock
WrapLimitsVacuumLock 46
NotifyQueueTailLock 47
+WALPreallocationLock 48
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index b7d9da0aa9..5790d586ef 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -181,6 +181,7 @@ InitProcGlobal(void)
ProcGlobal->startupProcPid = 0;
ProcGlobal->startupBufferPinWaitBufId = -1;
ProcGlobal->walwriterLatch = NULL;
+ ProcGlobal->walAllocatorLatch = NULL;
ProcGlobal->checkpointerLatch = NULL;
pg_atomic_init_u32(&ProcGlobal->procArrayGroupFirst, INVALID_PGPROCNO);
pg_atomic_init_u32(&ProcGlobal->clogGroupFirst, INVALID_PGPROCNO);
diff --git a/src/backend/utils/activity/wait_event.c b/src/backend/utils/activity/wait_event.c
index ef7e6bfb77..a3c4324c92 100644
--- a/src/backend/utils/activity/wait_event.c
+++ b/src/backend/utils/activity/wait_event.c
@@ -248,6 +248,9 @@ pgstat_get_wait_activity(WaitEventActivity w)
case WAIT_EVENT_WAL_WRITER_MAIN:
event_name = "WalWriterMain";
break;
+ case WAIT_EVENT_WAL_ALLOCATOR_MAIN:
+ event_name = "WalAllocatorMain";
+ break;
/* no default case, so that compiler will warn */
}
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 88801374b5..9f12e18b66 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -285,6 +285,9 @@ GetBackendTypeDesc(BackendType backendType)
case B_WAL_WRITER:
backendDesc = "walwriter";
break;
+ case B_WAL_ALLOCATOR:
+ backendDesc = "wal allocator";
+ break;
case B_ARCHIVER:
backendDesc = "archiver";
break;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 23236fa4c3..1ed714d370 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -72,6 +72,7 @@
#include "postmaster/bgwriter.h"
#include "postmaster/postmaster.h"
#include "postmaster/syslogger.h"
+#include "postmaster/wal_allocator.h"
#include "postmaster/walwriter.h"
#include "replication/logicallauncher.h"
#include "replication/reorderbuffer.h"
@@ -2828,6 +2829,17 @@ static struct config_int ConfigureNamesInt[] =
NULL, NULL, NULL
},
+ {
+ {"wal_allocator_max_size", PGC_SIGHUP, WAL_SETTINGS,
+ gettext_noop("Sets the maximum amount of WAL to pre-allocate."),
+ NULL,
+ GUC_UNIT_MB
+ },
+ &wal_alloc_max_size_mb,
+ 64, 0, 102400,
+ NULL, NULL, NULL
+ },
+
{
{"wal_buffers", PGC_POSTMASTER, WAL_SETTINGS,
gettext_noop("Sets the number of disk-page buffers in shared memory for WAL."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 3fe9a53cb3..a517dfee6d 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -223,6 +223,7 @@
# off, pglz, lz4, or on
#wal_init_zero = on # zero-fill new WAL files
#wal_recycle = on # recycle WAL files
+#wal_allocator_max_size = 64MB # amount of WAL to pre-allocate
#wal_buffers = -1 # min 32kB, -1 sets based on shared_buffers
# (change requires restart)
#wal_writer_delay = 200ms # 1-10000 milliseconds
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 1ed4808d53..aa52aaeffb 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -208,6 +208,7 @@ static char *extra_options = "";
static const char *const subdirs[] = {
"global",
"pg_wal/archive_status",
+ "pg_wal/preallocated_segments",
"pg_commit_ts",
"pg_dynshmem",
"pg_notify",
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index 7296eb97d0..1778c9c91b 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -680,6 +680,22 @@ StartLogStreamer(char *startpos, uint32 timeline, char *sysidentifier)
pg_log_error("could not create directory \"%s\": %m", statusdir);
exit(1);
}
+
+ /*
+ * Also create pg_wal/preallocated_segments if necessary.
+ */
+ if (PQserverVersion(conn) >= 150000)
+ {
+ char prealloc_dir[MAXPGPATH];
+
+ snprintf(prealloc_dir, sizeof(prealloc_dir), "%s/pg_wal/preallocated_segments",
+ basedir);
+ if (pg_mkdir_p(prealloc_dir, pg_dir_create_mode) != 0 && errno != EEXIST)
+ {
+ pg_log_error("could not create directory \"%s\": %m", prealloc_dir);
+ exit(1);
+ }
+ }
}
/*
@@ -1616,7 +1632,8 @@ ReceiveTarAndUnpackCopyChunk(size_t r, char *copybuf, void *callback_data)
*/
if (!((pg_str_endswith(state->filename, "/pg_wal") ||
pg_str_endswith(state->filename, "/pg_xlog") ||
- pg_str_endswith(state->filename, "/archive_status")) &&
+ pg_str_endswith(state->filename, "/archive_status") ||
+ pg_str_endswith(state->filename, "/preallocated_segments")) &&
errno == EEXIST))
{
pg_log_error("could not create directory \"%s\": %m",
diff --git a/src/bin/pg_basebackup/t/010_pg_basebackup.pl b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
index a2cb2a7679..43e1e3e89d 100644
--- a/src/bin/pg_basebackup/t/010_pg_basebackup.pl
+++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
@@ -120,10 +120,10 @@ SKIP:
"check backup dir permissions");
}
-# Only archive_status directory should be copied in pg_wal/.
+# Only archive_status and preallocated_segments directories should be copied in pg_wal/.
is_deeply(
[ sort(slurp_dir("$tempdir/backup/pg_wal/")) ],
- [ sort qw(. .. archive_status) ],
+ [ sort qw(. .. archive_status preallocated_segments) ],
'no WAL files copied');
# Contents of these directories should not be copied.
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 84b6f70ad6..234a867895 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -85,6 +85,7 @@ static void RewriteControlFile(void);
static void FindEndOfXLOG(void);
static void KillExistingXLOG(void);
static void KillExistingArchiveStatus(void);
+static void KillExistingPreallocatedSegments(void);
static void WriteEmptyXLOG(void);
static void usage(void);
@@ -530,6 +531,7 @@ main(int argc, char *argv[])
RewriteControlFile();
KillExistingXLOG();
KillExistingArchiveStatus();
+ KillExistingPreallocatedSegments();
WriteEmptyXLOG();
printf(_("Write-ahead log reset\n"));
@@ -1070,6 +1072,52 @@ KillExistingXLOG(void)
}
+/*
+ * Remove existing preallocated segments
+ */
+static void
+KillExistingPreallocatedSegments(void)
+{
+#define PREALLOCSEGDIR XLOGDIR "/preallocated_segments"
+
+ DIR *xldir;
+ struct dirent *xlde;
+ char path[MAXPGPATH + sizeof(PREALLOCSEGDIR)];
+
+ xldir = opendir(PREALLOCSEGDIR);
+ if (xldir == NULL)
+ {
+ pg_log_error("could not open directory \"%s\": %m", PREALLOCSEGDIR);
+ exit(1);
+ }
+
+ while (errno = 0, (xlde = readdir(xldir)) != NULL)
+ {
+ if (strncmp(xlde->d_name, "xlogtemp", strlen("xlogtemp")) == 0)
+ {
+ snprintf(path, sizeof(path), "%s/%s", PREALLOCSEGDIR, xlde->d_name);
+ if (unlink(path) < 0)
+ {
+ pg_log_error("could not delete file \"%s\": %m", path);
+ exit(1);
+ }
+ }
+ }
+
+ if (errno)
+ {
+ pg_log_error("could not read directory \"%s\": %m", PREALLOCSEGDIR);
+ exit(1);
+ }
+
+ if (closedir(xldir))
+ {
+ pg_log_error("could not close directory \"%s\": %m", PREALLOCSEGDIR);
+ exit(1);
+ }
+}
+
+
/*
* Remove existing archive status files
*/
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index 0a8ede700d..7d99d90244 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -327,6 +327,11 @@ extern void XLogRequestWalReceiverReply(void);
extern void assign_max_wal_size(int newval, void *extra);
extern void assign_checkpoint_completion_target(double newval, void *extra);
+extern int GetNumPreallocatedWalSegs(void);
+extern void InstallPreallocatedWalSeg(const char *path);
+extern void SetNumPreallocatedWalSegs(int i);
+extern void RequestWalPreallocation(void);
+
/*
* Routines to start, stop, and get status of a base backup.
*/
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 2e2e9a364a..e10d37bc00 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -333,6 +333,7 @@ typedef enum BackendType
B_WAL_RECEIVER,
B_WAL_SENDER,
B_WAL_WRITER,
+ B_WAL_ALLOCATOR,
B_ARCHIVER,
B_STATS_COLLECTOR,
B_LOGGER,
@@ -433,6 +434,7 @@ typedef enum
CheckpointerProcess,
WalWriterProcess,
WalReceiverProcess,
+ WalAllocatorProcess,
NUM_AUXPROCTYPES /* Must be last! */
} AuxProcType;
@@ -445,6 +447,7 @@ extern AuxProcType MyAuxProcType;
#define AmCheckpointerProcess() (MyAuxProcType == CheckpointerProcess)
#define AmWalWriterProcess() (MyAuxProcType == WalWriterProcess)
#define AmWalReceiverProcess() (MyAuxProcType == WalReceiverProcess)
+#define AmWalAllocatorProcess() (MyAuxProcType == WalAllocatorProcess)
/*****************************************************************************
diff --git a/src/include/postmaster/wal_allocator.h b/src/include/postmaster/wal_allocator.h
new file mode 100644
index 0000000000..c7e139226b
--- /dev/null
+++ b/src/include/postmaster/wal_allocator.h
@@ -0,0 +1,21 @@
+/*-------------------------------------------------------------------------
+ *
+ * wal_allocator.h
+ * Exports from postmaster/wal_allocator.c.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ *
+ * src/include/postmaster/wal_allocator.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _WAL_ALLOCATOR_H
+#define _WAL_ALLOCATOR_H
+
+/* GUC options */
+extern int wal_alloc_max_size_mb;
+
+extern void WalAllocatorMain(void) pg_attribute_noreturn();
+extern bool WalPreallocationEnabled(void);
+
+#endif /* _WAL_ALLOCATOR_H */
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index 34602ae006..abcd74bd51 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -190,6 +190,7 @@ extern int durable_unlink(const char *fname, int loglevel);
extern int durable_rename_excl(const char *oldfile, const char *newfile, int loglevel);
extern void SyncDataDirectory(void);
extern int data_sync_elevel(int elevel);
+extern void CreateEmptyWalSegment(const char *path);
/* Filename components */
#define PG_TEMP_FILES_DIR "pgsql_tmp"
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index be67d8a861..8d0cae37b2 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -348,6 +348,8 @@ typedef struct PROC_HDR
pg_atomic_uint32 clogGroupFirst;
/* WALWriter process's latch */
Latch *walwriterLatch;
+ /* WAL Allocator process's latch */
+ Latch *walAllocatorLatch;
/* Checkpointer process's latch */
Latch *checkpointerLatch;
/* Current shared estimate of appropriate spins_per_delay value */
@@ -370,11 +372,12 @@ extern PGPROC *PreparedXactProcs;
* We set aside some extra PGPROC structures for auxiliary processes,
* ie things that aren't full-fledged backends but need shmem access.
*
- * Background writer, checkpointer, WAL writer and archiver run during normal
- * operation. Startup process and WAL receiver also consume 2 slots, but WAL
- * writer is launched only after startup has exited, so we only need 5 slots.
+ * Background writer, checkpointer, WAL writer, archiver, and WAL allocator run
+ * during normal operation. Startup process and WAL receiver also consume 2
+ * slots, but WAL writer is launched only after startup has exited, so we only
+ * need 6 slots.
*/
-#define NUM_AUXILIARY_PROCS 5
+#define NUM_AUXILIARY_PROCS 6
/* configurable options */
extern PGDLLIMPORT int DeadlockTimeout;
diff --git a/src/include/utils/wait_event.h b/src/include/utils/wait_event.h
index 6007827b44..18a05f8d59 100644
--- a/src/include/utils/wait_event.h
+++ b/src/include/utils/wait_event.h
@@ -47,7 +47,8 @@ typedef enum
WAIT_EVENT_SYSLOGGER_MAIN,
WAIT_EVENT_WAL_RECEIVER_MAIN,
WAIT_EVENT_WAL_SENDER_MAIN,
- WAIT_EVENT_WAL_WRITER_MAIN
+ WAIT_EVENT_WAL_WRITER_MAIN,
+ WAIT_EVENT_WAL_ALLOCATOR_MAIN
} WaitEventActivity;
/* ----------
--
2.16.6
^ permalink raw reply [nested|flat] 19+ messages in thread
* Re: Pre-allocating WAL files
@ 2021-10-06 12:19 Maxim Orlov <[email protected]>
parent: Bossart, Nathan <[email protected]>
0 siblings, 3 replies; 19+ messages in thread
From: Maxim Orlov @ 2021-10-06 12:19 UTC (permalink / raw)
To: [email protected]; +Cc: Nathan Bossart <[email protected]>
The following review has been posted through the commitfest application:
make installcheck-world: tested, passed
Implements feature: tested, failed
Spec compliant: not tested
Documentation: not tested
Hi!
We've looked through the code and everything looks good except few minor things:
1). Using dedicated bg worker seems not optimal, it introduces a lot of redundant code for little single action.
We'd join initial proposal of Andres to implement it as an extra fuction of checkpointer.
2). In our view, it is better shift #define PREALLOCSEGDIR outside the function body.
3). It is better to have at least small comments on functions GetNumPreallocatedWalSegs, SetNumPreallocatedWalSegs,
We've also tested performance difference between master branch and this patch and noticed no significant difference in performance.
We used pgbench with some sort of "standard" settings:
$ pgbench -c50 -s50 -T200 -P1 -r postgres
...and with...
$ pgbench -c100 -s50 -T200 -P1 -r postgres
When looking at every second output of pgbench we saw regular spikes of latency (aroud 5-10 times increase) and this pattern was similar with and without patch. Overall average latency stat for 200 sec of pgbench also looks pretty much the same with and without patch. Could you provide your testing setup to see the effect, please.
The check-world was successfull.
Overall patch looks good, but in our view it's better to have experimental support of the performance improvements to be commited.
---
Best regards,
Maxim Orlov, Pavel Borisov.
The new status of this patch is: Waiting on Author
^ permalink raw reply [nested|flat] 19+ messages in thread
* Re: Pre-allocating WAL files
@ 2021-10-06 16:31 Bossart, Nathan <[email protected]>
parent: Maxim Orlov <[email protected]>
2 siblings, 0 replies; 19+ messages in thread
From: Bossart, Nathan @ 2021-10-06 16:31 UTC (permalink / raw)
To: Maxim Orlov <[email protected]>; [email protected] <[email protected]>
On 10/6/21, 5:20 AM, "Maxim Orlov" <[email protected]> wrote:
> We've looked through the code and everything looks good except few minor things:
> 1). Using dedicated bg worker seems not optimal, it introduces a lot of redundant code for little single action.
> We'd join initial proposal of Andres to implement it as an extra fuction of checkpointer.
Thanks for taking a look!
I have been thinking about the right place to put this logic. My
first thought is that it sounds like something that ought to go in the
WAL writer process, but as Andres noted upthread, that is undesirable
because it'll add latency for asynchronous commits. The checkpointer
process seems to be another candidate, but I'm not totally sure how
it'll fit in. My patch works by maintaining a small pool of pre-
allocated segments that is quickly replenished whenever one is used.
If the checkpointer is spending most of its time checkpointing, this
small pool could remain empty for long periods of time. To keep pre-
allocating WAL while we're checkpointing, perhaps we could add another
function like CheckpointWriteDelay() that is called periodically.
There still might be several operations in CheckPointGuts() that take
a while and leave the segment pool empty, but maybe that's okay for
now.
Nathan
^ permalink raw reply [nested|flat] 19+ messages in thread
* Re: Pre-allocating WAL files
@ 2021-10-08 20:48 Bossart, Nathan <[email protected]>
parent: Maxim Orlov <[email protected]>
2 siblings, 0 replies; 19+ messages in thread
From: Bossart, Nathan @ 2021-10-08 20:48 UTC (permalink / raw)
To: Maxim Orlov <[email protected]>; [email protected] <[email protected]>
On 10/6/21, 9:34 AM, "Bossart, Nathan" <[email protected]> wrote:
> I have been thinking about the right place to put this logic. My
> first thought is that it sounds like something that ought to go in the
> WAL writer process, but as Andres noted upthread, that is undesirable
> because it'll add latency for asynchronous commits. The checkpointer
> process seems to be another candidate, but I'm not totally sure how
> it'll fit in. My patch works by maintaining a small pool of pre-
> allocated segments that is quickly replenished whenever one is used.
> If the checkpointer is spending most of its time checkpointing, this
> small pool could remain empty for long periods of time. To keep pre-
> allocating WAL while we're checkpointing, perhaps we could add another
> function like CheckpointWriteDelay() that is called periodically.
> There still might be several operations in CheckPointGuts() that take
> a while and leave the segment pool empty, but maybe that's okay for
> now.
Here is a first attempt at adding the pre-allocation logic to the
checkpointer. I went ahead and just used CheckpointWriteDelay() for
pre-allocating during checkpoints. I've done a few pgbench runs, and
it seems to work pretty well. Initialization is around 15% faster,
and I'm seeing about a 5% increase in TPS with a simple-update
workload with wal_recycle turned off. Of course, these improvements
go away once segments can be recycled.
Nathan
Attachments:
[application/octet-stream] v6-0001-Move-WAL-segment-creation-logic-to-its-own-functi.patch (7.5K, ../../[email protected]/2-v6-0001-Move-WAL-segment-creation-logic-to-its-own-functi.patch)
download | inline diff:
From 8974fbb7339ca8b75d2c500dde83a26324c92e51 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <[email protected]>
Date: Wed, 6 Oct 2021 17:16:34 +0000
Subject: [PATCH v6 1/2] Move WAL segment creation logic to its own function.
---
src/backend/access/transam/xlog.c | 103 +---------------------------------
src/backend/storage/file/fd.c | 114 ++++++++++++++++++++++++++++++++++++++
src/include/storage/fd.h | 1 +
3 files changed, 116 insertions(+), 102 deletions(-)
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 26dcc00ac0..d0be54bc9b 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -3284,11 +3284,9 @@ static int
XLogFileInitInternal(XLogSegNo logsegno, bool *added, char *path)
{
char tmppath[MAXPGPATH];
- PGAlignedXLogBlock zbuffer;
XLogSegNo installed_segno;
XLogSegNo max_segno;
int fd;
- int save_errno;
XLogFilePath(path, ThisTimeLineID, logsegno, wal_segment_size);
@@ -3316,106 +3314,7 @@ XLogFileInitInternal(XLogSegNo logsegno, bool *added, char *path)
elog(DEBUG2, "creating and filling new WAL file");
snprintf(tmppath, MAXPGPATH, XLOGDIR "/xlogtemp.%d", (int) getpid());
-
- unlink(tmppath);
-
- /* do not use get_sync_bit() here --- want to fsync only at end of fill */
- fd = BasicOpenFile(tmppath, O_RDWR | O_CREAT | O_EXCL | PG_BINARY);
- if (fd < 0)
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not create file \"%s\": %m", tmppath)));
-
- memset(zbuffer.data, 0, XLOG_BLCKSZ);
-
- pgstat_report_wait_start(WAIT_EVENT_WAL_INIT_WRITE);
- save_errno = 0;
- if (wal_init_zero)
- {
- struct iovec iov[PG_IOV_MAX];
- int blocks;
-
- /*
- * Zero-fill the file. With this setting, we do this the hard way to
- * ensure that all the file space has really been allocated. On
- * platforms that allow "holes" in files, just seeking to the end
- * doesn't allocate intermediate space. This way, we know that we
- * have all the space and (after the fsync below) that all the
- * indirect blocks are down on disk. Therefore, fdatasync(2) or
- * O_DSYNC will be sufficient to sync future writes to the log file.
- */
-
- /* Prepare to write out a lot of copies of our zero buffer at once. */
- for (int i = 0; i < lengthof(iov); ++i)
- {
- iov[i].iov_base = zbuffer.data;
- iov[i].iov_len = XLOG_BLCKSZ;
- }
-
- /* Loop, writing as many blocks as we can for each system call. */
- blocks = wal_segment_size / XLOG_BLCKSZ;
- for (int i = 0; i < blocks;)
- {
- int iovcnt = Min(blocks - i, lengthof(iov));
- off_t offset = i * XLOG_BLCKSZ;
-
- if (pg_pwritev_with_retry(fd, iov, iovcnt, offset) < 0)
- {
- save_errno = errno;
- break;
- }
-
- i += iovcnt;
- }
- }
- else
- {
- /*
- * Otherwise, seeking to the end and writing a solitary byte is
- * enough.
- */
- errno = 0;
- if (pg_pwrite(fd, zbuffer.data, 1, wal_segment_size - 1) != 1)
- {
- /* if write didn't set errno, assume no disk space */
- save_errno = errno ? errno : ENOSPC;
- }
- }
- pgstat_report_wait_end();
-
- if (save_errno)
- {
- /*
- * If we fail to make the file, delete it to release disk space
- */
- unlink(tmppath);
-
- close(fd);
-
- errno = save_errno;
-
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not write to file \"%s\": %m", tmppath)));
- }
-
- pgstat_report_wait_start(WAIT_EVENT_WAL_INIT_SYNC);
- if (pg_fsync(fd) != 0)
- {
- int save_errno = errno;
-
- close(fd);
- errno = save_errno;
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not fsync file \"%s\": %m", tmppath)));
- }
- pgstat_report_wait_end();
-
- if (close(fd) != 0)
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not close file \"%s\": %m", tmppath)));
+ CreateEmptyWalSegment(tmppath);
/*
* Now move the segment into place with its final name. Cope with
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index f9cda6906d..a44df86537 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -3870,3 +3870,117 @@ pg_pwritev_with_retry(int fd, const struct iovec *iov, int iovcnt, off_t offset)
return sum;
}
+
+/*
+ * CreateEmptyWalSegment
+ *
+ * Create a new file that can be used as a new WAL segment. The caller is
+ * responsible for installing the new file in pg_wal.
+ */
+void
+CreateEmptyWalSegment(const char *path)
+{
+ PGAlignedXLogBlock zbuffer;
+ int fd;
+ int save_errno;
+
+ unlink(path);
+
+ /* do not use get_sync_bit() here --- want to fsync only at end of fill */
+ fd = BasicOpenFile(path, O_RDWR | O_CREAT | O_EXCL | PG_BINARY);
+ if (fd < 0)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not create file \"%s\": %m", path)));
+
+ memset(zbuffer.data, 0, XLOG_BLCKSZ);
+
+ pgstat_report_wait_start(WAIT_EVENT_WAL_INIT_WRITE);
+ save_errno = 0;
+ if (wal_init_zero)
+ {
+ struct iovec iov[PG_IOV_MAX];
+ int blocks;
+
+ /*
+ * Zero-fill the file. With this setting, we do this the hard way to
+ * ensure that all the file space has really been allocated. On
+ * platforms that allow "holes" in files, just seeking to the end
+ * doesn't allocate intermediate space. This way, we know that we
+ * have all the space and (after the fsync below) that all the
+ * indirect blocks are down on disk. Therefore, fdatasync(2) or
+ * O_DSYNC will be sufficient to sync future writes to the log file.
+ */
+
+ /* Prepare to write out a lot of copies of our zero buffer at once. */
+ for (int i = 0; i < lengthof(iov); ++i)
+ {
+ iov[i].iov_base = zbuffer.data;
+ iov[i].iov_len = XLOG_BLCKSZ;
+ }
+
+ /* Loop, writing as many blocks as we can for each system call. */
+ blocks = wal_segment_size / XLOG_BLCKSZ;
+ for (int i = 0; i < blocks;)
+ {
+ int iovcnt = Min(blocks - i, lengthof(iov));
+ off_t offset = i * XLOG_BLCKSZ;
+
+ if (pg_pwritev_with_retry(fd, iov, iovcnt, offset) < 0)
+ {
+ save_errno = errno;
+ break;
+ }
+
+ i += iovcnt;
+ }
+ }
+ else
+ {
+ /*
+ * Otherwise, seeking to the end and writing a solitary byte is
+ * enough.
+ */
+ errno = 0;
+ if (pg_pwrite(fd, zbuffer.data, 1, wal_segment_size - 1) != 1)
+ {
+ /* if write didn't set errno, assume no disk space */
+ save_errno = errno ? errno : ENOSPC;
+ }
+ }
+ pgstat_report_wait_end();
+
+ if (save_errno)
+ {
+ /*
+ * If we fail to make the file, delete it to release disk space
+ */
+ unlink(path);
+
+ close(fd);
+
+ errno = save_errno;
+
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not write to file \"%s\": %m", path)));
+ }
+
+ pgstat_report_wait_start(WAIT_EVENT_WAL_INIT_SYNC);
+ if (pg_fsync(fd) != 0)
+ {
+ int save_errno = errno;
+
+ close(fd);
+ errno = save_errno;
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not fsync file \"%s\": %m", path)));
+ }
+ pgstat_report_wait_end();
+
+ if (close(fd) != 0)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not close file \"%s\": %m", path)));
+}
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index 34602ae006..abcd74bd51 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -190,6 +190,7 @@ extern int durable_unlink(const char *fname, int loglevel);
extern int durable_rename_excl(const char *oldfile, const char *newfile, int loglevel);
extern void SyncDataDirectory(void);
extern int data_sync_elevel(int elevel);
+extern void CreateEmptyWalSegment(const char *path);
/* Filename components */
#define PG_TEMP_FILES_DIR "pgsql_tmp"
--
2.16.6
[application/octet-stream] v6-0002-WAL-segment-pre-allocation.patch (24.0K, ../../[email protected]/3-v6-0002-WAL-segment-pre-allocation.patch)
download | inline diff:
From 78329f1a6b51639eb8f69426ef127091b44c3895 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <[email protected]>
Date: Fri, 8 Oct 2021 20:22:23 +0000
Subject: [PATCH v6 2/2] WAL segment pre-allocation.
---
doc/src/sgml/config.sgml | 23 +++
src/backend/access/transam/xlog.c | 60 ++++++-
src/backend/postmaster/checkpointer.c | 234 ++++++++++++++++++++++++++
src/backend/replication/basebackup.c | 6 +-
src/backend/storage/file/fd.c | 22 ++-
src/backend/storage/lmgr/lwlocknames.txt | 1 +
src/backend/utils/misc/guc.c | 11 ++
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/bin/initdb/initdb.c | 1 +
src/bin/pg_basebackup/pg_basebackup.c | 19 ++-
src/bin/pg_basebackup/t/010_pg_basebackup.pl | 4 +-
src/bin/pg_resetwal/pg_resetwal.c | 48 ++++++
src/include/postmaster/bgwriter.h | 5 +
src/include/storage/fd.h | 2 +-
14 files changed, 417 insertions(+), 20 deletions(-)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 0a8e35c59f..0e66e415ff 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -3203,6 +3203,29 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-wal-preallocate-max-size" xreflabel="wal_preallocate_max_size">
+ <term><varname>wal_preallocate_max_size</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>wal_preallocate_max_size</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ The maximum amount of WAL to pre-allocate for use when a new WAL segment
+ must be initialized. Setting this parameter to a size less than the WAL
+ segment size disables this behavior. The default value is 64 megabytes
+ (<literal>64MB</literal>). This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command line.
+ </para>
+
+ <para>
+ WAL pre-allocation may improve performance in scenarios where new WAL
+ segments must be created (e.g., during checkpoints when WAL segments
+ cannot be recycled).
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-wal-buffers" xreflabel="wal_buffers">
<term><varname>wal_buffers</varname> (<type>integer</type>)
<indexterm>
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index d0be54bc9b..e0b84321fb 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -3287,6 +3287,8 @@ XLogFileInitInternal(XLogSegNo logsegno, bool *added, char *path)
XLogSegNo installed_segno;
XLogSegNo max_segno;
int fd;
+ int prealloc_segs;
+ bool found_prealloc = false;
XLogFilePath(path, ThisTimeLineID, logsegno, wal_segment_size);
@@ -3306,15 +3308,45 @@ XLogFileInitInternal(XLogSegNo logsegno, bool *added, char *path)
return fd;
/*
- * Initialize an empty (all zeroes) segment. NOTE: it is possible that
- * another process is doing the same thing. If so, we will end up
- * pre-creating an extra log segment. That seems OK, and better than
- * holding the lock throughout this lengthy process.
+ * Try to use a pre-allocated segment, if one exists. If none are
+ * available, we fall back to creating a new segment on our own.
+ *
+ * Note that we still look for a pre-allocated segment even if the pre-
+ * allocation functionality is disabled via the GUCs. This ensures that any
+ * pre-allocated segments left over after turning off the pre-allocation
+ * functionality are still eligible for use.
*/
- elog(DEBUG2, "creating and filling new WAL file");
+ LWLockAcquire(WALPreallocationLock, LW_EXCLUSIVE);
+ prealloc_segs = GetNumPreallocatedWalSegs();
+ if (prealloc_segs > 0)
+ {
+ elog(DEBUG2, "using pre-allocated WAL file");
- snprintf(tmppath, MAXPGPATH, XLOGDIR "/xlogtemp.%d", (int) getpid());
- CreateEmptyWalSegment(tmppath);
+ found_prealloc = true;
+ prealloc_segs--;
+ SetNumPreallocatedWalSegs(prealloc_segs);
+ snprintf(tmppath, MAXPGPATH, "%s/preallocated_segments/xlogtemp.%d",
+ XLOGDIR, prealloc_segs);
+ }
+ else
+ {
+ /*
+ * We're not using a pre-allocated segment, so there's no need to keep
+ * holding the WALPreallocationLock.
+ */
+ LWLockRelease(WALPreallocationLock);
+
+ /*
+ * Initialize an empty (all zeroes) segment. NOTE: it is possible that
+ * another process is doing the same thing. If so, we will end up
+ * pre-creating an extra log segment. That seems OK, and better than
+ * holding the lock throughout this lengthy process.
+ */
+ elog(DEBUG2, "creating and filling new WAL file");
+
+ snprintf(tmppath, MAXPGPATH, XLOGDIR "/xlogtemp.%d", (int) getpid());
+ CreateEmptyWalSegment(tmppath, ERROR);
+ }
/*
* Now move the segment into place with its final name. Cope with
@@ -3336,7 +3368,7 @@ XLogFileInitInternal(XLogSegNo logsegno, bool *added, char *path)
if (InstallXLogFileSegment(&installed_segno, tmppath, true, max_segno))
{
*added = true;
- elog(DEBUG2, "done creating and filling new WAL file");
+ elog(DEBUG2, "done installing new WAL file");
}
else
{
@@ -3349,6 +3381,18 @@ XLogFileInitInternal(XLogSegNo logsegno, bool *added, char *path)
elog(DEBUG2, "abandoned new WAL file");
}
+ /*
+ * If we are using a pre-allocated segment, we've been holding onto the
+ * WALPreallocationLock all this time so that the checkpointer process can't
+ * overwrite the file before we've installed it.
+ *
+ * While we're at it, also nudge the checkpointer process so that it pre-
+ * allocates new segments if possible.
+ */
+ if (found_prealloc)
+ LWLockRelease(WALPreallocationLock);
+ RequestWalPreallocation();
+
return -1;
}
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index be7366379d..3237595dbf 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -127,6 +127,9 @@ typedef struct
uint32 num_backend_writes; /* counts user backend buffer writes */
uint32 num_backend_fsync; /* counts user backend fsync calls */
+ bool wal_prealloc_requested; /* protected by ckpt_lck */
+ int num_prealloc_segs; /* protected by WALPreallocationLock */
+
int num_requests; /* current # of requests */
int max_requests; /* allocated array size */
CheckpointerRequest requests[FLEXIBLE_ARRAY_MEMBER];
@@ -143,6 +146,7 @@ static CheckpointerShmemStruct *CheckpointerShmem;
int CheckPointTimeout = 300;
int CheckPointWarning = 30;
double CheckPointCompletionTarget = 0.9;
+int wal_prealloc_max_size_mb = 0;
/*
* Private state
@@ -165,6 +169,10 @@ static bool IsCheckpointOnSchedule(double progress);
static bool ImmediateCheckpointRequested(void);
static bool CompactCheckpointerRequestQueue(void);
static void UpdateSharedMemoryConfig(void);
+static void ScanForExistingPreallocatedSegments(void);
+static bool InstallPreallocatedWalSeg(const char *path);
+static void DoWalPreAllocation(void);
+static int GetMaxPreallocatedWalSegs(void);
/* Signal handlers */
static void ReqCheckpointHandler(SIGNAL_ARGS);
@@ -329,6 +337,11 @@ CheckpointerMain(void)
*/
ProcGlobal->checkpointerLatch = &MyProc->procLatch;
+ /*
+ * Look for any leftover pre-allocated segments we can use.
+ */
+ ScanForExistingPreallocatedSegments();
+
/*
* Loop forever
*/
@@ -349,6 +362,11 @@ CheckpointerMain(void)
AbsorbSyncRequests();
HandleCheckpointerInterrupts();
+ /*
+ * First do WAL pre-allocation.
+ */
+ DoWalPreAllocation();
+
/*
* Detect a pending checkpoint request by checking whether the flags
* word in shared memory is nonzero. We shouldn't need to acquire the
@@ -507,6 +525,12 @@ CheckpointerMain(void)
if (((volatile CheckpointerShmemStruct *) CheckpointerShmem)->ckpt_flags)
continue;
+ /*
+ * Also skip sleeping if WAL pre-allocation has been requested.
+ */
+ if (((volatile CheckpointerShmemStruct *) CheckpointerShmem)->wal_prealloc_requested)
+ continue;
+
/*
* Sleep until we are signaled or it's time for another checkpoint or
* xlog file switch.
@@ -682,6 +706,8 @@ ImmediateCheckpointRequested(void)
*
* 'progress' is an estimate of how much of the work has been done, as a
* fraction between 0.0 meaning none, and 1.0 meaning all done.
+ *
+ * This function also takes care of handling any WAL pre-allocation requests.
*/
void
CheckpointWriteDelay(int flags, double progress)
@@ -692,6 +718,15 @@ CheckpointWriteDelay(int flags, double progress)
if (!AmCheckpointerProcess())
return;
+ /*
+ * WAL pre-allocation has nothing to do with throttling BufferSync()'s write
+ * rate. However, since this function is called frequently during
+ * checkpoints, it is a convenient place to handle any pending WAL pre-
+ * allocation requests.
+ */
+ if (((volatile CheckpointerShmemStruct *) CheckpointerShmem)->wal_prealloc_requested)
+ DoWalPreAllocation();
+
/*
* Perform the usual duties and take a nap, unless we're behind schedule,
* in which case we just try to catch up as quickly as possible.
@@ -823,6 +858,203 @@ IsCheckpointOnSchedule(double progress)
return true;
}
+/*
+ * GetNumPreallocatedWalSegs
+ *
+ * Returns the number of pre-allocated WAL segments in the preallocated_segments
+ * directory. Callers are expected to hold the WALPreallocationLock.
+ */
+int
+GetNumPreallocatedWalSegs(void)
+{
+ Assert(LWLockHeldByMe(WALPreallocationLock));
+ return CheckpointerShmem->num_prealloc_segs;
+}
+
+/*
+ * SetNumPreallocatedWalSegs
+ *
+ * Sets the number of pre-allocated WAL segments in the preallocated_segments
+ * directory. Callers are expected to hold the WALPreallocationLock
+ * exclusively.
+ */
+void
+SetNumPreallocatedWalSegs(int i)
+{
+ Assert(LWLockHeldByMeInMode(WALPreallocationLock, LW_EXCLUSIVE));
+ CheckpointerShmem->num_prealloc_segs = i;
+}
+
+/*
+ * ScanForExistingPreallocatedSegments
+ *
+ * This function searches through pg_wal/preallocated_segments for any segments
+ * that were left over and sets the tracking variable in shared memory
+ * accordingly.
+ */
+static void
+ScanForExistingPreallocatedSegments(void)
+{
+ int i = 0;
+
+ /*
+ * fsync the preallocated_segments directory in case any renames have yet to
+ * be flushed to disk.
+ */
+ fsync_fname_ext(XLOGDIR "/preallocated_segments", true, false, FATAL);
+
+ /*
+ * Gather all the preallocated segments we can find.
+ */
+ while (true)
+ {
+ FILE *fd;
+ char path[MAXPGPATH];
+
+ snprintf(path, MAXPGPATH, "%s/preallocated_segments/xlogtemp.%d",
+ XLOGDIR, i);
+
+ fd = AllocateFile(path, "r");
+ if (fd != NULL)
+ {
+ FreeFile(fd);
+ i++;
+ }
+ else
+ {
+ if (errno != ENOENT)
+ ereport(FATAL,
+ (errcode_for_file_access(),
+ errmsg("could not open file \"%s\": %m", path)));
+ break;
+ }
+ }
+
+ LWLockAcquire(WALPreallocationLock, LW_EXCLUSIVE);
+ SetNumPreallocatedWalSegs(i);
+ LWLockRelease(WALPreallocationLock);
+
+ elog(DEBUG2, "found %d preallocated segments during startup", i);
+}
+
+/*
+ * InstallPreallocatedWalSeg
+ *
+ * Renames the file at "path" to the next open pre-allocated segment slot and
+ * bumps up num_prealloc_segs. If there is a problem, a WARNING is emitted, we
+ * attempt to delete the file, and false is returned. Otherwise, true is
+ * returned.
+ */
+static bool
+InstallPreallocatedWalSeg(const char *path)
+{
+ char newpath[MAXPGPATH];
+ int rc;
+
+ LWLockAcquire(WALPreallocationLock, LW_EXCLUSIVE);
+
+ snprintf(newpath, MAXPGPATH, "%s/preallocated_segments/xlogtemp.%d",
+ XLOGDIR, CheckpointerShmem->num_prealloc_segs);
+
+ rc = durable_rename(path, newpath, DEBUG1);
+ if (rc == 0)
+ CheckpointerShmem->num_prealloc_segs++;
+ else
+ {
+ ereport(WARNING,
+ (errcode_for_file_access(),
+ errmsg("file \"%s\" could not be renamed to \"%s\": %m",
+ path, newpath)));
+
+ (void) durable_unlink(path, DEBUG1);
+ (void) durable_unlink(newpath, DEBUG1);
+ }
+
+ LWLockRelease(WALPreallocationLock);
+
+ return (rc == 0);
+}
+
+/*
+ * DoWalPreAllocation
+ *
+ * Tries to allocate up to wal_preallocate_max_size worth of WAL.
+ */
+static void
+DoWalPreAllocation(void)
+{
+ int segs_to_prealloc;
+
+ /*
+ * Reset the request flag.
+ */
+ SpinLockAcquire(&CheckpointerShmem->ckpt_lck);
+ CheckpointerShmem->wal_prealloc_requested = false;
+ SpinLockRelease(&CheckpointerShmem->ckpt_lck);
+
+ /*
+ * Determine how many segments to pre-allocate.
+ */
+ LWLockAcquire(WALPreallocationLock, LW_SHARED);
+ segs_to_prealloc = GetMaxPreallocatedWalSegs() - GetNumPreallocatedWalSegs();
+ LWLockRelease(WALPreallocationLock);
+
+ /*
+ * Do the pre-allocation.
+ */
+ for (int i = 0; i < segs_to_prealloc; i++)
+ {
+ char tmppath[MAXPGPATH];
+
+ snprintf(tmppath, MAXPGPATH, "%s/preallocated_segments/xlogtemp", XLOGDIR);
+
+ if (!CreateEmptyWalSegment(tmppath, WARNING) ||
+ !InstallPreallocatedWalSeg(tmppath))
+ {
+ elog(DEBUG2, "failed to pre-allocate WAL segment");
+ return;
+ }
+ else
+ elog(DEBUG2, "pre-allocated WAL segment");
+
+ /* Check for barrier events. */
+ if (ProcSignalBarrierPending)
+ ProcessProcSignalBarrier();
+
+ if (ShutdownRequestPending)
+ return;
+ }
+}
+
+/*
+ * GetMaxPreallocatedWalSegs
+ *
+ * Returns the maximum number of pre-allocated WAL segments to create based on
+ * the current value of wal_preallocate_max_size.
+ */
+static int
+GetMaxPreallocatedWalSegs(void)
+{
+ return wal_prealloc_max_size_mb / (wal_segment_size / (1024 * 1024));
+}
+
+/*
+ * RequestWalPreallocation
+ *
+ * Requests that more segments be pre-allocated for future use.
+ */
+void
+RequestWalPreallocation(void)
+{
+ SpinLockAcquire(&CheckpointerShmem->ckpt_lck);
+ CheckpointerShmem->wal_prealloc_requested = true;
+ SpinLockRelease(&CheckpointerShmem->ckpt_lck);
+
+ if (ProcGlobal->checkpointerLatch &&
+ GetMaxPreallocatedWalSegs() > 0)
+ SetLatch(ProcGlobal->checkpointerLatch);
+}
+
/* --------------------------------
* signal handler routines
@@ -896,6 +1128,8 @@ CheckpointerShmemInit(void)
CheckpointerShmem->max_requests = NBuffers;
ConditionVariableInit(&CheckpointerShmem->start_cv);
ConditionVariableInit(&CheckpointerShmem->done_cv);
+ CheckpointerShmem->wal_prealloc_requested = false;
+ CheckpointerShmem->num_prealloc_segs = 0;
}
}
diff --git a/src/backend/replication/basebackup.c b/src/backend/replication/basebackup.c
index 4c97ab7b5a..e84288a8a7 100644
--- a/src/backend/replication/basebackup.c
+++ b/src/backend/replication/basebackup.c
@@ -1412,11 +1412,13 @@ sendDir(const char *path, int basepathlen, bool sizeonly, List *tablespaces,
size += _tarWriteDir(pathbuf, basepathlen, &statbuf, sizeonly);
/*
- * Also send archive_status directory (by hackishly reusing
- * statbuf from above ...).
+ * Also send archive_status and preallocated_segments (by hackishly
+ * reusing statbuf from above ...).
*/
size += _tarWriteHeader("./pg_wal/archive_status", NULL, &statbuf,
sizeonly);
+ size += _tarWriteHeader("./pg_wal/preallocated_segments", NULL, &statbuf,
+ sizeonly);
continue; /* don't recurse into pg_wal */
}
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index a44df86537..e5425597df 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -3877,8 +3877,8 @@ pg_pwritev_with_retry(int fd, const struct iovec *iov, int iovcnt, off_t offset)
* Create a new file that can be used as a new WAL segment. The caller is
* responsible for installing the new file in pg_wal.
*/
-void
-CreateEmptyWalSegment(const char *path)
+bool
+CreateEmptyWalSegment(const char *path, int elevel)
{
PGAlignedXLogBlock zbuffer;
int fd;
@@ -3889,9 +3889,12 @@ CreateEmptyWalSegment(const char *path)
/* do not use get_sync_bit() here --- want to fsync only at end of fill */
fd = BasicOpenFile(path, O_RDWR | O_CREAT | O_EXCL | PG_BINARY);
if (fd < 0)
- ereport(ERROR,
+ {
+ ereport(elevel,
(errcode_for_file_access(),
errmsg("could not create file \"%s\": %m", path)));
+ return false;
+ }
memset(zbuffer.data, 0, XLOG_BLCKSZ);
@@ -3961,9 +3964,10 @@ CreateEmptyWalSegment(const char *path)
errno = save_errno;
- ereport(ERROR,
+ ereport(elevel,
(errcode_for_file_access(),
errmsg("could not write to file \"%s\": %m", path)));
+ return false;
}
pgstat_report_wait_start(WAIT_EVENT_WAL_INIT_SYNC);
@@ -3973,14 +3977,20 @@ CreateEmptyWalSegment(const char *path)
close(fd);
errno = save_errno;
- ereport(ERROR,
+ ereport(elevel,
(errcode_for_file_access(),
errmsg("could not fsync file \"%s\": %m", path)));
+ return false;
}
pgstat_report_wait_end();
if (close(fd) != 0)
- ereport(ERROR,
+ {
+ ereport(elevel,
(errcode_for_file_access(),
errmsg("could not close file \"%s\": %m", path)));
+ return false;
+ }
+
+ return true;
}
diff --git a/src/backend/storage/lmgr/lwlocknames.txt b/src/backend/storage/lmgr/lwlocknames.txt
index 6c7cf6c295..212bda8e7d 100644
--- a/src/backend/storage/lmgr/lwlocknames.txt
+++ b/src/backend/storage/lmgr/lwlocknames.txt
@@ -53,3 +53,4 @@ XactTruncationLock 44
# 45 was XactTruncationLock until removal of BackendRandomLock
WrapLimitsVacuumLock 46
NotifyQueueTailLock 47
+WALPreallocationLock 48
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index d2ce4a8450..4d3274a70c 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -2840,6 +2840,17 @@ static struct config_int ConfigureNamesInt[] =
NULL, NULL, NULL
},
+ {
+ {"wal_preallocate_max_size", PGC_SIGHUP, WAL_SETTINGS,
+ gettext_noop("Sets the maximum amount of WAL to pre-allocate."),
+ NULL,
+ GUC_UNIT_MB
+ },
+ &wal_prealloc_max_size_mb,
+ 64, 0, 102400,
+ NULL, NULL, NULL
+ },
+
{
{"wal_buffers", PGC_POSTMASTER, WAL_SETTINGS,
gettext_noop("Sets the number of disk-page buffers in shared memory for WAL."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 3fe9a53cb3..309745e932 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -223,6 +223,7 @@
# off, pglz, lz4, or on
#wal_init_zero = on # zero-fill new WAL files
#wal_recycle = on # recycle WAL files
+#wal_preallocate_max_size = 64MB # amount of WAL to pre-allocate
#wal_buffers = -1 # min 32kB, -1 sets based on shared_buffers
# (change requires restart)
#wal_writer_delay = 200ms # 1-10000 milliseconds
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 1ed4808d53..aa52aaeffb 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -208,6 +208,7 @@ static char *extra_options = "";
static const char *const subdirs[] = {
"global",
"pg_wal/archive_status",
+ "pg_wal/preallocated_segments",
"pg_commit_ts",
"pg_dynshmem",
"pg_notify",
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index 27ee6394cf..0623cfe4b3 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -680,6 +680,22 @@ StartLogStreamer(char *startpos, uint32 timeline, char *sysidentifier)
pg_log_error("could not create directory \"%s\": %m", statusdir);
exit(1);
}
+
+ /*
+ * Also create pg_wal/preallocated_segments if necessary.
+ */
+ if (PQserverVersion(conn) >= 150000)
+ {
+ char prealloc_dir[MAXPGPATH];
+
+ snprintf(prealloc_dir, sizeof(prealloc_dir), "%s/pg_wal/preallocated_segments",
+ basedir);
+ if (pg_mkdir_p(prealloc_dir, pg_dir_create_mode) != 0 && errno != EEXIST)
+ {
+ pg_log_error("could not create directory \"%s\": %m", prealloc_dir);
+ exit(1);
+ }
+ }
}
/*
@@ -1611,7 +1627,8 @@ ReceiveTarAndUnpackCopyChunk(size_t r, char *copybuf, void *callback_data)
*/
if (!((pg_str_endswith(state->filename, "/pg_wal") ||
pg_str_endswith(state->filename, "/pg_xlog") ||
- pg_str_endswith(state->filename, "/archive_status")) &&
+ pg_str_endswith(state->filename, "/archive_status") ||
+ pg_str_endswith(state->filename, "/preallocated_segments")) &&
errno == EEXIST))
{
pg_log_error("could not create directory \"%s\": %m",
diff --git a/src/bin/pg_basebackup/t/010_pg_basebackup.pl b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
index a2cb2a7679..43e1e3e89d 100644
--- a/src/bin/pg_basebackup/t/010_pg_basebackup.pl
+++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
@@ -120,10 +120,10 @@ SKIP:
"check backup dir permissions");
}
-# Only archive_status directory should be copied in pg_wal/.
+# Only archive_status and preallocated_segments directories should be copied in pg_wal/.
is_deeply(
[ sort(slurp_dir("$tempdir/backup/pg_wal/")) ],
- [ sort qw(. .. archive_status) ],
+ [ sort qw(. .. archive_status preallocated_segments) ],
'no WAL files copied');
# Contents of these directories should not be copied.
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 84b6f70ad6..ca09ad72d2 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -85,6 +85,7 @@ static void RewriteControlFile(void);
static void FindEndOfXLOG(void);
static void KillExistingXLOG(void);
static void KillExistingArchiveStatus(void);
+static void KillExistingPreallocatedSegments(void);
static void WriteEmptyXLOG(void);
static void usage(void);
@@ -530,6 +531,7 @@ main(int argc, char *argv[])
RewriteControlFile();
KillExistingXLOG();
KillExistingArchiveStatus();
+ KillExistingPreallocatedSegments();
WriteEmptyXLOG();
printf(_("Write-ahead log reset\n"));
@@ -1120,6 +1122,52 @@ KillExistingArchiveStatus(void)
}
+/*
+ * Remove existing preallocated segments
+ */
+static void
+KillExistingPreallocatedSegments(void)
+{
+#define PREALLOCSEGDIR XLOGDIR "/preallocated_segments"
+
+ DIR *xldir;
+ struct dirent *xlde;
+ char path[MAXPGPATH + sizeof(PREALLOCSEGDIR)];
+
+ xldir = opendir(PREALLOCSEGDIR);
+ if (xldir == NULL)
+ {
+ pg_log_error("could not open directory \"%s\": %m", PREALLOCSEGDIR);
+ exit(1);
+ }
+
+ while (errno = 0, (xlde = readdir(xldir)) != NULL)
+ {
+ if (strncmp(xlde->d_name, "xlogtemp", strlen("xlogtemp")) == 0)
+ {
+ snprintf(path, sizeof(path), "%s/%s", PREALLOCSEGDIR, xlde->d_name);
+ if (unlink(path) < 0)
+ {
+ pg_log_error("could not delete file \"%s\": %m", path);
+ exit(1);
+ }
+ }
+ }
+
+ if (errno)
+ {
+ pg_log_error("could not read directory \"%s\": %m", PREALLOCSEGDIR);
+ exit(1);
+ }
+
+ if (closedir(xldir))
+ {
+ pg_log_error("could not close directory \"%s\": %m", PREALLOCSEGDIR);
+ exit(1);
+ }
+}
+
+
/*
* Write an empty XLOG file, containing only the checkpoint record
* already set up in ControlFile.
diff --git a/src/include/postmaster/bgwriter.h b/src/include/postmaster/bgwriter.h
index c430b1b236..8303eb48cb 100644
--- a/src/include/postmaster/bgwriter.h
+++ b/src/include/postmaster/bgwriter.h
@@ -26,6 +26,7 @@ extern int BgWriterDelay;
extern int CheckPointTimeout;
extern int CheckPointWarning;
extern double CheckPointCompletionTarget;
+extern int wal_prealloc_max_size_mb;
extern void BackgroundWriterMain(void) pg_attribute_noreturn();
extern void CheckpointerMain(void) pg_attribute_noreturn();
@@ -42,4 +43,8 @@ extern void CheckpointerShmemInit(void);
extern bool FirstCallSinceLastCheckpoint(void);
+extern int GetNumPreallocatedWalSegs(void);
+extern void SetNumPreallocatedWalSegs(int i);
+extern void RequestWalPreallocation(void);
+
#endif /* _BGWRITER_H */
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index abcd74bd51..2d174509c5 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -190,7 +190,7 @@ extern int durable_unlink(const char *fname, int loglevel);
extern int durable_rename_excl(const char *oldfile, const char *newfile, int loglevel);
extern void SyncDataDirectory(void);
extern int data_sync_elevel(int elevel);
-extern void CreateEmptyWalSegment(const char *path);
+extern bool CreateEmptyWalSegment(const char *path, int elevel);
/* Filename components */
#define PG_TEMP_FILES_DIR "pgsql_tmp"
--
2.16.6
^ permalink raw reply [nested|flat] 19+ messages in thread
* Re: Pre-allocating WAL files
@ 2021-11-10 18:59 Bossart, Nathan <[email protected]>
parent: Maxim Orlov <[email protected]>
2 siblings, 2 replies; 19+ messages in thread
From: Bossart, Nathan @ 2021-11-10 18:59 UTC (permalink / raw)
To: Maxim Orlov <[email protected]>; [email protected] <[email protected]>
On 10/8/21, 1:55 PM, "Bossart, Nathan" <[email protected]> wrote:
> Here is a first attempt at adding the pre-allocation logic to the
> checkpointer. I went ahead and just used CheckpointWriteDelay() for
> pre-allocating during checkpoints. I've done a few pgbench runs, and
> it seems to work pretty well. Initialization is around 15% faster,
> and I'm seeing about a 5% increase in TPS with a simple-update
> workload with wal_recycle turned off. Of course, these improvements
> go away once segments can be recycled.
Here is a rebased version of this patch set. I'm getting the sense
that there isn't a whole lot of interest in this feature, so I'll
likely withdraw it if it goes too much longer without traction.
Nathan
Attachments:
[application/octet-stream] v7-0001-Move-WAL-segment-creation-logic-to-its-own-functi.patch (7.5K, ../../[email protected]/2-v7-0001-Move-WAL-segment-creation-logic-to-its-own-functi.patch)
download | inline diff:
From e6c004cc7778f9fe077a95ca8761ea4c36c26fcf Mon Sep 17 00:00:00 2001
From: Nathan Bossart <[email protected]>
Date: Wed, 10 Nov 2021 18:35:14 +0000
Subject: [PATCH v7 1/2] Move WAL segment creation logic to its own function.
---
src/backend/access/transam/xlog.c | 103 +---------------------------------
src/backend/storage/file/fd.c | 114 ++++++++++++++++++++++++++++++++++++++
src/include/storage/fd.h | 1 +
3 files changed, 116 insertions(+), 102 deletions(-)
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index e073121a7e..60346f5406 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -3325,11 +3325,9 @@ XLogFileInitInternal(XLogSegNo logsegno, TimeLineID logtli,
bool *added, char *path)
{
char tmppath[MAXPGPATH];
- PGAlignedXLogBlock zbuffer;
XLogSegNo installed_segno;
XLogSegNo max_segno;
int fd;
- int save_errno;
Assert(logtli != 0);
@@ -3359,106 +3357,7 @@ XLogFileInitInternal(XLogSegNo logsegno, TimeLineID logtli,
elog(DEBUG2, "creating and filling new WAL file");
snprintf(tmppath, MAXPGPATH, XLOGDIR "/xlogtemp.%d", (int) getpid());
-
- unlink(tmppath);
-
- /* do not use get_sync_bit() here --- want to fsync only at end of fill */
- fd = BasicOpenFile(tmppath, O_RDWR | O_CREAT | O_EXCL | PG_BINARY);
- if (fd < 0)
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not create file \"%s\": %m", tmppath)));
-
- memset(zbuffer.data, 0, XLOG_BLCKSZ);
-
- pgstat_report_wait_start(WAIT_EVENT_WAL_INIT_WRITE);
- save_errno = 0;
- if (wal_init_zero)
- {
- struct iovec iov[PG_IOV_MAX];
- int blocks;
-
- /*
- * Zero-fill the file. With this setting, we do this the hard way to
- * ensure that all the file space has really been allocated. On
- * platforms that allow "holes" in files, just seeking to the end
- * doesn't allocate intermediate space. This way, we know that we
- * have all the space and (after the fsync below) that all the
- * indirect blocks are down on disk. Therefore, fdatasync(2) or
- * O_DSYNC will be sufficient to sync future writes to the log file.
- */
-
- /* Prepare to write out a lot of copies of our zero buffer at once. */
- for (int i = 0; i < lengthof(iov); ++i)
- {
- iov[i].iov_base = zbuffer.data;
- iov[i].iov_len = XLOG_BLCKSZ;
- }
-
- /* Loop, writing as many blocks as we can for each system call. */
- blocks = wal_segment_size / XLOG_BLCKSZ;
- for (int i = 0; i < blocks;)
- {
- int iovcnt = Min(blocks - i, lengthof(iov));
- off_t offset = i * XLOG_BLCKSZ;
-
- if (pg_pwritev_with_retry(fd, iov, iovcnt, offset) < 0)
- {
- save_errno = errno;
- break;
- }
-
- i += iovcnt;
- }
- }
- else
- {
- /*
- * Otherwise, seeking to the end and writing a solitary byte is
- * enough.
- */
- errno = 0;
- if (pg_pwrite(fd, zbuffer.data, 1, wal_segment_size - 1) != 1)
- {
- /* if write didn't set errno, assume no disk space */
- save_errno = errno ? errno : ENOSPC;
- }
- }
- pgstat_report_wait_end();
-
- if (save_errno)
- {
- /*
- * If we fail to make the file, delete it to release disk space
- */
- unlink(tmppath);
-
- close(fd);
-
- errno = save_errno;
-
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not write to file \"%s\": %m", tmppath)));
- }
-
- pgstat_report_wait_start(WAIT_EVENT_WAL_INIT_SYNC);
- if (pg_fsync(fd) != 0)
- {
- int save_errno = errno;
-
- close(fd);
- errno = save_errno;
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not fsync file \"%s\": %m", tmppath)));
- }
- pgstat_report_wait_end();
-
- if (close(fd) != 0)
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not close file \"%s\": %m", tmppath)));
+ CreateEmptyWalSegment(tmppath);
/*
* Now move the segment into place with its final name. Cope with
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index cb1a8dd34f..d42bca6cab 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -3889,3 +3889,117 @@ pg_pwritev_with_retry(int fd, const struct iovec *iov, int iovcnt, off_t offset)
return sum;
}
+
+/*
+ * CreateEmptyWalSegment
+ *
+ * Create a new file that can be used as a new WAL segment. The caller is
+ * responsible for installing the new file in pg_wal.
+ */
+void
+CreateEmptyWalSegment(const char *path)
+{
+ PGAlignedXLogBlock zbuffer;
+ int fd;
+ int save_errno;
+
+ unlink(path);
+
+ /* do not use get_sync_bit() here --- want to fsync only at end of fill */
+ fd = BasicOpenFile(path, O_RDWR | O_CREAT | O_EXCL | PG_BINARY);
+ if (fd < 0)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not create file \"%s\": %m", path)));
+
+ memset(zbuffer.data, 0, XLOG_BLCKSZ);
+
+ pgstat_report_wait_start(WAIT_EVENT_WAL_INIT_WRITE);
+ save_errno = 0;
+ if (wal_init_zero)
+ {
+ struct iovec iov[PG_IOV_MAX];
+ int blocks;
+
+ /*
+ * Zero-fill the file. With this setting, we do this the hard way to
+ * ensure that all the file space has really been allocated. On
+ * platforms that allow "holes" in files, just seeking to the end
+ * doesn't allocate intermediate space. This way, we know that we
+ * have all the space and (after the fsync below) that all the
+ * indirect blocks are down on disk. Therefore, fdatasync(2) or
+ * O_DSYNC will be sufficient to sync future writes to the log file.
+ */
+
+ /* Prepare to write out a lot of copies of our zero buffer at once. */
+ for (int i = 0; i < lengthof(iov); ++i)
+ {
+ iov[i].iov_base = zbuffer.data;
+ iov[i].iov_len = XLOG_BLCKSZ;
+ }
+
+ /* Loop, writing as many blocks as we can for each system call. */
+ blocks = wal_segment_size / XLOG_BLCKSZ;
+ for (int i = 0; i < blocks;)
+ {
+ int iovcnt = Min(blocks - i, lengthof(iov));
+ off_t offset = i * XLOG_BLCKSZ;
+
+ if (pg_pwritev_with_retry(fd, iov, iovcnt, offset) < 0)
+ {
+ save_errno = errno;
+ break;
+ }
+
+ i += iovcnt;
+ }
+ }
+ else
+ {
+ /*
+ * Otherwise, seeking to the end and writing a solitary byte is
+ * enough.
+ */
+ errno = 0;
+ if (pg_pwrite(fd, zbuffer.data, 1, wal_segment_size - 1) != 1)
+ {
+ /* if write didn't set errno, assume no disk space */
+ save_errno = errno ? errno : ENOSPC;
+ }
+ }
+ pgstat_report_wait_end();
+
+ if (save_errno)
+ {
+ /*
+ * If we fail to make the file, delete it to release disk space
+ */
+ unlink(path);
+
+ close(fd);
+
+ errno = save_errno;
+
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not write to file \"%s\": %m", path)));
+ }
+
+ pgstat_report_wait_start(WAIT_EVENT_WAL_INIT_SYNC);
+ if (pg_fsync(fd) != 0)
+ {
+ int save_errno = errno;
+
+ close(fd);
+ errno = save_errno;
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not fsync file \"%s\": %m", path)));
+ }
+ pgstat_report_wait_end();
+
+ if (close(fd) != 0)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not close file \"%s\": %m", path)));
+}
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index 34602ae006..abcd74bd51 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -190,6 +190,7 @@ extern int durable_unlink(const char *fname, int loglevel);
extern int durable_rename_excl(const char *oldfile, const char *newfile, int loglevel);
extern void SyncDataDirectory(void);
extern int data_sync_elevel(int elevel);
+extern void CreateEmptyWalSegment(const char *path);
/* Filename components */
#define PG_TEMP_FILES_DIR "pgsql_tmp"
--
2.16.6
[application/octet-stream] v7-0002-WAL-segment-pre-allocation.patch (24.0K, ../../[email protected]/3-v7-0002-WAL-segment-pre-allocation.patch)
download | inline diff:
From c9c7746d11a52c00f963b17cc4efc5b16b126758 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <[email protected]>
Date: Wed, 10 Nov 2021 18:46:56 +0000
Subject: [PATCH v7 2/2] WAL segment pre-allocation.
---
doc/src/sgml/config.sgml | 23 +++
src/backend/access/transam/xlog.c | 60 ++++++-
src/backend/postmaster/checkpointer.c | 234 ++++++++++++++++++++++++++
src/backend/replication/basebackup.c | 6 +-
src/backend/storage/file/fd.c | 22 ++-
src/backend/storage/lmgr/lwlocknames.txt | 1 +
src/backend/utils/misc/guc.c | 11 ++
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/bin/initdb/initdb.c | 1 +
src/bin/pg_basebackup/bbstreamer_file.c | 3 +-
src/bin/pg_basebackup/pg_basebackup.c | 16 ++
src/bin/pg_basebackup/t/010_pg_basebackup.pl | 4 +-
src/bin/pg_resetwal/pg_resetwal.c | 48 ++++++
src/include/postmaster/bgwriter.h | 5 +
src/include/storage/fd.h | 2 +-
15 files changed, 417 insertions(+), 20 deletions(-)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 3f806740d5..9ea36b8109 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -3205,6 +3205,29 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-wal-preallocate-max-size" xreflabel="wal_preallocate_max_size">
+ <term><varname>wal_preallocate_max_size</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>wal_preallocate_max_size</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ The maximum amount of WAL to pre-allocate for use when a new WAL segment
+ must be initialized. Setting this parameter to a size less than the WAL
+ segment size disables this behavior. The default value is 64 megabytes
+ (<literal>64MB</literal>). This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command line.
+ </para>
+
+ <para>
+ WAL pre-allocation may improve performance in scenarios where new WAL
+ segments must be created (e.g., during checkpoints when WAL segments
+ cannot be recycled).
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-wal-buffers" xreflabel="wal_buffers">
<term><varname>wal_buffers</varname> (<type>integer</type>)
<indexterm>
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 60346f5406..c441016329 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -3328,6 +3328,8 @@ XLogFileInitInternal(XLogSegNo logsegno, TimeLineID logtli,
XLogSegNo installed_segno;
XLogSegNo max_segno;
int fd;
+ int prealloc_segs;
+ bool found_prealloc = false;
Assert(logtli != 0);
@@ -3349,15 +3351,45 @@ XLogFileInitInternal(XLogSegNo logsegno, TimeLineID logtli,
return fd;
/*
- * Initialize an empty (all zeroes) segment. NOTE: it is possible that
- * another process is doing the same thing. If so, we will end up
- * pre-creating an extra log segment. That seems OK, and better than
- * holding the lock throughout this lengthy process.
+ * Try to use a pre-allocated segment, if one exists. If none are
+ * available, we fall back to creating a new segment on our own.
+ *
+ * Note that we still look for a pre-allocated segment even if the pre-
+ * allocation functionality is disabled via the GUCs. This ensures that any
+ * pre-allocated segments left over after turning off the pre-allocation
+ * functionality are still eligible for use.
*/
- elog(DEBUG2, "creating and filling new WAL file");
+ LWLockAcquire(WALPreallocationLock, LW_EXCLUSIVE);
+ prealloc_segs = GetNumPreallocatedWalSegs();
+ if (prealloc_segs > 0)
+ {
+ elog(DEBUG2, "using pre-allocated WAL file");
- snprintf(tmppath, MAXPGPATH, XLOGDIR "/xlogtemp.%d", (int) getpid());
- CreateEmptyWalSegment(tmppath);
+ found_prealloc = true;
+ prealloc_segs--;
+ SetNumPreallocatedWalSegs(prealloc_segs);
+ snprintf(tmppath, MAXPGPATH, "%s/preallocated_segments/xlogtemp.%d",
+ XLOGDIR, prealloc_segs);
+ }
+ else
+ {
+ /*
+ * We're not using a pre-allocated segment, so there's no need to keep
+ * holding the WALPreallocationLock.
+ */
+ LWLockRelease(WALPreallocationLock);
+
+ /*
+ * Initialize an empty (all zeroes) segment. NOTE: it is possible that
+ * another process is doing the same thing. If so, we will end up
+ * pre-creating an extra log segment. That seems OK, and better than
+ * holding the lock throughout this lengthy process.
+ */
+ elog(DEBUG2, "creating and filling new WAL file");
+
+ snprintf(tmppath, MAXPGPATH, XLOGDIR "/xlogtemp.%d", (int) getpid());
+ CreateEmptyWalSegment(tmppath, ERROR);
+ }
/*
* Now move the segment into place with its final name. Cope with
@@ -3380,7 +3412,7 @@ XLogFileInitInternal(XLogSegNo logsegno, TimeLineID logtli,
logtli))
{
*added = true;
- elog(DEBUG2, "done creating and filling new WAL file");
+ elog(DEBUG2, "done installing new WAL file");
}
else
{
@@ -3393,6 +3425,18 @@ XLogFileInitInternal(XLogSegNo logsegno, TimeLineID logtli,
elog(DEBUG2, "abandoned new WAL file");
}
+ /*
+ * If we are using a pre-allocated segment, we've been holding onto the
+ * WALPreallocationLock all this time so that the checkpointer process can't
+ * overwrite the file before we've installed it.
+ *
+ * While we're at it, also nudge the checkpointer process so that it pre-
+ * allocates new segments if possible.
+ */
+ if (found_prealloc)
+ LWLockRelease(WALPreallocationLock);
+ RequestWalPreallocation();
+
return -1;
}
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index be7366379d..3237595dbf 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -127,6 +127,9 @@ typedef struct
uint32 num_backend_writes; /* counts user backend buffer writes */
uint32 num_backend_fsync; /* counts user backend fsync calls */
+ bool wal_prealloc_requested; /* protected by ckpt_lck */
+ int num_prealloc_segs; /* protected by WALPreallocationLock */
+
int num_requests; /* current # of requests */
int max_requests; /* allocated array size */
CheckpointerRequest requests[FLEXIBLE_ARRAY_MEMBER];
@@ -143,6 +146,7 @@ static CheckpointerShmemStruct *CheckpointerShmem;
int CheckPointTimeout = 300;
int CheckPointWarning = 30;
double CheckPointCompletionTarget = 0.9;
+int wal_prealloc_max_size_mb = 0;
/*
* Private state
@@ -165,6 +169,10 @@ static bool IsCheckpointOnSchedule(double progress);
static bool ImmediateCheckpointRequested(void);
static bool CompactCheckpointerRequestQueue(void);
static void UpdateSharedMemoryConfig(void);
+static void ScanForExistingPreallocatedSegments(void);
+static bool InstallPreallocatedWalSeg(const char *path);
+static void DoWalPreAllocation(void);
+static int GetMaxPreallocatedWalSegs(void);
/* Signal handlers */
static void ReqCheckpointHandler(SIGNAL_ARGS);
@@ -329,6 +337,11 @@ CheckpointerMain(void)
*/
ProcGlobal->checkpointerLatch = &MyProc->procLatch;
+ /*
+ * Look for any leftover pre-allocated segments we can use.
+ */
+ ScanForExistingPreallocatedSegments();
+
/*
* Loop forever
*/
@@ -349,6 +362,11 @@ CheckpointerMain(void)
AbsorbSyncRequests();
HandleCheckpointerInterrupts();
+ /*
+ * First do WAL pre-allocation.
+ */
+ DoWalPreAllocation();
+
/*
* Detect a pending checkpoint request by checking whether the flags
* word in shared memory is nonzero. We shouldn't need to acquire the
@@ -507,6 +525,12 @@ CheckpointerMain(void)
if (((volatile CheckpointerShmemStruct *) CheckpointerShmem)->ckpt_flags)
continue;
+ /*
+ * Also skip sleeping if WAL pre-allocation has been requested.
+ */
+ if (((volatile CheckpointerShmemStruct *) CheckpointerShmem)->wal_prealloc_requested)
+ continue;
+
/*
* Sleep until we are signaled or it's time for another checkpoint or
* xlog file switch.
@@ -682,6 +706,8 @@ ImmediateCheckpointRequested(void)
*
* 'progress' is an estimate of how much of the work has been done, as a
* fraction between 0.0 meaning none, and 1.0 meaning all done.
+ *
+ * This function also takes care of handling any WAL pre-allocation requests.
*/
void
CheckpointWriteDelay(int flags, double progress)
@@ -692,6 +718,15 @@ CheckpointWriteDelay(int flags, double progress)
if (!AmCheckpointerProcess())
return;
+ /*
+ * WAL pre-allocation has nothing to do with throttling BufferSync()'s write
+ * rate. However, since this function is called frequently during
+ * checkpoints, it is a convenient place to handle any pending WAL pre-
+ * allocation requests.
+ */
+ if (((volatile CheckpointerShmemStruct *) CheckpointerShmem)->wal_prealloc_requested)
+ DoWalPreAllocation();
+
/*
* Perform the usual duties and take a nap, unless we're behind schedule,
* in which case we just try to catch up as quickly as possible.
@@ -823,6 +858,203 @@ IsCheckpointOnSchedule(double progress)
return true;
}
+/*
+ * GetNumPreallocatedWalSegs
+ *
+ * Returns the number of pre-allocated WAL segments in the preallocated_segments
+ * directory. Callers are expected to hold the WALPreallocationLock.
+ */
+int
+GetNumPreallocatedWalSegs(void)
+{
+ Assert(LWLockHeldByMe(WALPreallocationLock));
+ return CheckpointerShmem->num_prealloc_segs;
+}
+
+/*
+ * SetNumPreallocatedWalSegs
+ *
+ * Sets the number of pre-allocated WAL segments in the preallocated_segments
+ * directory. Callers are expected to hold the WALPreallocationLock
+ * exclusively.
+ */
+void
+SetNumPreallocatedWalSegs(int i)
+{
+ Assert(LWLockHeldByMeInMode(WALPreallocationLock, LW_EXCLUSIVE));
+ CheckpointerShmem->num_prealloc_segs = i;
+}
+
+/*
+ * ScanForExistingPreallocatedSegments
+ *
+ * This function searches through pg_wal/preallocated_segments for any segments
+ * that were left over and sets the tracking variable in shared memory
+ * accordingly.
+ */
+static void
+ScanForExistingPreallocatedSegments(void)
+{
+ int i = 0;
+
+ /*
+ * fsync the preallocated_segments directory in case any renames have yet to
+ * be flushed to disk.
+ */
+ fsync_fname_ext(XLOGDIR "/preallocated_segments", true, false, FATAL);
+
+ /*
+ * Gather all the preallocated segments we can find.
+ */
+ while (true)
+ {
+ FILE *fd;
+ char path[MAXPGPATH];
+
+ snprintf(path, MAXPGPATH, "%s/preallocated_segments/xlogtemp.%d",
+ XLOGDIR, i);
+
+ fd = AllocateFile(path, "r");
+ if (fd != NULL)
+ {
+ FreeFile(fd);
+ i++;
+ }
+ else
+ {
+ if (errno != ENOENT)
+ ereport(FATAL,
+ (errcode_for_file_access(),
+ errmsg("could not open file \"%s\": %m", path)));
+ break;
+ }
+ }
+
+ LWLockAcquire(WALPreallocationLock, LW_EXCLUSIVE);
+ SetNumPreallocatedWalSegs(i);
+ LWLockRelease(WALPreallocationLock);
+
+ elog(DEBUG2, "found %d preallocated segments during startup", i);
+}
+
+/*
+ * InstallPreallocatedWalSeg
+ *
+ * Renames the file at "path" to the next open pre-allocated segment slot and
+ * bumps up num_prealloc_segs. If there is a problem, a WARNING is emitted, we
+ * attempt to delete the file, and false is returned. Otherwise, true is
+ * returned.
+ */
+static bool
+InstallPreallocatedWalSeg(const char *path)
+{
+ char newpath[MAXPGPATH];
+ int rc;
+
+ LWLockAcquire(WALPreallocationLock, LW_EXCLUSIVE);
+
+ snprintf(newpath, MAXPGPATH, "%s/preallocated_segments/xlogtemp.%d",
+ XLOGDIR, CheckpointerShmem->num_prealloc_segs);
+
+ rc = durable_rename(path, newpath, DEBUG1);
+ if (rc == 0)
+ CheckpointerShmem->num_prealloc_segs++;
+ else
+ {
+ ereport(WARNING,
+ (errcode_for_file_access(),
+ errmsg("file \"%s\" could not be renamed to \"%s\": %m",
+ path, newpath)));
+
+ (void) durable_unlink(path, DEBUG1);
+ (void) durable_unlink(newpath, DEBUG1);
+ }
+
+ LWLockRelease(WALPreallocationLock);
+
+ return (rc == 0);
+}
+
+/*
+ * DoWalPreAllocation
+ *
+ * Tries to allocate up to wal_preallocate_max_size worth of WAL.
+ */
+static void
+DoWalPreAllocation(void)
+{
+ int segs_to_prealloc;
+
+ /*
+ * Reset the request flag.
+ */
+ SpinLockAcquire(&CheckpointerShmem->ckpt_lck);
+ CheckpointerShmem->wal_prealloc_requested = false;
+ SpinLockRelease(&CheckpointerShmem->ckpt_lck);
+
+ /*
+ * Determine how many segments to pre-allocate.
+ */
+ LWLockAcquire(WALPreallocationLock, LW_SHARED);
+ segs_to_prealloc = GetMaxPreallocatedWalSegs() - GetNumPreallocatedWalSegs();
+ LWLockRelease(WALPreallocationLock);
+
+ /*
+ * Do the pre-allocation.
+ */
+ for (int i = 0; i < segs_to_prealloc; i++)
+ {
+ char tmppath[MAXPGPATH];
+
+ snprintf(tmppath, MAXPGPATH, "%s/preallocated_segments/xlogtemp", XLOGDIR);
+
+ if (!CreateEmptyWalSegment(tmppath, WARNING) ||
+ !InstallPreallocatedWalSeg(tmppath))
+ {
+ elog(DEBUG2, "failed to pre-allocate WAL segment");
+ return;
+ }
+ else
+ elog(DEBUG2, "pre-allocated WAL segment");
+
+ /* Check for barrier events. */
+ if (ProcSignalBarrierPending)
+ ProcessProcSignalBarrier();
+
+ if (ShutdownRequestPending)
+ return;
+ }
+}
+
+/*
+ * GetMaxPreallocatedWalSegs
+ *
+ * Returns the maximum number of pre-allocated WAL segments to create based on
+ * the current value of wal_preallocate_max_size.
+ */
+static int
+GetMaxPreallocatedWalSegs(void)
+{
+ return wal_prealloc_max_size_mb / (wal_segment_size / (1024 * 1024));
+}
+
+/*
+ * RequestWalPreallocation
+ *
+ * Requests that more segments be pre-allocated for future use.
+ */
+void
+RequestWalPreallocation(void)
+{
+ SpinLockAcquire(&CheckpointerShmem->ckpt_lck);
+ CheckpointerShmem->wal_prealloc_requested = true;
+ SpinLockRelease(&CheckpointerShmem->ckpt_lck);
+
+ if (ProcGlobal->checkpointerLatch &&
+ GetMaxPreallocatedWalSegs() > 0)
+ SetLatch(ProcGlobal->checkpointerLatch);
+}
+
/* --------------------------------
* signal handler routines
@@ -896,6 +1128,8 @@ CheckpointerShmemInit(void)
CheckpointerShmem->max_requests = NBuffers;
ConditionVariableInit(&CheckpointerShmem->start_cv);
ConditionVariableInit(&CheckpointerShmem->done_cv);
+ CheckpointerShmem->wal_prealloc_requested = false;
+ CheckpointerShmem->num_prealloc_segs = 0;
}
}
diff --git a/src/backend/replication/basebackup.c b/src/backend/replication/basebackup.c
index ec0485705d..431db26774 100644
--- a/src/backend/replication/basebackup.c
+++ b/src/backend/replication/basebackup.c
@@ -1220,11 +1220,13 @@ sendDir(bbsink *sink, const char *path, int basepathlen, bool sizeonly,
&statbuf, sizeonly);
/*
- * Also send archive_status directory (by hackishly reusing
- * statbuf from above ...).
+ * Also send archive_status and preallocated_segments (by hackishly
+ * reusing statbuf from above ...).
*/
size += _tarWriteHeader(sink, "./pg_wal/archive_status", NULL,
&statbuf, sizeonly);
+ size += _tarWriteHeader(sink, "./pg_wal/preallocated_segments", NULL,
+ &statbuf, sizeonly);
continue; /* don't recurse into pg_wal */
}
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index d42bca6cab..c8ac981a32 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -3896,8 +3896,8 @@ pg_pwritev_with_retry(int fd, const struct iovec *iov, int iovcnt, off_t offset)
* Create a new file that can be used as a new WAL segment. The caller is
* responsible for installing the new file in pg_wal.
*/
-void
-CreateEmptyWalSegment(const char *path)
+bool
+CreateEmptyWalSegment(const char *path, int elevel)
{
PGAlignedXLogBlock zbuffer;
int fd;
@@ -3908,9 +3908,12 @@ CreateEmptyWalSegment(const char *path)
/* do not use get_sync_bit() here --- want to fsync only at end of fill */
fd = BasicOpenFile(path, O_RDWR | O_CREAT | O_EXCL | PG_BINARY);
if (fd < 0)
- ereport(ERROR,
+ {
+ ereport(elevel,
(errcode_for_file_access(),
errmsg("could not create file \"%s\": %m", path)));
+ return false;
+ }
memset(zbuffer.data, 0, XLOG_BLCKSZ);
@@ -3980,9 +3983,10 @@ CreateEmptyWalSegment(const char *path)
errno = save_errno;
- ereport(ERROR,
+ ereport(elevel,
(errcode_for_file_access(),
errmsg("could not write to file \"%s\": %m", path)));
+ return false;
}
pgstat_report_wait_start(WAIT_EVENT_WAL_INIT_SYNC);
@@ -3992,14 +3996,20 @@ CreateEmptyWalSegment(const char *path)
close(fd);
errno = save_errno;
- ereport(ERROR,
+ ereport(elevel,
(errcode_for_file_access(),
errmsg("could not fsync file \"%s\": %m", path)));
+ return false;
}
pgstat_report_wait_end();
if (close(fd) != 0)
- ereport(ERROR,
+ {
+ ereport(elevel,
(errcode_for_file_access(),
errmsg("could not close file \"%s\": %m", path)));
+ return false;
+ }
+
+ return true;
}
diff --git a/src/backend/storage/lmgr/lwlocknames.txt b/src/backend/storage/lmgr/lwlocknames.txt
index 6c7cf6c295..212bda8e7d 100644
--- a/src/backend/storage/lmgr/lwlocknames.txt
+++ b/src/backend/storage/lmgr/lwlocknames.txt
@@ -53,3 +53,4 @@ XactTruncationLock 44
# 45 was XactTruncationLock until removal of BackendRandomLock
WrapLimitsVacuumLock 46
NotifyQueueTailLock 47
+WALPreallocationLock 48
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index e91d5a3cfd..2e4545795a 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -2841,6 +2841,17 @@ static struct config_int ConfigureNamesInt[] =
NULL, NULL, NULL
},
+ {
+ {"wal_preallocate_max_size", PGC_SIGHUP, WAL_SETTINGS,
+ gettext_noop("Sets the maximum amount of WAL to pre-allocate."),
+ NULL,
+ GUC_UNIT_MB
+ },
+ &wal_prealloc_max_size_mb,
+ 64, 0, 102400,
+ NULL, NULL, NULL
+ },
+
{
{"wal_buffers", PGC_POSTMASTER, WAL_SETTINGS,
gettext_noop("Sets the number of disk-page buffers in shared memory for WAL."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 1cbc9feeb6..cf4ef28903 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -223,6 +223,7 @@
# off, pglz, lz4, or on
#wal_init_zero = on # zero-fill new WAL files
#wal_recycle = on # recycle WAL files
+#wal_preallocate_max_size = 64MB # amount of WAL to pre-allocate
#wal_buffers = -1 # min 32kB, -1 sets based on shared_buffers
# (change requires restart)
#wal_writer_delay = 200ms # 1-10000 milliseconds
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 31839c1a19..c42524842e 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -208,6 +208,7 @@ static char *extra_options = "";
static const char *const subdirs[] = {
"global",
"pg_wal/archive_status",
+ "pg_wal/preallocated_segments",
"pg_commit_ts",
"pg_dynshmem",
"pg_notify",
diff --git a/src/bin/pg_basebackup/bbstreamer_file.c b/src/bin/pg_basebackup/bbstreamer_file.c
index eba173f688..26e5f956e8 100644
--- a/src/bin/pg_basebackup/bbstreamer_file.c
+++ b/src/bin/pg_basebackup/bbstreamer_file.c
@@ -484,7 +484,8 @@ extract_directory(const char *filename, mode_t mode)
*/
if (!((pg_str_endswith(filename, "/pg_wal") ||
pg_str_endswith(filename, "/pg_xlog") ||
- pg_str_endswith(filename, "/archive_status")) &&
+ pg_str_endswith(filename, "/archive_status") ||
+ pg_str_endswith(filename, "/preallocated_segments")) &&
errno == EEXIST))
{
pg_log_error("could not create directory \"%s\": %m",
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index 1739ac6382..6a7ad3947f 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -647,6 +647,22 @@ StartLogStreamer(char *startpos, uint32 timeline, char *sysidentifier)
pg_log_error("could not create directory \"%s\": %m", statusdir);
exit(1);
}
+
+ /*
+ * Also create pg_wal/preallocated_segments if necessary.
+ */
+ if (PQserverVersion(conn) >= 150000)
+ {
+ char prealloc_dir[MAXPGPATH];
+
+ snprintf(prealloc_dir, sizeof(prealloc_dir), "%s/pg_wal/preallocated_segments",
+ basedir);
+ if (pg_mkdir_p(prealloc_dir, pg_dir_create_mode) != 0 && errno != EEXIST)
+ {
+ pg_log_error("could not create directory \"%s\": %m", prealloc_dir);
+ exit(1);
+ }
+ }
}
/*
diff --git a/src/bin/pg_basebackup/t/010_pg_basebackup.pl b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
index 89f45b77a3..4dec12a18b 100644
--- a/src/bin/pg_basebackup/t/010_pg_basebackup.pl
+++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
@@ -120,10 +120,10 @@ SKIP:
"check backup dir permissions");
}
-# Only archive_status directory should be copied in pg_wal/.
+# Only archive_status and preallocated_segments directories should be copied in pg_wal/.
is_deeply(
[ sort(slurp_dir("$tempdir/backup/pg_wal/")) ],
- [ sort qw(. .. archive_status) ],
+ [ sort qw(. .. archive_status preallocated_segments) ],
'no WAL files copied');
# Contents of these directories should not be copied.
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 84b6f70ad6..ca09ad72d2 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -85,6 +85,7 @@ static void RewriteControlFile(void);
static void FindEndOfXLOG(void);
static void KillExistingXLOG(void);
static void KillExistingArchiveStatus(void);
+static void KillExistingPreallocatedSegments(void);
static void WriteEmptyXLOG(void);
static void usage(void);
@@ -530,6 +531,7 @@ main(int argc, char *argv[])
RewriteControlFile();
KillExistingXLOG();
KillExistingArchiveStatus();
+ KillExistingPreallocatedSegments();
WriteEmptyXLOG();
printf(_("Write-ahead log reset\n"));
@@ -1120,6 +1122,52 @@ KillExistingArchiveStatus(void)
}
+/*
+ * Remove existing preallocated segments
+ */
+static void
+KillExistingPreallocatedSegments(void)
+{
+#define PREALLOCSEGDIR XLOGDIR "/preallocated_segments"
+
+ DIR *xldir;
+ struct dirent *xlde;
+ char path[MAXPGPATH + sizeof(PREALLOCSEGDIR)];
+
+ xldir = opendir(PREALLOCSEGDIR);
+ if (xldir == NULL)
+ {
+ pg_log_error("could not open directory \"%s\": %m", PREALLOCSEGDIR);
+ exit(1);
+ }
+
+ while (errno = 0, (xlde = readdir(xldir)) != NULL)
+ {
+ if (strncmp(xlde->d_name, "xlogtemp", strlen("xlogtemp")) == 0)
+ {
+ snprintf(path, sizeof(path), "%s/%s", PREALLOCSEGDIR, xlde->d_name);
+ if (unlink(path) < 0)
+ {
+ pg_log_error("could not delete file \"%s\": %m", path);
+ exit(1);
+ }
+ }
+ }
+
+ if (errno)
+ {
+ pg_log_error("could not read directory \"%s\": %m", PREALLOCSEGDIR);
+ exit(1);
+ }
+
+ if (closedir(xldir))
+ {
+ pg_log_error("could not close directory \"%s\": %m", PREALLOCSEGDIR);
+ exit(1);
+ }
+}
+
+
/*
* Write an empty XLOG file, containing only the checkpoint record
* already set up in ControlFile.
diff --git a/src/include/postmaster/bgwriter.h b/src/include/postmaster/bgwriter.h
index c430b1b236..8303eb48cb 100644
--- a/src/include/postmaster/bgwriter.h
+++ b/src/include/postmaster/bgwriter.h
@@ -26,6 +26,7 @@ extern int BgWriterDelay;
extern int CheckPointTimeout;
extern int CheckPointWarning;
extern double CheckPointCompletionTarget;
+extern int wal_prealloc_max_size_mb;
extern void BackgroundWriterMain(void) pg_attribute_noreturn();
extern void CheckpointerMain(void) pg_attribute_noreturn();
@@ -42,4 +43,8 @@ extern void CheckpointerShmemInit(void);
extern bool FirstCallSinceLastCheckpoint(void);
+extern int GetNumPreallocatedWalSegs(void);
+extern void SetNumPreallocatedWalSegs(int i);
+extern void RequestWalPreallocation(void);
+
#endif /* _BGWRITER_H */
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index abcd74bd51..2d174509c5 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -190,7 +190,7 @@ extern int durable_unlink(const char *fname, int loglevel);
extern int durable_rename_excl(const char *oldfile, const char *newfile, int loglevel);
extern void SyncDataDirectory(void);
extern int data_sync_elevel(int elevel);
-extern void CreateEmptyWalSegment(const char *path);
+extern bool CreateEmptyWalSegment(const char *path, int elevel);
/* Filename components */
#define PG_TEMP_FILES_DIR "pgsql_tmp"
--
2.16.6
^ permalink raw reply [nested|flat] 19+ messages in thread
* Re: Pre-allocating WAL files
@ 2021-12-07 08:27 Bharath Rupireddy <[email protected]>
parent: Bossart, Nathan <[email protected]>
1 sibling, 1 reply; 19+ messages in thread
From: Bharath Rupireddy @ 2021-12-07 08:27 UTC (permalink / raw)
To: Bossart, Nathan <[email protected]>; +Cc: Maxim Orlov <[email protected]>; [email protected] <[email protected]>
On Thu, Nov 11, 2021 at 12:29 AM Bossart, Nathan <[email protected]> wrote:
>
> On 10/8/21, 1:55 PM, "Bossart, Nathan" <[email protected]> wrote:
> > Here is a first attempt at adding the pre-allocation logic to the
> > checkpointer. I went ahead and just used CheckpointWriteDelay() for
> > pre-allocating during checkpoints. I've done a few pgbench runs, and
> > it seems to work pretty well. Initialization is around 15% faster,
> > and I'm seeing about a 5% increase in TPS with a simple-update
> > workload with wal_recycle turned off. Of course, these improvements
> > go away once segments can be recycled.
>
> Here is a rebased version of this patch set. I'm getting the sense
> that there isn't a whole lot of interest in this feature, so I'll
> likely withdraw it if it goes too much longer without traction.
As I mentioned in the other thread at [1], let's continue the discussion here.
Why can't the walwriter pre-allocate some of the WAL segments instead
of a new background process? Of course, it might delay the main
functionality of the walwriter i.e. flush and sync the WAL files, but
having checkpointer do the pre-allocation makes it do another extra
task. Here the amount of walwriter work vs checkpointer work, I'm not
sure which one does more work compared to the other.
Another idea could be to let walwrtier or checkpointer pre-allocate
the WAL files whichever seems free as-of-the-moment when the WAL
segment pre-allocation request comes. We can go further to let the
user choose which process i.e. checkpointer or walwrtier do the
pre-allocation with a GUC maybe?
[1] - https://www.postgresql.org/message-id/CALj2ACVqYJX9JugooRC1chb2sHqv-C9mYEBE1kxwn%2BTn9vY42A%40mail.g...
Regards,
Bharath Rupireddy.
^ permalink raw reply [nested|flat] 19+ messages in thread
* Re: Pre-allocating WAL files
@ 2021-12-07 17:34 Bossart, Nathan <[email protected]>
parent: Bharath Rupireddy <[email protected]>
0 siblings, 1 reply; 19+ messages in thread
From: Bossart, Nathan @ 2021-12-07 17:34 UTC (permalink / raw)
To: Bharath Rupireddy <[email protected]>; +Cc: Maxim Orlov <[email protected]>; [email protected] <[email protected]>
On 12/7/21, 12:29 AM, "Bharath Rupireddy" <[email protected]> wrote:
> Why can't the walwriter pre-allocate some of the WAL segments instead
> of a new background process? Of course, it might delay the main
> functionality of the walwriter i.e. flush and sync the WAL files, but
> having checkpointer do the pre-allocation makes it do another extra
> task. Here the amount of walwriter work vs checkpointer work, I'm not
> sure which one does more work compared to the other.
The argument against adding it to the WAL writer is that we want it to
run with low latency to flush asynchronous commits. If we added WAL
pre-allocation to the WAL writer, there could periodically be large
delays.
> Another idea could be to let walwrtier or checkpointer pre-allocate
> the WAL files whichever seems free as-of-the-moment when the WAL
> segment pre-allocation request comes. We can go further to let the
> user choose which process i.e. checkpointer or walwrtier do the
> pre-allocation with a GUC maybe?
My latest patch set [0] adds WAL pre-allocation to the checkpointer.
In that patch set, WAL pre-allocation is done both outside of
checkpoints as well as during checkpoints (via
CheckPointWriteDelay()).
Nathan
[0] https://www.postgresql.org/message-id/CB15BEBD-98FC-4E72-86AE-513D34014176%40amazon.com
^ permalink raw reply [nested|flat] 19+ messages in thread
* Re: Pre-allocating WAL files
@ 2021-12-07 21:01 Bossart, Nathan <[email protected]>
parent: Bossart, Nathan <[email protected]>
0 siblings, 0 replies; 19+ messages in thread
From: Bossart, Nathan @ 2021-12-07 21:01 UTC (permalink / raw)
To: Bharath Rupireddy <[email protected]>; +Cc: Maxim Orlov <[email protected]>; [email protected] <[email protected]>
On 12/7/21, 9:35 AM, "Bossart, Nathan" <[email protected]> wrote:
> On 12/7/21, 12:29 AM, "Bharath Rupireddy" <[email protected]> wrote:
>> Why can't the walwriter pre-allocate some of the WAL segments instead
>> of a new background process? Of course, it might delay the main
>> functionality of the walwriter i.e. flush and sync the WAL files, but
>> having checkpointer do the pre-allocation makes it do another extra
>> task. Here the amount of walwriter work vs checkpointer work, I'm not
>> sure which one does more work compared to the other.
>
> The argument against adding it to the WAL writer is that we want it to
> run with low latency to flush asynchronous commits. If we added WAL
> pre-allocation to the WAL writer, there could periodically be large
> delays.
To your point on trying to avoid giving the checkpointer extra tasks
(basically what we are talking about on the other thread [0]), WAL
pre-allocation might not be of much concern because it will generally
be a small, fixed (and configurable) amount of work, and it can be
performed concurrently with the checkpoint. Plus, WAL pre-allocation
should ordinarily be phased out as WAL segments become eligible for
recycling. IMO it's not comparable to tasks like
CheckPointSnapBuild() that can delay checkpointing for a long time.
Nathan
[0] https://www.postgresql.org/message-id/flat/C1EE64B0-D4DB-40F3-98C8-0CED324D34CB%40amazon.com
^ permalink raw reply [nested|flat] 19+ messages in thread
* Re: Pre-allocating WAL files
@ 2021-12-30 11:45 Pavel Borisov <[email protected]>
parent: Bossart, Nathan <[email protected]>
1 sibling, 1 reply; 19+ messages in thread
From: Pavel Borisov @ 2021-12-30 11:45 UTC (permalink / raw)
To: Bossart, Nathan <[email protected]>; +Cc: Maxim Orlov <[email protected]>; [email protected] <[email protected]>
>
> > pre-allocating during checkpoints. I've done a few pgbench runs, and
> > it seems to work pretty well. Initialization is around 15% faster,
> > and I'm seeing about a 5% increase in TPS with a simple-update
> > workload with wal_recycle turned off. Of course, these improvements
> > go away once segments can be recycled.
>
I've checked the patch v7. It applies cleanly, code is good, check-world
tests passed without problems.
I think it's ok to use checkpointer for this and the overall patch can be
committed. But the seen performance gain makes me think again before adding
this feature. I did tests myself a couple of months ago and got similar
results.
Really don't know whether is it worth the effort.
Wish you and all hackers happy New Year!
--
Best regards,
Pavel Borisov
Postgres Professional: http://postgrespro.com <http://www.postgrespro.com;
^ permalink raw reply [nested|flat] 19+ messages in thread
* Re: Pre-allocating WAL files
@ 2021-12-30 11:51 Maxim Orlov <[email protected]>
parent: Pavel Borisov <[email protected]>
0 siblings, 1 reply; 19+ messages in thread
From: Maxim Orlov @ 2021-12-30 11:51 UTC (permalink / raw)
To: Pavel Borisov <[email protected]>; +Cc: Bossart, Nathan <[email protected]>; Maxim Orlov <[email protected]>; [email protected] <[email protected]>
I did check the patch too and found it to be ok. Check and check-world are
passed.
Overall idea seems to be good in my opinion, but I'm not sure where is the
optimal place to put the pre-allocation.
On Thu, Dec 30, 2021 at 2:46 PM Pavel Borisov <[email protected]>
wrote:
> > pre-allocating during checkpoints. I've done a few pgbench runs, and
>> > it seems to work pretty well. Initialization is around 15% faster,
>> > and I'm seeing about a 5% increase in TPS with a simple-update
>> > workload with wal_recycle turned off. Of course, these improvements
>> > go away once segments can be recycled.
>>
>
> I've checked the patch v7. It applies cleanly, code is good, check-world
> tests passed without problems.
> I think it's ok to use checkpointer for this and the overall patch can be
> committed. But the seen performance gain makes me think again before adding
> this feature. I did tests myself a couple of months ago and got similar
> results.
> Really don't know whether is it worth the effort.
>
> Wish you and all hackers happy New Year!
> --
> Best regards,
> Pavel Borisov
>
> Postgres Professional: http://postgrespro.com <http://www.postgrespro.com;
>
--
---
Best regards,
Maxim Orlov.
^ permalink raw reply [nested|flat] 19+ messages in thread
* [PATCH 10/17] comment typo/consistency
@ 2022-02-05 02:17 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 19+ messages in thread
From: Justin Pryzby @ 2022-02-05 02:17 UTC (permalink / raw)
---
src/backend/postmaster/bgworker.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index e441726c83e..30682b63b3f 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -531,7 +531,7 @@ BackgroundWorkerStopNotifications(pid_t pid)
* This is called during a normal ("smart" or "fast") database shutdown.
* After this point, no new background workers will be started, so anything
* that might be waiting for them needs to be kicked off its wait. We do
- * that by cancelling the bgworker registration entirely, which is perhaps
+ * that by canceling the bgworker registration entirely, which is perhaps
* overkill, but since we're shutting down it does not matter whether the
* registration record sticks around.
*
--
2.17.1
--juZjCTNxrMaZdGZC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0011-comment-typo-since-ff9f111bce24fd9bbca7a20315586de87.patch"
^ permalink raw reply [nested|flat] 19+ messages in thread
* Re: Pre-allocating WAL files
@ 2022-07-14 18:34 Nathan Bossart <[email protected]>
parent: Maxim Orlov <[email protected]>
0 siblings, 1 reply; 19+ messages in thread
From: Nathan Bossart @ 2022-07-14 18:34 UTC (permalink / raw)
To: Justin Pryzby <[email protected]>; +Cc: Maxim Orlov <[email protected]>; Pavel Borisov <[email protected]>; Bossart, Nathan <[email protected]>; Maxim Orlov <[email protected]>; Andres Freund <[email protected]>; [email protected]
On Fri, Apr 08, 2022 at 01:30:03PM -0700, Nathan Bossart wrote:
> On Thu, Mar 17, 2022 at 04:12:12PM -0700, Nathan Bossart wrote:
>> It seems unlikely that this will be committed for v15, so I've adjusted the
>> commitfest entry to v16 and moved it to the next commitfest.
>
> rebased
It's now been over a year since I first posted a patch in this thread, and
I still sense very little interest for this feature. I intend to mark it
as Withdrawn at the end of this commitfest.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 19+ messages in thread
* Re: Pre-allocating WAL files
@ 2022-07-25 16:24 Nathan Bossart <[email protected]>
parent: Nathan Bossart <[email protected]>
0 siblings, 0 replies; 19+ messages in thread
From: Nathan Bossart @ 2022-07-25 16:24 UTC (permalink / raw)
To: Justin Pryzby <[email protected]>; +Cc: Maxim Orlov <[email protected]>; Pavel Borisov <[email protected]>; Bossart, Nathan <[email protected]>; Maxim Orlov <[email protected]>; Andres Freund <[email protected]>; [email protected]
On Thu, Jul 14, 2022 at 11:34:07AM -0700, Nathan Bossart wrote:
> It's now been over a year since I first posted a patch in this thread, and
> I still sense very little interest for this feature. I intend to mark it
> as Withdrawn at the end of this commitfest.
Done.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 19+ messages in thread
end of thread, other threads:[~2022-07-25 16:24 UTC | newest]
Thread overview: 19+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2020-12-25 20:09 Pre-allocating WAL files Andres Freund <[email protected]>
2021-06-07 15:18 ` Bossart, Nathan <[email protected]>
2021-07-05 16:52 ` vignesh C <[email protected]>
2021-07-09 21:08 ` Bossart, Nathan <[email protected]>
2021-08-06 20:25 ` Bossart, Nathan <[email protected]>
2021-08-31 17:24 ` Bossart, Nathan <[email protected]>
2021-09-14 20:06 ` Bossart, Nathan <[email protected]>
2021-10-06 12:19 ` Maxim Orlov <[email protected]>
2021-10-06 16:31 ` Bossart, Nathan <[email protected]>
2021-10-08 20:48 ` Bossart, Nathan <[email protected]>
2021-11-10 18:59 ` Bossart, Nathan <[email protected]>
2021-12-07 08:27 ` Bharath Rupireddy <[email protected]>
2021-12-07 17:34 ` Bossart, Nathan <[email protected]>
2021-12-07 21:01 ` Bossart, Nathan <[email protected]>
2021-12-30 11:45 ` Pavel Borisov <[email protected]>
2021-12-30 11:51 ` Maxim Orlov <[email protected]>
2022-07-14 18:34 ` Nathan Bossart <[email protected]>
2022-07-25 16:24 ` Nathan Bossart <[email protected]>
2022-02-05 02:17 [PATCH 10/17] comment typo/consistency Justin Pryzby <[email protected]>
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox