Subject: [PATCH] Use direct hash lookup in logicalrep_partmap_invalidate_cb()

From: DaeMyung Kang <[email-masked]>
Date: Sat, 18 Apr 2026 02:44:50 +0900

LogicalRepPartMap is keyed by the partition's OID, which is assigned
to entry->relmapentry.localreloid when the entry is populated in
logicalrep_partition_open().  The invalidation callback therefore
does not need to iterate over every entry to find the one matching
the invalidated relation OID; a single hash_search(HASH_FIND) on
reloid suffices.

This replaces an O(N) hash_seq_search loop with an O(1) lookup and
removes the longstanding "TODO, use inverse lookup hashtable?"
comment.  An Assert in logicalrep_partition_open() documents the
partOid == entry->relmapentry.localreloid invariant that the new
path relies on.
---

Tested on an --enable-cassert build: the full src/test/subscription
TAP suite (39 files, 579 tests) passes, including 013_partition.pl
which exercises the partmap invalidation path via ADD COLUMN on a
partitioned root (cascades a relcache inval to every leaf partition).

 src/backend/replication/logical/relation.c | 23 +++++++++-------------
 1 file changed, 9 insertions(+), 14 deletions(-)

diff --git a/src/backend/replication/logical/relation.c b/src/backend/replication/logical/relation.c
index 0b1d80b5b0f..f6e2a9a6422 100644
--- a/src/backend/replication/logical/relation.c
+++ b/src/backend/replication/logical/relation.c
@@ -544,20 +544,14 @@ logicalrep_partmap_invalidate_cb(Datum arg, Oid reloid)
 
 	if (reloid != InvalidOid)
 	{
-		HASH_SEQ_STATUS status;
-
-		hash_seq_init(&status, LogicalRepPartMap);
-
-		/* TODO, use inverse lookup hashtable? */
-		while ((entry = (LogicalRepPartMapEntry *) hash_seq_search(&status)) != NULL)
-		{
-			if (entry->relmapentry.localreloid == reloid)
-			{
-				entry->relmapentry.localrelvalid = false;
-				hash_seq_term(&status);
-				break;
-			}
-		}
+		/*
+		 * LogicalRepPartMap is keyed by partition OID, which matches
+		 * entry->relmapentry.localreloid (see logicalrep_partition_open), so
+		 * we can invalidate via a direct hash lookup.
+		 */
+		entry = hash_search(LogicalRepPartMap, &reloid, HASH_FIND, NULL);
+		if (entry != NULL)
+			entry->relmapentry.localrelvalid = false;
 	}
 	else
 	{
@@ -676,6 +670,7 @@ logicalrep_partition_open(LogicalRepRelMapEntry *root,
 	 */
 	if (found && entry->localrelvalid)
 	{
+		Assert(entry->localreloid == partOid);
 		entry->localrel = partrel;
 		return entry;
 	}

---

From: Michael Paquier <[email-masked]>
Date: Sat, 18 Apr 2026 06:35:54 +0900

> the invalidated relation OID; a single hash_search(HASH_FIND) on

At this stage of the release cycle (aka feature freeze is in effect as
of v19), I'd recommend that you add this patch to the next commit
fest, so as this could be considered for v20, whose development should
begin next July.  See [1].

[1]: https://commitfest.postgresql.org/59/
--
Michael