postgres.git / summary / log / commit / refs

commit    13d21b48a3a4eef4227339d177a5ce2ed2879447
Author:   Tom Lane <tgl@sss.pgh.pa.us>
Date:     Fri May 23 18:43:44 2025 +0000
First released in: 18.0, 15.14, 16.10, 17.6

    Fix per-relation memory leakage in autovacuum.
    
    PgStat_StatTabEntry and AutoVacOpts structs were leaked until
    the end of the autovacuum worker's run, which is bad news if
    there are a lot of relations in the database.
    
    Note: pfree'ing the PgStat_StatTabEntry structs here seems a bit
    risky, because pgstat_fetch_stat_tabentry_ext does not guarantee
    anything about whether its result is long-lived.  It appears okay
    so long as autovacuum forces PGSTAT_FETCH_CONSISTENCY_NONE, but
    I think that API could use a re-think.
    
    Also ensure that the VacuumRelation structure passed to
    vacuum() is in recoverable storage.
    
    Back-patch to v15 where we started to manage table statistics
    this way.  (The AutoVacOpts leakage is probably older, but
    I'm not excited enough to worry about just that part.)
    
    Author: Tom Lane <tgl@sss.pgh.pa.us>
    Reviewed-by: Andres Freund <andres@anarazel.de>
    Discussion: https://postgr.es/m/285483.1746756246@sss.pgh.pa.us
    Backpatch-through: 15


src/backend/postmaster/autovacuum.c | 38 +++++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c index d8876fbcb6c..c511a132263 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2154,6 +2154,12 @@ do_autovacuum(void) } } } + + /* Release stuff to avoid per-relation leakage */ + if (relopts) + pfree(relopts); + if (tabentry) + pfree(tabentry); } table_endscan(relScan); @@ -2170,7 +2176,8 @@ do_autovacuum(void) Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple); PgStat_StatTabEntry *tabentry; Oid relid; - AutoVacOpts *relopts = NULL; + AutoVacOpts *relopts; + bool free_relopts = false; bool dovacuum; bool doanalyze; bool wraparound; @@ -2188,7 +2195,9 @@ do_autovacuum(void) * main rel */ relopts = extract_autovac_opts(tuple, pg_class_desc); - if (relopts == NULL) + if (relopts) + free_relopts = true; + else { av_relation *hentry; bool found; @@ -2209,6 +2218,12 @@ do_autovacuum(void) /* ignore analyze for toast tables */ if (dovacuum) table_oids = lappend_oid(table_oids, relid); + + /* Release stuff to avoid leakage */ + if (free_relopts) + pfree(relopts); + if (tabentry) + pfree(tabentry); } table_endscan(relScan); @@ -2572,6 +2587,8 @@ deleted: VacuumCostLimit = stdVacuumCostLimit; } + list_free(table_oids); + /* * Perform additional work items, as requested by backends. */ @@ -2749,8 +2766,8 @@ deleted2: /* * extract_autovac_opts * - * Given a relation's pg_class tuple, return the AutoVacOpts portion of - * reloptions, if set; otherwise, return NULL. + * Given a relation's pg_class tuple, return a palloc'd copy of the + * AutoVacOpts portion of reloptions, if set; otherwise, return NULL. * * Note: callers do not have a relation lock on the table at this point, * so the table could have been dropped, and its catalog rows gone, after @@ -2799,6 +2816,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map, autovac_table *tab = NULL; bool wraparound; AutoVacOpts *avopts; + bool free_avopts = false; /* fetch the relation's relcache entry */ classTup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid)); @@ -2811,8 +2829,10 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map, * main table reloptions if the toast table itself doesn't have. */ avopts = extract_autovac_opts(classTup, pg_class_desc); - if (classForm->relkind == RELKIND_TOASTVALUE && - avopts == NULL && table_toast_map != NULL) + if (avopts) + free_avopts = true; + else if (classForm->relkind == RELKIND_TOASTVALUE && + table_toast_map != NULL) { av_relation *hentry; bool found; @@ -2921,6 +2941,8 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map, avopts->vacuum_cost_delay >= 0)); } + if (free_avopts) + pfree(avopts); heap_freetuple(classTup); return tab; } @@ -2952,6 +2974,10 @@ recheck_relation_needs_vacanalyze(Oid relid, effective_multixact_freeze_max_age, dovacuum, doanalyze, wraparound); + /* Release tabentry to avoid leakage */ + if (tabentry) + pfree(tabentry); + /* ignore ANALYZE for toast tables */ if (classForm->relkind == RELKIND_TOASTVALUE) *doanalyze = false; [parent: 0e0174b4973a]