Re: Optimize LISTEN/NOTIFY

Joel Jacobson <joel@compiler.org>

From: "Joel Jacobson" <joel@compiler.org>
To: pgsql-hackers <pgsql-hackers@postgresql.org>
Cc: "Tom Lane" <tgl@sss.pgh.pa.us>
Date: 2026-01-13T18:09:53Z
Lists: pgsql-hackers
On Tue, Jan 13, 2026, at 13:10, Joel Jacobson wrote:
> On Tue, Jan 13, 2026, at 06:46, Joel Jacobson wrote:
>> On Wed, Jan 7, 2026, at 21:29, Tom Lane wrote:
>>> That seems a little weird: surely such usage is not something we need
>>> to optimize.  Maybe it can be justified because it makes the code
>>> simpler, but that's not immediately obvious to me.
>>
>> The reason for not sticking to the two-boolean approach (staged/current)
>> like in v32 was to minimize shared dshash operations in favor of local
>> hash table operations.

I made a closer comparison between v32 and v34, adopting the same naming
in v32 to allow for easier comparison. This exercise paid off; I now
remember another important reason why I looked for alternatives to the
two-boolean approach.  There is a significant difference for UNLISTEN *.

In v32, we need to scan the *entire* global hash table while holding an
exclusive lock.

In v34, we only need to scan our own local hash tables.

v32 (naming adopted to match v34):
```
static void
PrepareTableEntriesForUnlistenAll(void)
{
	dshash_seq_status status;
	GlobalChannelEntry *entry;

	dshash_seq_init(&status, globalChannelTable, true);
	while ((entry = dshash_seq_next(&status)) != NULL)
	{
		if (entry->key.dboid == MyDatabaseId)
		{
			ListenerEntry *listeners = (ListenerEntry *) dsa_get_address(globalChannelDSA,
																		 entry->listenersArray);

			for (int i = 0; i < entry->numListeners; i++)
			{
				if (listeners[i].procNo == MyProcNumber && listeners[i].staged)
				{
					listeners[i].staged = false;
					pendingListenChannels = lappend(pendingListenChannels,
													pstrdup(entry->key.channel));
				}
			}
		}
	}
	dshash_seq_term(&status);
}
```

v34:
```
static void
PrepareTableEntriesForUnlistenAll(void)
{
	HASH_SEQ_STATUS seq;
	struct ChannelName *channelEntry;
	struct PendingListenEntry *pending;

	hash_seq_init(&seq, pendingListenActions);
	while ((pending = (struct PendingListenEntry *) hash_seq_search(&seq)) != NULL)
		pending->action = PENDING_UNLISTEN;

	if (localChannelTable != NULL)
	{
		hash_seq_init(&seq, localChannelTable);
		while ((channelEntry = (struct ChannelName *) hash_seq_search(&seq)) != NULL)
		{
			bool		found;

			pending = (struct PendingListenEntry *)
				hash_search(pendingListenActions, channelEntry->channel, HASH_ENTER, &found);
			pending->action = PENDING_UNLISTEN;
		}
	}
}
```

/Joel



Commits

Same data as JSON: GET /api/v1/messages/:b64id/commits the thread's linked commits as JSON, with link sources. API reference →
  1. Optimize LISTEN/NOTIFY via shared channel map and direct advancement.

  2. Fix incorrect logic for caching ResultRelInfos for triggers