Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

PostgreSQL Asynchronous Messaging with LISTEN and NOTIFY

Tech Jul 31 2

PostgreSQL includes a built-in publish-subscribe framework that enables database sessions to exchange asynchronous signals without relying on external message brokers. This communication layer is implemented through the LISTEN and NOTIFY commands, allowing backend processes and client applications to coordinate over named channels.

Basic Subscription and Publication

To establish a communication path, a client connection must first register as a subscriber. In one terminal, attach a listener to a specific channel:

app_db=# LISTEN event_stream;
LISTEN

From a separate database connection, disptach a notification to that channel. An optional text payload can be appended to the signal:

app_db=# NOTIFY event_stream, 'service_ready';
NOTIFY

The publishing statement executes immediately. However, the listening session only processes incoming payloads when it runs a subsequent query. Executing any SQL statement in the first session triggers message delivery:

app_db=# SELECT current_timestamp;
     current_timestamp
----------------------------
 2023-10-25 14:32:10.123456+00
(1 row)
Asynchronous notification "event_stream" with payload "service_ready" received from server process with PID 7821.

Core Commands and Utility Functions

The notification subsystem operates through a small set of declarative statements and server-side functions:

  • LISTEN channel: Registers the active connection as a subscriber. Executing this command multiple times for the same channel is idempotent and generates no errors.
  • UNLISTEN { channel | * }: Removes an active subscription. Using the wildcard * clears all channel registrations for the current session.
  • NOTIFY channel [, payload]: Broadcasts a signal to all listeners of the target channel. The payload string is optional and defaults to empty.
  • pg_notify(channel text, payload text): A function equivalent to the NOTIFY command, useful for embedding within procedural code or dynamic SQL.
  • pg_listening_channels(): Returns a set containing all channel names currently monitored by the active session.

Querying active subscriptions is straightforward:

app_db=# LISTEN db_metrics;
LISTEN
app_db=# LISTEN cache_invalidation;
LISTEN
app_db=# SELECT * FROM pg_listening_channels();
     pg_listening_channels
-----------------------------
 db_metrics
 cache_invalidation
(2 rows)

Multi-Client Delivery and Transactional Boundaries

The framework natively supports broadcasting. When a signal is dispatched, every active subscriber to the target channel receives the payload independently.

A critical design characteristic of NOTIFY is its strict transactional semantics. Notifications generated inside an uncommitted transaction remain queued in memory and are not delivered until the transaction successfully commits. If the transaction is rolled back, the pending signals are permanently discarded.

app_db=# BEGIN;
app_db=# NOTIFY event_stream, 'queued_action';
app_db=# ROLLBACK;
-- Checking the listener session after rollback yields no new messages.

Only after replacing ROLLBACK with COMMIT will the payload propagate to the registered listeners.

Ordering Guarantees and Deduplication

The database engine enforces strict delivery sequencing. Signals originating within a single transaction are delivered in the exact order they were queued. Across separate transactions, delivery follows the chronological sequence of transaction commits.

To minimize overhead, PostgreSQL automatically deduplicates identical notifications within a transaction scope. If the same channel and payload combination is emitted multiple times before commit, subscribers receive only a single instance.

app_db=# BEGIN;
app_db=# NOTIFY event_stream, 'batch_processed';
app_db=# NOTIFY event_stream, 'batch_processed';
app_db=# COMMIT;
-- Polling the listener will show exactly one "batch_processed" payload.

System Limits and Protocol Constraints

An internal queue stores pending notifications awaiting consumption by active listeners. The default maximum capacity is 8 GB. Queue exhautsion typically occurs when a session subscribes to a channel, opens a long-lived transaction, and fails to periodically process incoming messages. Once the queue reaches its limit, committing a transaction that contains new NOTIFY calls will fail.

Furthermore, asynchronous notifications cannot participate in two-phase commit protocols. Attempting to prepare a transaction that includes LISTEN, UNLISTEN, or NOTIFY statements will immediately abort:

app_db=# BEGIN;
app_db=# NOTIFY event_stream, 'phase_one_complete';
app_db=# PREPARE TRANSACTION 'xid_global_99';
ERROR: NOTIFY cannot PREPARE a transaction that has executed LISTEN, UNLISTEN, or NOTIFY
Back to List

Prev: Computer Vision Fundamentals

No newer posts...

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.