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.

Share

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.