Skip to content

doc: document LISTEN/NOTIFY commit-time serialization#83

Draft
NikolayS wants to merge 1 commit into
masterfrom
claude/notify-global-lock-bottleneck-29bpn5
Draft

doc: document LISTEN/NOTIFY commit-time serialization#83
NikolayS wants to merge 1 commit into
masterfrom
claude/notify-global-lock-bottleneck-29bpn5

Conversation

@NikolayS

@NikolayS NikolayS commented Jul 25, 2026

Copy link
Copy Markdown
Owner

Single documentation commit, intended for upstream submission. Context and analysis: #82. The measurement harness that produced the supporting numbers is deliberately kept out of this PR — see #84.

What it adds

A Performance Considerations section on the NOTIFY reference page, covering:

  • A committing NOTIFY transaction takes a heavyweight lock that serializes it against every other committing NOTIFY transaction. The lock is cluster-wide, not per-database — notifiers in different databases of the same cluster contend. Transactions that did not execute NOTIFY, and LISTEN/UNLISTEN, do not take it.
  • It is held until commit completes, including the WAL flush and any synchronous-replication wait, so notifying transactions cannot group-commit and their aggregate rate is bounded by roughly one commit round trip at a time regardless of client concurrency. Larger effect with synchronous_commit=on, with synchronous_standby_names set, or on high-latency storage.
  • The cost is per notifying transaction, not per notification — so batch, and don't NOTIFY from a row-level trigger.
  • How to diagnose it, and when a queue table with SKIP LOCKED or logical decoding is the better tool.

Revisions vs. the July 2025 proposal

This is a corrected version of CAM527d8…, which was never committed. Two claims in it do not survive checking against the tree:

The blanket O(N²) duplicate-checking claim is dropped. Duplicate elimination has not been quadratic since PG13: AsyncExistsPendingNotify() probes a hash table once a list reaches MIN_HASHABLE_NOTIFIES (16), built by AddEventToPendingNotifies() — present in v18 and master. The subtransaction merge probes the parent list, which is hashed too, so it is effectively linear as well. This was the easiest part of the original patch to reject on accuracy.

The diagnosis guidance now leads with pg_stat_activity. proc.c:1631 gates log_lock_waits behind deadlock_state != DS_NOT_YET_CHECKED, set only once deadlock_timeout fires. At its 1 s default, waits for this lock — measured here at 1–70 ms — are never logged. The original advice did not work at default settings. The section now says so and tells the reader to reduce deadlock_timeout if they want server-log reporting.

Backpatchability

Restricted to behaviour true on all supported branches (14–18) and master. It deliberately says nothing about the v19 shared channel map or direct advancement (282b1cde), which are listener-side and version-specific. Note v13 reached EOL in Nov 2025.

It also deliberately avoids restating or sharpening the existing ordering guarantee at notify.sgml:104-107, so that it does not constrain any future change to the locking.

Validation

xmllint --noout well-formed with the file's entities declared; no tabs, no non-breaking spaces; all seven linkend targets resolve (guc-synchronous-commit, guc-synchronous-standby-names, guc-log-lock-waits, guc-deadlock-timeout, sql-select, logicaldecoding, monitoring-pg-stat-activity-view).

Full DTD validation has not been run — the DocBook 4.5 DTD is not installed in this environment and network fetch is blocked, so make -C doc/src/sgml check fails on the DTD rather than on this patch. It should be run before submission to -hackers.

🤖 Generated with Claude Code

https://claude.ai/code/session_01M3RLizCyeXBBFJdBHGDvKW

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 <[email protected]>
Claude-Session: https://claude.ai/code/session_01M3RLizCyeXBBFJdBHGDvKW
@NikolayS
NikolayS force-pushed the claude/notify-global-lock-bottleneck-29bpn5 branch from 57ef5a6 to 24e8480 Compare July 25, 2026 06:42
@NikolayS NikolayS changed the title LISTEN/NOTIFY commit lock: docs patch + measurement harness doc: document LISTEN/NOTIFY commit-time serialization Jul 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants