Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
5712ea2
Add short-lived double write buffer skeleton (Stage 1)
vbp1 Jul 10, 2026
8d75a77
Harden the DWB skeleton after review (Stage 1 follow-up)
vbp1 Jul 24, 2026
35828d8
Fix ABA hijack of a reopened open batch in DWBOpenNewBatch
vbp1 Jul 24, 2026
1d3843a
Remove unused DWBCloseBatchFiles
vbp1 Jul 24, 2026
21da0b0
Integrate the DWB into FlushBuffer and add real retirement (Stage 2)
vbp1 Jul 24, 2026
9fe2303
Fix fsync error-path accounting and close review gaps (Stage 2 follow…
vbp1 Jul 24, 2026
ad7acfc
Fix exit-callback ordering and error-proof the repair path (Stage 2 f…
vbp1 Jul 24, 2026
d813a0f
Simplify after the Stage 2 review round
vbp1 Jul 24, 2026
40611dc
Integrate checkpoint, hot standby and base backups (Stage 3)
vbp1 Jul 24, 2026
d7a2ed1
Fix cross-class batch aliasing and harden the FPI-off surface (Stage …
vbp1 Jul 25, 2026
4c1ddee
Harden the pg_dwb surface of pg_rewind (Stage 3 follow-up)
vbp1 Jul 25, 2026
a1c999f
Trim duplication left by the Stage 3 review round
vbp1 Jul 25, 2026
114ec68
Repair torn pages at startup and record the mode in pg_control (Stage 4)
vbp1 Jul 25, 2026
fd99427
Harden startup recovery after the Stage 4 review round (Stage 4 follo…
vbp1 Jul 25, 2026
5f575d2
Close the review-verification findings on the Stage 4 follow-up
vbp1 Jul 25, 2026
0b64bbd
Reword a comment to avoid a line-wrapped hyphenation
vbp1 Jul 25, 2026
d1358ef
Trim duplication left by the Stage 4 review rounds
vbp1 Jul 26, 2026
96716b7
Cover the deferred recovery paths and pg_upgrade (Stage 5)
vbp1 Jul 26, 2026
29c55cd
Document the double write buffer (Stage 5)
vbp1 Jul 26, 2026
9e26479
Fix documentation overclaims and a test gap from the Stage 5 review
vbp1 Jul 26, 2026
a95fa35
Apply pgindent, perltidy and perlcritic to the branch
vbp1 Jul 29, 2026
c9c17c5
Vectorize the checkpoint flush and slice the ring reserves (Stage 5)
vbp1 Aug 1, 2026
5a7d793
Check buffer permanence under the header lock in the checkpoint bin f…
vbp1 Aug 1, 2026
ae03d41
Replace the ring-wait broadcast with targeted per-class wakeups (Stag…
vbp1 Aug 1, 2026
5c3d74b
Keep probe releases of staging buffers silent in the ring wait
vbp1 Aug 1, 2026
477abf0
Gate the self-help sweep and vectorize the bgwriter flush (Stage 5)
vbp1 Aug 1, 2026
3745ed3
Pin the sweep gate with a test and tighten the bin comments
vbp1 Aug 1, 2026
6c7a6d9
Retire batches with syncfs and account seal reasons (Stage 5)
vbp1 Aug 2, 2026
d885dc1
Gate the wholesale retire round against concurrent duplicates
vbp1 Aug 2, 2026
853eaec
Suppress the lone-writer fast seal while the class is hot (Stage 5)
vbp1 Aug 2, 2026
2369212
Scale background LRU cleaning with a cleaner worker pool (Stage 5)
vbp1 Aug 2, 2026
418afd0
Make the bgwriter a pure scanner while the cleaner pool is active (St…
vbp1 Aug 3, 2026
a2303c2
Route autovacuum workers through the DWB background class (Stage 5)
vbp1 Aug 3, 2026
395717d
Tolerate pg_waldump's start-LSN skip notice in the promotion test
vbp1 Aug 3, 2026
eae0a59
Raise NUM_XLOGINSERT_LOCKS to 32
vbp1 Aug 3, 2026
eacd041
Add benchmark charts for the PR discussion
vbp1 Aug 4, 2026
e3a31c1
Make the checkpointer yield to a loaded cleaner pool (Stage 5)
vbp1 Aug 4, 2026
dda57a9
Refresh the time-series benchmark charts
vbp1 Aug 4, 2026
5885066
Add the replication benchmark charts
vbp1 Aug 5, 2026
21cd908
Read pages for replay in a pool of background workers
vbp1 Aug 5, 2026
bef8d00
Let a warm worker remember the sizes it measured
vbp1 Aug 5, 2026
b520f8b
Add the over-time charts for the warm-pool replication point
vbp1 Aug 5, 2026
a870b4b
Add the two-host socket-grid charts
vbp1 Aug 6, 2026
6793a01
Pace the double write buffer's kernel writeback hints
vbp1 Aug 6, 2026
8e00a12
Stop waking a warm worker for every block replay publishes
vbp1 Aug 6, 2026
c102cc0
Widen the replay warm ring to what the measurements ask for
vbp1 Aug 7, 2026
123334a
Skip the head-of-queue scenario on Windows
vbp1 Aug 7, 2026
eb3c1c0
Add the ring-size series charts
vbp1 Aug 7, 2026
f5c0ef5
Add the replay time-budget and backlog charts
vbp1 Aug 7, 2026
f7fa657
Register the double write buffer's exit backstop once per process
vbp1 Aug 8, 2026
8b97260
Declare the warm pool's hooks where smgr.c calls them
vbp1 Aug 8, 2026
9daebc6
Align the one page buffer this test hands to smgr
vbp1 Aug 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions bench/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Benchmark charts

Assets referenced by the benchmark comments of the short-lived double
write buffer pull request. Generated from the pgbench series described
there (104-thread NVMe stand, update-heavy pgbench, 1.5 TB cluster);
`vanilla` is the same tree with `io_torn_pages_protection = full_pages`
and data checksums enabled, converted from the same reference cluster.

* `users-tps.png`, `users-lat.png` — TPS and average latency vs
connection count at `checkpoint_timeout = 300s`.
* `time-tps.png`, `time-lat.png` — TPS and average latency over the
900 s run at 2700 connections (10 s pgbench samples, 30 s step).
* `repl-tps-wal.png`, `repl-lag.png` — the primary/synchronous-standby
series: throughput and shipped WAL per client count, and the standby's
replay backlog over the run. Both instances share the stand, split by
socket, with a netem-emulated 10 GbE hop between them.
* `repl-time-tps.png`, `repl-time-lat.png` — TPS and average latency over
the run at 750 connections against a synchronous standby, vanilla
against DWB with the replay warm pool (`replay_warm_workers = 12`).
The vanilla run is 900 s, the pool run 600 s.
* `repl2-tps-sockets.png`, `repl2-time-tps.png`, `repl2-time-lat.png` —
the two-host grid: the primary owns 1, 2 or 4 whole NUMA sockets of a
240-thread host, the synchronous standby has a second host to itself,
and the link between them is a real 100 GbE hop (RTT 0.126 ms). The
bar chart is throughput per socket count; the two line charts follow
the two-socket point over its 600 s run.
* `ring-size.png`, `ring-time-tps.png`, `ring-time-lat.png` — the
`replay_warm_queue_size` series on the two-host pair: what each ring size
buys the standby's replay and what it costs the pool, and the primary's
throughput and latency over each 600 s run at 750 connections against a
synchronous standby. The four points ran back to back on one pair, so
only neighbours are comparable.
* `replay-lag-512.png`, `replay-time-budget.png` — the standby side of the
ring-512 point: the backlog it accumulates over the run, and where the
replay process's time goes — its wall clock split between running and
waiting, and the running part broken down by symbol group.
Binary file added bench/repl-lag.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added bench/repl-time-lat.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added bench/repl-time-tps.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added bench/repl-tps-wal.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added bench/repl2-time-lat.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added bench/repl2-time-tps.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added bench/repl2-tps-sockets.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added bench/replay-lag-512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added bench/replay-time-budget.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added bench/ring-size.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added bench/ring-time-lat.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added bench/ring-time-tps.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added bench/time-lat.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added bench/time-tps.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added bench/users-lat.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added bench/users-tps.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 10 additions & 2 deletions doc/src/sgml/backup.sgml
Original file line number Diff line number Diff line change
Expand Up @@ -819,7 +819,11 @@ test ! -f /mnt/server/archivedir/00000001000000A900000065 && cp pg_wal/0
to make a base backup. However, if you normally run the
server with <varname>full_page_writes</varname> disabled, you might notice a drop
in performance while the backup runs since <varname>full_page_writes</varname> is
effectively forced on during backup mode.
effectively forced on during backup mode. The same applies when the
server runs with <xref linkend="guc-io-torn-pages-protection"/> set to
<literal>double_writes</literal>: page images are forced back into WAL
for the duration of the backup, because a base backup can legitimately
copy a torn page that only WAL replay with page images can repair.
</para>

<para>
Expand Down Expand Up @@ -1130,11 +1134,15 @@ SELECT * FROM pg_backup_stop(wait_for_archive => true);
</para>

<para>
The contents of the directories <filename>pg_dynshmem/</filename>,
The contents of the directories <filename>pg_dwb/</filename>,
<filename>pg_dynshmem/</filename>,
<filename>pg_notify/</filename>, <filename>pg_serial/</filename>,
<filename>pg_snapshots/</filename>, <filename>pg_stat_tmp/</filename>,
and <filename>pg_subtrans/</filename> (but not the directories themselves) can be
omitted from the backup as they will be initialized on postmaster startup.
The double write buffer ring in <filename>pg_dwb/</filename> in particular
must never be restored to another cluster: its page copies only repair
torn writes of the instance that wrote them.
</para>

<para>
Expand Down
457 changes: 457 additions & 0 deletions doc/src/sgml/config.sgml

Large diffs are not rendered by default.

28 changes: 24 additions & 4 deletions doc/src/sgml/monitoring.sgml
Original file line number Diff line number Diff line change
Expand Up @@ -1854,6 +1854,15 @@ description | Waiting for a newly initialized WAL file to reach durable storage
with the <function>pg_stat_reset_shared</function> function.
</para>

<para>
When <xref linkend="guc-replay-warm-workers"/> is set, whether a block was
already in the buffer pool is decided by the pool's workers rather than by
recovery itself, and <structfield>prefetch</structfield> and
<structfield>hit</structfield> are counted there. A request that no worker
ever serves — one withdrawn because recovery moved its read position, or
one whose worker exited while holding it — is counted in neither.
</para>

<table id="pg-stat-recovery-prefetch-view" xreflabel="pg_stat_recovery_prefetch">
<title><structname>pg_stat_recovery_prefetch</structname> View</title>
<tgroup cols="1">
Expand Down Expand Up @@ -2606,8 +2615,9 @@ description | Waiting for a newly initialized WAL file to reach durable storage
</para>

<para>
Currently, I/O on relations (e.g. tables, indexes) and WAL activity are
tracked. However, relation I/O which bypasses shared buffers
Currently, I/O on relations (e.g. tables, indexes), WAL activity and the
double write buffer (see <xref linkend="guc-io-torn-pages-protection"/>)
are tracked. However, relation I/O which bypasses shared buffers
(e.g. when moving a table from one tablespace to another) is currently
not tracked.
</para>
Expand Down Expand Up @@ -2662,6 +2672,13 @@ description | Waiting for a newly initialized WAL file to reach durable storage
<literal>temp relation</literal>: Temporary relations.
</para>
</listitem>
<listitem>
<para>
<literal>dwb</literal>: The double write buffer ring in
<filename>pg_dwb</filename> (see
<xref linkend="guc-io-torn-pages-protection"/>).
</para>
</listitem>
<listitem>
<para>
<literal>wal</literal>: Write Ahead Logs.
Expand Down Expand Up @@ -3059,7 +3076,9 @@ description | Waiting for a newly initialized WAL file to reach durable storage
<structfield>buffers_clean</structfield> <type>bigint</type>
</para>
<para>
Number of buffers written by the background writer
Number of buffers written by the background writer's LRU cleaning,
including buffers its scan handed to the cleaner worker pool
(see <xref linkend="guc-dwb-cleaner-workers"/>)
</para></entry>
</row>

Expand All @@ -3069,7 +3088,8 @@ description | Waiting for a newly initialized WAL file to reach durable storage
</para>
<para>
Number of times the background writer stopped a cleaning
scan because it had written too many buffers
scan because it had issued too many buffers, counting both its own
writes and bins handed to the cleaner worker pool
</para></entry>
</row>

Expand Down
3 changes: 2 additions & 1 deletion doc/src/sgml/protocol.sgml
Original file line number Diff line number Diff line change
Expand Up @@ -3388,7 +3388,8 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
</listitem>
<listitem>
<para>
<filename>pg_dynshmem</filename>, <filename>pg_notify</filename>,
<filename>pg_dwb</filename>, <filename>pg_dynshmem</filename>,
<filename>pg_notify</filename>,
<filename>pg_replslot</filename>, <filename>pg_serial</filename>,
<filename>pg_snapshots</filename>, <filename>pg_stat_tmp</filename>, and
<filename>pg_subtrans</filename> are copied as empty directories (even if
Expand Down
7 changes: 6 additions & 1 deletion doc/src/sgml/ref/pg_basebackup.sgml
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,12 @@ PostgreSQL documentation
<listitem>
<para>
All WAL records required for the backup must contain sufficient full-page writes,
which requires you to enable <varname>full_page_writes</varname> on the primary.
which requires you to enable <varname>full_page_writes</varname> on the
primary and to run it with <xref linkend="guc-io-torn-pages-protection"/>
set to <literal>full_pages</literal>. In the other modes the primary
does not write the automatic page images the backup depends on, and a
standby has no way to request them, so a backup taken from the
standby is refused.
</para>
</listitem>
</itemizedlist>
Expand Down
13 changes: 13 additions & 0 deletions doc/src/sgml/ref/pg_rewind.sgml
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,19 @@ GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text, bigint, bigint, b
</programlisting>
</para>

<para>
When the source is a running server, it must run with
<xref linkend="guc-io-torn-pages-protection"/> set to
<literal>full_pages</literal> and <varname>full_page_writes</varname>
enabled: <application>pg_rewind</application> reads data blocks from the
source while they may be concurrently written, and such a torn read is
only repaired by the full page images the target replays afterwards. A
cleanly shut down source can be used whatever its mode. The double
write buffer ring in <filename>pg_dwb</filename> is never copied from
the source, and the target's own ring contents are discarded when the
rewound cluster first starts.
</para>

<refsect2>
<title>How It Works</title>

Expand Down
7 changes: 7 additions & 0 deletions doc/src/sgml/storage.sgml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,13 @@ Item
<entry>Subdirectory containing transaction commit timestamp data</entry>
</row>

<row>
<entry><filename>pg_dwb</filename></entry>
<entry>Subdirectory containing the double write buffer ring
(when <varname>io_torn_pages_protection</varname> is set to
<literal>double_writes</literal>)</entry>
</row>

<row>
<entry><filename>pg_dynshmem</filename></entry>
<entry>Subdirectory containing files used by the dynamic shared memory
Expand Down
27 changes: 27 additions & 0 deletions doc/src/sgml/wal.sgml
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,33 @@
(BBU) disk controllers do not prevent partial page writes unless
they guarantee that data is written to the BBU as full (8kB) pages.
</para>
<para>
An alternative protection against partial page writes is the
<firstterm>double write buffer</firstterm>, selected by setting
<xref linkend="guc-io-torn-pages-protection"/> to
<literal>double_writes</literal>. Instead of recording page images in
WAL, every permanent data page leaving shared buffers is first written
and flushed to a small reusable ring of files in the
<filename>pg_dwb</filename>
directory, and only then written to its actual location; a batch of
pages is flushed to the ring with a single <function>fsync</function>,
and ring space is reused as soon as the covered data-file writes have
been made durable. After a crash, the server scans the ring before WAL
replay begins and rewrites every data page that is torn or older than
its ring copy, so replay always starts from intact pages. Since the
torn-page repair no longer depends on WAL contents, the automatic page
images written on the first modification after a checkpoint disappear
from WAL (operations that explicitly request a page image still log
one): WAL volume shrinks by their share, and the write
bursts that follow each checkpoint flatten out. In exchange every
flushed data page is written twice, which moves the cost from the WAL
device to the data-file write path. This mode relies on data checksums
to detect torn pages, so checksums must be enabled. The ring only
repairs pages torn by the local instance's own writes: it is excluded
from base backups, and a backup taken while this mode is active
temporarily carries forced page images in WAL instead (see
<xref linkend="backup-base-backup"/>).
</para>
<para>
<productname>PostgreSQL</productname> also protects against some kinds of data corruption
on storage devices that may occur because of hardware errors or media failure over time,
Expand Down
6 changes: 4 additions & 2 deletions src/backend/access/rmgrdesc/xlogdesc.c
Original file line number Diff line number Diff line change
Expand Up @@ -124,15 +124,17 @@ xlog_desc(StringInfo buf, XLogReaderState *record)
appendStringInfo(buf, "max_connections=%d max_worker_processes=%d "
"max_wal_senders=%d max_prepared_xacts=%d "
"max_locks_per_xact=%d wal_level=%s "
"wal_log_hints=%s track_commit_timestamp=%s",
"wal_log_hints=%s track_commit_timestamp=%s "
"io_torn_pages_protection=%s",
xlrec.MaxConnections,
xlrec.max_worker_processes,
xlrec.max_wal_senders,
xlrec.max_prepared_xacts,
xlrec.max_locks_per_xact,
wal_level_str,
xlrec.wal_log_hints ? "on" : "off",
xlrec.track_commit_timestamp ? "on" : "off");
xlrec.track_commit_timestamp ? "on" : "off",
DWBProtectionModeName(xlrec.io_torn_pages_protection));
}
else if (info == XLOG_FPW_CHANGE)
{
Expand Down
3 changes: 2 additions & 1 deletion src/backend/access/transam/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ OBJS = \
xlogreader.o \
xlogrecovery.o \
xlogstats.o \
xlogutils.o
xlogutils.o \
xlogwarm.o

include $(top_srcdir)/src/backend/common.mk

Expand Down
1 change: 1 addition & 0 deletions src/backend/access/transam/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ backend_sources += files(
'xlogrecovery.c',
'xlogstats.c',
'xlogutils.c',
'xlogwarm.c',
)

# used by frontend programs to build a frontend xlogreader
Expand Down
Loading