From be37a18af33aaa436a571ead5508e89a9889b679 Mon Sep 17 00:00:00 2001 From: Nathan Bossart Date: Fri, 10 Oct 2025 12:28:37 -0500 Subject: [PATCH v2 1/1] autovacuum scheduling improvements --- src/backend/postmaster/autovacuum.c | 92 +++++++++++++++++++++++++---- src/tools/pgindent/typedefs.list | 1 + 2 files changed, 81 insertions(+), 12 deletions(-) diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c index fb5d3b27224..95e928924cb 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -309,6 +309,13 @@ static AutoVacuumShmemStruct *AutoVacuumShmem; static dlist_head DatabaseList = DLIST_STATIC_INIT(DatabaseList); static MemoryContext DatabaseListCxt = NULL; +typedef struct +{ + Oid oid; + double wraparound_score; + double vacuum_score; +} TableToProcess; + /* * Dummy pointer to persuade Valgrind that we've not leaked the array of * avl_dbase structs. Make it global to ensure the compiler doesn't @@ -350,7 +357,8 @@ static void relation_needs_vacanalyze(Oid relid, AutoVacOpts *relopts, Form_pg_class classForm, PgStat_StatTabEntry *tabentry, int effective_multixact_freeze_max_age, - bool *dovacuum, bool *doanalyze, bool *wraparound); + bool *dovacuum, bool *doanalyze, bool *wraparound, + double *wraparound_score, double *vacuum_score); static void autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy); @@ -1888,6 +1896,30 @@ get_database_list(void) return dblist; } +static int +TableToProcessComparator(const ListCell *a, const ListCell *b) +{ + TableToProcess *t1 = (TableToProcess *) lfirst(a); + TableToProcess *t2 = (TableToProcess *) lfirst(b); + + if (t1->wraparound_score >= 1.0 || t2->wraparound_score >= 1.0) + { + if (t2->wraparound_score < t1->wraparound_score) + return -1; + else if (t2->wraparound_score > t1->wraparound_score) + return 1; + else + return 0; + } + + if (t2->vacuum_score < t1->vacuum_score) + return -1; + else if (t2->vacuum_score > t2->vacuum_score) + return 1; + else + return 0; +} + /* * Process a database table-by-table * @@ -1901,7 +1933,7 @@ do_autovacuum(void) HeapTuple tuple; TableScanDesc relScan; Form_pg_database dbForm; - List *table_oids = NIL; + List *tables_to_process = NIL; List *orphan_oids = NIL; HASHCTL ctl; HTAB *table_toast_map; @@ -2013,6 +2045,8 @@ do_autovacuum(void) bool dovacuum; bool doanalyze; bool wraparound; + double wraparound_score = 0.0; + double vacuum_score = 0.0; if (classForm->relkind != RELKIND_RELATION && classForm->relkind != RELKIND_MATVIEW) @@ -2053,11 +2087,20 @@ do_autovacuum(void) /* Check if it needs vacuum or analyze */ relation_needs_vacanalyze(relid, relopts, classForm, tabentry, effective_multixact_freeze_max_age, - &dovacuum, &doanalyze, &wraparound); + &dovacuum, &doanalyze, &wraparound, + &wraparound_score, &vacuum_score); - /* Relations that need work are added to table_oids */ + /* Relations that need work are added to tables_to_process */ if (dovacuum || doanalyze) - table_oids = lappend_oid(table_oids, relid); + { + TableToProcess *table = palloc(sizeof(TableToProcess)); + + table->oid = relid; + table->wraparound_score = wraparound_score; + table->vacuum_score = vacuum_score; + + tables_to_process = lappend(tables_to_process, table); + } /* * Remember TOAST associations for the second pass. Note: we must do @@ -2113,6 +2156,8 @@ do_autovacuum(void) bool dovacuum; bool doanalyze; bool wraparound; + double wraparound_score = 0.0; + double vacuum_score = 0.0; /* * We cannot safely process other backends' temp tables, so skip 'em. @@ -2145,11 +2190,20 @@ do_autovacuum(void) relation_needs_vacanalyze(relid, relopts, classForm, tabentry, effective_multixact_freeze_max_age, - &dovacuum, &doanalyze, &wraparound); + &dovacuum, &doanalyze, &wraparound, + &wraparound_score, &vacuum_score); /* ignore analyze for toast tables */ if (dovacuum) - table_oids = lappend_oid(table_oids, relid); + { + TableToProcess *table = palloc(sizeof(TableToProcess)); + + table->oid = relid; + table->wraparound_score = wraparound_score; + table->vacuum_score = vacuum_score; + + tables_to_process = lappend(tables_to_process, table); + } /* Release stuff to avoid leakage */ if (free_relopts) @@ -2273,6 +2327,8 @@ do_autovacuum(void) MemoryContextSwitchTo(AutovacMemCxt); } + list_sort(tables_to_process, TableToProcessComparator); + /* * Optionally, create a buffer access strategy object for VACUUM to use. * We use the same BufferAccessStrategy object for all tables VACUUMed by @@ -2301,9 +2357,9 @@ do_autovacuum(void) /* * Perform operations on collected tables. */ - foreach(cell, table_oids) + foreach_ptr(TableToProcess, table, tables_to_process) { - Oid relid = lfirst_oid(cell); + Oid relid = table->oid; HeapTuple classTup; autovac_table *tab; bool isshared; @@ -2534,7 +2590,7 @@ deleted: pg_atomic_test_set_flag(&MyWorkerInfo->wi_dobalance); } - list_free(table_oids); + list_free_deep(tables_to_process); /* * Perform additional work items, as requested by backends. @@ -2926,6 +2982,8 @@ recheck_relation_needs_vacanalyze(Oid relid, bool *wraparound) { PgStat_StatTabEntry *tabentry; + double wraparound_score; + double vacuum_score; /* fetch the pgstat table entry */ tabentry = pgstat_fetch_stat_tabentry_ext(classForm->relisshared, @@ -2933,7 +2991,8 @@ recheck_relation_needs_vacanalyze(Oid relid, relation_needs_vacanalyze(relid, avopts, classForm, tabentry, effective_multixact_freeze_max_age, - dovacuum, doanalyze, wraparound); + dovacuum, doanalyze, wraparound, + &wraparound_score, &vacuum_score); /* Release tabentry to avoid leakage */ if (tabentry) @@ -2992,7 +3051,8 @@ relation_needs_vacanalyze(Oid relid, /* output params below */ bool *dovacuum, bool *doanalyze, - bool *wraparound) + bool *wraparound, + double *wraparound_score, double *vacuum_score) { bool force_vacuum; bool av_enabled; @@ -3092,6 +3152,10 @@ relation_needs_vacanalyze(Oid relid, } *wraparound = force_vacuum; + *wraparound_score = (double) relfrozenxid / freeze_max_age; + *wraparound_score = Max(*wraparound_score, + (double) classForm->relminmxid / multixact_freeze_max_age); + /* User disabled it in pg_class.reloptions? (But ignore if at risk) */ if (!av_enabled && !force_vacuum) { @@ -3165,6 +3229,10 @@ relation_needs_vacanalyze(Oid relid, *dovacuum = force_vacuum || (vactuples > vacthresh) || (vac_ins_base_thresh >= 0 && instuples > vacinsthresh); *doanalyze = (anltuples > anlthresh); + + *vacuum_score = *wraparound_score; + *vacuum_score += (double) vactuples / vacthresh; + *vacuum_score += (double) instuples / vacinsthresh; } else { diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 5290b91e83e..9ba84250926 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3006,6 +3006,7 @@ TableScanDesc TableScanDescData TableSpaceCacheEntry TableSpaceOpts +TableToProcess TablespaceList TablespaceListCell TapeBlockTrailer -- 2.39.5 (Apple Git-154)