LISTEN and NOTIFY
Send a message to every client that listens on a channel when a transaction commits - over the PostgreSQL port (LISTEN) or HTTP (GET /listen, server-sent events).
- Category: Postgres protocol
- Keywords: LISTEN, NOTIFY, UNLISTEN, UNLISTEN *, pg_notify, notification, notifications, pub/sub, pubsub, publish, subscribe, PUBLISH, SUBSCRIBE, PSUBSCRIBE, pattern, pmessage, redis, push, event, events, channel, payload, NotificationResponse, GET /listen, /listen, server-sent events, SSE, EventSource, text/event-stream, payload string too long, channel name cannot be empty, 54000, trigger notify, LISTEN needs a connection that stays open
- See also: PostgreSQL protocol (port 8832), Triggers, Transactions, Run SQL over HTTP (/exec, /exp), CREATE TABLE (row-ttl), Key-value store (Redis-like) (kv-pubsub)
NOTIFY sends a short message on a channel. Every client that listens on that channel gets it, the moment the sending transaction commits. It works as in PostgreSQL 14, and web pages can listen over HTTP too.
| How | |
|---|---|
| Send | NOTIFY orders, 'order 42 paid' or SELECT pg_notify('orders', 'order 42 paid'), from any client, a trigger or a procedure |
| Listen (PostgreSQL port) | LISTEN orders on a connection that stays open (below) |
| Listen (HTTP, web pages) | GET /listen?channel=orders: server-sent events (below); ?pattern=price.* for every channel a pattern matches |
| Redis-style | PUBLISH, PUBSUB and keyspace events through POST /kv (key-value store): the same channels, sent at once |
The rules, the same as PostgreSQL's:
- A message is delivered when its transaction commits, never after a
ROLLBACK.ROLLBACK TO SAVEPOINTdrops the ones sent after the savepoint. - The same channel and payload sent twice in one transaction arrive once. Otherwise the order is kept.
- Channels belong to one database: a
NOTIFYinmarketnever reaches a listener ofapp. - A message is a signal for clients connected now: nothing is stored, and nothing is sent again after a restart. Read the table after a message to get the data.
NOTIFY and pg_notify()
Syntax:
NOTIFY channel [, 'payload']
pg_notify(channel_text, payload_text)
| Part | Meaning |
|---|---|
channel | a name. Without quotes it is lower case (NOTIFY Orders goes to orders); "Orders" keeps its case |
payload | text of at most 7999 bytes; none = empty |
pg_notify(...) | the same as a function, for a value made in the query. It takes the channel as written (no lower case) |
- Returns:
NOTIFY;pg_notify()givesNULL. - Errors:
payload string too long,channel name cannot be empty,channel name too long(more than 63 bytes). - Anyone who may read the database may send and listen, as in PostgreSQL.
Example: Send inside a transaction: it goes out at COMMIT
BEGIN;
NOTIFY orders, 'order 42 paid';
SELECT pg_notify('orders', 'order ' || 43 || ' paid');
COMMIT;
Result:
BEGIN
transaction started
NOTIFY orders, 'order 42 paid'
notification on channel 'orders': sent to its listeners when the transaction commits
SELECT pg_notify('orders', 'order ' || 43 || ' paid')
| pg_notify('orders', 'order ' || 43 || ' paid') (STRING) |
|---|
| NULL |
(1 row)
COMMIT
transaction committed: 0 change(s) saved
Example: A payload that is too long (shows an error)
SELECT pg_notify('orders', repeat('x', 8000));
Result:
Error: line 1, column 8: pg_notify: payload string too long
From a trigger
A trigger can send a message for each new row. It is part of the statement's transaction: a failed insert sends nothing.
Example: Tell the listeners about every paid order
CREATE TABLE payments (id LONG PRIMARY KEY, amount DOUBLE);
CREATE TRIGGER tell_paid AFTER INSERT ON payments EXECUTE BEGIN
SELECT pg_notify('payments', 'paid ' || id) FROM NEW;
END;
INSERT INTO payments VALUES (1, 250.0), (2, 99.5);
Result:
CREATE TABLE payments (
table 'payments' created
CREATE TRIGGER tell_paid AFTER INSERT ON payments EXECUTE BEGIN
trigger 'tell_paid' created on table 'payments'
INSERT INTO payments VALUES (1, 250.0), (2, 99.5)
2 row(s) inserted
SELECT pg_notify('payments', 'paid ' || id) FROM NEW
| pg_notify('payments', 'paid ' || id) (STRING) |
|---|
| NULL |
| NULL |
(2 rows)
LISTEN on the PostgreSQL port
Syntax:
LISTEN channel
UNLISTEN channel | *
- The connection gets a
NotificationResponse(channel, payload, the sender's process id) for each message. Drivers show them their own way: psql printsAsynchronous notification "orders" with payload "..." received from server process with PID 17., pgx hasWaitForNotification, psycopgconn.notifies(), node-pg thenotificationevent. - An idle connection gets it at once. A connection inside
BEGIN ... COMMITgets it when that transaction ends, as in PostgreSQL. Its own messages come right after itsCOMMIT. LISTENinsideBEGIN ... COMMITstarts atCOMMIT, and not at all afterROLLBACK.DISCARD ALLstops every listen.
GET /listen: server-sent events
Syntax:
GET /listen?channel=orders[&channel=payments][&pattern=price.*][&db=name]
The reply is text/event-stream and stays open. A browser reads it with EventSource:
const es = new EventSource('/listen?channel=orders');
es.onmessage = (e) => {
const n = JSON.parse(e.data); // {"channel":"orders","payload":"order 42 paid","pid":0}
refreshOrders();
};
| Event | When |
|---|---|
listen | once, when listening started: {"channels":[...],"patterns":[...],"database":"..."} |
| (message) | one per notification: {"channel","payload","pid"}; pid is 0 for a sender that is not a PostgreSQL connection; "pattern" too when it came through a pattern |
: ping (a comment) | every 15 s, so a proxy keeps the stream open |
error | the stream ends after it (see limits) |
- It needs a login, like /exec (
Authorization: Bearer ..., or the console's cookie). Up to 64 channels and patterns in one stream; the names are taken as written. channel=is Redis'sSUBSCRIBE,pattern=itsPSUBSCRIBE: a glob asKEYStakes it (*any text,?one character,[abc],\before a special character)./listen?pattern=price.*getsprice.NABIL,price.HDL, ... A message that matches a channel and a pattern of the same stream comes once for each.- Over HTTP a channel may be up to 4096 bytes long (a keyspace event holds the whole key);
NOTIFYkeeps PostgreSQL's 63. LISTENsent through/execis refused with the way to do it: a request has no connection that stays open to be told.
Limits
- A listener that does not read keeps up to 32 MB of messages. Past that, it is cut off: the PostgreSQL connection ends with SQLSTATE
54000, the HTTP stream with anerrorevent. Nothing is lost quietly: connect again,LISTENagain, and read the table. - At most 256 HTTP streams at once (
503beyond). - Messages of one sending connection arrive in commit order. Messages of two connections that commit at the same moment may arrive in either order.