From eb1c84f224c3eb9dec32fb9a9331df67b3beee9e Mon Sep 17 00:00:00 2001 From: Abhishek Chanda Date: Fri, 20 Jun 2025 23:07:53 -0500 Subject: [PATCH] Implement autovacuum hooks --- contrib/Makefile | 1 + contrib/custom_autovacuum/Makefile | 23 ++ contrib/custom_autovacuum/README.md | 122 +++++++++++ .../custom_autovacuum--1.0.sql | 6 + contrib/custom_autovacuum/custom_autovacuum.c | 199 ++++++++++++++++++ .../custom_autovacuum.control | 5 + contrib/custom_autovacuum/meson.build | 23 ++ contrib/meson.build | 1 + src/backend/postmaster/autovacuum.c | 122 ++++++++++- src/include/custom_autovacuum.h | 75 +++++++ src/include/postmaster/autovacuum.h | 1 - 11 files changed, 571 insertions(+), 7 deletions(-) create mode 100644 contrib/custom_autovacuum/Makefile create mode 100644 contrib/custom_autovacuum/README.md create mode 100644 contrib/custom_autovacuum/custom_autovacuum--1.0.sql create mode 100644 contrib/custom_autovacuum/custom_autovacuum.c create mode 100644 contrib/custom_autovacuum/custom_autovacuum.control create mode 100644 contrib/custom_autovacuum/meson.build create mode 100644 src/include/custom_autovacuum.h diff --git a/contrib/Makefile b/contrib/Makefile index 2f0a88d3f7744..f729529530968 100644 --- a/contrib/Makefile +++ b/contrib/Makefile @@ -15,6 +15,7 @@ SUBDIRS = \ btree_gist \ citext \ cube \ + custom_autovacuum \ dblink \ dict_int \ dict_xsyn \ diff --git a/contrib/custom_autovacuum/Makefile b/contrib/custom_autovacuum/Makefile new file mode 100644 index 0000000000000..6bb7df66ff8fe --- /dev/null +++ b/contrib/custom_autovacuum/Makefile @@ -0,0 +1,23 @@ +# contrib/custom_autovacuum/Makefile + +MODULE_big = custom_autovacuum +OBJS = \ + $(WIN32RES) \ + custom_autovacuum.o +PGFILEDESC = "custom_autovacuum - Custom autovacuum policy framework" + +EXTENSION = custom_autovacuum +DATA = custom_autovacuum--1.0.sql + +REGRESS = + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/custom_autovacuum +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif \ No newline at end of file diff --git a/contrib/custom_autovacuum/README.md b/contrib/custom_autovacuum/README.md new file mode 100644 index 0000000000000..dfb6adfdabe37 --- /dev/null +++ b/contrib/custom_autovacuum/README.md @@ -0,0 +1,122 @@ +# Custom Autovacuum Extension + +A PostgreSQL extension that provides a custom autovacuum policy framework, allowing you to override the default autovacuum decision logic with a single adaptive policy. + +## Overview + +This extension implements a proportional controller-based autovacuum policy that dynamically adjusts vacuum aggressiveness based on the rate of dead tuple accumulation. The policy: + +- **Adapts to workload patterns**: More aggressive vacuuming when dead tuples accumulate quickly +- **Minimizes overhead**: Conservative vacuuming when dead tuples accumulate slowly +- **Uses proportional control**: Dynamically adjusts thresholds based on accumulation rate +- **Provides cost control**: Adjusts vacuum cost limits and delays based on workload + +## Installation + +### 1. Build the Extension + +```bash +cd contrib/custom_autovacuum +make +make install +``` + +### 2. Configure PostgreSQL + +Add to `postgresql.conf`: + +```ini +shared_preload_libraries = 'custom_autovacuum' +``` + +### 3. Create the Extension + +```sql +CREATE EXTENSION custom_autovacuum; +``` + +## How It Works + +The extension hooks into PostgreSQL's autovacuum decision process and replaces the default threshold-based logic with an adaptive policy: + +### Proportional Controller Logic + +1. **Calculate accumulation rate**: Dead tuples per second since last vacuum +2. **Compare to target rate**: Default target is 0.1 dead tuples/second +3. **Adjust threshold**: Lower threshold for high accumulation, higher for low accumulation +4. **Set cost parameters**: Aggressive settings for high rates, conservative for low rates + +### Policy Behavior + +- **High accumulation rate** (>0.2 dead tuples/sec): Aggressive vacuum (cost_limit=2000, delay=0) +- **Moderate accumulation rate** (0.1-0.2 dead tuples/sec): Balanced vacuum (cost_limit=1000, delay=5ms) +- **Low accumulation rate** (<0.1 dead tuples/sec): Conservative vacuum (cost_limit=500, delay=10ms) + +## Configuration + +### GUC Variables + +- `custom_autovacuum.enabled` (boolean): Enable/disable the custom policy (default: true) + +### Example Configuration + +```sql +-- Enable custom autovacuum policy +SET custom_autovacuum.enabled = true; + +-- Check current setting +SHOW custom_autovacuum.enabled; +``` + +## Monitoring + +Custom policy decisions are logged at DEBUG2 level. Enable debug logging to see policy decisions: + +```sql +SET log_min_messages = 'debug2'; +``` + +For detailed proportional controller decisions, use DEBUG3: + +```sql +SET log_min_messages = 'debug3'; +``` + +## Example Log Output + +``` +DEBUG: Custom autovacuum policy: high accumulation rate (0.25 dead tuples/sec), aggressive vacuum +DEBUG3: Custom autovacuum policy: dead_tuples=1500, time_since_vacuum=6000000 ms, rate=0.25/sec, base_thresh=1000, adj_thresh=750, error=0.15 +``` + +## Limitations + +- Only one policy can be active (the proportional controller) +- Policies cannot override wraparound vacuum requirements + +## Troubleshooting + +1. **Extension not loading**: Check `shared_preload_libraries` in postgresql.conf +2. **Policy not working**: Verify the extension is created in the database +3. **No debug output**: Set `log_min_messages = 'debug2'` and restart + +## Customization + +To modify the policy behavior, edit the `custom_autovacuum_policy()` function in `custom_autovacuum.c`: + +```c +/* Proportional controller parameters */ +kp = 0.1; /* Proportional gain - adjust this to control sensitivity */ +target_rate = 0.1; /* Target dead tuple rate (dead tuples per second) */ +min_threshold = 50.0; /* Minimum threshold */ +max_threshold = base_threshold * 3.0; /* Maximum threshold */ +``` + +## Contributing + +To modify the policy: + +1. Edit the `custom_autovacuum_policy()` function in `custom_autovacuum.c` +2. Rebuild and reinstall the extension +3. Restart PostgreSQL +4. Test thoroughly with various table sizes and update patterns \ No newline at end of file diff --git a/contrib/custom_autovacuum/custom_autovacuum--1.0.sql b/contrib/custom_autovacuum/custom_autovacuum--1.0.sql new file mode 100644 index 0000000000000..269b9033e67e5 --- /dev/null +++ b/contrib/custom_autovacuum/custom_autovacuum--1.0.sql @@ -0,0 +1,6 @@ +-- Custom autovacuum policy framework +-- This extension provides a framework for implementing custom autovacuum +-- policies as C functions. + +-- Create a schema for organization +CREATE SCHEMA IF NOT EXISTS custom_autovacuum; diff --git a/contrib/custom_autovacuum/custom_autovacuum.c b/contrib/custom_autovacuum/custom_autovacuum.c new file mode 100644 index 0000000000000..593ed83077664 --- /dev/null +++ b/contrib/custom_autovacuum/custom_autovacuum.c @@ -0,0 +1,199 @@ +/*------------------------------------------------------------------------- + * + * custom_autovacuum.c + * Custom autovacuum policy framework + * + * This extension provides a framework for implementing custom autovacuum + * policies as C functions. It allows users to override the default + * autovacuum decision logic with a single custom policy. + * + * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * contrib/custom_autovacuum/custom_autovacuum.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include +#include + +#include "fmgr.h" +#include "utils/guc.h" +#include "utils/memutils.h" +#include "utils/timestamp.h" +#include "pgstat.h" +#include "access/htup_details.h" +#include "utils/rel.h" +#include "postmaster/autovacuum.h" +#include "custom_autovacuum.h" + +PG_MODULE_MAGIC_EXT( + .name = "custom_autovacuum", + .version = PG_VERSION +); + +/* Global variables */ +static custom_autovacuum_policy_hook_type original_hook = NULL; +static bool custom_autovacuum_enabled = true; + +/* Hook variable - declared in autovacuum.c, accessible to extensions */ +extern PGDLLIMPORT custom_autovacuum_policy_hook_type custom_autovacuum_policy_hook; + +/* + * Single custom autovacuum policy function + * + * This policy uses a proportional controller to dynamically adjust vacuum + * aggressiveness based on the rate of dead tuple accumulation. The idea is: + * - If dead tuples are accumulating quickly, vacuum more aggressively (lower threshold) + * - If dead tuples are accumulating slowly, vacuum less aggressively (higher threshold) + * + * The controller calculates the rate of dead tuple accumulation and adjusts + * the vacuum threshold proportionally. + */ +static CustomAutovacuumPolicyResult +custom_autovacuum_policy(CustomAutovacuumPolicyContext *context) +{ + CustomAutovacuumPolicyResult result = {0}; + float4 current_dead_tuples; + TimestampTz last_vacuum; + TimestampTz now; + double time_since_last_vacuum_ms; + double dead_tuple_rate_per_ms; + double dead_tuple_rate_per_sec; + float4 base_threshold; + float4 kp; + float4 target_rate; + float4 min_threshold; + float4 max_threshold; + float4 error; + float4 adjusted_threshold; + + if (!context->stats) + return result; + + /* Get current statistics */ + current_dead_tuples = context->stats->dead_tuples; + last_vacuum = context->stats->last_vacuum_time; + now = GetCurrentTimestamp(); + + /* Calculate time since last vacuum in milliseconds (matching PostgreSQL's approach) */ + time_since_last_vacuum_ms = 0; + if (last_vacuum > 0) + { + time_since_last_vacuum_ms = (now - last_vacuum) / 1000.0; /* Convert to milliseconds */ + } + + /* If no vacuum has been done yet or very recent, use a conservative approach */ + if (time_since_last_vacuum_ms <= 0 || time_since_last_vacuum_ms < 60000) /* Less than 1 minute */ + { + /* Use default threshold for new tables */ + if (current_dead_tuples > context->vacthresh) + { + result.should_vacuum = true; + result.reason = "Custom policy: new table with high dead tuple count"; + } + return result; + } + + /* Calculate dead tuple accumulation rate (dead tuples per millisecond, then convert to per second) + * This matches the approach in vacuum_log_rates() where rates are calculated as: + * rate = count / time_in_milliseconds + */ + dead_tuple_rate_per_ms = current_dead_tuples / time_since_last_vacuum_ms; + dead_tuple_rate_per_sec = dead_tuple_rate_per_ms * 1000.0; /* Convert to per second */ + + /* Base threshold from context (this is the default autovacuum threshold) */ + base_threshold = context->vacthresh; + + /* Proportional controller parameters */ + kp = 0.1; /* Proportional gain - adjust this to control sensitivity */ + target_rate = 0.1; /* Target dead tuple rate (dead tuples per second) */ + min_threshold = 50.0; /* Minimum threshold */ + max_threshold = base_threshold * 3.0; /* Maximum threshold */ + + /* Calculate error (difference between current rate and target rate) */ + error = dead_tuple_rate_per_sec - target_rate; + + /* Apply proportional control to adjust threshold */ + adjusted_threshold = base_threshold - (kp * error * base_threshold); + + /* Clamp threshold to reasonable bounds */ + if (adjusted_threshold < min_threshold) + adjusted_threshold = min_threshold; + if (adjusted_threshold > max_threshold) + adjusted_threshold = max_threshold; + + /* Determine if we should vacuum based on adjusted threshold */ + if (current_dead_tuples > adjusted_threshold) + { + result.should_vacuum = true; + + /* Adjust cost parameters based on accumulation rate */ + if (dead_tuple_rate_per_sec > target_rate * 2.0) /* High accumulation rate */ + { + result.custom_vac_cost_limit = 2000; /* Higher cost limit for aggressive vacuum */ + result.custom_vac_cost_delay = 0.0; /* No delay for aggressive vacuum */ + result.reason = pstrdup(psprintf("Custom policy: high accumulation rate (%.2f dead tuples/sec), aggressive vacuum", dead_tuple_rate_per_sec)); + } + else if (dead_tuple_rate_per_sec > target_rate) /* Moderate accumulation rate */ + { + result.custom_vac_cost_limit = 1000; /* Moderate cost limit */ + result.custom_vac_cost_delay = 5.0; /* Small delay */ + result.reason = pstrdup(psprintf("Custom policy: moderate accumulation rate (%.2f dead tuples/sec)", dead_tuple_rate_per_sec)); + } + else /* Low accumulation rate */ + { + result.custom_vac_cost_limit = 500; /* Lower cost limit */ + result.custom_vac_cost_delay = 10.0; /* Higher delay */ + result.reason = pstrdup(psprintf("Custom policy: low accumulation rate (%.2f dead tuples/sec), conservative vacuum", dead_tuple_rate_per_sec)); + } + + /* Log the controller decision for debugging (matching PostgreSQL's logging format) */ + elog(DEBUG3, "Custom autovacuum policy: dead_tuples=%.0f, time_since_vacuum=%.0f ms, rate=%.2f/sec, base_thresh=%.0f, adj_thresh=%.0f, error=%.2f", + current_dead_tuples, time_since_last_vacuum_ms, dead_tuple_rate_per_sec, base_threshold, adjusted_threshold, error); + } + else + { + result.skip_table = true; + result.reason = pstrdup(psprintf("Custom policy: below adjusted threshold (%.0f < %.0f, rate=%.2f/sec)", + current_dead_tuples, adjusted_threshold, dead_tuple_rate_per_sec)); + } + + return result; +} + +/* Hook function that calls the single policy */ +static CustomAutovacuumPolicyResult +custom_autovacuum_policy_hook_func(CustomAutovacuumPolicyContext *context) +{ + if (!custom_autovacuum_enabled) + return (CustomAutovacuumPolicyResult) {0}; + + return custom_autovacuum_policy(context); +} + +/* + * Module initialization + */ +void +_PG_init(void) +{ + /* Install the hook */ + original_hook = custom_autovacuum_policy_hook; + custom_autovacuum_policy_hook = custom_autovacuum_policy_hook_func; + + /* Define GUC variables */ + DefineCustomBoolVariable("custom_autovacuum.enabled", + "Enable custom autovacuum policy", + NULL, + &custom_autovacuum_enabled, + true, + PGC_SIGHUP, + 0, + NULL, NULL, NULL); + + MarkGUCPrefixReserved("custom_autovacuum"); +} \ No newline at end of file diff --git a/contrib/custom_autovacuum/custom_autovacuum.control b/contrib/custom_autovacuum/custom_autovacuum.control new file mode 100644 index 0000000000000..003a80f8f4bb9 --- /dev/null +++ b/contrib/custom_autovacuum/custom_autovacuum.control @@ -0,0 +1,5 @@ +# custom_autovacuum extension +comment = 'Custom autovacuum policy framework' +default_version = '1.0' +module_pathname = '$libdir/custom_autovacuum' +relocatable = true \ No newline at end of file diff --git a/contrib/custom_autovacuum/meson.build b/contrib/custom_autovacuum/meson.build new file mode 100644 index 0000000000000..3797f8c25f91b --- /dev/null +++ b/contrib/custom_autovacuum/meson.build @@ -0,0 +1,23 @@ +# contrib/custom_autovacuum/meson.build + +custom_autovacuum_sources = files( + 'custom_autovacuum.c', +) + +custom_autovacuum = shared_module('custom_autovacuum', + custom_autovacuum_sources, + kwargs: contrib_mod_args, +) +contrib_targets += custom_autovacuum + +install_data( + 'custom_autovacuum.control', + 'custom_autovacuum--1.0.sql', + kwargs: contrib_data_args, +) + +tests += { + 'name': 'custom_autovacuum', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), +} \ No newline at end of file diff --git a/contrib/meson.build b/contrib/meson.build index ed30ee7d639f6..4c1e35097c7bd 100644 --- a/contrib/meson.build +++ b/contrib/meson.build @@ -23,6 +23,7 @@ subdir('btree_gin') subdir('btree_gist') subdir('citext') subdir('cube') +subdir('custom_autovacuum') subdir('dblink') subdir('dict_int') subdir('dict_xsyn') diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c index 451fb90a610a7..70a43f689702d 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -66,28 +66,37 @@ #include #include +#include "access/amapi.h" #include "access/heapam.h" #include "access/htup_details.h" #include "access/multixact.h" #include "access/reloptions.h" +#include "access/table.h" #include "access/tableam.h" #include "access/transam.h" #include "access/xact.h" +#include "access/xlog.h" #include "catalog/dependency.h" #include "catalog/namespace.h" +#include "catalog/pg_am.h" +#include "catalog/pg_class.h" #include "catalog/pg_database.h" #include "catalog/pg_namespace.h" #include "commands/dbcommands.h" #include "commands/vacuum.h" #include "common/int.h" +#include "common/ip.h" +#include "common/string.h" #include "lib/ilist.h" #include "libpq/pqsignal.h" #include "miscadmin.h" #include "nodes/makefuncs.h" #include "pgstat.h" #include "postmaster/autovacuum.h" +#include "postmaster/fork_process.h" #include "postmaster/interrupt.h" #include "postmaster/postmaster.h" +#include "postmaster/startup.h" #include "storage/aio_subsys.h" #include "storage/bufmgr.h" #include "storage/ipc.h" @@ -95,22 +104,48 @@ #include "storage/lmgr.h" #include "storage/pmsignal.h" #include "storage/proc.h" +#include "storage/procarray.h" #include "storage/procsignal.h" +#include "storage/shmem.h" #include "storage/smgr.h" +#include "storage/spin.h" #include "tcop/tcopprot.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/datetime.h" #include "utils/fmgroids.h" #include "utils/fmgrprotos.h" +#include "utils/guc.h" #include "utils/guc_hooks.h" #include "utils/injection_point.h" +#include "utils/inval.h" #include "utils/lsyscache.h" #include "utils/memutils.h" +#include "utils/pg_rusage.h" #include "utils/ps_status.h" #include "utils/rel.h" #include "utils/snapmgr.h" #include "utils/syscache.h" #include "utils/timeout.h" #include "utils/timestamp.h" +#include "custom_autovacuum.h" +/* + * Default implementation of the custom autovacuum policy hook. + * This provides a fallback when the extension is not loaded. + */ +static CustomAutovacuumPolicyResult +default_custom_autovacuum_policy_hook(CustomAutovacuumPolicyContext * context) +{ + CustomAutovacuumPolicyResult result = {0}; + + return result; +} + +/* + * Hook variable - will be overridden by the extension if loaded + */ +PGDLLEXPORT custom_autovacuum_policy_hook_type custom_autovacuum_policy_hook = default_custom_autovacuum_policy_hook; /* * GUC parameters @@ -151,6 +186,13 @@ int Log_autovacuum_min_duration = 600000; static double av_storage_param_cost_delay = -1; static int av_storage_param_cost_limit = -1; +/* Custom autovacuum policy cost parameters - initialized to "invalid" values + * and will be set in do_autovacuum() after checking the custom policy hook + * in table_recheck_autovac(). + */ +static double av_custom_vac_cost_delay = -1; +static int av_custom_vac_cost_limit = -1; + /* Flags set by signal handlers */ static volatile sig_atomic_t got_SIGUSR2 = false; @@ -208,6 +250,10 @@ typedef struct autovac_table char *at_relname; char *at_nspname; char *at_datname; + /* Custom autovacuum policy overrides */ + double at_custom_vac_cost_delay; + int at_custom_vac_cost_limit; + bool at_has_custom_cost_params; } autovac_table; /*------------- @@ -1655,7 +1701,10 @@ VacuumUpdateCosts(void) { if (MyWorkerInfo) { - if (av_storage_param_cost_delay >= 0) + /* Check custom cost parameters first (highest priority) */ + if (av_custom_vac_cost_delay >= 0) + vacuum_cost_delay = av_custom_vac_cost_delay; + else if (av_storage_param_cost_delay >= 0) vacuum_cost_delay = av_storage_param_cost_delay; else if (autovacuum_vac_cost_delay >= 0) vacuum_cost_delay = autovacuum_vac_cost_delay; @@ -1730,7 +1779,10 @@ AutoVacuumUpdateCostLimit(void) * zero is not a valid value. */ - if (av_storage_param_cost_limit > 0) + /* Check custom cost limit first (highest priority) */ + if (av_custom_vac_cost_limit > 0) + vacuum_cost_limit = av_custom_vac_cost_limit; + else if (av_storage_param_cost_limit > 0) vacuum_cost_limit = av_storage_param_cost_limit; else { @@ -2405,6 +2457,14 @@ do_autovacuum(void) av_storage_param_cost_delay = tab->at_storage_param_vac_cost_delay; av_storage_param_cost_limit = tab->at_storage_param_vac_cost_limit; + /* + * Save the custom cost parameter values from the custom autovacuum + * policy hook for reference when updating vacuum_cost_delay and + * vacuum_cost_limit during vacuuming this table. + */ + av_custom_vac_cost_delay = tab->at_custom_vac_cost_delay; + av_custom_vac_cost_limit = tab->at_custom_vac_cost_limit; + /* * We only expect this worker to ever set the flag, so don't bother * checking the return value. We shouldn't have to retry. @@ -2870,6 +2930,11 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map, tab->at_nspname = NULL; tab->at_datname = NULL; + /* Store custom cost parameters from policy hook */ + tab->at_custom_vac_cost_delay = av_custom_vac_cost_delay; + tab->at_custom_vac_cost_limit = av_custom_vac_cost_limit; + tab->at_has_custom_cost_params = (av_custom_vac_cost_limit > 0 || av_custom_vac_cost_delay >= 0); + /* * If any of the cost delay parameters has been set individually for * this table, disable the balancing algorithm. @@ -2984,14 +3049,14 @@ relation_needs_vacanalyze(Oid relid, anl_scale_factor; /* thresholds calculated from above constants */ - float4 vacthresh, + float4 vacthresh = 0, vacinsthresh, - anlthresh; + anlthresh = 0; /* number of vacuum (resp. analyze) tuples at this time */ - float4 vactuples, + float4 vactuples = 0, instuples, - anltuples; + anltuples = 0; /* freeze parameters */ int freeze_max_age; @@ -3157,6 +3222,51 @@ relation_needs_vacanalyze(Oid relid, /* ANALYZE refuses to work with pg_statistic */ if (relid == StatisticRelationId) *doanalyze = false; + + /* Call custom autovacuum policy hook if available */ + if (custom_autovacuum_policy_hook != NULL) + { + CustomAutovacuumPolicyContext context = {0}; + CustomAutovacuumPolicyResult result = {0}; + + /* Fill in the context structure */ + context.relation_oid = relid; + context.database_oid = MyDatabaseId; + context.relation_name = NameStr(classForm->relname); + context.schema_name = get_namespace_name(classForm->relnamespace); + context.class_form = classForm; + context.stats = tabentry; + context.relopts = relopts; + context.force_vacuum = force_vacuum; + context.effective_multixact_freeze_max_age = effective_multixact_freeze_max_age; + context.vacthresh = vacthresh; + context.anlthresh = anlthresh; + context.vactuples = vactuples; + context.anltuples = anltuples; + + /* Call the hook */ + result = custom_autovacuum_policy_hook(&context); + + /* Store custom cost parameters from the hook result */ + av_custom_vac_cost_delay = result.custom_vac_cost_delay; + av_custom_vac_cost_limit = result.custom_vac_cost_limit; + + /* Apply the result if the hook provided a decision */ + if (result.skip_table) + { + *dovacuum = false; + *doanalyze = false; + } + else if (result.should_vacuum || result.should_analyze) + { + *dovacuum = result.should_vacuum; + *doanalyze = result.should_analyze; + + /* Log the custom policy decision */ + if (result.reason) + elog(DEBUG2, "Custom autovacuum policy: %s", result.reason); + } + } } /* diff --git a/src/include/custom_autovacuum.h b/src/include/custom_autovacuum.h new file mode 100644 index 0000000000000..dc81be618adc5 --- /dev/null +++ b/src/include/custom_autovacuum.h @@ -0,0 +1,75 @@ +/*------------------------------------------------------------------------- + * + * custom_autovacuum.h + * Header file for custom autovacuum policy framework + * + * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/custom_autovacuum.h + * + *------------------------------------------------------------------------- + */ +#ifndef CUSTOM_AUTOVACUUM_H +#define CUSTOM_AUTOVACUUM_H + +#include "postgres.h" +#include "catalog/pg_class.h" + +/* Forward declarations */ +struct PgStat_StatTabEntry; +struct AutoVacOpts; + +/* + * Context structure passed to custom autovacuum policy functions + */ +typedef struct CustomAutovacuumPolicyContext +{ + Oid relation_oid; /* The table being considered */ + Oid database_oid; /* Database containing the table */ + char *relation_name; /* Table name */ + char *schema_name; /* Schema name */ + Form_pg_class class_form; /* pg_class tuple data */ + struct PgStat_StatTabEntry *stats; /* Statistics for the table */ + struct AutoVacOpts *relopts; /* Table's autovacuum options */ + bool force_vacuum; /* Whether wraparound forces vacuum */ + int effective_multixact_freeze_max_age; + /* Additional context for policy decisions */ + float4 vacthresh; /* Calculated vacuum threshold */ + float4 anlthresh; /* Calculated analyze threshold */ + float4 vactuples; /* Current dead tuples */ + float4 anltuples; /* Current modified tuples since analyze */ +} CustomAutovacuumPolicyContext; + +/* + * Result structure returned by custom autovacuum policy functions + */ +typedef struct CustomAutovacuumPolicyResult +{ + bool should_vacuum; /* Whether to vacuum this table */ + bool should_analyze; /* Whether to analyze this table */ + bool skip_table; /* Skip this table entirely */ + char *reason; /* Human-readable reason for decision */ + /* Optional overrides */ + int custom_vac_cost_limit; /* Override vacuum cost limit */ + double custom_vac_cost_delay; /* Override vacuum cost delay */ +} CustomAutovacuumPolicyResult; + +/* + * Function pointer type for custom autovacuum policy functions + */ +typedef CustomAutovacuumPolicyResult(*CustomAutovacuumPolicyFunction) ( + CustomAutovacuumPolicyContext * context); + +/* + * Hook type for custom autovacuum policy decisions + */ +typedef CustomAutovacuumPolicyResult(*custom_autovacuum_policy_hook_type) ( + CustomAutovacuumPolicyContext * context); + +/* + * Hook variable - declared in autovacuum.c, accessible to extensions + */ +extern PGDLLIMPORT custom_autovacuum_policy_hook_type custom_autovacuum_policy_hook; + +#endif /* CUSTOM_AUTOVACUUM_H */ diff --git a/src/include/postmaster/autovacuum.h b/src/include/postmaster/autovacuum.h index e8135f41a1c24..0ab3d0ed06e0d 100644 --- a/src/include/postmaster/autovacuum.h +++ b/src/include/postmaster/autovacuum.h @@ -25,7 +25,6 @@ typedef enum AVW_BRINSummarizeRange, } AutoVacuumWorkItemType; - /* GUC variables */ extern PGDLLIMPORT bool autovacuum_start_daemon; extern PGDLLIMPORT int autovacuum_worker_slots;