Postgres LISTEN/NOTIFY Survives Real Traffic
Most engineers dismiss Postgres pub/sub as a toy. New benchmarks show it handles serious throughput — here's when to drop Redis.
You have a Postgres database. You need to notify your app when rows change. Your first instinct? Bolt on Redis pub/sub, or stand up a Kafka cluster. Stop. The database you already run has a built-in event system, and fresh benchmarks prove it doesn't fold under pressure.
A detailed new analysis of Postgres LISTEN/NOTIFY shows the feature sustaining thousands of notifications per second across hundreds of concurrent listeners — without the latency spikes or memory blowups everyone assumed would kill it.
Why this matters
If you're already running Postgres, adding Redis just for pub/sub means a second system to operate, monitor, back up, and keep in sync. That's extra failure modes, extra cost, and another on-call rotation.
LISTEN/NOTIFY lets you push events directly from your database to any connection that cares — zero additional infrastructure, no consistency gaps between your data store and your message broker.
How it works
LISTEN registers a connection to a named channel. NOTIFY fires a message to every listener on that channel. The payload is a string up to 8000 bytes. Crucially, notifications only deliver after the transaction commits — listeners never see events for rolled-back writes.
The mechanism runs through Postgres's async notification queue, which lives in shared memory. Listeners poll their connection socket, so there's no busy-waiting. A trigger can call NOTIFY automatically when rows change, giving you change-data-capture without Debezium.
Where this helps
- Cache invalidation: NOTIFY from an UPDATE trigger; app connections LISTEN and drop stale keys instantly.
- Real-time dashboards: Push row-change events to a WebSocket gateway without a separate broker.
- Job wakeups: A worker LISTENs on a queue channel; inserts into a jobs table fire NOTIFY to wake it immediately — no polling interval.
Watch out
The 8000-byte payload cap is real. Don't stream row bodies — send IDs and let listeners fetch. Each LISTEN holds a connection open, so a connection pool with thousands of idle listeners will exhaust your max_connections. Use a multiplexer like pgx in Go or node-postgres with a single listener per process that fans events to workers.
If your queue fills up (rare, but possible during notification storms), Postgres logs a warning and backpressures. Monitor pg_stat_activity for stuck senders.
Try it yourself
Open two psql sessions against the same database:
-- Session 1
LISTEN order_events;
-- Session 2
NOTIFY order_events, '{"order_id": 42, "status": "shipped"}';
-- Session 1 (immediately)
Asynchronous notification "order_events" with payload '{"order_id": 42, "status": "shipped"}' received from server process with PID 12345.
Wire that into a trigger and you've got CDC in one line of SQL.
TL;DR
- What changed: Benchmarks confirm LISTEN/NOTIFY handles thousands of events/sec across hundreds of listeners — far beyond the "toy" reputation.
- Why it matters: You can drop Redis or Kafka from many pub/sub workloads and keep everything inside Postgres.
- What to try today: Add a NOTIFY trigger to your most-queried table and have your app LISTEN for cache invalidation instead of polling.