Postgres LISTEN/NOTIFY does not scale
Core issue: LISTEN/NOTIFY and global commit lock
- Commenters reiterate the article’s finding: issuing
NOTIFYinside a transaction causes a global lock at commit, effectively serializing commits that use it. - Several people note this behavior is not mentioned in the Postgres docs, which they find surprising for a lock-heavy feature.
- Some ask for clarification whether only transactions that called
NOTIFYare serialized; the exact scope is left somewhat unclear in the thread.
“Doesn’t scale” vs “used incorrectly”
- Many argue the feature scales fine for typical loads and that the problem is using
NOTIFYfor every transaction at very high write concurrency. - Viewpoint 1: this is primarily a misuse and lack of due diligence (no debouncing, no grouping, no outbox), not an indictment of LISTEN/NOTIFY in general.
- Viewpoint 2: even if misused, it’s a realistic trap; the doc and API design make it easy to step on a landmine under growth.
Design limitations of LISTEN/NOTIFY
- No authorization model: you can’t restrict which roles may
LISTEN/NOTIFYon which channels. - Payload constraints and typing are absent; some wish for a
CREATE CHANNEL-style DDL with authz and type checking. - LISTEN/NOTIFY is lossy (missed messages if nobody is listening) and awkward with connection poolers.
- A benchmark is cited: latency grows ~linearly with number of idle listeners (O(N) backend scan), making many listeners problematic.
Alternative patterns within Postgres
- Notify outside the main transaction, with:
- A transactional outbox table plus a sweeper, accepting duplicates and eventual consistency.
- Notifications used only as “wake up” hints; consumers then poll canonical tables.
- Use tables as queues via
SELECT … FOR UPDATE SKIP LOCKED, backoff polling, and sometimes partitioning to reduce dead-tuple impact. - Multiple people describe hard problems here: dead tuples, autovacuum tuning, planner misestimation, HOT updates, and queue-table design. Extensions like pgmq/pgflow and higher-level frameworks (e.g., DBOS, Chancy) are mentioned.
WAL / logical decoding and external systems
- Several recommend reading the WAL / logical replication instead of LISTEN/NOTIFY, using:
pg_logical_emit_message()for an outbox without table bloat.- Logical decoding consumers (e.g., via Debezium) or streaming replication.
- Others advocate dedicated infra for pub/sub and queues: Redis, Kafka, NATS, RabbitMQ, MQTT, SQS, Centrifugo, etc., often with Postgres as system-of-record only.
Business logic in DB vs app
- Long sub-thread debates:
- Pro-DB: constraints, triggers, and stored procedures centralize invariants and protect against misbehaving apps.
- Pro-app: triggers and in-DB logic can be opaque, surprising, and hard to scale; RDBMS should not be overused as message queues or workflow engines.
- Consensus: use DB features for data integrity and some lifecycle logic, but be cautious putting high-volume messaging or broad business workflows entirely inside Postgres.
Broader scaling lessons
- Many say LISTEN/NOTIFY is fine for low-to-moderate scale and very convenient when you “just want one dependency.”
- Repeated theme: start simple (often Postgres), but know where it breaks—queueing, pub/sub, and very write-heavy workloads are common cliffs that eventually call for specialized components.