{"post":{"slug":"listen-notify-explained-2026","title":"LISTEN and NOTIFY: Push Without a Message Broker","excerpt":"Your database can tell your application when something changed, instead of your application asking every two seconds. It is elegant, built in, and not a message queue.","category":"code","date":"August 14, 2026","author":"ByteLedger Team","content":"<span class=\"dropcap\">Y</span>our application polls a table every two seconds looking for new work. Most of the time there is none, so you are running a query eighteen hundred times an hour to learn nothing. Lower the interval and the load rises; raise it and latency does.\n\n`LISTEN` and `NOTIFY` invert this. The application subscribes to a channel, the database sends a message when something happens, and the polling disappears. It is built in, costs nothing extra to run, and is emphatically not a message queue — which is where teams get into trouble with it.\n\n## What changed in 2026\n\n- **Change data capture matured as the alternative.** For durable, ordered event streaming, CDC tooling became the standard answer, which clarified what LISTEN/NOTIFY is actually for.\n- **Serverless made the connection requirement awkward.** A persistent listening connection sits badly with function-based architectures that expect to be stateless.\n- **Pooler support stayed a sharp edge.** Transaction-mode pooling continued to break it, and the failure remains quiet.\n- **It stayed popular for the narrow case.** Cache invalidation and job wake-ups remain a genuinely good fit that needs no extra infrastructure.\n\n## How it works\n\nA client issues `LISTEN channel_name` and holds the connection open. Anything connected to the same database can issue `NOTIFY channel_name, 'payload'`. Every listener on that channel receives the message.\n\nThe mechanism is transactional, which is more useful than it first appears: a notification issued inside a transaction is delivered only when that transaction commits. If it rolls back, nothing is sent. That means you cannot notify about a change that did not actually happen — a guarantee that is genuinely hard to get with an external queue, where the write and the publish are separate operations that can diverge.\n\nCombined with a trigger, this gives you notifications on data changes without any application code publishing them.\n\n## What it is not\n\n| | LISTEN/NOTIFY | Message queue |\n| --- | --- | --- |\n| Durability | None — missed if not connected | Persisted |\n| Retries | None | Configurable |\n| Acknowledgement | None | Explicit |\n| Ordering | Per channel, per connection | Usually guaranteed |\n| Payload size | Small limit | Large |\n| Consumer groups | No — all listeners get it | Yes |\n| Backpressure | Queue can overflow | Handled |\n\nThe durability row is the one that matters. **If nothing is listening when a notification fires, it is gone.** No buffer, no replay, no dead letter. A listener that reconnects after a network blip has missed everything that happened while it was away, and there is no way to find out what.\n\nThis makes it unsuitable as a work queue. A job that must run cannot depend on a notification that may never arrive.\n\nThe pattern that works around it: treat the notification as a *hint* rather than a *guarantee*. The durable state lives in a table; the notification just says \"look now instead of waiting\". If a notification is missed, a slow fallback poll — every thirty seconds rather than every two — catches it eventually. You get low latency in the common case and correctness in the uncommon one.\n\n## Practical constraints\n\n**Send identifiers, not data.** Payloads have a size limit, and a large one will be rejected. Send a row ID and let the listener query for the current state. That also avoids stale payloads describing a row that changed again before the listener read it.\n\n**You need a dedicated connection.** `LISTEN` is session-scoped, so the connection must stay open and stay yours. Behind a transaction-mode pooler this simply does not work — the connection is not yours between transactions. Hold a connection outside the pool for listening, the same constraint that applies to session-scoped [advisory locks](/blog/advisory-locks-explained-2026).\n\n**Watch the notification queue.** Notifications are buffered until listeners consume them, and that buffer is finite. A slow listener, or a transaction that generates a large number of notifications, can fill it — and when it fills, things start failing in ways that are not obviously about notifications.\n\n**Do not notify per row in bulk operations.** A trigger firing on a million-row update generates a million notifications. Notify once per statement, or notify about the batch.\n\n## Common mistakes\n\n- **Treating it as a durable queue.** Missed notifications are gone.\n- **No fallback poll.** A reconnect gap becomes silently lost work.\n- **Large payloads.** Rejected at the size limit, often discovered in production.\n- **Listening through a transaction pooler.** Quietly broken.\n- **Per-row triggers on bulk updates.** Floods the queue.\n- **Assuming all listeners are load-balanced.** Every listener on a channel receives every message; there is no consumer group semantics.\n- **No reconnect logic.** Connections drop; a listener must reconnect and re-issue `LISTEN`.\n\n## FAQ\n\n**Can I use it for real-time features?**\nFor pushing a \"something changed, refresh\" signal to a backend that then updates clients, yes, and it is a good fit. Do not connect browsers to your database — the notification should reach your application, which decides what to send to users.\n\n**How does it compare to polling a table?**\nLower latency and far less load in the common case. The honest comparison keeps a slow poll as a safety net, so you end up with both — which is still dramatically better than a fast poll alone.\n\n**What about change data capture instead?**\nCDC reads the write-ahead log and gives durable, ordered, replayable events. That is a stronger guarantee and more infrastructure. Use CDC when you need reliability; use notify when you need a cheap wake-up — see [write-ahead logging](/blog/write-ahead-logging-explained-2026).\n\n**Does it work with read replicas?**\nNotifications do not propagate to replicas in the usual configuration. Listeners need to connect to the instance where the notification is issued, which is a real constraint if your read traffic is on replicas — see [read replicas explained](/blog/read-replicas-explained-2026).\n\n## Where to go next\n\nFor the connection constraint this shares, read [advisory locks](/blog/advisory-locks-explained-2026) and [connection pooling explained](/blog/connection-pooling-explained-2026). For the durable alternative when notifications must not be missed, [write-ahead logging](/blog/write-ahead-logging-explained-2026).","hue":313,"metaTitle":"LISTEN and NOTIFY: Push Without a Message Broker","metaDescription":"How database LISTEN and NOTIFY replaces polling with push notifications, why it is not a message queue, and the connection pooling constraints that break it.","keywords":["listen notify","postgres notify","database push notification","replace polling","event notification database","lightweight pubsub","cache invalidation notify","realtime updates 2026"],"tldr":["**Notifications are fire-and-forget** — a listener that is disconnected when one fires never receives it, and nothing retries.","**They are transactional** — a notification sent inside a transaction that rolls back is never delivered.","**Payloads are small** — send an identifier and let the listener fetch, rather than the data itself.","**Connection pooling breaks it** — LISTEN is session-scoped, so you need a dedicated connection.","**Skip:** using it as a message queue. No persistence, no retries, no acknowledgement, no ordering guarantees across channels."],"readTime":"5 min read","wordCount":1013},"related":[{"slug":"webhooks-vs-polling-2026","title":"Webhooks vs polling in 2026: which push model wins?","excerpt":"Choosing between webhooks and polling shapes your app's latency, cost, and complexity. Here is the honest 2026 tradeoff guide with real numbers.","category":"code","date":"March 3, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":307},{"slug":"server-sent-events-explained-2026","title":"Server-Sent Events in 2026: A Simpler Real-Time Option","excerpt":"Server-Sent Events push updates to the browser over one plain HTTP response, with automatic reconnection built in. This guide explains how SSE works and when it beats WebSockets for simplicity.","category":"code","date":"August 9, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":308},{"slug":"sqlite-vector-search-2026","title":"SQLite Vector Search: Embeddings Without a Vector DB","excerpt":"SQLite can store and search embeddings with an extension, no separate service required. Here is where that is genuinely enough and where it stops scaling.","category":"code","date":"August 18, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":158}],"sameCategory":[{"slug":"bazel-build-explained-2026","title":"Bazel Explained: What Hermetic Builds Actually Cost","excerpt":"Bazel delivers reproducible, precisely cached builds and demands that you describe everything. Here is what you get, what it costs, and who should say no.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":145},{"slug":"graphql-federation-2026","title":"GraphQL Federation: One Graph, Many Teams","excerpt":"Federation composes independently-owned subgraphs into a single API. Here is what it solves, the operational cost it adds, and when a simpler gateway is enough.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":300},{"slug":"hermetic-builds-explained-2026","title":"Hermetic Builds Explained: Same Inputs, Same Output","excerpt":"A hermetic build depends only on declared inputs. Here is what that property gives you, the common leaks that break it, and how to move toward it incrementally.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":130},{"slug":"monorepo-tooling-compared-2026","title":"Monorepo Tooling Compared: What You Actually Need","excerpt":"Monorepo tools solve task orchestration and caching, not repository layout. Here is what each tier provides and how to tell when you have outgrown the simple option.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":150}],"newer":{"slug":"home-network-segmentation-2026","title":"Home Network Segmentation: Keeping the Smart Bulbs Away","excerpt":"A cheap camera on the same network as your laptop can reach it. Separating devices takes an evening and limits what one compromised gadget can touch.","category":"tech","date":"August 14, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":240},"older":{"slug":"llm-guardrail-testing-2026","title":"LLM Guardrail Testing: Proving the Limits Actually Hold","excerpt":"A guardrail you have not attacked is a guardrail you are guessing about. Here is how to build an adversarial test suite and what to measure beyond pass rate.","category":"ai","date":"August 14, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":219}}