PostgreSQL 17’s New notify_buffers Setting: Much Ado About Performance, or a Hidden Gem for Heavy Queues?

DATABASE ADMINISTRATION — Among the headline features of major database releases, subtle configuration knobs often slip past casual observers. In PostgreSQL 17, developers quietly introduced seven newly configurable Simple Least-Recently-Used (SLRU) cache sizes, giving DBAs unprecedented control over internal memory subsystems.
One of these new parameters is notify_buffers, which controls the memory allocated for caching pages of the LISTEN/NOTIFY asynchronous messaging queue. According to the original comment embedded in the database engine’s async.c source code, this buffer size “can be varied without affecting anything but performance.”
Curious about how much performance could actually be gained or lost, database engineers recently put the parameter through rigorous stress tests. The findings may surprise production architects: for the vast majority of systems, the default setting is already optimal. However, under extreme, hyper-specialized workloads, tuning this parameter yields measurable—though modest—improvements.
Main Facts: Decoding notify_buffers
To understand notify_buffers, one must first understand what an SLRU cache does in PostgreSQL. SLRUs manage specialized, fixed-size memory structures outside of the main shared_buffers pool. In the case of notify_buffers, it caches pages belonging to the LISTEN/NOTIFY queue, the length of which is bounded by max_notify_queue_pages.
- The Defaults: The default size for
notify_buffersis16buffers (representing 128 kB, as each buffer counts 8 kB). This is also the absolute minimum value. - The Scale: Unlike
max_notify_queue_pages,notify_buffersaccepts standard memory units (e.g.,notify_buffers = '2MB'). It scales up to a maximum of131,072(1 GB). - Configuration Constraints: Values must be multiples of 16. Because the parameter context is strictly
postmaster, changing it requires a full database server restart. Furthermore, it does not scale automatically withshared_buffers. - Historical Context: In older versions of PostgreSQL (ranging from version 14 through 16), the size was hardcoded to 8 buffers and buried deep within a header file.
Under normal, healthy operating conditions, transactions that send notifications append them to the head of the queue upon committing. Listening backends maintain a read position, catching up to the head whenever a signal arrives. Because listeners rarely fall far behind on a well-provisioned system, those few pages at the head of the queue are all the cache ever needs to hold.
Chronology and Evolution of the Feature
The journey toward configurable SLRU caches spans several years of PostgreSQL development, culminating recently with critical bug fixes and architectural refinements.
Pre-PostgreSQL 17: The Hardcoded Era
For years, the size of the notify SLRU cache was static. Hardcoded to a minuscule 8 buffers in a header file, database administrators experiencing high-throughput messaging had no recourse to tune memory allocation for the LISTEN/NOTIFY subsystem without recompiling the source code.
PostgreSQL 17: Introduction of Configurability
With the release of PostgreSQL 17, developers decoupled seven internal SLRU caches from their static definitions, exposing them as user-configurable parameters. notify_buffers emerged as part of this sweeping overhaul, allowing administrators to scale notification memory up to 1 GB.
Early 2026: Minor Releases and the VACUUM Interaction
A crucial turning point for understanding cache misses occurred with the February 2026 minor releases (covering versions 18.2, 17.8, and related iterations). Prior to these patches, slow-moving listeners could trigger severe “could not access status of transaction” errors.
To resolve this, engineers modified VACUUM behavior: whenever a VACUUM operation advances a database’s datfrozenxid, it must now scan the entire notification queue before truncating pg_xact. This maintenance scan interacts directly with the notification cache, turning routine database hygiene into a potential source of massive cache thrashing if the queue is heavily backlogged.
PostgreSQL 19 Beta: Targeting Signaling Overheads
As testing continued into PostgreSQL 19 beta builds, developers addressed a separate, more pressing bottleneck regarding how notifications are broadcast. Prior to version 19, every notifying commit would signal every single listener in the database, regardless of channel subscription. PostgreSQL 19 beta 4 introduced targeted waking, dramatically reducing CPU overhead and shifting the performance spotlight away from mere buffer tuning.

Supporting Data: Benchmarks and Cache Behavior
To quantify the real-world impact of notify_buffers, benchmarks were conducted on a two-core virtual machine with synchronous_commit disabled (ensuring ratio integrity over absolute figures).
The Baseline: Healthy Queues
Running eight client connections sending 200-byte notifications as fast as possible (roughly 22,000 transactions per second) with an active listener yielded telling results:
blks_readin thenotifyrow ofpg_stat_slruremained locked at zero.- Throughput remained virtually identical whether
notify_bufferswas set to16,256, or1024(averaging between 21,800 and 22,300 tps).
Forcing Cache Misses
To force cache misses, a listener was intentionally parked inside an open transaction while 100,000 pages (approximately 781 MB, just under 10% of the default limit) were queued behind it. Tests were then run to measure the time required for a first-time LISTEN and a subsequent VACUUM (FREEZE):
notify_buffers Setting |
First LISTEN Duration |
VACUUM (FREEZE) Duration |
blks_read per Operation |
|---|---|---|---|
16 (128 kB) |
287 ms | 274 ms | 99,999 |
131,072 (1 GB) |
76 ms | 81 ms | 0 |
On an empty queue, the exact same VACUUM operation completed in 7 to 27 ms. Essentially, a cache miss cost roughly two microseconds more than a hit. Avoiding 100,000 cache misses saved about a fifth of a second—at the cost of maintaining a cache large enough to hold the entire backlog in memory.
The 10% Throughput Frontier
The only workload where tweaking notify_buffers demonstrably altered throughput involved payloads near the 8,000-byte limit, where every single commit completely filled a queue page.
- At the default
16buffers, eight clients averaged 13,553 tps. - At
32buffers, performance rose to 14,281 tps. - At
256buffers, it peaked at 14,906 tps.
Why did this happen? At 16 buffers, roughly half of all page writes were forced evictions executed by committing transactions while they held cluster-wide locks. At 256 buffers, those writes shifted entirely to the background trimming of the queue’s tail after locks were released.
Official Insights and Diagnostic Pitfalls
Database administrators monitoring their systems must be aware of how PostgreSQL reports SLRU statistics. A backend process only reports its SLRU counter metrics to pg_stat_slru when it executes a command or disconnects. Because reading notifications while sitting idle constitutes neither, a long-lived, idle listener can experience tens of thousands of cache misses without pg_stat_slru reflecting a single one—until the moment that session runs a trivial command like SELECT 1;.
Furthermore, empirical testing shows that when NOTIFY performance degrades, the SLRU cache is rarely the culprit. Far more often, notifying backends contend for cluster-wide locks (such as Lock:object), or waste CPU cycles signaling uninterested listeners across idle channels.
Implications for Production Environments
What does this mean for DBAs managing enterprise PostgreSQL deployments?
- Leave the Default Alone: For 99% of applications,
notify_buffers = 16is completely sufficient. Unless your application is pushing massive payloads on every single transaction—thousands of times per second—increasing this buffer pool yields negligible real-world benefits. - Focus on Listeners, Not Buffers: If notification processing slows down, do not waste time adjusting memory parameters. Instead, investigate lagging listeners, queue depth via
pg_notification_queue_usage(), and channel subscription architectures. - Understand Upgrade Benefits: Upgrading to newer PostgreSQL releases (such as version 19 with its intelligent channel-targeted listener wakes) will yield exponentially higher performance gains than adjusting SLRU memory allocations.
In summary, while PostgreSQL 17’s notify_buffers provides a fascinating window into the engine’s internal memory management, it remains a specialized tool for edge-case optimization rather than a silver bullet for database scaling.
