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 1wt3jE-000xtB-0f for pgsql-hackers@arkaria.postgresql.org; Sun, 09 Aug 2026 13:38: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 1wt3jD-00DIgv-2L for pgsql-hackers@arkaria.postgresql.org; Sun, 09 Aug 2026 13:38:15 +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 1wt3jC-00DIgH-2N for pgsql-hackers@lists.postgresql.org; Sun, 09 Aug 2026 13:38:14 +0000 Received: from fhigh-a4-smtp.messagingengine.com ([103.168.172.155]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.98.2) (envelope-from ) id 1wt3jA-000000012WF-01Hr for pgsql-hackers@postgresql.org; Sun, 09 Aug 2026 13:38:13 +0000 Received: from phl-compute-02.internal (phl-compute-02.internal [10.202.2.42]) by mailfhigh.phl.internal (Postfix) with ESMTP id 71D711400056; Sun, 9 Aug 2026 09:38:10 -0400 (EDT) Received: from phl-imap-02 ([10.202.2.81]) by phl-compute-02.internal (MEProxy); Sun, 09 Aug 2026 09:38:10 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=burd.me; h=cc:cc :content-transfer-encoding:content-type:content-type:date:date :from:from:in-reply-to:in-reply-to:message-id:mime-version :references:reply-to:subject:subject:to:to; s=fm3; t=1786282690; x=1786369090; bh=0moc4NUidqGg/6uMxWcMCrGYzbTW8S1G5UXnkGHNl7Q=; b= UnHhyQSKMwissgFwYcWdFi0ZAhM2UBvVXPuccm8QD0kR4RmuzZv7Rc4YnlovL1cB hclKUDmNH8s+YspRusN+VpfPtWgzXFz2nASKRYvtLqv5hFPGZYFHdQbIdq4iIDS3 Umi7ahc77VrIEyKk/g22Nt2H55WFoccj9xjujeV0sepY2hTyu5ibSW8/zZVzmn3Y 1hdYXsa4jNSqXuYbl55B+XK2Y17dq3hBaeg8slSQ1LK4kBMJbQhvME8Cd67ad6TS Cw4ySlZ7ARRA0sw1GQxfS+nuwdJvCPtUmIHRoshzwZUkIHtaKv+xTYsp6HJ1eTfC lvPhfTuC/pOHlym/538ZYw== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:cc:content-transfer-encoding :content-type:content-type:date:date:feedback-id:feedback-id :from:from:in-reply-to:in-reply-to:message-id:mime-version :references:reply-to:subject:subject:to:to:x-me-proxy :x-me-sender:x-me-sender:x-sasl-enc; s=fm3; t=1786282690; x= 1786369090; bh=0moc4NUidqGg/6uMxWcMCrGYzbTW8S1G5UXnkGHNl7Q=; b=K q3FnlqBaZEvOkfCyv1e4Lm3l+cKmUzDZVl9YpfspmOeT2+K+zIlhwL2ACGId6KMC QjIiE7Ci+JGjDQ8gDLRmXmL7Ad9Tbqt58M5dWDAxxhGP72Fp8L2ZzvEVitbn+epS QqE+KsVERv5wDGiObFSBNQrja0QwXI9y4uKrnQTS4hd+0Sh76Cd/S4PZFr6Nh6iX vEBcK1fMm6VHS5O26I8VUQ6AXcy/oixGTG/uCo/JJQjGB0e6xMpV/3YGVoabM9R6 8IYuvd50nGCUQS0rw/30iNJStTt6kcf7j+WR6/RzjJ/OXVfhJ4HQ83TQqAIGIqCX 1iKCcY83R3JY4+CKmW0MQ== X-ME-Sender: X-ME-Proxy-Cause: dmFkZTExGovE3NRNpAC1DuvpD86kIg+x8pUOoeaRkkyZzSi9nD/sinHm4JIZAS3YajT/H0 xniIpdeT5IUWQLRT6iCtBmbZEiFsEzkWtvwxLZprE7xii94eSnI9n+AVo40R08Ei5ZaJvT 5ChgkajQl3G9JBy9a8nsnBvcM/8u+jiQSDIqF9Hyei2vp5cMjL4VkglYUsjepWn23p2xVA sCAqMJiemDJnXrhkZGJtuIaND8WbLx8k9UketYtdBHvAepR02xw+d+tea1SMFb2b0g71BZ 9BxTv7yY8/qu8HwrzJbLb0WvfZZ+SbZRYVfa8hRa4/7Fid1NLjkDqZnFctv9Lm64SK8NcK WZNvxpq/tbMKeo4n1EL8BdZIVRFmjvkKBu6aMiIbe1j1qiwrZHD/AABJlnvDs+MLPEfzV0 REfpZ3vxKIflnDN/U62gc8zifmoRqzMGQHsqtlsQ+thcIYx3PuVr2jnl74/bLPXW6AEyV9 chn3mBvvVkvnSfinHM7eaiW4jQ9RghIv2tLo3JepE/uV8tdJqdPmL7VAoC401s4x+EzdYo fifrWW0aIuv+zv67l/O7a7tVVGbcmlLMH/0e1qZZxFG8mgRLgGRj9bYv44p1uEKTIjFlex WPzomy04b2WirgOmW8lCvDpC3k0t7ruByf67wfNKnzKtDWpKgAbNgRVS6zFw X-ME-Proxy: Feedback-ID: i675e48f3:Fastmail Received: by mailuser.phl.internal (Postfix, from userid 501) id 2558E700065; Sun, 9 Aug 2026 09:38:10 -0400 (EDT) X-Mailer: MessagingEngine.com Webmail Interface MIME-Version: 1.0 X-ThreadId: ATvhun8a3JOk Date: Sun, 09 Aug 2026 09:37:49 -0400 From: "Greg Burd" To: "Nathan Bossart" , "solai v" Cc: "Nikita Malakhov" , "Michael Paquier" , pgsql-hackers Message-Id: <896e1dbc-5ca1-4d9d-9f85-ae5a2ccac4f4@app.fastmail.com> In-Reply-To: References: Subject: Re: problems with toast.* reloptions Content-Type: text/plain Content-Transfer-Encoding: 7bit List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk On Fri, Aug 7, 2026, at 12:50 PM, Nathan Bossart wrote: > On Thu, Aug 06, 2026 at 04:38:28PM -0500, Nathan Bossart wrote: >> Thanks for looking. Here is a new patch set with some small fixes. > > Sorry for the noise. I found some other small bugs and found another small > cleanup opportunity. Hey Nathan, First, thanks for taking on this subtle and confusing aspect of heap/TOAST/reloptions interactions. I agree with your diagnosis and your approach and I think this would be a solid step in the right direction. I applied your v7 patch set and ran the test world, works as advertised. Am I misunderstanding this? It seems to me that making autovacuum_enabled a ternary and then merging it means a heap table with autovacuum_enabled=false and some toast.* option set now stops autovacuuming the TOAST table. In v7-0002 the option moves from bool to ternary: --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ typedef struct AutoVacOpts - bool enabled; + pg_ternary enabled; --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ relation_needs_vacanalyze - av_enabled = (avopts ? avopts->enabled : true); + av_enabled = (avopts ? avopts->enabled != PG_TERNARY_FALSE : true); and in v7-0005 the merge fills an unset TOAST value from the main table: + /* ternary fields */ + static const int ternary_offsets[] = { + offsetof(AutoVacOpts, enabled), + }; ... + for (int i = 0; i < lengthof(ternary_offsets); i++) + { + pg_ternary *toast_opt; + pg_ternary *main_opt; + + toast_opt = (pg_ternary *) ((char *) toast_avopts + ternary_offsets[i]); + main_opt = (pg_ternary *) ((char *) main_avopts + ternary_offsets[i]); + + if (*toast_opt == PG_TERNARY_UNSET) + *toast_opt = *main_opt; + } So main enabled=PG_TERNARY_FALSE + toast unset -> toast enabled becomes PG_TERNARY_FALSE -> av_enabled is false. Today's all-or-nothing bug leaves that TOAST table getting vacuumed. I agree the new behavior matches the documented contract, but it is a behavior change for the person who disabled autovac on a table they vacuum by hand and never thought about the TOAST side. Wraparound is still forced, but ordinary dead-tuple bloat on the TOAST relation is now on them. So, maybe a line in the commit message and in the CREATE TABLE docs to make that more explicit would help people avoid making that mistake in practice? In merge_autovac_opts() the four offset arrays keyed by "which sentinel means unset", is that duplicating knowledge that already lives in the relopt tables in reloptions.c? + /* integer fields whose unset sentinel is -1 */ + static const int int_offsets_1[] = { + offsetof(AutoVacOpts, vacuum_threshold), + offsetof(AutoVacOpts, vacuum_cost_limit), + offsetof(AutoVacOpts, freeze_min_age), + offsetof(AutoVacOpts, freeze_max_age), + offsetof(AutoVacOpts, freeze_table_age), + offsetof(AutoVacOpts, multixact_freeze_min_age), + offsetof(AutoVacOpts, multixact_freeze_max_age), + offsetof(AutoVacOpts, multixact_freeze_table_age), + }; + + /* integer fields whose unset sentinel is -2 */ + static const int int_offsets_2[] = { + offsetof(AutoVacOpts, vacuum_max_threshold), + offsetof(AutoVacOpts, vacuum_ins_threshold), + offsetof(AutoVacOpts, log_vacuum_min_duration), + }; Those -1/-2 sentinels are the very defaults declared over in reloptions.c this same patch even moves one of them from -1 to -2 there: --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ static relopt_int intRelOpts[] = RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, ShareUpdateExclusiveLock }, - -1, -1, INT_MAX + -2, -1, INT_MAX so the "which sentinel means unset for field X" fact now lives in two places, kept in agreement only by the NB comment added in rel.h: + * NB: When adding a new member, be sure to update merge_autovac_opts() and/or + * table_recheck_autovac() as necessary! Add an AutoVacOpts field, or change a field's default sentinel, and forget to update the matching array here, and the merge silently keeps the TOAST table's default instead of inheriting, nothing fails to compile and no test goes red. Can this be driven off the relopt metadata (relopt_parse_elt already knows each option's type and default) instead of the hand-maintained offset arrays? On testing: the coverage doesn't touch the risky code. There's one injection-point case, and it's manual VACUUM only, index_cleanup/truncate only: +-- TOAST table inherits main table's resolved values +CREATE TABLE vac_tab_toast_inherit(i int, j text) WITH + (autovacuum_enabled=false, + vacuum_index_cleanup=false, + vacuum_truncate=false, toast.vacuum_truncate=true); +VACUUM vac_tab_toast_inherit; +DROP TABLE vac_tab_toast_inherit; Nothing exercises the autovacuum decision path, autovacuum_enabled inheritance, or any of the numeric AutoVacOpts that merge_autovac_opts() actually resolves which is precisely the code I'm worried about above. FWIW the pg_stat_get_autovacuum_scores() SRF that 0005 extends looks like it could drive a deterministic test of the autovac path (compute the decision without spawning a worker), which sidesteps the flakiness worry raised upthread. In summary, solid work and I hope it lands. Just a few small issues to clean up. best. -greg > -- > nathan > > Attachments: > * v7-0001-Remove-extract_autovac_opts.patch > * v7-0002-Make-autovacuum_enabled-a-ternary-reloption.patch > * v7-0003-Add-an-unset-value-for-vacuum_index_cleanup.patch > * v7-0004-Simplify-autovacuum-s-TOAST-to-main-relation-relo.patch > * v7-0005-Fix-VACUUM-and-autovacuum-handling-of-TOAST-stora.patch