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 1vkSqq-00Fgbx-2K for pgsql-hackers@arkaria.postgresql.org; Mon, 26 Jan 2026 20:06:21 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1vkSqp-00AYOt-28 for pgsql-hackers@arkaria.postgresql.org; Mon, 26 Jan 2026 20:06:19 +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 1vkSqp-00AYOk-07 for pgsql-hackers@lists.postgresql.org; Mon, 26 Jan 2026 20:06:19 +0000 Received: from fout-a7-smtp.messagingengine.com ([103.168.172.150]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.98.2) (envelope-from ) id 1vkSqm-00000000asE-14rg for pgsql-hackers@postgresql.org; Mon, 26 Jan 2026 20:06:19 +0000 Received: from phl-compute-06.internal (phl-compute-06.internal [10.202.2.46]) by mailfout.phl.internal (Postfix) with ESMTP id 5F1ABEC010B; Mon, 26 Jan 2026 15:06:14 -0500 (EST) Received: from phl-frontend-04 ([10.202.2.163]) by phl-compute-06.internal (MEProxy); Mon, 26 Jan 2026 15:06:14 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kurilemu.de; h= cc:cc:content-transfer-encoding:content-type:content-type:date :date:from:from:in-reply-to:in-reply-to:message-id:mime-version :reply-to:subject:subject:to:to; s=fm1; t=1769457974; x= 1769544374; bh=3ehxhizbYe/NHpBBTiTUi0QcDRtztwRu5VxJabUoH/8=; b=i 0PEUVUBO/48TfmYtNynNP2IyQrlAnXfFzEoaeKOj1fgVT9zRTBq7liFfSY+9Gx60 j8GnA3lX7/6QgLV3fh/plyet5LI5rJfO0EoFppa8JX+lXu+KL9xk9Dn3WQUrT7r9 9JN8PR959hq1vULdcf2q1OmiC80gkSmCQ9SxMZV3Zyi7Z1DFbGUmC2Iejqnkgf9a ty4oJOEqAarrW0NxVQCmyF0AHadFkYKwVI3oWEr+gIyma/IPe46rqoxYtylvTbuM SebcvFzF4lBg1l/FL40OM6BrZiq0nABF73aw/pzPGhk/LoqHwIq9FDsSuGDo0Iuy voOJAQzrIEjwWBeuQ4/3A== 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 :reply-to:subject:subject:to:to:x-me-proxy:x-me-sender :x-me-sender:x-sasl-enc; s=fm2; t=1769457974; x=1769544374; bh=3 ehxhizbYe/NHpBBTiTUi0QcDRtztwRu5VxJabUoH/8=; b=hMQ/uB2UY3tZ+peSS S8tOjjQg0le0r1Iv2lDKaUB1iFRT+rfbg/2GCspOpJtRvsonPlgudesZa5OJg1vI aAxWNHSFlVCJYzS/8wmqCuenipeUzDF7kJtlhFgNzkSLyvAr0kiA2vOtxb1Cg24F ngF7EgQcwFK1sFai4lw0S0JEXAgXX9eJNt9UiEyVZkjyCChQSBrZRCmqhPQYVobC oEMCIdnG6z8RTEJTJVz+BIX8pKdWxJHwOY//0QnpFp7mkEWAjeeQTbgIq/QzklF9 W3HnBVPJno13PcynYfqtiLj66Nwm6t1PdtEbkeVhEKQrApso/J7GPt0zbOEfznvO kSLmw== X-ME-Sender: X-ME-Received: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeefgedrtddtgdduheekheekucetufdoteggodetrf dotffvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdfurfetoffkrfgpnffqhgenuceu rghilhhouhhtmecufedttdenucesvcftvggtihhpihgvnhhtshculddquddttddmnecujf gurhepfffhvfevuffkgggtugfgjgesmhekreertddtjeenucfhrhhomheplmhlvhgrrhho ucfjvghrrhgvrhgruceorghlvhhhvghrrhgvsehkuhhrihhlvghmuhdruggvqeenucggtf frrghtthgvrhhnpeegudetudejheduveevgeehjeegleevveevvdeutdejtdekuefhheeh geevtdejteenucffohhmrghinhepvghnthgvrhhprhhishgvuggsrdgtohhmnecuvehluh hsthgvrhfuihiivgeptdenucfrrghrrghmpehmrghilhhfrhhomheprghlvhhhvghrrhgv sehkuhhrihhlvghmuhdruggvpdhnsggprhgtphhtthhopeehpdhmohguvgepshhmthhpoh huthdprhgtphhtthhopegvgigtlhhushhiohhnsehgmhgrihhlrdgtohhmpdhrtghpthht ohepmhhihhgrihhlnhhikhgrlhgrhigvuhesghhmrghilhdrtghomhdprhgtphhtthhope hnohgrhheslhgvrggusghorghtrdgtohhmpdhrtghpthhtohepmhhitghhrggvlhesphgr qhhuihgvrhdrgiihiidprhgtphhtthhopehpghhsqhhlqdhhrggtkhgvrhhssehpohhsth hgrhgvshhqlhdrohhrgh X-ME-Proxy: Feedback-ID: ie3de48e3:Fastmail Received: by mail.messagingengine.com (Postfix) with ESMTPA; Mon, 26 Jan 2026 15:06:13 -0500 (EST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kurilemu.de; s=schmee; t=1769457971; bh=e+G1JbaMCtpwHjJKVmIZQZvq56sn/7j2Gpt6e6FENmQ=; h=Date:From:To:Cc:Subject:In-Reply-To:From; b=BbyWscgX49rIGJ87wQe+XfLRCv8iBrxhe6ZSSZPJdF9bruZ0rpYsidBvBMIKeeD8e vFcRekcippa+FkXdtSlvbaNe6CiTL2rJOwQQyfwhJTqrvFqFHb7YtY9bvEAp1YA8nT JExvjM1wxeU93hBochTuR4PHcz9JYLnD3kzPvqdix0Wi3yFRKgX7NycAVQCiFxztwA EEU5QJU+Oo4pkcCAKBqTj2KDWBPeotg9XwX4lkn1TB8KTTxrA4Bx7TAIJZ2jE9jpFl SW1U/9+KOiZhPnCsSnmAYq4xtNsvluKQcrhaIUEQ6jEfR/1g07xgUMwQEbcVGUUdWt 4E1icVlLB7MHA== Received: by schmee.kurilemu.internal (Postfix, from userid 1000) id 54DA67A; Mon, 26 Jan 2026 21:06:11 +0100 (CET) Date: Mon, 26 Jan 2026 21:06:11 +0100 From: =?utf-8?Q?=C3=81lvaro?= Herrera To: Mihail Nikalayeu Cc: Alexander Lakhin , Michael Paquier , Noah Misch , PostgreSQL Hackers Subject: Re: Issues with ON CONFLICT UPDATE and REINDEX CONCURRENTLY Message-ID: <202601261855.btsbm3vgwpos@alvherre.pgsql> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="64l6h6ca6ah5fvk6" Content-Disposition: inline Content-Transfer-Encoding: 8bit In-Reply-To: List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --64l6h6ca6ah5fvk6 Content-Type: text/plain; charset=utf-8 Content-Disposition: inline Content-Transfer-Encoding: 8bit Hello, On 2026-Jan-25, Mihail Nikalayeu wrote: > Hello, Álvaro! > > Fixes are in attachment. I think the comment message and comments are good > enough to explain the changes. Great, many thanks for this. The commit message looks quite good, but I decided to rewrite the comment in the code. What do you think of this? (There's also a typo fix for one of the previous commits) > Also, the second commit adds syscache for pg_inherites. I am not very > confident with it, but it feels correct to me. Hmm, I think a syscache on (inhrelid, inhseqno) is a bit weird. This might be okay, but I'm not sure, and I don't think we absolutely need this right now. That is to say, I'm not rejecting this, but I'm not going to pursue getting it pushed for now. > Another approach - put information about parents into [relcache] - I > can rebuild the patch that way. I think that change would be a larger revamp that I definitely don't want to touch at this time. > Also, for the first commit it is possible to create a batched version of > get_partition_ancestors (with the same snapshot for multiple indexes). Yeah, I've had such thoughts too, but I'd rather fix the bug in a reasonably non invasive way (which perhaps we can consider backpatching in some not-too-distant future); major rearchitecting like that can happen later without pressure. -- Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/ --64l6h6ca6ah5fvk6 Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="v2-0001-Fix-duplicate-arbiter-detection-during-REINDEX-CO.patch" Content-Transfer-Encoding: 8bit From 94a1af3393ba4d87695b67572e2a1d9b02ddffc3 Mon Sep 17 00:00:00 2001 From: Mikhail Nikalayeu Date: Sun, 25 Jan 2026 18:01:38 +0100 Subject: [PATCH v2] Fix duplicate arbiter detection during REINDEX CONCURRENTLY on partitions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 90eae926a fixed ON CONFLICT handling during REINDEX CONCURRENTLY on partitioned tables by treating unparented indexes as potential arbiters. However, there's a remaining race condition: when pg_inherits records are swapped between consecutive calls to get_partition_ancestors(), two different child indexes can appear to have the same parent, causing duplicate entries in the arbiter list and triggering "invalid arbiter index list" errors. Note that this is not a new problem introduced by 90eae926a. The same error could occur before that commit in a slightly different scenario: an index is selected during planning, then index_concurrently_swap commits, and a subsequent call to get_partition_ancestors() uses a new catalog snapshot that sees zero ancestors for that index. Fix by tracking which parent indexes have already been processed. If a subsequent call to get_partition_ancestors() returns a parent we've already seen, treat that index as unparented instead, allowing it to be matched via IsIndexCompatibleAsArbiter() like other concurrent reindex scenarios. Author: Mihail Nikalayeu Reported-by: Alexander Lakhin Reviewed-by: Álvaro Herrera Discussion: https://postgr.es/m/e5a8c1df-04e5-4343-85ef-5df2a7e3d90c@gmail.com --- src/backend/executor/execPartition.c | 36 ++++++++++---- .../t/010_index_concurrently_upsert.pl | 47 +++++++++++++++++++ 2 files changed, 75 insertions(+), 8 deletions(-) diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c index 55bdf5c4835..d4625d90ad6 100644 --- a/src/backend/executor/execPartition.c +++ b/src/backend/executor/execPartition.c @@ -28,6 +28,7 @@ #include "partitioning/partprune.h" #include "rewrite/rewriteManip.h" #include "utils/acl.h" +#include "utils/injection_point.h" #include "utils/lsyscache.h" #include "utils/partcache.h" #include "utils/rls.h" @@ -761,7 +762,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate, if (rootResultRelInfo->ri_onConflictArbiterIndexes != NIL) { List *unparented_idxs = NIL, - *arbiters_listidxs = NIL; + *arbiters_listidxs = NIL, + *ancestors_seen = NIL; for (int listidx = 0; listidx < leaf_part_rri->ri_NumIndices; listidx++) { @@ -775,13 +777,29 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate, * in case REINDEX CONCURRENTLY is working on one of the * arbiters. * - * XXX get_partition_ancestors is slow: it scans pg_inherits - * each time. Consider a syscache or some other way to cache? + * However, if two indexes appear to have the same parent, + * treat the second of these as if it had no parent. This + * sounds counterintuitive, but it can happen if a transaction + * running REINDEX CONCURRENTLY commits right between those + * two indexes are checked by another process in this loop. + * This will have the effect of also treating that second + * index as arbiter. + * + * XXX get_partition_ancestors scans pg_inherits, which is not + * only slow, but also means the catalog snapshot can get + * invalidated each time through the loop (cf. + * GetNonHistoricCatalogSnapshot). Consider a syscache or + * some other way to cache? */ indexoid = RelationGetRelid(leaf_part_rri->ri_IndexRelationDescs[listidx]); ancestors = get_partition_ancestors(indexoid); - if (ancestors != NIL) + INJECTION_POINT("exec-init-partition-after-get-partition-ancestors", NULL); + + if (ancestors != NIL && + !list_member_oid(ancestors_seen, linitial_oid(ancestors))) { + ancestors_seen = lappend_oid(ancestors_seen, linitial_oid(ancestors)); + foreach_oid(parent_idx, rootResultRelInfo->ri_onConflictArbiterIndexes) { if (list_member_oid(ancestors, parent_idx)) @@ -794,6 +812,7 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate, } else unparented_idxs = lappend_int(unparented_idxs, listidx); + list_free(ancestors); } @@ -812,16 +831,16 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate, foreach_int(unparented_i, unparented_idxs) { Relation unparented_rel; - IndexInfo *unparenred_ii; + IndexInfo *unparented_ii; unparented_rel = leaf_part_rri->ri_IndexRelationDescs[unparented_i]; - unparenred_ii = leaf_part_rri->ri_IndexRelationInfo[unparented_i]; + unparented_ii = leaf_part_rri->ri_IndexRelationInfo[unparented_i]; Assert(!list_member_oid(arbiterIndexes, unparented_rel->rd_index->indexrelid)); /* Ignore indexes not ready */ - if (!unparenred_ii->ii_ReadyForInserts) + if (!unparented_ii->ii_ReadyForInserts) continue; foreach_int(arbiter_i, arbiters_listidxs) @@ -839,7 +858,7 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate, if (IsIndexCompatibleAsArbiter(arbiter_rel, arbiter_ii, unparented_rel, - unparenred_ii)) + unparented_ii)) { arbiterIndexes = lappend_oid(arbiterIndexes, unparented_rel->rd_index->indexrelid); @@ -851,6 +870,7 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate, } list_free(unparented_idxs); list_free(arbiters_listidxs); + list_free(ancestors_seen); } /* diff --git a/src/test/modules/test_misc/t/010_index_concurrently_upsert.pl b/src/test/modules/test_misc/t/010_index_concurrently_upsert.pl index 26682ebc55a..69178b129c7 100644 --- a/src/test/modules/test_misc/t/010_index_concurrently_upsert.pl +++ b/src/test/modules/test_misc/t/010_index_concurrently_upsert.pl @@ -785,6 +785,53 @@ clean_safe_quit_ok($s1, $s2, $s3); $node->safe_psql('postgres', 'TRUNCATE TABLE test.tblexpr'); +############################################################################ +note('Test: REINDEX on partitioned table (invalid arbiter list)'); + +# causes invalid ERROR: invalid arbiter index list + +$s1 = $node->background_psql('postgres', on_error_stop => 0); +$s2 = $node->background_psql('postgres', on_error_stop => 0); +$s3 = $node->background_psql('postgres', on_error_stop => 0); + +$s1->query_safe( + q[ +SELECT injection_points_set_local(); +SELECT injection_points_attach('exec-init-partition-after-get-partition-ancestors', 'wait'); +]); + +$s2->query_safe( + q[ +SELECT injection_points_set_local(); +SELECT injection_points_attach('reindex-relation-concurrently-before-swap', 'wait'); +]); + +$s2->query_until( + qr/starting_reindex/, q[ +\echo starting_reindex +REINDEX INDEX CONCURRENTLY test.tbl_partition_pkey; +]); + +ok_injection_point($node, 'reindex-relation-concurrently-before-swap'); + +$s1->query_until( + qr/starting_upsert_s1/, q[ +\echo starting_upsert_s1 +INSERT INTO test.tblparted VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now(); +]); + +ok_injection_point($node, + 'exec-init-partition-after-get-partition-ancestors'); + +wakeup_injection_point($node, 'reindex-relation-concurrently-before-swap'); + +wakeup_injection_point($node, + 'exec-init-partition-after-get-partition-ancestors'); + +clean_safe_quit_ok($s1, $s2, $s3); + +$node->safe_psql('postgres', 'TRUNCATE TABLE test.tblparted'); + done_testing(); ############################################################################ -- 2.47.3 --64l6h6ca6ah5fvk6--