From ac838953de9c4ab0cb5f13d1e1b8ad0a18e73e39 Mon Sep 17 00:00:00 2001 From: reshke Date: Thu, 18 Dec 2025 18:00:22 +0000 Subject: [PATCH v1] Add TAP test for empty page vacuum. VACUUM can be run for empty pages with DISABLE_PAGE_SKIPPING option. In this case, VACUUM wil set up page-level visibility bit (PD_ALL_VISIBLE) if not previously set. To end up with empty page which is missing visibility hint bit, we need to forcefuly cancel (kill -9) backend, executing page freezing, jsut after it did page pruning. Use injeciton point for this purpose and add TAP test to cover "recovery" after error code path. --- src/backend/access/heap/vacuumlazy.c | 6 ++ .../test_misc/t/010_vacuum_empty_page.pl | 75 +++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 src/test/modules/test_misc/t/010_vacuum_empty_page.pl diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c index 30778a15639..9b8cbb67f11 100644 --- a/src/backend/access/heap/vacuumlazy.c +++ b/src/backend/access/heap/vacuumlazy.c @@ -153,6 +153,7 @@ #include "storage/freespace.h" #include "storage/lmgr.h" #include "storage/read_stream.h" +#include "utils/injection_point.h" #include "utils/lsyscache.h" #include "utils/pg_rusage.h" #include "utils/timestamp.h" @@ -1899,6 +1900,8 @@ lazy_scan_new_or_empty(LVRelState *vacrel, Buffer buf, BlockNumber blkno, */ if (!PageIsAllVisible(page)) { + INJECTION_POINT("vacuum-empty-page-non-all-vis", NULL); + START_CRIT_SECTION(); /* mark buffer dirty before writing a WAL record */ @@ -2012,6 +2015,9 @@ lazy_scan_prune(LVRelState *vacrel, &vacrel->offnum, &vacrel->NewRelfrozenXid, &vacrel->NewRelminMxid); + + INJECTION_POINT("vacuum-heap-prune-and-freeze-after", NULL); + Assert(MultiXactIdIsValid(vacrel->NewRelminMxid)); Assert(TransactionIdIsValid(vacrel->NewRelfrozenXid)); diff --git a/src/test/modules/test_misc/t/010_vacuum_empty_page.pl b/src/test/modules/test_misc/t/010_vacuum_empty_page.pl new file mode 100644 index 00000000000..af5c39d2435 --- /dev/null +++ b/src/test/modules/test_misc/t/010_vacuum_empty_page.pl @@ -0,0 +1,75 @@ +# Copyright (c) 2025, PostgreSQL Global Development Group + +# Check how temporary file removals and statement queries are associated +# in the server logs for various query sequences with the simple and +# extended query protocols. + +use strict; +use warnings FATAL => 'all'; +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; + +if ($ENV{enable_injection_points} ne 'yes') +{ + plan skip_all => 'Injection points not supported by this build'; +} + +# Initialize a new PostgreSQL test cluster +my $node = PostgreSQL::Test::Cluster->new('primary'); +$node->init(); +$node->append_conf( + 'postgresql.conf', qq( +log_min_messages = 'notice' +)); +$node->start; + +# Check if the extension injection_points is available, as it may be +# possible that this script is run with installcheck, where the module +# would not be installed by default. +if (!$node->check_extension('injection_points')) +{ + plan skip_all => 'Extension injection_points not installed'; +} + +$node->safe_psql('postgres', 'CREATE EXTENSION injection_points;'); + + +# Setup table and populate with data +$node->safe_psql( + "postgres", qq{ +CREATE TABLE vac_empty_test(a int); +BEGIN; +INSERT INTO vac_empty_test DEFAULT VALUES; +ROLLBACK; +}); + +# From this point, autovacuum worker will wait at startup. +$node->safe_psql('postgres', + "SELECT injection_points_attach('vacuum-heap-prune-and-freeze-after', 'error');"); +$node->safe_psql('postgres', + "SELECT injection_points_attach('vacuum-empty-page-non-all-vis', 'notice');"); + +$node->psql('postgres', "VACUUM (FREEZE) vac_empty_test;", on_error_stop => 1); + +my $offset = -s $node->logfile; + +# Run vacuum, force it on empty page. +$node->safe_psql( + "postgres", qq{ +VACUUM (DISABLE_PAGE_SKIPPING) vac_empty_test; +}); + +ok( $node->log_contains( + qr/NOTICE: notice triggered for injection point vacuum-empty-page-non-all-vis/, + $offset), + "vacuum sets all-visible page bit for empty page"); + + +$node->safe_psql('postgres', + "SELECT injection_points_detach('vacuum-heap-prune-and-freeze-after');"); +$node->safe_psql('postgres', + "SELECT injection_points_detach('vacuum-empty-page-non-all-vis');"); + +$node->stop('fast'); +done_testing(); -- 2.43.0