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 1wzyJT-004NAP-2w for pgsql-hackers@arkaria.postgresql.org; Fri, 28 Aug 2026 15:16:16 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wzyJS-008Gmu-2y for pgsql-hackers@arkaria.postgresql.org; Fri, 28 Aug 2026 15:16:14 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wzyJS-008Gmm-1g for pgsql-hackers@lists.postgresql.org; Fri, 28 Aug 2026 15:16:14 +0000 Received: from mail-qk1-x731.google.com ([2607:f8b0:4864:20::731]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1wzyJQ-00000002rZB-2glL for pgsql-hackers@postgresql.org; Fri, 28 Aug 2026 15:16:13 +0000 Received: by mail-qk1-x731.google.com with SMTP id af79cd13be357-9371bcf1f8fso48003085a.1 for ; Fri, 28 Aug 2026 08:16:12 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1787930172; x=1788534972; 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=bh5d5OOiJdYabkktNSmAifbQywFOotnxRCSaV5o/vTM=; b=BgLR2z9MvfAZqN/0W60liiPTr6DvFwURQe0bOvAAuOHLOyN6+Mx8MOAzZA38BWU0iV T/HNVLyO0J92MgzQXeWQfNsWvUaHReVLkCKc7XULe0kUMbdwXq51VHUJl8qEmaB/RMst NUwTNLsKs0zQAddPUIbuYMi4uYxPxyWeE7pxmTxchWQbez2iLFGfXHW2tHZTAWsWsZ3J vtiGLUQyWfB4ig5ichYpTYa2+t5TXrG99Hpatjjl0HY2pNhYltHS8EzVGL2lWxkpJ72/ PSn42zyqyAdp7QQf+pSnpT5tH1tKPekOES9qdmIVzIePpwkne7PaWM+9mS2vsy92y/VS vyVw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1787930172; x=1788534972; 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=bh5d5OOiJdYabkktNSmAifbQywFOotnxRCSaV5o/vTM=; b=l++IKrDzFTMr7vJXdJmvF04iJ0E6uY02vNMqBARucBBe4bxIukbDJZLn17ozzr1Vxq jFZuWTtSBHt4IFq5EmpRtpvzYr+INIBzO/sDz4blUjTr4oLB90UGjHKBQWh1Sx/yI2EZ PxOBI+Iq6DM6YjhRobU1cuectLwl/X0P9Zg7+fbgQsa0TPDlR3304njypIPH3TYPeBUC IlXu62vc1LfT469i0YsGUswMIX+6Jid2WOmB34zv5aNhcECFyksv5KEOxy5OxUL6iI2x vVNWEiW3oUtBiYJLbllai+uI8ykhwTGj7VnMw/bjk7/ypdPZRT7JQ79znB0fNzykw7w1 fnlw== X-Gm-Message-State: AFuF++kdVUgRi7+iN0udKI66YgS/ZCxV71cFfkYiMgOa3UvvL3JpGtsb tFLX35yBhPvvWcIk+jjV67CQtM6jPHb/2mFeJBzqjDV7hU08KNBV1isI X-Gm-Gg: AR+sD12IMS8LRYOFvqKNnaasfAGYGQU3IHVY0gng2WjRrOMdlp925UMQuH92d7o5X5R hjEzUBTDVWVnmHUeFZx9zGfgUiDtF17OiiRZgdPeIdVB/O9g8re6QK9XkE3fPabOrGboaau4SMV z6FT2ytsaBAZ251bp6RB9yWf5DlZce7FMrym1vG+/YjiNTR6z/IDEBl5e3kufMDg3Fip7t0sdtn KZt3xU7vDhcPMuKIHTg5YXiFvAtm/koXFaZ6srAogjs9x48q+yuzj2/tzLumA1n8ZMIfRRUHh1Q pzqiKq8jJKFyOwoQoPiSLKVTMG3Y8EIq3WdxOX4OxTW8udLHCyhXHRyf50kANAPAJBUfz9pglKB we+EvT+hUhSU7avi3Yxu6xt+M3i4PKMJL47nq2JAlApKkC7Bcg2qF38oq+Otvjx0eF31geumJ23 fMC2VdZWXovfSoM67RxIt4AoxliWTA+mmq+Vbpr3aBoPw3On6hummhJ0I79Rl/9Q5HbWtLM2zLx vNNM61+z3L4b7d8jicuKCj2d3ikC1HHhmHc9QxDlgKCxR1u3HZgaxm9cGOlCmNiXtexw13kwqqy s/HfdvdnXcJnj0w= X-Received: by 2002:a05:620a:3704:b0:936:f9f5:61e3 with SMTP id af79cd13be357-93913749718mr763367385a.1.1787930171720; Fri, 28 Aug 2026 08:16:11 -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-90ce4467957sm17446776d6.15.2026.08.28.08.16.10 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 28 Aug 2026 08:16:11 -0700 (PDT) Date: Fri, 28 Aug 2026 10:16:09 -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="Vc0lQihT2FmcmEhV" Content-Disposition: inline In-Reply-To: List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --Vc0lQihT2FmcmEhV Content-Type: text/plain; charset=us-ascii Content-Disposition: inline On Fri, Aug 28, 2026 at 10:02:42AM -0500, Nathan Bossart wrote: > Here is a patch. Sorry for the noise. I noticed some silly mistakes in v1, so here's a v2 with those fixed. -- nathan --Vc0lQihT2FmcmEhV Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0001-Fix-pg_stat_autovacuum_scores-for-TOAST-tables.patch From 704573f8367e8065f9e7d02d3af02f21a4870ace Mon Sep 17 00:00:00 2001 From: Nathan Bossart Date: Fri, 28 Aug 2026 09:54:06 -0500 Subject: [PATCH v2 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..badd545ba0e 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 parameters, 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; + } + 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 --Vc0lQihT2FmcmEhV--