Subject: Re: Issues with ON CONFLICT UPDATE and REINDEX CONCURRENTLY

From: Álvaro Herrera <[email-masked]>
Date: Tue, 27 Jan 2026 16:22:51 +0100

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.

Actually, about this fragment ... if we track these ancestors for all
indexes, not just the ones that we consider as arbiters, don't we risk
doing something stupid when a completely unrelated index is being
reindexed?  Namely, also consider that unrelated index as arbiter.

                if (ancestors != NIL &&
                    !list_member_oid(ancestors_seen, linitial_oid(ancestors)))
                {
                    foreach_oid(parent_idx, rootResultRelInfo->ri_onConflictArbiterIndexes)
                    {
                        if (list_member_oid(ancestors, parent_idx))
                        {
                            arbiterIndexes = lappend_oid(arbiterIndexes, indexoid);
                            arbiters_listidxs = lappend_int(arbiters_listidxs, listidx);
                            break;
                        }
                    }

                    /*
                     * Track which ancestor we saw, add other indexes that seem to have
                     * the same ancestor as "unparented".
                     */
                    ancestors_seen = lappend_oid(ancestors_seen, linitial_oid(ancestors));
                }
                else
                    unparented_idxs = lappend_int(unparented_idxs, listidx);

I think the lappend_oid(ancestors_seen) should happen inside the
"if list_member_oid(ancestors)" block instead.  I'm imagining a test
case like:

CREATE TABLE test.tblparted(i int primary key, updated_at timestamp) PARTITION BY RANGE (i);
CREATE TABLE test.tbl_partition PARTITION OF test.tblparted
    FOR VALUES FROM (0) TO (10000)
    WITH (parallel_workers = 0);
CREATE INDEX unrelated_index ON test.tblparted (updated_at);

where the reindex is
REINDEX INDEX CONCURRENTLY test.tbl_partition_updated_at_idx;

This doesn't fail, but I suspect it's just from me not setting up the
test case correctly.

I'm also missing a comment for why the use linitial_oid(ancestors) is
correct.  At first I thought we should walk the entire ancestors list,
and do this dance if any OID there matches the ancestors_seen list.  I
convinced myself that this isn't necessary because the scenario is a
single table being under REINDEX CONCURRENTLY, so the two indexes would
have the same root relation (top-most ancestor index).  So comparing
linitial() is correct.

-- 
Álvaro Herrera        Breisgau, Deutschland  —  https://www.EnterpriseDB.com/
"La persona que no quería pecar / estaba obligada a sentarse
 en duras y empinadas sillas    / desprovistas, por cierto
 de blandos atenuantes"                          (Patricio Vogel)

---

From: Mihail Nikalayeu <[email-masked]>
Date: Wed, 28 Jan 2026 01:45:00 +0100

Hello, Álvaro!

Thanks for your review!

> reindexed?  Namely, also consider that unrelated index as arbiter.

Yes, such a thing may happen. It will not cause any error, because such an
index will be used as artiber only if another compatible index in that same
relation is present.
And additional_arbiters will be incremented.

>  "if list_member_oid(ancestors)" block instead.

But yes, it is a more clear way. Added to v3.

> have the same root relation (top-most ancestor index).  So comparing

Probably you think linitial() is "root", but it is an immediate parent.
I am not sure I understood you correctly, but I added a comment about it.
And about the opposite race + assert (not sure we need to keep it).

Best regards,
Mikhail.