Skip to content

Commit

Permalink
Add a new WAL summarizer process.
Browse files Browse the repository at this point in the history
When active, this process writes WAL summary files to
$PGDATA/pg_wal/summaries. Each summary file contains information for a
certain range of LSNs on a certain TLI. For each relation, it stores a
"limit block" which is 0 if a relation is created or destroyed within
a certain range of WAL records, or otherwise the shortest length to
which the relation was truncated during that range of WAL records, or
otherwise InvalidBlockNumber. In addition, it stores a list of blocks
which have been modified during that range of WAL records, but
excluding blocks which were removed by truncation after they were
modified and never subsequently modified again.

In other words, it tells us which blocks need to copied in case of an
incremental backup covering that range of WAL records. But this
doesn't yet add the capability to actually perform an incremental
backup; the next patch will do that.

A new parameter summarize_wal enables or disables this new background
process.  The background process also automatically deletes summary
files that are older than wal_summarize_keep_time, if that parameter
has a non-zero value and the summarizer is configured to run.

Patch by me, with some design help from Dilip Kumar and Andres Freund.
Reviewed by Matthias van de Meent, Dilip Kumar, Jakub Wartak, Peter
Eisentraut, and Álvaro Herrera.

Discussion: http://postgr.es/m/CA+TgmoYOYZfMCyOXFyC-P+-mdrZqm5pP2N7S-r0z3_402h9rsA@mail.gmail.com
  • Loading branch information
robertmhaas committed Dec 20, 2023
1 parent 00498b7 commit 174c480
Show file tree
Hide file tree
Showing 30 changed files with 3,743 additions and 11 deletions.
61 changes: 61 additions & 0 deletions doc/src/sgml/config.sgml
Expand Up @@ -4150,6 +4150,67 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</variablelist>
</sect2>

<sect2 id="runtime-config-wal-summarization">
<title>WAL Summarization</title>

<!--
<para>
These settings control WAL summarization, a feature which must be
enabled in order to perform an
<link linkend="backup-incremental-backup">incremental backup</link>.
</para>
-->

<variablelist>
<varlistentry id="guc-summarize-wal" xreflabel="summarize_wal">
<term><varname>summarize_wal</varname> (<type>boolean</type>)
<indexterm>
<primary><varname>summarize_wal</varname> configuration parameter</primary>
</indexterm>
</term>
<listitem>
<para>
Enables the WAL summarizer process. Note that WAL summarization can
be enabled either on a primary or on a standby. WAL summarization
cannot be enabled when <varname>wal_level</varname> is set to
<literal>minimal</literal>. This parameter can only be set in the
<filename>postgresql.conf</filename> file or on the server command line.
The default is <literal>off</literal>.
</para>
</listitem>
</varlistentry>

<varlistentry id="guc-wal-summary-keep-time" xreflabel="wal_summary_keep_time">
<term><varname>wal_summary_keep_time</varname> (<type>boolean</type>)
<indexterm>
<primary><varname>wal_summary_keep_time</varname> configuration parameter</primary>
</indexterm>
</term>
<listitem>
<para>
Configures the amount of time after which the WAL summarizer
automatically removes old WAL summaries. The file timestamp is used to
determine which files are old enough to remove. Typically, you should set
this comfortably higher than the time that could pass between a backup
and a later incremental backup that depends on it. WAL summaries must
be available for the entire range of WAL records between the preceding
backup and the new one being taken; if not, the incremental backup will
fail. If this parameter is set to zero, WAL summaries will not be
automatically deleted, but it is safe to manually remove files that you
know will not be required for future incremental backups.
This parameter can only be set in the
<filename>postgresql.conf</filename> file or on the server command line.
The default is 10 days. If <literal>summarize_wal = off</literal>,
existing WAL summaries will not be removed regardless of the value of
this parameter, because the WAL summarizer will not run.
</para>
</listitem>
</varlistentry>

</variablelist>

</sect2>

</sect1>

<sect1 id="runtime-config-replication">
Expand Down
101 changes: 96 additions & 5 deletions src/backend/access/transam/xlog.c
Expand Up @@ -77,6 +77,7 @@
#include "port/pg_iovec.h"
#include "postmaster/bgwriter.h"
#include "postmaster/startup.h"
#include "postmaster/walsummarizer.h"
#include "postmaster/walwriter.h"
#include "replication/logical.h"
#include "replication/origin.h"
Expand Down Expand Up @@ -3592,6 +3593,43 @@ XLogGetLastRemovedSegno(void)
return lastRemovedSegNo;
}

/*
* Return the oldest WAL segment on the given TLI that still exists in
* XLOGDIR, or 0 if none.
*/
XLogSegNo
XLogGetOldestSegno(TimeLineID tli)
{
DIR *xldir;
struct dirent *xlde;
XLogSegNo oldest_segno = 0;

xldir = AllocateDir(XLOGDIR);
while ((xlde = ReadDir(xldir, XLOGDIR)) != NULL)
{
TimeLineID file_tli;
XLogSegNo file_segno;

/* Ignore files that are not XLOG segments. */
if (!IsXLogFileName(xlde->d_name))
continue;

/* Parse filename to get TLI and segno. */
XLogFromFileName(xlde->d_name, &file_tli, &file_segno,
wal_segment_size);

/* Ignore anything that's not from the TLI of interest. */
if (tli != file_tli)
continue;

/* If it's the oldest so far, update oldest_segno. */
if (oldest_segno == 0 || file_segno < oldest_segno)
oldest_segno = file_segno;
}

FreeDir(xldir);
return oldest_segno;
}

/*
* Update the last removed segno pointer in shared memory, to reflect that the
Expand Down Expand Up @@ -3872,8 +3910,8 @@ RemoveXlogFile(const struct dirent *segment_de,
}

/*
* 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/summaries exist.
* If the latter do not exist, recreate them.
*
* 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
Expand Down Expand Up @@ -3916,6 +3954,26 @@ ValidateXLOGDirectoryStructure(void)
(errmsg("could not create missing directory \"%s\": %m",
path)));
}

/* Check for summaries */
snprintf(path, MAXPGPATH, XLOGDIR "/summaries");
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)));
}
}

/*
Expand Down Expand Up @@ -5243,9 +5301,9 @@ StartupXLOG(void)
#endif

/*
* Verify that pg_wal and pg_wal/archive_status exist. In cases where
* someone has performed a copy for PITR, these directories may have been
* excluded and need to be re-created.
* Verify that pg_wal, pg_wal/archive_status, and pg_wal/summaries exist.
* In cases where someone has performed a copy for PITR, these directories
* may have been excluded and need to be re-created.
*/
ValidateXLOGDirectoryStructure();

Expand Down Expand Up @@ -6962,6 +7020,25 @@ CreateCheckPoint(int flags)
*/
END_CRIT_SECTION();

/*
* WAL summaries end when the next XLOG_CHECKPOINT_REDO or
* XLOG_CHECKPOINT_SHUTDOWN record is reached. This is the first point
* where (a) we're not inside of a critical section and (b) we can be
* certain that the relevant record has been flushed to disk, which must
* happen before it can be summarized.
*
* If this is a shutdown checkpoint, then this happens reasonably
* promptly: we've only just inserted and flushed the
* XLOG_CHECKPOINT_SHUTDOWN record. If this is not a shutdown checkpoint,
* then this might not be very prompt at all: the XLOG_CHECKPOINT_REDO
* record was written before we began flushing data to disk, and that
* could be many minutes ago at this point. However, we don't XLogFlush()
* after inserting that record, so we're not guaranteed that it's on disk
* until after the above call that flushes the XLOG_CHECKPOINT_ONLINE
* record.
*/
SetWalSummarizerLatch();

/*
* Let smgr do post-checkpoint cleanup (eg, deleting old files).
*/
Expand Down Expand Up @@ -7636,6 +7713,20 @@ KeepLogSeg(XLogRecPtr recptr, XLogSegNo *logSegNo)
}
}

/*
* If WAL summarization is in use, don't remove WAL that has yet to be
* summarized.
*/
keep = GetOldestUnsummarizedLSN(NULL, NULL, false);
if (keep != InvalidXLogRecPtr)
{
XLogSegNo unsummarized_segno;

XLByteToSeg(keep, unsummarized_segno, wal_segment_size);
if (unsummarized_segno < segno)
segno = unsummarized_segno;
}

/* but, keep at least wal_keep_size if that's set */
if (wal_keep_size_mb > 0)
{
Expand Down
4 changes: 3 additions & 1 deletion src/backend/backup/Makefile
Expand Up @@ -25,6 +25,8 @@ OBJS = \
basebackup_server.o \
basebackup_sink.o \
basebackup_target.o \
basebackup_throttle.o
basebackup_throttle.o \
walsummary.o \
walsummaryfuncs.o

include $(top_srcdir)/src/backend/common.mk
2 changes: 2 additions & 0 deletions src/backend/backup/meson.build
Expand Up @@ -12,4 +12,6 @@ backend_sources += files(
'basebackup_target.c',
'basebackup_throttle.c',
'basebackup_zstd.c',
'walsummary.c',
'walsummaryfuncs.c',
)

0 comments on commit 174c480

Please sign in to comment.