From d4175f7ffc623c8d4acc283e51d8865017fa14e3 Mon Sep 17 00:00:00 2001 From: Shihao Date: Sun, 27 Sep 2026 09:58:16 -0700 Subject: [PATCH v1] Add test for standby restart after VM truncation With full_page_writes off, records that clear VM bits carry no image of the VM page. The test clears VM bits by delete, same-page update and cross-page update, truncates the tables, and restarts the standby from a restartpoint taken before those changes. Discussion: https://postgr.es/m/CAL4mQLAp562c1rCgg2Dqx6TBSdOk6vOvaLFjwFG9E6k4uwvJJw@mail.gmail.com --- src/test/recovery/meson.build | 1 + src/test/recovery/t/058_vm_clear_truncate.pl | 81 ++++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 src/test/recovery/t/058_vm_clear_truncate.pl diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build index ebb12dd8766..bb28f9cfb81 100644 --- a/src/test/recovery/meson.build +++ b/src/test/recovery/meson.build @@ -66,6 +66,7 @@ tests += { 't/055_cascade_reconnect.pl', 't/056_standby_snapshot_export.pl', 't/057_snapshot_commit_race.pl', + 't/058_vm_clear_truncate.pl', ], }, } diff --git a/src/test/recovery/t/058_vm_clear_truncate.pl b/src/test/recovery/t/058_vm_clear_truncate.pl new file mode 100644 index 00000000000..ee1f805b8eb --- /dev/null +++ b/src/test/recovery/t/058_vm_clear_truncate.pl @@ -0,0 +1,81 @@ + +# Copyright (c) 2026, PostgreSQL Global Development Group + +# A standby must be able to restart when WAL it replays again clears +# visibility map bits on a VM page that a later, already replayed, +# truncation removed. With full_page_writes off, the clearing records +# carry no image of the VM page, so redo has to cope with the page not +# existing. +use strict; +use warnings FATAL => 'all'; +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; + +my $primary = PostgreSQL::Test::Cluster->new('primary'); +$primary->init(allows_streaming => 1); +$primary->append_conf( + 'postgresql.conf', qq{ +full_page_writes = off +autovacuum = off +}); +$primary->start; +$primary->backup('bkp'); + +my $standby = PostgreSQL::Test::Cluster->new('standby'); +$standby->init_from_backup($primary, 'bkp', has_streaming => 1); +$standby->start; + +# Make every heap page all-visible, then make the standby create a +# restartpoint, so that a restart replays the changes below again. +$primary->safe_psql( + 'postgres', q{ +CREATE TABLE vm_del (a int); +CREATE TABLE vm_hot (a int) WITH (fillfactor = 50); +CREATE TABLE vm_upd (a int); +INSERT INTO vm_del SELECT generate_series(1, 1000); +INSERT INTO vm_hot SELECT generate_series(1, 1000); +INSERT INTO vm_upd SELECT generate_series(1, 1000); +VACUUM (FREEZE) vm_del, vm_hot, vm_upd; +CHECKPOINT; +}); +$primary->wait_for_replay_catchup($standby); +$standby->safe_psql('postgres', 'CHECKPOINT'); + +my $start_lsn = + $primary->safe_psql('postgres', 'SELECT pg_current_wal_insert_lsn()'); + +# Clear VM bits through delete, same-page update (old VM block) and +# cross-page update (new VM block), then truncate all three tables to +# zero blocks. +$primary->safe_psql( + 'postgres', q{ +DELETE FROM vm_del; +UPDATE vm_hot SET a = -a WHERE a = 1; +UPDATE vm_upd SET a = -a WHERE a = 1; +DELETE FROM vm_hot; +DELETE FROM vm_upd; +VACUUM vm_del, vm_hot, vm_upd; +}); +is( $primary->safe_psql( + 'postgres', + "SELECT sum(pg_relation_size(c, 'vm')) FROM unnest('{vm_del,vm_hot,vm_upd}'::regclass[]) c" + ), + '0', + 'VMs truncated on primary'); +$primary->wait_for_replay_catchup($standby); + +$standby->stop; +my $log_offset = -s $standby->logfile; +my $ret = $standby->start(fail_ok => 1); + +my $log = slurp_file($standby->logfile, $log_offset); +my ($redo_lsn) = $log =~ /redo starts at ([0-9A-F]+\/[0-9A-F]+)/; +ok( defined($redo_lsn) + && $primary->safe_psql('postgres', + "SELECT '$redo_lsn'::pg_lsn < '$start_lsn'::pg_lsn") eq 't', + 'redo after restart starts before the VM bits were cleared'); +ok($ret, 'standby restarts after replaying VM truncation'); +unlike($log, qr/invalid pages/, 'no invalid page references in standby log'); + +done_testing(); -- 2.37.1 (Apple Git-137.1)