How YugabyteDB Delivers LISTEN/NOTIFY Events

YugabyteDB v2025.2.3 introduced Early Access support for PostgreSQL-compatible LISTEN, NOTIFY, and UNLISTEN in YSQL.

In an earliert tip, LISTEN Up! Asynchronous Notifications with LISTEN/NOTIFY in YugabyteDB, we looked at how to use LISTEN/NOTIFY from the SQL side.

In this follow-up tip, we’ll look at what happens behind the scenes.

The SQL syntax is PostgreSQL-compatible, but the internal delivery path is different because YugabyteDB is distributed:

  • PostgreSQL runs inside a single database instance and uses shared memory for notification delivery.
  • YugabyteDB runs across multiple YB-TServers, so a notification sent through one node may need to be delivered to listeners connected to another node.

To make that work, YugabyteDB uses an internal system table, per-node notification pollers, and CDC-style logical replication.

Under the hood: In YugabyteDB, NOTIFY stores notification information in the internal yb_system.pg_yb_notifications table. Each YB-TServer has a notification poller that reads those changes using CDC-style logical replication and delivers matching notifications to local listening sessions.

PostgreSQL vs. YugabyteDB

Database How notifications are delivered What that means
PostgreSQL Notifications are delivered through shared memory. The sender and listener are inside the same database instance.
YugabyteDB Notifications are stored in an internal table and consumed by per-node pollers. A listener connected to one YB-TServer can receive a notification sent through another YB-TServer.

From the application side, the syntax is still simple:

				
					LISTEN ybtip_events;

NOTIFY ybtip_events, 'hello from yugabyte';
				
			

But internally, YugabyteDB has more work to do.

Enable LISTEN/NOTIFY

LISTEN/NOTIFY is currently an Early Access feature and is disabled by default.

To enable it, set this flag to true on both YB-TServers and YB-Masters:

				
					--ysql_yb_enable_listen_notify=true
				
			

For example, in a local yugabyted demo:

				
					./bin/yugabyted start \
  --advertise_address=127.0.0.1 \
  --base_dir=$HOME/yb-listen-notify/node1 \
  --tserver_flags="ysql_yb_enable_listen_notify=true" \
  --master_flags="ysql_yb_enable_listen_notify=true"
				
			

After the feature is enabled, the leader Master creates the internal objects in the background.

If you try to use LISTEN or NOTIFY immediately after startup and see an error asking you to retry shortly, wait a few seconds and try again.

Check 1: Confirm the YugabyteDB Version
LISTEN/NOTIFY is available in YugabyteDB v2025.2.3 and later.
				
					SELECT split_part(version(), '-', 3) AS yb_version;
				
			

Example output:

				
					.yb_version
------------
 2025.2.4.0
(1 row)
				
			
Check 2: Confirm the yb_system Database Exists

When LISTEN/NOTIFY is enabled, YugabyteDB creates internal objects in the yb_system database.

				
					SELECT datname
FROM pg_database
WHERE datname = 'yb_system';
				
			

Example output:

				
					. datname
-----------
 yb_system
(1 row)
				
			

The yb_system database is managed by YugabyteDB.

It is not an application database.

Check 3: Inspect the Internal Notification Table
Connect to the yb_system database:
				
					\c yb_system
				
			

Then inspect the internal notification table:

				
					\d pg_yb_notifications
				
			

Example output shape:

				
					.            Table "public.pg_yb_notifications"
      Column      |  Type   | Collation | Nullable | Default
------------------+---------+-----------+----------+---------
 notif_uuid       | uuid    |           | not null |
 sender_node_uuid | uuid    |           | not null |
 sender_pid       | integer |           | not null |
 db_oid           | oid     |           | not null |
 is_listen        | boolean |           | not null |
 data             | bytea   |           | not null |
 extra_options    | jsonb   |           |          |
Indexes:
    "pg_yb_notifications_pkey" PRIMARY KEY, lsm (notif_uuid HASH)
Publications:
    "pg_yb_notifications_publication"
				
			

The exact internal schema can change between YugabyteDB versions, so do not build application logic around this table.

Still, this output is useful because it shows a few important details:

  • ● The notification rows are keyed by notif_uuid.
  • ● The sender_node_uuid, sender_pid, and db_oid columns help YugabyteDB identify where the notification came from and which database it belongs to.
  • ● The data column stores the notification payload information internally.
  • ● The extra_options column is stored as jsonb.
  • ● The table has an internal publication named pg_yb_notifications_publication, which is part of the CDC-style path YugabyteDB uses to distribute notification events across YB-TServers.
Important: The yb_system.pg_yb_notifications table is for internal use only. It is fine to inspect it while learning, but do not modify, truncate, drop, or repurpose it.
Check 4: Look for the Internal Notification Replication Slots

YugabyteDB uses CDC-style logical replication internally to deliver notifications across YB-TServers.

Each YB-TServer creates an internal logical replication slot with a name like this:

				
					yb_notifications_<tserver-uuid>
				
			

You can inspect those slots with:

				
					SELECT slot_name,
       active_pid,
       yb_stream_id,
       yb_restart_time
FROM pg_replication_slots
WHERE slot_name LIKE 'yb_notifications_%'
ORDER BY slot_name;
				
			

Example output:

				
					.                           slot_name                              | active_pid |            yb_stream_id             |      yb_restart_time
-------------------------------------------------------------------+------------+-------------------------------------+----------------------------
 yb_notifications_0b1b2c3d4e5f67890123456789abcdef                 |     123456 | 11111111111111111111111111111111    | 2026-06-26 13:42:10.123-04
 yb_notifications_1a2b3c4d5e6f78900123456789abcdef                 |     123457 | 22222222222222222222222222222222    | 2026-06-26 13:42:10.456-04
 yb_notifications_2a3b4c5d6e7f89010123456789abcdef                 |     123458 | 33333333333333333333333333333333    | 2026-06-26 13:42:10.789-04
(3 rows)
				
			

In a three-node cluster, you should generally expect one internal yb_notifications_* slot per YB-TServer.

The exact slot names, process IDs, stream IDs, and timestamps will be different in your environment.

How the Delivery Flow Works

At a high level, YugabyteDB delivers notifications like this:

Step What happens
1 A session runs LISTEN channel_name;.
2 Another session runs NOTIFY channel_name, 'payload';.
3 YugabyteDB writes the notification to yb_system.pg_yb_notifications.
4 Each YB-TServer poller reads notification changes using its internal yb_notifications_* replication slot.
5 The local YB-TServer delivers matching notifications to local sessions listening on that channel.
That is how YugabyteDB keeps the familiar PostgreSQL LISTEN/NOTIFY behavior while making it work across a distributed cluster.
Check 5: Compare YB-TServers to Notification Slots

You can compare your YB-TServers to the internal notification slots.

First, check the YB-TServers:

				
					SELECT host,
       port,
       node_type,
       cloud,
       region,
       zone,
       uuid
FROM yb_servers()
ORDER BY host, port;
				
			

Then count the internal notification slots:

				
					SELECT count(*) AS notification_slot_count
FROM pg_replication_slots
WHERE slot_name LIKE 'yb_notifications_%';
				
			

Example output:

				
					.notification_slot_count
-------------------------
                       3
(1 row)
				
			

In this example, there are three YB-TServers and three internal notification slots.

Operational note: YugabyteDB creates one internal yb_notifications_* replication slot per YB-TServer. If you also use user-created logical replication slots, make sure max_replication_slots is large enough for both the internal LISTEN/NOTIFY slots and your application-created slots.

Do Not Touch the Internal Objects

Because the internal table and replication slots are visible through SQL, it can be tempting to treat them like regular database objects.

Do not do that.

Do not run commands like these:

				
					-- Do not do this.
DROP TABLE yb_system.public.pg_yb_notifications;

-- Do not do this.
TRUNCATE yb_system.public.pg_yb_notifications;

-- Do not do this.
SELECT pg_drop_replication_slot(slot_name)
FROM pg_replication_slots
WHERE slot_name LIKE 'yb_notifications_%';
				
			

These objects are managed by YugabyteDB. Changing them can break notification delivery.

Hands off: It is fine to inspect the internal objects while learning or troubleshooting, but do not modify them. The yb_system database, pg_yb_notifications table, and yb_notifications_* replication slots are managed by YugabyteDB.

Application View vs. Internal View

The application-level view is simple:

				
					LISTEN channel;
NOTIFY channel, payload;
				
			

The YugabyteDB internal view looks more like this:

				
					NOTIFY
  -> yb_system.pg_yb_notifications
  -> CDC-style logical replication
  -> per-TServer notification poller
  -> local listening sessions
				
			

That is the key difference…

  • ● PostgreSQL can deliver notifications inside one database instance.
  • ● YugabyteDB has to deliver notifications across a distributed SQL cluster.

Final Takeaway

LISTEN/NOTIFY in YugabyteDB keeps the PostgreSQL-compatible developer experience, but the internals are different.

YugabyteDB uses the internal yb_system.pg_yb_notifications table, per-node notification pollers, and yb_notifications_* logical replication slots to deliver asynchronous notifications across YB-TServers.

Use LISTEN and NOTIFY like PostgreSQL.

But leave the internal objects alone.

They are part of YugabyteDB’s distributed notification machinery.

Have Fun!

My mom bought this little Global guitar for my sister and me when we were kids, probably hoping one of us would become the next Elvis

Spoiler alert: That did not happen. Instead, I somehow ended up at YugabyteDB, which honestly turned out just fine by me.

Anyway, continuing the current theme of my wife encouraging me to part with “extra baggage” before we move to Dallas, I had to say goodbye to this guitar last night. It may not have launched a rock-and-roll career, but it definitely carried a lot of memories.