From: Bertrand Drouvot Date: Sat, 13 Jun 2026 07:20:08 +0000 Subject: [PATCH v1] Allow wal_log_hints to be changed without restart Change wal_log_hints from PGC_POSTMASTER to PGC_SIGHUP, allowing it to be changed without restarting the server. As f19c0ecca introduced online enabling and disabling of data checksums, it makes sense to do the same for wal_log_hints (as it can be used to test how much extra WAL-logging would occur if your database had data checksums enabled). This commit: - Changes wal_log_hints from PGC_POSTMASTER to PGC_SIGHUP. - uses the same pattern as full_page_writes as it does not need the complexity of procsignal barriers that has been used in f19c0ecca. Indeed, it's not a multi state transition and no all backends must agree simultaneously. The key concern is that when turning wal_log_hints OFF, no backend should stop WAL-logging hint bit updates before the parameter change is itself WAL-logged. Simply propagating the GUC via SIGHUP would leave a window where a backend could acknowledge the change before the WAL record is written. To address this, we introduce a shared memory flag (XLogCtl->walLogHints) that serves as the authoritative value read by all backends via XLogHintBitIsNeeded(). Only the checkpointer writes this flag, using the same ordering pattern as UpdateFullPageWrites(): - When enabling: set the shared flag to true first, then WAL-log the parameter change. Backends immediately start WAL-logging hints and the extra WAL before the record is harmless. - When disabling: WAL-log the parameter change first, then set the shared flag to false. As in commit 3b682df3260 (which added the same pattern to UpdateFullPageWrites()), a critical section is used as extra protection to ensure consistency between the flag and the WAL record, even though the ordering makes both failure directions harmless (extra WAL-logging). Author: Bertrand Drouvot Reviewed-by: Discussion: --- doc/src/sgml/config.sgml | 4 +- src/backend/access/transam/xlog.c | 82 +++++++++++++++++++++++ src/backend/postmaster/checkpointer.c | 6 ++ src/backend/utils/misc/guc_parameters.dat | 2 +- src/bin/pg_rewind/t/012_wal_log_hints.pl | 77 +++++++++++++++++++++ src/include/access/xlog.h | 3 +- 6 files changed, 171 insertions(+), 3 deletions(-) 5.0% doc/src/sgml/ 44.8% src/backend/access/transam/ 3.3% src/backend/utils/misc/ 41.4% src/bin/pg_rewind/t/ 5.3% src/ diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index fa566c9e553..ffa6235a5ba 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -3646,7 +3646,9 @@ include_dir 'conf.d' - This parameter can only be set at server start. The default value is off. + This parameter can only be set in the postgresql.conf + file or on the server command line. + The default value is off. diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index 6c2304fef33..f0ba2291433 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -559,6 +559,16 @@ typedef struct XLogCtlData /* last data_checksum_version we've seen */ uint32 data_checksum_version; + /* + * walLogHints is the authoritative value used by all backends to + * determine whether to WAL-log hint bit updates. This shared value, + * instead of the process-local wal_log_hints, is required because, when + * wal_log_hints is changed by SIGHUP, we must ensure proper ordering + * between the WAL parameter change record and the actual behavior change. + * Checkpointer updates it after SIGHUP. + */ + bool walLogHints; + slock_t info_lck; /* locks shared variables shown above */ } XLogCtlData; @@ -4678,6 +4688,23 @@ DataChecksumsNeedWrite(void) LocalDataChecksumState == PG_DATA_CHECKSUM_INPROGRESS_OFF); } +/* + * XLogHintBitIsNeeded + * Returns whether hint bit must be written or not + * + * Returns true if wal_log_hints is enabled in shared memory, or if data + * checksums require writes. The shared memory value is used (rather than + * the process-local wal_log_hints) to ensure all backends observe the change + * atomically with respect to the WAL record that logs the state transition. + */ +bool +XLogHintBitIsNeeded(void) +{ + volatile XLogCtlData *xlogctl = XLogCtl; + + return (xlogctl->walLogHints || DataChecksumsNeedWrite()); +} + bool DataChecksumsOff(void) @@ -5429,6 +5456,9 @@ XLOGShmemInit(void *arg) XLogCtl->data_checksum_version = ControlFile->data_checksum_version; SetLocalDataChecksumState(XLogCtl->data_checksum_version); + /* Use wal_log_hints from control file */ + XLogCtl->walLogHints = ControlFile->wal_log_hints; + SpinLockInit(&XLogCtl->Insert.insertpos_lck); SpinLockInit(&XLogCtl->info_lck); pg_atomic_init_u64(&XLogCtl->logInsertResult, InvalidXLogRecPtr); @@ -6555,6 +6585,9 @@ StartupXLOG(void) Insert->fullPageWrites = lastFullPageWrites; UpdateFullPageWrites(); + /* Update wal_log_hints in shared memory */ + UpdateWalLogHints(); + /* * Emit checkpoint or end-of-recovery record in XLOG, if required. */ @@ -8812,6 +8845,55 @@ UpdateFullPageWrites(void) END_CRIT_SECTION(); } +/* + * Update wal_log_hints in shared memory, and write an + * XLOG_PARAMETER_CHANGE record if necessary. + * + * This follows the same ordering pattern as UpdateFullPageWrites(): + * when setting to true, update shared memory first (so backends start + * WAL-logging hints before the WAL record), and when setting to false, + * write the WAL record first (so the change is logged before backends + * stop WAL-logging hints). + */ +void +UpdateWalLogHints(void) +{ + bool recoveryInProgress; + + if (wal_log_hints == XLogCtl->walLogHints) + return; + + /* + * Perform this outside critical section so that the WAL insert + * initialization done by RecoveryInProgress() doesn't trigger an + * assertion failure. + */ + recoveryInProgress = RecoveryInProgress(); + + START_CRIT_SECTION(); + + /* + * It's always safe to WAL-log hint bits, even when not strictly required, + * but not the other round. So if we're setting wal_log_hints to true, + * first set it true and then write the WAL record. If we're setting it to + * false, first write the WAL record and then set the global flag. + */ + if (wal_log_hints) + XLogCtl->walLogHints = true; + + /* + * XLogReportParameters will WAL-log and update the control file. During + * recovery, we can't write WAL so just update the shared flag. + */ + if (!recoveryInProgress) + XLogReportParameters(); + + if (!wal_log_hints) + XLogCtl->walLogHints = false; + + END_CRIT_SECTION(); +} + /* * XLOG resource manager's routines * diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c index 087120db090..463a24edf55 100644 --- a/src/backend/postmaster/checkpointer.c +++ b/src/backend/postmaster/checkpointer.c @@ -1510,6 +1510,12 @@ UpdateSharedMemoryConfig(void) */ UpdateFullPageWrites(); + /* + * If wal_log_hints has been changed by SIGHUP, we update pg_control and + * write an XLOG_PARAMETER_CHANGE record. + */ + UpdateWalLogHints(); + elog(DEBUG2, "checkpointer updated shared memory configuration values"); } diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat index afaa058b046..1a39fa09ea1 100644 --- a/src/backend/utils/misc/guc_parameters.dat +++ b/src/backend/utils/misc/guc_parameters.dat @@ -3502,7 +3502,7 @@ options => 'wal_level_options', }, -{ name => 'wal_log_hints', type => 'bool', context => 'PGC_POSTMASTER', group => 'WAL_SETTINGS', +{ name => 'wal_log_hints', type => 'bool', context => 'PGC_SIGHUP', group => 'WAL_SETTINGS', short_desc => 'Writes full pages to WAL when first modified after a checkpoint, even for a non-critical modification.', variable => 'wal_log_hints', boot_val => 'false', diff --git a/src/bin/pg_rewind/t/012_wal_log_hints.pl b/src/bin/pg_rewind/t/012_wal_log_hints.pl new file mode 100644 index 00000000000..9eb9c48db7c --- /dev/null +++ b/src/bin/pg_rewind/t/012_wal_log_hints.pl @@ -0,0 +1,77 @@ + +# Copyright (c) 2021-2026, PostgreSQL Global Development Group + +# +# Test pg_rewind interaction with wal_log_hints: +# - Error out when wal_log_hints=off and no data checksums +# - Succeeds after reload to on +# +use strict; +use warnings FATAL => 'all'; +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; + +# Initialize primary without data checksums and with wal_log_hints=off +my $node_primary = PostgreSQL::Test::Cluster->new('primary'); +$node_primary->init(allows_streaming => 1, no_data_checksums => 1); +$node_primary->append_conf( + 'postgresql.conf', qq{ +wal_log_hints = off +wal_level = replica +wal_keep_size = 64MB +autovacuum = off +}); +$node_primary->start; + +$node_primary->safe_psql('postgres', + "CREATE TABLE t(id int); INSERT INTO t SELECT generate_series(1,100)"); +$node_primary->safe_psql('postgres', "CHECKPOINT"); + +# Create standby, diverge +my $node_standby = PostgreSQL::Test::Cluster->new('standby'); +$node_primary->backup('my_backup'); +$node_standby->init_from_backup($node_primary, 'my_backup', + has_streaming => 1); +$node_standby->start; +$node_primary->wait_for_catchup($node_standby); + +$node_standby->promote; +$node_standby->safe_psql('postgres', "INSERT INTO t VALUES (999)"); +$node_primary->safe_psql('postgres', "INSERT INTO t VALUES (888)"); +$node_primary->safe_psql('postgres', "CHECKPOINT"); +$node_primary->stop; + +# pg_rewind must refuse: wal_log_hints=off +command_fails_like( + [ + 'pg_rewind', + '--target-pgdata' => $node_primary->data_dir, + '--source-server' => $node_standby->connstr('postgres'), + '--no-sync', + ], + qr/target server needs to use either data checksums or "wal_log_hints = on"/, + 'pg_rewind refuses with wal_log_hints=off and no data checksums'); + +# Restart primary and enable wal_log_hints via reload +$node_primary->start; +$node_primary->append_conf('postgresql.conf', 'wal_log_hints = on'); +$node_primary->reload; + +my $result = $node_primary->safe_psql('postgres', "SHOW wal_log_hints"); +is($result, 'on', 'wal_log_hints changed to on via reload'); + +$node_primary->stop; + +# Same pg_rewind now succeeds +command_ok( + [ + 'pg_rewind', + '--target-pgdata' => $node_primary->data_dir, + '--source-server' => $node_standby->connstr('postgres'), + '--no-sync', + ], + 'pg_rewind succeeds after enabling wal_log_hints via reload'); + +$node_standby->stop; +done_testing(); diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h index 4dd98624204..8016eaadb53 100644 --- a/src/include/access/xlog.h +++ b/src/include/access/xlog.h @@ -120,7 +120,7 @@ extern PGDLLIMPORT bool XLogLogicalInfo; * of the bits make it to disk, but the checksum wouldn't match. Also WAL-log * them if forced by wal_log_hints=on. */ -#define XLogHintBitIsNeeded() (wal_log_hints || DataChecksumsNeedWrite()) +extern bool XLogHintBitIsNeeded(void); /* Do we need to WAL-log information required only for Hot Standby and logical replication? */ #define XLogStandbyInfoActive() (wal_level >= WAL_LEVEL_REPLICA) @@ -274,6 +274,7 @@ extern void XLogPutNextOid(Oid nextOid); extern XLogRecPtr XLogRestorePoint(const char *rpName); extern XLogRecPtr XLogAssignLSN(void); extern void UpdateFullPageWrites(void); +extern void UpdateWalLogHints(void); extern void GetFullPageWriteInfo(XLogRecPtr *RedoRecPtr_p, bool *doPageWrites_p); extern XLogRecPtr GetRedoRecPtr(void); extern XLogRecPtr GetInsertRecPtr(void); -- 2.34.1 --9ItUckmLlnvfmpcW--