Re: [PATCH] Preserve replication origin OIDs in pg_upgrade

From: shveta malik <shveta(dot)malik(at)gmail(dot)com>
To: Rui Zhao <zhaorui126(at)gmail(dot)com>
Cc: Ajin Cherian <itsajin(at)gmail(dot)com>, Shlok Kyal <shlok(dot)kyal(dot)oss(at)gmail(dot)com>, Amit Kapila <amit(dot)kapila16(at)gmail(dot)com>, "Hayato Kuroda (Fujitsu)" <kuroda(dot)hayato(at)fujitsu(dot)com>, Zsolt Parragi <zsolt(dot)parragi(at)percona(dot)com>, "pgsql-hackers(at)lists(dot)postgresql(dot)org" <pgsql-hackers(at)lists(dot)postgresql(dot)org>, shveta malik <shveta(dot)malik(at)gmail(dot)com>
Subject: Re: [PATCH] Preserve replication origin OIDs in pg_upgrade
Date: 2026-07-31 09:40:58
Message-ID: CAJpy0uBcF-scYCz9k6WZRpW_YZAHb24-S4mPucns74pRCrJ5Hw@mail.gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

On Thu, Jul 30, 2026 at 12:47 PM Rui Zhao <zhaorui126(at)gmail(dot)com> wrote:
>
> Hi Ajin,
>
> Thanks -- 0/0 lines up now, and the pg_upgrade.c comment is fixed too. But
> `if (IsBinaryUpgrade)` also advances origins that were never tracked.

Yeah, I see the problem.

> Old cluster, max_active_replication_origins = 1. The second origin can be
> created because it is never tracked:
>
> SELECT pg_replication_origin_create('advanced_one');
> SELECT pg_replication_origin_advance('advanced_one', '0/1000');
> SELECT pg_replication_origin_create('unadvanced_one');
>
> SELECT * FROM pg_replication_origin;
> roident | roname
> ---------+----------------
> 1 | advanced_one
> 2 | unadvanced_one
>
> SELECT * FROM pg_replication_origin_status;
> local_id | external_id | remote_lsn | local_lsn
> ----------+--------------+------------+------------
> 1 | advanced_one | 0/00001000 | 0/00000000
>
> Upgrading to a new cluster with the same max_active_replication_origins = 1:
>
> Performing Consistency Checks
> ...
> Checking replication origins in new cluster ok
> Performing Upgrade
> ...
> Restoring global objects in the new cluster
> *failure*
>
> ERROR: could not find free replication state slot for replication origin
> with ID 2
>
> The check passed because it counted the one status row; the restore then wanted
> two slots. And it gets there after "If pg_upgrade fails after this point, you
> must re-initdb the new cluster before continuing.", so a worse landing place
> than the refusal I reported on v13.
>
> HEAD guards this in the dumpSubscription() path the patch replaces:
>
> if (subinfo->suboriginremotelsn)
>
> NULL exactly when there was no status row, so HEAD advances iff the origin was
> tracked, 0/0 included. dumpReplicationOrigins() still draws the same line --
>
> if (!PQgetisnull(res, i, i_remotelsn))
>
> emits the lsn, and NULL otherwise -- so the distinction survives the dump; it
> just gets dropped in the backend, where a missing LSN and a real 0/0 are both
> InvalidXLogRecPtr by the time replorigin_create_with_id() sees them. An
> untracked origin then starts counting against max_active_replication_origins,
> which is what counting status rows was meant to avoid.
>
> The caller still knows which is which -- NULL vs '0/0' -- so passing it down:
>
> void
> replorigin_create_with_id(ReplOriginId roident, const char *roname,
> - XLogRecPtr remote_lsn, Relation rel)
> + XLogRecPtr remote_lsn, bool need_advance, Relation rel)
> @@
> - if (IsBinaryUpgrade)
> + if (need_advance)
> {
> - /* If part of a binary upgrade, advance the origin as well */
> + Assert(IsBinaryUpgrade);
> replorigin_advance(roident, remote_lsn, InvalidXLogRecPtr,
> @@ replorigin_create()
> - replorigin_create_with_id(roident, roname, InvalidXLogRecPtr, rel);
> + replorigin_create_with_id(roident, roname, InvalidXLogRecPtr, false, rel);
> @@ binary_upgrade_create_replication_origin()
> - replorigin_create_with_id(node, originname, remote_lsn, rel);
> + replorigin_create_with_id(node, originname, remote_lsn,
> + !PG_ARGISNULL(2), rel);
>
> reproduces the old cluster's tracking exactly: the case above upgrades, and an
> origin sitting at 0/0 still keeps its status row.
>

I agree with the problem and the solution.

The origins which were not tracked on old node should not be tracked
(and should not occupy a slot) on new node. And the origins which were
tracked on old node should be tracked even at 0x0. And through
remote_lsn, replorigin_create_with_id() can not distinguish these
cases as remote_lsn will be InvalidXLogRecPtr for both the cases. So
passing the argument seems a reasonable solution here.

> 004_subscription.pl already creates a user origin and never advances it, so it
> only wanted an assertion that the origin is still untracked afterwards; that
> one fails on v14 and passes with the change. Attached is the whole thing, code
> and test, on top of v14 -- the pg_upgrade suite and the main regression tests
> are green with it.
>
> The other way to close the gap is to leave the advance alone and count all
> origins in the check instead. That works too, but it makes pg_upgrade ask for a
> max_active_replication_origins the server itself doesn't require -- untracked
> origins are free to create today -- so matching the old cluster's tracking
> looked like the smaller change.
>

We had discussed this kind of solution earlier (for a different
problem) and decided against it, as it will need user to set
unnecessarily high 'max_active_replication_origins' on new cluster. I
prefer the solution you provided in the patch. Thanks for working on
this.

thanks
Shveta

In response to

Browse pgsql-hackers by date

  From Date Subject
Next Message solai v 2026-07-31 10:01:34 Re: Add a pg_wal_preallocate() SQL function to eagerly create future WAL segments
Previous Message Matthias van de Meent 2026-07-31 09:34:27 Re: Bug: XLogReader mishandles oversized multi-page xl_tot_len (potential memory corruption)