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 1wcNe5-003J38-13 for pgsql-hackers@arkaria.postgresql.org; Wed, 24 Jun 2026 13:28:01 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wcNe4-00112d-0n for pgsql-hackers@arkaria.postgresql.org; Wed, 24 Jun 2026 13:28:00 +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 1wcNe3-00112V-2I for pgsql-hackers@lists.postgresql.org; Wed, 24 Jun 2026 13:27:59 +0000 Received: from mail-oi1-x233.google.com ([2607:f8b0:4864:20::233]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1wcNe1-000000001pT-1Fl4 for pgsql-hackers@lists.postgresql.org; Wed, 24 Jun 2026 13:27:58 +0000 Received: by mail-oi1-x233.google.com with SMTP id 5614622812f47-4896ae35be9so490980b6e.0 for ; Wed, 24 Jun 2026 06:27:57 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1782307676; cv=none; d=google.com; s=arc-20240605; b=Fh8KvSMGxJlN8pYjnVdVuB4/kgH4S84MrnMqpx3HQzuN7Wv/BM+dsfwEizR2KqwNVq bIde+tyDGEUenO3KBT+5NGCUwB+q11PVBb6bePCl6k66OGH7infcf9S4WYbUTvBdvuMO hQAq2Q/KN3Sa7OCaLvepgvzcn0ITE9OU6G3ivnIqOln0J8ioGeM9nUfQ/uifLXXt+Ko2 3qLuH+8hVm3GOtlUgjQq9CxHkJ4vEUe2bKnc331mspdkGHGPmQvN1mB4huJSQGrMyfue FJn5Vq+YgmqdoGUl3kBLepqtNKVjjuQXTJ8CoQ79x/2hyHUJDx5bE+En5+UOuxUlvhwv oxHw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20240605; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:dkim-signature; bh=0B0/lKlj2Ie0kOte0HolJPDy0c4Ldt5XgdlqSiFbPWY=; fh=v4sqrNEiF8VkHTBomW/4ot8xIu+wlavR6EnIDBHoSPc=; b=WCaUyl1PZD1W/iSf1pmk++w0guPl5LcR9Dv5gK+fVQXEVPQef3F83nLDPfmi80wpY1 NeuHtGCIE7BMFAdOOq/W+2JTUzsqSeV51Pmat0V+YDvR+OI1ysljjrY8rLUs4wBvUZin IdUPCbHcL8GRsGtNdbYxJ0DGRfApqeRqxRdFrJ99VwYgD8xnOIvYoONbqrhrjy9RKRBh IVEFAWcNblup/XcnKry0qNwW9Jz8NMZHmzTw+PnfrDqX+wZ4IOWVtqJYcaQfd/xlJ/vD SSiMykJFgP9P1/u4J5u5dPnFtRswm6NILXwKwLdSJOCLk2AfFV5dR/qE2+xk550Bnw2Z nySw==; darn=lists.postgresql.org ARC-Authentication-Results: i=1; mx.google.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1782307676; x=1782912476; darn=lists.postgresql.org; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:from:to:cc:subject:date :message-id:reply-to; bh=0B0/lKlj2Ie0kOte0HolJPDy0c4Ldt5XgdlqSiFbPWY=; b=oKg9STG0wyyn3FUraIgdctoofl1221BsxRoyonkv//UyPAYm2GKv6EIFuncFzNGMFD nsR38WgFP0Mq/s+DZNO2+w/Mi9LrxULPBz52VKNQLq49SfPAFScEbT5up5GMI2a2Fb7Z 1Q9w/cjFenaO8o+dlXSallhxBSwJJnBnMsIkeM+My56LGSipDRkl3s7/SC2OeAVJKT9g 8u+pCadzyANEdH3iJid+WGUybfwR6Rx7mrmjbPwAkf0mIXrisuiqzwXPskUcyOPILg5S tZfnC79sdVQhbQ0U27VhMsQx2QJ4T22e6kACIBvhrJZASYyQU8PYmvexDpJQaW3y1n3p kCPA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1782307676; x=1782912476; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=0B0/lKlj2Ie0kOte0HolJPDy0c4Ldt5XgdlqSiFbPWY=; b=guN6NCHEY+KxwELZGAMMvls0+WGUXqPHprGZnmX+b9FLFNn6C/2OjnYLWFDipzabG1 8LKQzxiThRC9oQ9SHqf5UcLwJdPdngoJ/Tkwk4jHajcS/+PQKfrPDE6hECQBOkQWbwK1 lIkmTu9J+WYe7y9AdfTdP4g1JMOxBJo5cfDOxYACR7R5Ko3FFRqLZxrrPF1RYp2w5azA sWEfJQpUs7/U12MgRUVKFPPIh5FHM94bzYEO9xtxxPtL8pv8CBFv7vZ3vcVmKElnPwv3 Nm0wi34kN50DolioqHHr6/vwy3dqDSKrl21LHFpYS0wb13vtOFkgTvnmUVKrqQ3EAmLI AKWw== X-Forwarded-Encrypted: i=1; AFNElJ+Xwqu9Od2wuQgleqzqYcJWOkbaK1zw4ZP9wH6qQSs/bCYjA+mF7pjerPi5/FqsRD6/kA9/7dMzuZepjfd1@lists.postgresql.org X-Gm-Message-State: AOJu0YxmCVlHSiS68NYWXqeIhv5YJb502nnHxuOOIqqqAK+j07qOxLhm EUlpv/vOXd6RriOdFv1V/sBkExmRC2DkKwhiBBoY1QrHATZfua9qlbyDqp9Deot4gQrD276RmPA ku10WInBBzVdTW7aHix+56NW1+lcyH4E= X-Gm-Gg: AfdE7ckRhmsu5ISUrjHCvlGGF7Z0hVKyT00z8jAI+LPgg0AmwrgkftnGDe8QBLNDH11 Vl4oLZoB4lmg3TEGDMlKKxSs8PLiHIu19E4lRNVNp9sY6YqtHjx08x8J4qA3+EolKevyCxzjCcg x13NjTB/iU6jv27AGtFgJxMpssZ1jzIf9dhDSROVU7fj0h8EejnZN+2W9NMkf3Zf3e1NMZKx+4O gARzhBKwEH7/OoJO+FWNUOXSeZ3iZznv5Gmb13ga1SjMr1qUMMPVnxnmkaR5/drv06uVOo4Vg== X-Received: by 2002:a05:6808:181c:b0:489:9733:cb39 with SMTP id 5614622812f47-4907749a958mr2145832b6e.11.1782307676346; Wed, 24 Jun 2026 06:27:56 -0700 (PDT) MIME-Version: 1.0 References: In-Reply-To: From: Shlok Kyal Date: Wed, 24 Jun 2026 18:57:44 +0530 X-Gm-Features: AVVi8Cc88ibXF4FnJzi20OiIwyEFWJbq1dN94_Tug2hu1vfVSkg2oXOAjskT87E Message-ID: Subject: Re: [PATCH] Preserve replication origin OIDs in pg_upgrade To: Ajin Cherian Cc: Rui Zhao , shveta malik , Zsolt Parragi , vignesh C , "Hayato Kuroda (Fujitsu)" , PostgreSQL Hackers , shveta malik Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk On Tue, 23 Jun 2026 at 17:20, Ajin Cherian wrote: > > On Wed, Jun 17, 2026 at 8:59=E2=80=AFPM Nisha Moond wrote: > > > > Hi Ajin, Thanks for the patches. > > I have started reviewing the patches. Please find a few initial > > comments below (from v7 review, still applicable to v8): > > > > 1) Patch-002: pg_dump.c > > + if (dopt->binary_upgrade && subinfo->subenabled && > > fout->remoteVersion >=3D 170000) > > { > > - if (subinfo->suboriginremotelsn) > > - { > > - /* > > - * Preserve the remote_lsn for the subscriber's replication > > > > I could not find any usage of subinfo[i].suboriginremotelsn anymore. > > It seems to be dead code now. Can we remove it? > > Removed it. > > > > ~~~ > > > > 2) Patch-002 : binary_upgrade_create_replication_origin() > > - PG_RETURN_VOID(); > > + node_oid =3D PG_GETARG_OID(0); > > + > > + if (node_oid =3D=3D InvalidOid || node_oid > PG_UINT16_MAX) > > + ereport(ERROR, > > + errcode(ERRCODE_INVALID_PARAMETER_VALUE), > > + errmsg("replication origin ID %u is out of range", node_oid)); > > + > > > > The above range check allows PG_UINT16_MAX, but that value is not part > > of the valid replication origin ID range. > > > > In origin.h: > > #define DoNotReplicateId PG_UINT16_MAX > > > > And we already ensure "ReplOriginId !=3D DoNotReplicateId" everywhere > > else. Would something like the below be more appropriate? > > if (node_oid =3D=3D InvalidReplOriginId || node_oid >=3D DoNotReplica= teId) > > ereport(ERROR, ... > > ~~~ > > Done. > > > > > > 3) I think we should add some additional details in > > logical-replication.sgml, especially under Prepare for > > Subscriber Upgrades. > > Some suggestions: > > 3a) Since all replication origins are now migrated, this: > > "...greater than or equal to the number of subscriptions present in > > the old cluster." > > could become: > > "...greater than or equal to the number of replication origins > > present in the old cluster." > > > > 3b) We should also mention: > > The new cluster must contain no replication origins. > > > > Done. > > > > On Wed, Jun 17, 2026 at 9:42=E2=80=AFPM Nisha Moond wrote: > > > > 4) I was able to build successfully without all three new header > > inclusions below, so we may not need them. > > --- a/src/backend/utils/adt/pg_upgrade_support.c > > +++ b/src/backend/utils/adt/pg_upgrade_support.c > > @@ -11,6 +11,7 @@ > > +#include "access/genam.h" > > ... > > +#include "utils/fmgroids.h" > > ... > > +#include "utils/snapmgr.h" > > ~~~ > > > > Removed. > > > > 5) Race condition in origin.c: replorigin_create() > > - heap_freetuple(tuple); > > + table_close(rel, ExclusiveLock); > > + > > + replorigin_create_with_id(roident, roname, InvalidXLogRecPtr); > > + > > > > Due to the above refactoring, CREATE SUBSCRIPTION can now fail because > > of a race between two concurrent CREATE SUBSCRIPTION commands: > > > > postgres=3D# create subscription sub2 connection 'dbname=3Dpostgres > > host=3Dlocalhost port=3D7733' publication pub1; > > ERROR: replication origin with ID 2 already exists > > > > I think this happens because the table lock is released, allowing > > another session to create a pg_origin with the next available roident, > > which replorigin_create_with_id() later attempts to use. > > > > Would it make sense to call replorigin_create_with_id() before > > table_close() so the lock is still held? I tested this and it works, > > though I'm not fully sure whether self-locking could be a concern > > here, since replorigin_create_with_id() again acquires a > > RowExclusiveLock on the same table. > > ~~~ > > > > I have fixed this as suggested by Shveta > > ~~~~~~~~~~ > > > > On Thu, Jun 18, 2026 at 7:17=E2=80=AFPM shveta malik wrote: > > > > > I think this happens because the table lock is released, allowing > > > another session to create a pg_origin with the next available roident= , > > > which replorigin_create_with_id() later attempts to use. > > > > > > Would it make sense to call replorigin_create_with_id() before > > > table_close() so the lock is still held? I tested this and it works, > > > though I'm not fully sure whether self-locking could be a concern > > > here, since replorigin_create_with_id() again acquires a > > > RowExclusiveLock on the same table. > > > > We can handle this race-condition by moving the table-open to callers > > rather than internal call replorigin_create_with_id(). But we also > > need the following changes first: > > > > Yes, did it that way. > > > > 1) > > replorigin_create_with_id has: > > > > + /* > > + * To avoid needing a TOAST table for pg_replication_origin, we limit > > + * replication origin names to 512 bytes. This should be more than en= ough > > + * for all practical use. > > + */ > > + if (strlen(roname) > MAX_RONAME_LEN) > > + ereport(ERROR, > > + errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), > > + errmsg("replication origin name is too long"), > > + errdetail("Replication origin names must be no longer than %d bytes."= , > > + MAX_RONAME_LEN)); > > > > replorigin_create_with_id() is called by replorigin_create() and > > binary_upgrade_create_replication_origin(). replorigin_create() > > already has this check. We should move above check to > > binary_upgrade_create_replication_origin() and get rid of it from > > internal function replorigin_create_with_id(). > > Moved. > > > > > 2) > > > > Also the checks: > > + if (SearchSysCacheExists1(REPLORIGIDENT, ObjectIdGetDatum(roident))) > > + ereport(ERROR, > > + errcode(ERRCODE_DUPLICATE_OBJECT), > > + errmsg("replication origin with ID %u already exists", > > + (Oid) roident)); > > + > > + if (SearchSysCacheExists1(REPLORIGNAME, roname_d)) > > + ereport(ERROR, > > + errcode(ERRCODE_DUPLICATE_OBJECT), > > + errmsg("replication origin \"%s\" already exists", roname)); > > > > should be moved to caller binary_upgrade_create_replication_origin(). > > IMO, these checks are not needed for replorigin_create(), it alread > > has this, which should suffice. > > > > ereport(ERROR, > > (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED= ), > > errmsg("could not find free > > replication origin ID"))); > > > > ~~ > > > > Once we correctly place these checks, we can move the 'table_open with > > lock' to caller (before name and id collision checks) instead of > > replorigin_create_with_id(), which will take care of race-conditions > > reported by Nisha. > > > > I have moved the roident check out of replorigin_create_with_id(), but > kept the roname check there as that is not there in > replorigin_create() > > On Fri, Jun 19, 2026 at 5:48=E2=80=AFPM Shlok Kyal wrote: > > Hi Ajin, > > > > Thanks for providing the updated patch, I reviewed the patch and I > > have some comments: > > > > 1. patch is not applying on HEAD and needs a rebase. > > > > 2. New line after 'check_new_cluster_replication_origins' is not requir= ed: > > + check_new_cluster_replication_origins(); > > + > > > > 3. pg_dumpall with option '--roles-only' also dumps replication origins > > eg: > > ./pg_dumpall -p 9000 --binary-upgrade --roles-only > op.sql > > output contains: > > -- For binary upgrade, must preserve replication origin roident and rem= ote_lsn > > SELECT pg_catalog.binary_upgrade_create_replication_origin('1'::pg_cata= log.oid, > > 'pg_16387'::pg_catalog.text, '0/00000000'::pg_catalog.pg_lsn); > > > > I think in this case we should not dump the replication origins as we > > explicitly gave the option of '--roles-only' > > > > I have moved the dump_replication_origins under !roles_only and > !tablespaces_only checks, I think "globals only" is valid for > replication origins. > > > > 4. pg_dumpall with --globals-only option also dumps replication origins= . > > eg: > > ./pg_dumpall -p 9000 --binary-upgrade --globals-only> op.sql and > > output contains: > > -- For binary upgrade, must preserve replication origin roident and rem= ote_lsn > > SELECT pg_catalog.binary_upgrade_create_replication_origin('1'::pg_cata= log.oid, > > 'pg_16387'::pg_catalog.text, '0/00000000'::pg_catalog.pg_lsn); > > > > Is this behaviour intentional? Other logical replication objects > > (e.g., publications and subscriptions) are not included in pg_dumpall > > --globals-only, so is it okay to dump replication origin? > > > > This is expected as publications and subscriptions are database > specific and not globals, while replication origins are global. > > > > On Mon, Jun 22, 2026 at 2:22=E2=80=AFAM Rui Zhao w= rote: > >> > >> Hi Ajin, > >> > >> Thanks for v8. A couple of comments: > >> > >> 1) The PG17 gating seems inconsistent between the dump and the check n= ow. > >> dumpReplicationOrigins() is called unconditionally: > >> > >> if (binary_upgrade && archDumpFormat =3D=3D archNull) > >> dumpReplicationOrigins(conn); > >> > >> but get_subscription_info(), which sets nrepl_origins, is still only > >> called for old clusters >=3D 17 in check_new_cluster(): > >> > >> if (GET_MAJOR_VERSION(old_cluster.major_version) >=3D 1700) > >> ... > >> get_subscription_info(&old_cluster); > >> > >> and check_new_cluster_replication_origins() returns early on it: > >> > >> if (old_cluster.nrepl_origins =3D=3D 0) > >> return; > >> > >> For a < 17 old cluster get_subscription_info() is never called, so > >> nrepl_origins stays 0 and this returns early. So when such a cluster h= as a > >> (user-created) origin, the origin still gets dumped and recreated, whi= le > >> the "new cluster must have no origins" and max_active_replication_orig= ins > >> checks are skipped, and the upgrade fails during restore instead of at= the > >> check step. nrepl_origins is just a count(*) on pg_replication_origin,= so > >> moving its collection out of the >=3D 1700 block should be enough. > >> > > Yes, good catch. I have separated out the replication origin speciifc > checks into a different function and moved it oustide the >=3D 1700 > block > > >> 2) 004_subscription.pl only checks subscription origins (pg_). Si= nce > >> the patch migrates all origins, should we also create a user origin > >> (pg_replication_origin_create()) and verify its roident/remote_lsn aft= er > >> the upgrade? That would also cover the case in (1). > > > Added. > > All the above changes have been addressed in patch v9. > Thanks for the updated patch. I have reviewed the patch and here are some my comments: 1. Since we are quering only one column, do we need to use 'i_norigins'? +void +get_replication_origin_info(ClusterInfo *cluster) +{ + PGconn *conn; + PGresult *res; + int i_norigins; + + conn =3D connectToServer(cluster, "template1"); + res =3D executeQueryOrDie(conn, "SELECT count(*) AS norigins " + "FROM pg_catalog.pg_replication_origin"); + i_norigins =3D PQfnumber(res, "norigins"); + + cluster->nrepl_origins =3D atoi(PQgetvalue(res, 0, i_norigins)); + PQclear(res); + + PQfinish(conn); +} I think we can directly use: cluster->nrepl_origins =3D atoi(PQgetvalue(res, 0, 0)); Thoughts? 2. In the above code should we also add a check like: if (PQntuples(res) !=3D 1) pg_fatal("could not count the number of replication origins"); 3. The brackets are not required: + if (!tablespaces_only && !roles_only && binary_upgrade) + { + /* Dump replication origins */ + dumpReplicationOrigins(conn); } We can move the comment '/* Dump replication origins */' above if condition= . It will make it consistent with the existing code. 4. I ran the tests locally and found that the test_decoding test suit is failing: not ok 10 - replorigin 249 ms diff: -- ensure duplicate creations fail SELECT pg_replication_origin_create('regress_test_decoding: regression_slo= t'); -ERROR: duplicate key value violates unique constraint "pg_replication_origin_roname_index" -DETAIL: Key (roname)=3D(regress_test_decoding: regression_slot) already e= xists. +ERROR: replication origin "regress_test_decoding: regression_slot" already exists -- ensure inactive origin cannot be set as session one if pid is specified SELECT pg_replication_origin_session_setup('regress_test_decoding: regression_slot', -1); ERROR: cannot use PID -1 for inactive replication origin with ID 1 I think this is expected because this patch adds a new function 'replorigin_create_with_id' which adds a check for the already existing replication origin. In HEAD we don't have such a check. So I think we should update the expected output file. Thanks, Shlok Kyal