From 24e8480b8d830e7cfe1cbda5fb55925b24ba9395 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 25 Jul 2026 06:37:10 +0000 Subject: [PATCH] doc: document LISTEN/NOTIFY commit-time serialization A transaction that has executed NOTIFY takes a cluster-wide heavyweight lock during commit (PreCommit_Notify, async.c), held until after the commit completes. The serialized region therefore includes the WAL flush and, where configured, the synchronous replication wait, so notifying transactions cannot group-commit with one another and their aggregate rate is bounded by roughly one commit round trip at a time regardless of client concurrency. None of this was documented, and users have had to read async.c to discover it. Add a Performance Considerations section to the NOTIFY reference page covering the serialization and its scope, that the cost is per notifying transaction rather than per notification (so batching helps and NOTIFY in a row-level trigger is an anti-pattern), how to diagnose the contention, and when to prefer a queue table or logical decoding instead. Note that log_lock_waits only reports waits exceeding deadlock_timeout, whose one-second default is far longer than a typical wait for this lock, so the diagnosis guidance leads with pg_stat_activity. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01M3RLizCyeXBBFJdBHGDvKW --- doc/src/sgml/ref/notify.sgml | 67 ++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/doc/src/sgml/ref/notify.sgml b/doc/src/sgml/ref/notify.sgml index fd6ed54e8f9d8..484868d556b63 100644 --- a/doc/src/sgml/ref/notify.sgml +++ b/doc/src/sgml/ref/notify.sgml @@ -175,6 +175,73 @@ NOTIFY channel [ , + + Performance Considerations + + + So that notifications are queued in commit order, a transaction that has + executed NOTIFY takes a heavyweight lock during commit + that serializes it against every other committing transaction that has + executed NOTIFY. This lock is cluster-wide, not + per-database: notifying transactions running in different databases of the + same cluster contend with one another. Transactions that have not executed + NOTIFY do not take this lock, and + LISTEN and UNLISTEN do not take it + either. + + + + The lock is held until the notifying transaction's commit is complete, + which includes flushing the transaction's WAL records to + disk and, if synchronous replication is in use, waiting for the standby + server(s) to respond. Notifying transactions therefore cannot be + group-committed with one another: each one pays for a commit round trip on + its own, and the aggregate rate of notifying transactions is limited to + roughly one such round trip at a time, no matter how many client sessions + are active. The effect is correspondingly larger when commits are + expensive, that is with set to + on, with + configured, or on storage with high write latency. + + + + The cost is incurred once per notifying transaction, not once per + notification, since a transaction that sends many notifications takes the + lock only once. Applications that need a high notification rate should + therefore accumulate notifications and send them from as few transactions + as possible. In particular, avoid executing NOTIFY in a + row-level trigger; collect the notifications in a statement-level trigger + instead, or send a single notification per transaction and let listeners + determine what changed. + + + + To diagnose this contention, look for sessions with + wait_event_type Lock and + wait_event object in + pg_stat_activity. + Reporting in the server log via is also + possible, but note that it only reports waits exceeding + , whose default of one second is + typically far longer than an individual wait for this lock, so that setting + must usually be reduced before any such wait is logged. The wait is + reported as AccessExclusiveLock on object 0 of class 1262 of + database 0. A characteristic symptom of this contention is that + transaction throughput falls while CPU and disk + utilization fall as well, because the waiting sessions are idle rather than + busy. + + + + If the required notification rate cannot be reduced by batching, consider + another mechanism: a queue table that consumers poll using + SELECT ... FOR UPDATE SKIP LOCKED (see + ) does not serialize commits, and + is a better fit when the goal is to + stream data changes to an external consumer. + + + pg_notify