From 7f5addd7b93132dbb4d8a9a0e45a961df4470805 Mon Sep 17 00:00:00 2001 From: Daniel Gustafsson Date: Tue, 5 May 2026 14:54:23 +0200 Subject: [PATCH 1/3] Skip WAL for unlogged relations during online checksum enable ProcessSingleRelationFork() unconditionally generated an FPI WAL record for every page of every relation when enabling checksums. Unlogged relations, which by definition never generate WAL for data changes, were not exempt which generated excessive WAL to be emitted. Fix by guarding the FPI WAL record call with RelationNeedsWAL() to avoid emitting WAL for unlogged relations. Unlogged pages are still dirtied to ensure the checksum is written to disk at the next checkpoint. A test which creates an unlogged table, enables checksums, and verifies via pg_relation_size() on the standby has been added. Author: SATYANARAYANA NARLAPURAM Reviewed-by: Daniel Gustafsson Discussion: https://postgr.es/m/CAHg+QDeGrpZbNZdLjd_T4b43xKEEXZN0HGhkFm-1bkBdyzK7AQ@mail.gmail.com --- src/backend/postmaster/datachecksum_state.c | 9 ++- .../test_checksums/t/003_standby_restarts.pl | 72 +++++++++++++++++++ 2 files changed, 79 insertions(+), 2 deletions(-) diff --git a/src/backend/postmaster/datachecksum_state.c b/src/backend/postmaster/datachecksum_state.c index d0d6acdd6a2..535da8b0c4a 100644 --- a/src/backend/postmaster/datachecksum_state.c +++ b/src/backend/postmaster/datachecksum_state.c @@ -690,11 +690,16 @@ ProcessSingleRelationFork(Relation reln, ForkNumber forkNum, BufferAccessStrateg * at one point in the past, so only when checksums are first on, then * off, and then turned on again. TODO: investigate if this could be * avoided if the checksum is calculated to be correct and wal_level - * is set to "minimal", + * is set to "minimal". + * + * Unlogged relations don't need WAL since they are reset to their + * init fork on recovery. We still dirty the buffer so that the + * checksum is written to disk at the next checkpoint. */ START_CRIT_SECTION(); MarkBufferDirty(buf); - log_newpage_buffer(buf, false); + if (RelationNeedsWAL(reln)) + log_newpage_buffer(buf, false); END_CRIT_SECTION(); UnlockReleaseBuffer(buf); diff --git a/src/test/modules/test_checksums/t/003_standby_restarts.pl b/src/test/modules/test_checksums/t/003_standby_restarts.pl index 11e15c9d734..b79a06b9b32 100644 --- a/src/test/modules/test_checksums/t/003_standby_restarts.pl +++ b/src/test/modules/test_checksums/t/003_standby_restarts.pl @@ -115,6 +115,78 @@ $result = $node_primary->safe_psql('postgres', "SELECT count(a) FROM t WHERE a > 1"); is($result, "19998", 'ensure we can safely read all data without checksums'); +# --------------------------------------------------------------------------- +# Test that enabling checksums does not emit WAL for unlogged relations. +# Unlogged relations are wiped on recovery, so FPIs for them would be +# pointless and waste WAL traffic / standby I/O. +# + +$node_primary->safe_psql('postgres', + 'CREATE UNLOGGED TABLE unlogged_tbl AS SELECT generate_series(1,1000) AS a;' +); +$node_primary->wait_for_catchup($node_standby, 'replay', + $node_primary->lsn('insert')); + +# Get the relfilenode and database OID so we can inspect the filesystem +my $unlogged_rfn = $node_primary->safe_psql('postgres', + "SELECT relfilenode FROM pg_class WHERE relname = 'unlogged_tbl';"); +my $db_oid = $node_primary->safe_psql('postgres', + "SELECT oid FROM pg_database WHERE datname = 'postgres';"); + +# Verify the standby only has the init fork (no main fork) +my $standby_datadir = $node_standby->data_dir; +ok( !-f "$standby_datadir/base/$db_oid/$unlogged_rfn", + 'standby has no main fork for unlogged table before enable'); + +# Re-enable data checksums +enable_data_checksums($node_primary, wait => 'on'); +wait_for_checksum_state($node_standby, 'on'); + +# After standby replays, the unlogged main file must still not exist. +# If the bug were present, FPI replay would materialize the full table. +$node_primary->wait_for_catchup($node_standby, 'replay', + $node_primary->lsn('insert')); +ok( !-f "$standby_datadir/base/$db_oid/$unlogged_rfn", + 'standby has no main fork for unlogged table after enable'); + +# Verify unlogged relation size is 0 on the standby (main fork missing) +my $standby_size = $node_standby->safe_psql('postgres', + "SELECT pg_relation_size('unlogged_tbl', 'main');"); +is($standby_size, '0', + 'unlogged table has zero size on standby after checksum enable'); + +# Unlogged table should still be readable on primary +$result = + $node_primary->safe_psql('postgres', 'SELECT count(*) FROM unlogged_tbl;'); +is($result, '1000', + 'unlogged table readable on primary after checksum enable'); + +# Alter persistence to logged, and make sure we can read it on both the primary +# and standby without any page verification errors in the logfiles. +$node_primary->safe_psql('postgres', 'ALTER TABLE unlogged_tbl SET logged;'); +$node_primary->wait_for_catchup($node_standby, 'replay', + $node_primary->lsn('insert')); + +$result = + $node_primary->safe_psql('postgres', 'SELECT sum(a) FROM unlogged_tbl;'); +is($result, '500500', 'previously unlogged table can be read on primary'); +$result = + $node_standby->safe_psql('postgres', 'SELECT sum(a) FROM unlogged_tbl;'); +is($result, '500500', 'previously unlogged table can be read on standby'); + $node_standby->stop; $node_primary->stop; + +# Perform one final pass over the logs and hunt for unexpected errors +my $log = PostgreSQL::Test::Utils::slurp_file($node_primary->logfile, 0); +unlike( + $log, + qr/page verification failed,.+\d$/m, + "no checksum validation errors in primary log"); +$log = PostgreSQL::Test::Utils::slurp_file($node_standby->logfile, 0); +unlike( + $log, + qr/page verification failed,.+\d$/m, + "no checksum validation errors in standby log"); + done_testing(); -- 2.39.3 (Apple Git-146)