From 534ed530a7fd11cad34f6c39678b9937971b80b0 Mon Sep 17 00:00:00 2001 From: Daniil Davidov Date: Fri, 31 Oct 2025 14:44:12 +0700 Subject: [PATCH v13 3/4] Tests for parallel autovacuum --- src/backend/commands/vacuumparallel.c | 8 + src/backend/postmaster/autovacuum.c | 14 + src/test/modules/Makefile | 1 + src/test/modules/meson.build | 1 + src/test/modules/test_autovacuum/.gitignore | 2 + src/test/modules/test_autovacuum/Makefile | 26 ++ src/test/modules/test_autovacuum/meson.build | 36 +++ .../modules/test_autovacuum/t/001_basic.pl | 165 ++++++++++++ .../test_autovacuum/test_autovacuum--1.0.sql | 34 +++ .../modules/test_autovacuum/test_autovacuum.c | 255 ++++++++++++++++++ .../test_autovacuum/test_autovacuum.control | 3 + 11 files changed, 545 insertions(+) create mode 100644 src/test/modules/test_autovacuum/.gitignore create mode 100644 src/test/modules/test_autovacuum/Makefile create mode 100644 src/test/modules/test_autovacuum/meson.build create mode 100644 src/test/modules/test_autovacuum/t/001_basic.pl create mode 100644 src/test/modules/test_autovacuum/test_autovacuum--1.0.sql create mode 100644 src/test/modules/test_autovacuum/test_autovacuum.c create mode 100644 src/test/modules/test_autovacuum/test_autovacuum.control diff --git a/src/backend/commands/vacuumparallel.c b/src/backend/commands/vacuumparallel.c index 9a258238650..0cfdf79cb6c 100644 --- a/src/backend/commands/vacuumparallel.c +++ b/src/backend/commands/vacuumparallel.c @@ -39,6 +39,7 @@ #include "postmaster/autovacuum.h" #include "storage/bufmgr.h" #include "tcop/tcopprot.h" +#include "utils/injection_point.h" #include "utils/lsyscache.h" #include "utils/rel.h" @@ -752,6 +753,13 @@ parallel_vacuum_process_all_indexes(ParallelVacuumState *pvs, int num_index_scan } } + /* + * To be able to exercise whether all reserved parallel workers are being + * released anyway, allow injection points to trigger a failure at this + * point. + */ + INJECTION_POINT("autovacuum-trigger-leader-failure", NULL); + /* Vacuum the indexes that can be processed by only leader process */ parallel_vacuum_process_unsafe_indexes(pvs); diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c index 9499d4f0c12..a6358200629 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -3437,6 +3437,13 @@ AutoVacuumReserveParallelWorkers(int nworkers) /* Remember how many workers we have reserved. */ av_nworkers_reserved += nworkers; + /* + * Injection point to help exercising number of available parallel + * autovacuum workers. + */ + INJECTION_POINT("autovacuum-set-free-parallel-workers-num", + &AutoVacuumShmem->av_freeParallelWorkers); + LWLockRelease(AutovacuumLock); return nreserved; } @@ -3467,6 +3474,13 @@ AutoVacuumReleaseParallelWorkers(int nworkers) /* Don't have to remember these workers anymore. */ av_nworkers_reserved -= nworkers; + /* + * Injection point to help exercising number of available parallel + * autovacuum workers. + */ + INJECTION_POINT("autovacuum-set-free-parallel-workers-num", + &AutoVacuumShmem->av_freeParallelWorkers); + LWLockRelease(AutovacuumLock); } diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile index 902a7954101..f09d0060248 100644 --- a/src/test/modules/Makefile +++ b/src/test/modules/Makefile @@ -15,6 +15,7 @@ SUBDIRS = \ plsample \ spgist_name_ops \ test_aio \ + test_autovacuum \ test_binaryheap \ test_bitmapset \ test_bloomfilter \ diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build index 14fc761c4cf..ee7e855def0 100644 --- a/src/test/modules/meson.build +++ b/src/test/modules/meson.build @@ -14,6 +14,7 @@ subdir('plsample') subdir('spgist_name_ops') subdir('ssl_passphrase_callback') subdir('test_aio') +subdir('test_autovacuum') subdir('test_binaryheap') subdir('test_bitmapset') subdir('test_bloomfilter') diff --git a/src/test/modules/test_autovacuum/.gitignore b/src/test/modules/test_autovacuum/.gitignore new file mode 100644 index 00000000000..716e17f5a2a --- /dev/null +++ b/src/test/modules/test_autovacuum/.gitignore @@ -0,0 +1,2 @@ +# Generated subdirectories +/tmp_check/ diff --git a/src/test/modules/test_autovacuum/Makefile b/src/test/modules/test_autovacuum/Makefile new file mode 100644 index 00000000000..4cf7344b2ac --- /dev/null +++ b/src/test/modules/test_autovacuum/Makefile @@ -0,0 +1,26 @@ +# src/test/modules/test_autovacuum/Makefile + +PGFILEDESC = "test_autovacuum - test code for parallel autovacuum" + +MODULE_big = test_autovacuum +OBJS = \ + $(WIN32RES) \ + test_autovacuum.o + +EXTENSION = test_autovacuum +DATA = test_autovacuum--1.0.sql + +TAP_TESTS = 1 + +export enable_injection_points + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = src/test/modules/test_autovacuum +top_builddir = ../../../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/src/test/modules/test_autovacuum/meson.build b/src/test/modules/test_autovacuum/meson.build new file mode 100644 index 00000000000..3441e5e49cf --- /dev/null +++ b/src/test/modules/test_autovacuum/meson.build @@ -0,0 +1,36 @@ +# Copyright (c) 2024-2025, PostgreSQL Global Development Group + +test_autovacuum_sources = files( + 'test_autovacuum.c', +) + +if host_system == 'windows' + test_autovacuum_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'test_autovacuum', + '--FILEDESC', 'test_autovacuum - test code for parallel autovacuum',]) +endif + +test_autovacuum = shared_module('test_autovacuum', + test_autovacuum_sources, + kwargs: pg_test_mod_args, +) +test_install_libs += test_autovacuum + +test_install_data += files( + 'test_autovacuum.control', + 'test_autovacuum--1.0.sql', +) + +tests += { + 'name': 'test_autovacuum', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'tap': { + 'env': { + 'enable_injection_points': get_option('injection_points') ? 'yes' : 'no', + }, + 'tests': [ + 't/001_basic.pl', + ], + }, +} diff --git a/src/test/modules/test_autovacuum/t/001_basic.pl b/src/test/modules/test_autovacuum/t/001_basic.pl new file mode 100644 index 00000000000..22eaaa7da9d --- /dev/null +++ b/src/test/modules/test_autovacuum/t/001_basic.pl @@ -0,0 +1,165 @@ +use warnings FATAL => 'all'; +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; + +my $psql_out; + +my $node = PostgreSQL::Test::Cluster->new('node1'); +$node->init; + +# Configure postgres, so it can launch parallel autovacuum workers, log all +# information we are interested in and autovacuum works frequently +$node->append_conf('postgresql.conf', qq{ + max_worker_processes = 20 + max_parallel_workers = 20 + max_parallel_maintenance_workers = 20 + autovacuum_max_parallel_workers = 10 + log_min_messages = debug2 + log_autovacuum_min_duration = 0 + autovacuum_naptime = '1s' + min_parallel_index_scan_size = 0 + shared_preload_libraries=test_autovacuum +}); +$node->start; + +my $indexes_num = 4; +my $initial_rows_num = 10_000; +my $autovacuum_parallel_workers = 2; + +# Create table with specified number of b-tree indexes on it +$node->safe_psql('postgres', qq{ + CREATE TABLE test_autovac ( + id SERIAL PRIMARY KEY, + col_1 INTEGER, col_2 INTEGER, col_3 INTEGER, col_4 INTEGER + ) WITH (autovacuum_parallel_workers = $autovacuum_parallel_workers, + autovacuum_enabled = false); + + DO \$\$ + DECLARE + i INTEGER; + BEGIN + FOR i IN 1..$indexes_num LOOP + EXECUTE format('CREATE INDEX idx_col_\%s ON test_autovac (col_\%s);', i, i); + END LOOP; + END \$\$; +}); + +# Insert specified tuples num into the table +$node->safe_psql('postgres', qq{ + DO \$\$ + DECLARE + i INTEGER; + BEGIN + FOR i IN 1..$initial_rows_num LOOP + INSERT INTO test_autovac VALUES (i, i + 1, i + 2, i + 3); + END LOOP; + END \$\$; +}); + +# Now, create some dead tuples and refresh table statistics +$node->safe_psql('postgres', qq{ + UPDATE test_autovac SET col_1 = 0 WHERE (col_1 % 3) = 0; + ANALYZE test_autovac; +}); + +# Create all functions needed for testing +$node->safe_psql('postgres', qq{ + CREATE EXTENSION test_autovacuum; + SELECT inj_set_free_workers_attach(); + SELECT inj_leader_failure_attach(); +}); + +# Test 1 : +# Our table has enough indexes and appropriate reloptions, so autovacuum must +# be able to process it in parallel mode. Just check if it can. +# Also check whether all requested workers: +# 1) launched +# 2) correctly released + +$node->safe_psql('postgres', qq{ + ALTER TABLE test_autovac SET (autovacuum_enabled = true); +}); + +# Wait until the parallel autovacuum on table is completed. At the same time, +# we check that the required number of parallel workers has been started. +$node->wait_for_log(qr/workers usage statistics for all of index scans : / . + qr/launched in total = 2, planned in total = 2/); + +$node->psql('postgres', + "SELECT get_parallel_autovacuum_free_workers();", + stdout => \$psql_out, +); +is($psql_out, 10, 'All parallel workers has been released by the leader'); + +# Disable autovacuum on table during preparation for the next test +$node->append_conf('postgresql.conf', qq{ + ALTER TABLE test_autovac SET (autovacuum_enabled = false); +}); + +# Create more dead tuples +$node->safe_psql('postgres', qq{ + UPDATE test_autovac SET col_2 = 0 WHERE (col_2 % 3) = 0; + ANALYZE test_autovac; +}); + +# Test 2: +# We want parallel autovacuum workers to be released even if leader gets an +# error. At first, simulate situation, when leader exites due to an ERROR. + +$node->safe_psql('postgres', qq( + SELECT trigger_leader_failure('ERROR'); +)); + +$node->safe_psql('postgres', qq{ + ALTER TABLE test_autovac SET (autovacuum_enabled = true); +}); + +$node->wait_for_log(qr/error, triggered by injection point/); + +$node->psql('postgres', + "SELECT get_parallel_autovacuum_free_workers();", + stdout => \$psql_out, +); +is($psql_out, 10, + 'All parallel workers has been released by the leader after ERROR'); + +# Disable autovacuum on table during preparation for the next test +$node->append_conf('postgresql.conf', qq{ + ALTER TABLE test_autovac SET (autovacuum_enabled = false); +}); + +# Create more dead tuples +$node->safe_psql('postgres', qq{ + UPDATE test_autovac SET col_3 = 0 WHERE (col_3 % 3) = 0; + ANALYZE test_autovac; +}); + +# Test 3: +# Same as Test 2, but simulate situation, when leader exites due to FATAL. + +$node->safe_psql('postgres', qq( + SELECT trigger_leader_failure('FATAL'); +)); + +$node->safe_psql('postgres', qq{ + ALTER TABLE test_autovac SET (autovacuum_enabled = true); +}); + +$node->wait_for_log(qr/fatal, triggered by injection point/); + +$node->psql('postgres', + "SELECT get_parallel_autovacuum_free_workers();", + stdout => \$psql_out, +); +is($psql_out, 10, + 'All parallel workers has been released by the leader after FATAL'); + +# Cleanup +$node->safe_psql('postgres', qq{ + SELECT inj_set_free_workers_detach(); + SELECT inj_leader_failure_detach(); +}); + +$node->stop; +done_testing(); diff --git a/src/test/modules/test_autovacuum/test_autovacuum--1.0.sql b/src/test/modules/test_autovacuum/test_autovacuum--1.0.sql new file mode 100644 index 00000000000..017d5da85ea --- /dev/null +++ b/src/test/modules/test_autovacuum/test_autovacuum--1.0.sql @@ -0,0 +1,34 @@ +/* src/test/modules/test_autovacuum/test_autovacuum--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION test_autovacuum" to load this file. \quit + +/* + * Functions for expecting or to interfere autovacuum state + */ +CREATE FUNCTION get_parallel_autovacuum_free_workers() +RETURNS INTEGER STRICT +AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION trigger_leader_failure(failure_type text) +RETURNS VOID STRICT +AS 'MODULE_PATHNAME' LANGUAGE C; + +/* + * Injection point related functions + */ +CREATE FUNCTION inj_set_free_workers_attach() +RETURNS VOID STRICT +AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION inj_set_free_workers_detach() +RETURNS VOID STRICT +AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION inj_leader_failure_attach() +RETURNS VOID STRICT +AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION inj_leader_failure_detach() +RETURNS VOID STRICT +AS 'MODULE_PATHNAME' LANGUAGE C; diff --git a/src/test/modules/test_autovacuum/test_autovacuum.c b/src/test/modules/test_autovacuum/test_autovacuum.c new file mode 100644 index 00000000000..2c979c405bd --- /dev/null +++ b/src/test/modules/test_autovacuum/test_autovacuum.c @@ -0,0 +1,255 @@ +/*------------------------------------------------------------------------- + * + * test_autovacuum.c + * Helpers to write tests for parallel autovacuum + * + * Copyright (c) 2020-2025, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/test/modules/test_autovacuum/test_autovacuum.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "fmgr.h" +#include "miscadmin.h" +#include "postmaster/autovacuum.h" +#include "storage/shmem.h" +#include "storage/ipc.h" +#include "storage/lwlock.h" +#include "utils/builtins.h" +#include "utils/injection_point.h" + +PG_MODULE_MAGIC; + +typedef enum AVLeaderFaulureType +{ + FAIL_NONE, + FAIL_ERROR, + FAIL_FATAL, +} AVLeaderFaulureType; + +typedef struct InjPointState +{ + bool enabled_set_free_workers; + uint32 free_parallel_workers; + + bool enabled_leader_failure; + AVLeaderFaulureType ftype; +} InjPointState; + +static InjPointState *inj_point_state; + +/* Shared memory init callbacks */ +static shmem_request_hook_type prev_shmem_request_hook = NULL; +static shmem_startup_hook_type prev_shmem_startup_hook = NULL; + +static void +test_autovacuum_shmem_request(void) +{ + if (prev_shmem_request_hook) + prev_shmem_request_hook(); + + RequestAddinShmemSpace(sizeof(InjPointState)); +} + +static void +test_autovacuum_shmem_startup(void) +{ + bool found; + + if (prev_shmem_startup_hook) + prev_shmem_startup_hook(); + + /* Create or attach to the shared memory state */ + LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE); + + inj_point_state = ShmemInitStruct("injection_points", + sizeof(InjPointState), + &found); + + if (!found) + { + /* First time through, initialize */ + inj_point_state->enabled_leader_failure = false; + inj_point_state->enabled_set_free_workers = false; + inj_point_state->ftype = FAIL_NONE; + + /* Keep it in sync with AutoVacuumShmemInit */ + inj_point_state->free_parallel_workers = + Min(autovacuum_max_parallel_workers, max_worker_processes); + + InjectionPointAttach("autovacuum-set-free-parallel-workers-num", + "test_autovacuum", + "inj_set_free_workers", + NULL, + 0); + + InjectionPointAttach("autovacuum-trigger-leader-failure", + "test_autovacuum", + "inj_trigger_leader_failure", + NULL, + 0); + } + + LWLockRelease(AddinShmemInitLock); +} + +void +_PG_init(void) +{ + if (!process_shared_preload_libraries_in_progress) + return; + + prev_shmem_request_hook = shmem_request_hook; + shmem_request_hook = test_autovacuum_shmem_request; + prev_shmem_startup_hook = shmem_startup_hook; + shmem_startup_hook = test_autovacuum_shmem_startup; +} + +extern PGDLLEXPORT void inj_set_free_workers(const char *name, + const void *private_data, + void *arg); +extern PGDLLEXPORT void inj_trigger_leader_failure(const char *name, + const void *private_data, + void *arg); + +/* + * Set number of currently available parallel a/v workers. This value may + * change after reserving or releasing such workers. + * + * Function called from parallel autovacuum leader. + */ +void +inj_set_free_workers(const char *name, const void *private_data, void *arg) +{ + ereport(LOG, + errmsg("set parallel workers injection point called"), + errhidestmt(true), errhidecontext(true)); + + if (inj_point_state->enabled_set_free_workers) + { + Assert(arg != NULL); + inj_point_state->free_parallel_workers = *(uint32 *) arg; + } +} + +/* + * Throw an ERROR or FATAL, if somebody requested it. + * + * Function called from parallel autovacuum leader. + */ +void +inj_trigger_leader_failure(const char *name, const void *private_data, + void *arg) +{ + int elevel; + char *elevel_str; + + ereport(LOG, + errmsg("trigger leader failure injection point called"), + errhidestmt(true), errhidecontext(true)); + + if (inj_point_state->ftype == FAIL_NONE || + !inj_point_state->enabled_leader_failure) + { + return; + } + + elevel = inj_point_state->ftype == FAIL_ERROR ? ERROR : FATAL; + elevel_str = elevel == ERROR ? "error" : "fatal"; + + ereport(elevel, errmsg("%s, triggered by injection point", elevel_str)); +} + +PG_FUNCTION_INFO_V1(get_parallel_autovacuum_free_workers); +Datum +get_parallel_autovacuum_free_workers(PG_FUNCTION_ARGS) +{ + uint32 nworkers; + +#ifndef USE_INJECTION_POINTS + elog(ERROR, "injection points not supported"); +#endif + + LWLockAcquire(AutovacuumLock, LW_EXCLUSIVE); + nworkers = inj_point_state->free_parallel_workers; + LWLockRelease(AutovacuumLock); + + PG_RETURN_UINT32(nworkers); +} + +PG_FUNCTION_INFO_V1(trigger_leader_failure); +Datum +trigger_leader_failure(PG_FUNCTION_ARGS) +{ + const char *failure_type = text_to_cstring(PG_GETARG_TEXT_PP(0)); + +#ifndef USE_INJECTION_POINTS + elog(ERROR, "injection points not supported"); +#endif + + if (strcmp(failure_type, "NONE") == 0) + inj_point_state->ftype = FAIL_NONE; + else if (strcmp(failure_type, "ERROR") == 0) + inj_point_state->ftype = FAIL_ERROR; + else if (strcmp(failure_type, "FATAL") == 0) + inj_point_state->ftype = FAIL_FATAL; + else + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid leader failure type : %s", failure_type))); + + PG_RETURN_VOID(); +} + +PG_FUNCTION_INFO_V1(inj_set_free_workers_attach); +Datum +inj_set_free_workers_attach(PG_FUNCTION_ARGS) +{ +#ifdef USE_INJECTION_POINTS + inj_point_state->enabled_set_free_workers = true; + inj_point_state->ftype = FAIL_NONE; +#else + elog(ERROR, "injection points not supported"); +#endif + PG_RETURN_VOID(); +} + +PG_FUNCTION_INFO_V1(inj_set_free_workers_detach); +Datum +inj_set_free_workers_detach(PG_FUNCTION_ARGS) +{ +#ifdef USE_INJECTION_POINTS + inj_point_state->enabled_set_free_workers = false; +#else + elog(ERROR, "injection points not supported"); +#endif + PG_RETURN_VOID(); +} + +PG_FUNCTION_INFO_V1(inj_leader_failure_attach); +Datum +inj_leader_failure_attach(PG_FUNCTION_ARGS) +{ +#ifdef USE_INJECTION_POINTS + inj_point_state->enabled_leader_failure = true; +#else + elog(ERROR, "injection points not supported"); +#endif + PG_RETURN_VOID(); +} + +PG_FUNCTION_INFO_V1(inj_leader_failure_detach); +Datum +inj_leader_failure_detach(PG_FUNCTION_ARGS) +{ +#ifdef USE_INJECTION_POINTS + inj_point_state->enabled_leader_failure = false; +#else + elog(ERROR, "injection points not supported"); +#endif + PG_RETURN_VOID(); +} diff --git a/src/test/modules/test_autovacuum/test_autovacuum.control b/src/test/modules/test_autovacuum/test_autovacuum.control new file mode 100644 index 00000000000..1b7fad258f0 --- /dev/null +++ b/src/test/modules/test_autovacuum/test_autovacuum.control @@ -0,0 +1,3 @@ +comment = 'Test code for parallel autovacuum' +default_version = '1.0' +module_pathname = '$libdir/test_autovacuum' -- 2.43.0