Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wzy6V-004N29-11 for pgsql-hackers@arkaria.postgresql.org; Fri, 28 Aug 2026 15:02:51 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wzy6U-008CEf-17 for pgsql-hackers@arkaria.postgresql.org; Fri, 28 Aug 2026 15:02:50 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wzy6U-008CEX-02 for pgsql-hackers@lists.postgresql.org; Fri, 28 Aug 2026 15:02:50 +0000 Received: from mail-yw1-x1131.google.com ([2607:f8b0:4864:20::1131]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1wzy6R-00000001jgP-0cjO for pgsql-hackers@postgresql.org; Fri, 28 Aug 2026 15:02:49 +0000 Received: by mail-yw1-x1131.google.com with SMTP id 00721157ae682-7ff05e5d009so11351397b3.1 for ; Fri, 28 Aug 2026 08:02:46 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1787929365; x=1788534165; darn=postgresql.org; h=in-reply-to:content-disposition:content-type:mime-version :references:message-id:subject:cc:to:from:date:from:to:cc:subject :date:message-id:reply-to:content-type; bh=AmeRq9XjCsyKV10hC8WZg7/hRHkt/8DJMvsHCXOhzUA=; b=pks1dMPYNk9OCl0QvNnNvZNY4Sql319bXmxuHZ+2LqcfJaC+fh8ijv+GvVqdAsnNoC yeIRfAcWZCRm+vnFhDXRYcTmNiXlEpy5z9serYX68fb+6HbBNjRJ5bLCk4kYK3ctQqqV 881m+Sa88ZUijT9GIqTNZkPZekqbzsWIrki6o07MD/Je7EFO3brThUK9b3YtXCv3cwU+ 58kK/pO6diQ8CXM7Hftg8c4GUvp8irPp7vr1L3nU/fkxB1iUmT+OgCwgdZy/mlp+W5Mj 9i6ijC6WKVKktlCF5NugxyKAgQ6CdIndfPWwyFnrxWaC/bm4yEXb6cvZaZ9RM+R8ullw OIrQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1787929365; x=1788534165; h=in-reply-to:content-disposition:content-type:mime-version :references:message-id:subject:cc:to:from:date:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=AmeRq9XjCsyKV10hC8WZg7/hRHkt/8DJMvsHCXOhzUA=; b=dZF+vKrLMowB6V8+MRpRkiEzWYkpVodfBhqVImBswP0vTiJKy38G66+36e7yjlJaSM L0ogys9UGEFbCqLh64is7x9oq9s/TPST4vJy699i5xlRbzbzMvGLh0WUe68FZivpB9/d kpZuYwlUaNMNhgGepcH30iPCEdyvoto59l1pSrUE72u1drTSJoGOjkQpNqiVNrbPz58g vtxOEOui0mnEKEXDiUMaPuxqRcaXvSl2Tf1PoaJMdNLwTKt2A/VyuaL3HrMZOXfqT/mF OdT1rrqSEnDM9h3s2jZWpHaP2rUINSqFlK23IXTBZB3jaeuJcwTqLuMmvXnAKVLIffob HEFw== X-Gm-Message-State: AFuF++m2lrdzzF5lPT9U32qSuJF8Hpez6sUireAJt+Riwoju/QzDBVn6 d1TlW3gb6vYjNwBnP7LcDeC7xzroKPkabSs87Gz1bwJE3aRhFdB77HrH X-Gm-Gg: AYBFou1jEGpxK3PGmpVWX75iKCyDBQlsMiCZWtoXMPGiSb3ZmMPuFsikz23+eHPdfiE 8rIZl+i3pz8J4Wd5JIvdVRE0a4BLYK5RX7kdEb2/zQZpq0J6UzWYMOdMubIQXFGfi64gMXiwc2V wj0hEGJMO8S7oq9VpshKtXeFt6quPCZ1kHI/X3uaSu5SUf66JckOt74W3Y+gyo1O2GselflsG3s udqbKAbBinnyrSLytAyftqGy31Mn9XQHmYLhKywRZDTq5wRInoNti7Hbu2nHoUXq2D0xqYMsmcQ L/n0nQNahmxfUboDFWgV7wGpG3IXqhYQ0PdNt6Zz8bKNLDIFSXgpIpHTMWDX1+ZMEd44/a1j4lG ZtQjMAkOtxqNMO5KAjBxhz60Zp6b+zJJdFq5JYRmk2wg4qAZ7eYgWEnOBieAZHaeR5qz4gl72Gg 1CTwu1bQBGV13OgWMYHbbQRwVqjF6DlL6v34iW2DoDv3K+ixpHsNDBJ4K1Fzhw7vsMfdmekV4BF EG5Kmye1nvvbC6Q7t9N9XUI3RBFe2kzSeVlXaDDoVM4CFM74jRoC2iCb4pJlc1sMOOtUgeBFUDQ /Cf80rwgYcG2s8H2 X-Received: by 2002:a05:690c:488a:b0:81f:3556:bc27 with SMTP id 00721157ae682-85d6e438b29mr33803807b3.27.1787929364868; Fri, 28 Aug 2026 08:02:44 -0700 (PDT) Received: from nathan (162-195-168-172.lightspeed.stlsmo.sbcglobal.net. [162.195.168.172]) by smtp.gmail.com with ESMTPSA id 6a1803df08f44-90ce4512dcasm16689206d6.34.2026.08.28.08.02.43 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 28 Aug 2026 08:02:44 -0700 (PDT) Date: Fri, 28 Aug 2026 10:02:42 -0500 From: Nathan Bossart To: Masahiko Sawada Cc: PostgreSQL-development Subject: Re: pg_stat_get_autovacuum_scores ignores the main table's reloptions for TOAST tables Message-ID: References: MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="S+XuMUjMztxz0jco" Content-Disposition: inline In-Reply-To: List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --S+XuMUjMztxz0jco Content-Type: text/plain; charset=us-ascii Content-Disposition: inline Here is a patch. -- nathan --S+XuMUjMztxz0jco Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0001-Fix-pg_stat_autovacuum_scores-for-TOAST-tables.patch From cd6e33acf8086d0c1b5b2d3fb0d177ea5c77cb8f Mon Sep 17 00:00:00 2001 From: Nathan Bossart Date: Fri, 28 Aug 2026 09:54:06 -0500 Subject: [PATCH v1 1/1] Fix pg_stat_autovacuum_scores for TOAST tables. In v19, pg_stat_autovacuum_scores computes a TOAST table's scores from its own storage parameters alone. Autovacuum instead falls back to the main table's parameters when the TOAST table has none of its own, so the view may report scores that don't match what autovacuum would calculate. This contradicts the documented promise that the view generates its results the same way autovacuum workers do. To fix, teach the view to do the same fallback. As in do_autovacuum(), we cannot know a TOAST table's parameters until we have seen its main relation, so the view now makes a preliminary pass over pg_class to collect the main relations' parameters. Commit fad70a09ff for v20 improved autovacuum's handling of TOAST storage parameters and adjusted the view to match, but it was deemed too intrusive to back-patch. This fix is for v19 only. Oversight in commit 87f61f0c82. Reported-by: Masahiko Sawada Discussion: https://postgr.es/m/CAD21AoB1CJRVfCDh8qYuD3eueiygXxk7F3nybgjN0RZXSD-QUw%40mail.gmail.com Backpatch-through: 19 only --- src/backend/postmaster/autovacuum.c | 75 ++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c index 0c975d0eda6..089b9c468a5 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -3652,6 +3652,8 @@ pg_stat_get_autovacuum_scores(PG_FUNCTION_ARGS) TableScanDesc scan; HeapTuple tup; ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + HTAB *table_toast_map; + HASHCTL ctl; InitMaterializedSRF(fcinfo, 0); @@ -3660,13 +3662,64 @@ pg_stat_get_autovacuum_scores(PG_FUNCTION_ARGS) recentXid = ReadNextTransactionId(); recentMulti = ReadNextMultiXactId(); - /* scan pg_class */ + /* create hash table for toast <-> main relid mapping */ + ctl.keysize = sizeof(Oid); + ctl.entrysize = sizeof(av_relation); + ctl.hcxt = CurrentMemoryContext; + table_toast_map = hash_create("TOAST to main relid map", + 100, + &ctl, + HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); + rel = table_open(RelationRelationId, AccessShareLock); + + /* + * Do an initial pass over pg_class to collect the main relations' + * autovacuum paramters, which a TOAST table with none of its own + * inherits. We cannot gather these as we go, since a TOAST table may + * precede its main relation in the scan. + */ scan = table_beginscan_catalog(rel, 0, NULL); while ((tup = heap_getnext(scan, ForwardScanDirection)) != NULL) { Form_pg_class form = (Form_pg_class) GETSTRUCT(tup); AutoVacOpts *avopts; + av_relation *hentry; + bool found; + + /* skip ineligible entries */ + if (form->relkind != RELKIND_RELATION && + form->relkind != RELKIND_MATVIEW) + continue; + if (form->relpersistence == RELPERSISTENCE_TEMP) + continue; + if (!OidIsValid(form->reltoastrelid)) + continue; + + avopts = extract_autovac_opts(tup, RelationGetDescr(rel)); + if (avopts == NULL) + continue; + + hentry = hash_search(table_toast_map, &form->reltoastrelid, + HASH_ENTER, &found); + Assert(!found); /* rels cannot share a TOAST table */ + + /* hash_search already filled in the key */ + hentry->ar_relid = form->oid; + hentry->ar_hasrelopts = true; + memcpy(&hentry->ar_reloptions, avopts, sizeof(AutoVacOpts)); + + pfree(avopts); + } + table_endscan(scan); + + /* now scan pg_class again to compute the scores */ + scan = table_beginscan_catalog(rel, 0, NULL); + while ((tup = heap_getnext(scan, ForwardScanDirection)) != NULL) + { + Form_pg_class form = (Form_pg_class) GETSTRUCT(tup); + AutoVacOpts *avopts; + bool free_avopts = false; bool dovacuum; bool doanalyze; bool wraparound; @@ -3682,13 +3735,30 @@ pg_stat_get_autovacuum_scores(PG_FUNCTION_ARGS) if (form->relpersistence == RELPERSISTENCE_TEMP) continue; + /* + * fetch reloptions -- if this toast table does not have them, try the + * main rel + */ + avopts = extract_autovac_opts(tup, RelationGetDescr(rel)); + if (avopts) + free_avopts = true; + else if (form->relkind == RELKIND_TOASTVALUE) + { + av_relation *hentry; + bool found; + + hentry = hash_search(table_toast_map, &form->oid, HASH_FIND, &found); + if (found && hentry->ar_hasrelopts) + avopts = &hentry->ar_reloptions; + } + avopts = extract_autovac_opts(tup, RelationGetDescr(rel)); relation_needs_vacanalyze(form->oid, avopts, form, effective_multixact_freeze_max_age, LOG_NEVER, &dovacuum, &doanalyze, &wraparound, &scores); - if (avopts) + if (free_avopts) pfree(avopts); vals[0] = ObjectIdGetDatum(form->oid); @@ -3706,6 +3776,7 @@ pg_stat_get_autovacuum_scores(PG_FUNCTION_ARGS) } table_endscan(scan); table_close(rel, AccessShareLock); + hash_destroy(table_toast_map); return (Datum) 0; } -- 2.55.0 --S+XuMUjMztxz0jco--