Problem:

A production Patroni-managed PostgreSQL 15.17 cluster (Patroni 3.3.2) reported persistent high memory consumption on database VMs, frequently exceeding 80% of allocated RAM and once reaching ~95% on a previous 300GB node. The deployment uses max_connections=4000 (cannot be reduced by the application), huge_pages configured as on with approximately 60,000 pages reserved, and vm.overcommit_memory=2 set per kernel hardening guidance. The operator added 100GB of OS-level memory after observing repeated pressure; this increase was not applied to PostgreSQL configuration parameters. Supporting artifacts supplied for analysis included /proc/meminfo, CPU info, Patroni config, pgbouncer.ini and full DB logs for a production day. No application-level change to connection count was possible; idle connections are used to absorb peak load.

Process:

Step 1: Memory and kernel state review

Observed the provided meminfo and kernel parameters showing vm.overcommit_memory=2 and huge_pages enabled with a large reserved count. The memory maps and free/used metrics indicated substantial resident usage and limited free RAM; huge_pages reservation reduced available normal pages. This mattered because strict overcommit plus large huge_pages reservations change how PostgreSQL allocates shared memory and process heaps, limiting dynamic allocation headroom and increasing risk of startup or runtime allocation failure.

Step 2: PostgreSQL and Patroni configuration inspection

Reviewed patroni.yml, postgres configuration dump and Patroni cluster settings. Shared_buffers, work_mem, maintenance_work_mem and max_worker_processes values were present but not sized against 4000 potential backends. The configuration showed a high number of allowed backends but per-connection memory parameters that, when multiplied by max_connections, created a theoretical worst-case memory footprint far exceeding the available RAM. This influenced the next step by focusing on per-connection versus shared allocations.

Step 3: Connection pattern and PgBouncer context

Analyzed user activities and pgbouncer.ini provided by the operator. The activity traces showed many idle sessions retained to handle peaks rather than constant active queries. PgBouncer was present in the stack but configured in a way that did not reduce server-side backend count enough for peak concurrency. This mattered because PostgreSQL process memory is largely per-backend; reducing server-side backends via transaction or statement pooling is the most effective lever when max_connections cannot be lowered.

Step 4: Log analysis for OOM/allocator signals

Reviewed the day-long database logs for allocation failures, server restarts or OOM killer entries. No consistent immediate crash trace was present, but repeated near-capacity periods and long GC/IO stalls were visible. The logs confirmed memory pressure events coincident with traffic peaks rather than a single buggy allocation path. This directed attention to capacity planning and pooling rather than to an internal PostgreSQL bug.

Step 5: Risk assessment of huge_pages and overcommit settings

Compared kernel huge_pages behavior with PostgreSQL requirements: with huge_pages=on and strict overcommit, the server risks failing to start if reservations are insufficient. The current huge_pages reservation provided a buffer but reduced flexible RAM for session heaps. This mattered because trade-offs exist between transparent hugepage performance gains and reduced allocation flexibility; the configuration choice was intentional but increases operational risk under unbounded backend growth.

Step 6: Synthesis and recommendation packaging

Correlated findings into a concise set of mitigations and sizing notes documented in a delivered configuration validation PDF. Also recorded the operator’s prior action (adding 100GB at OS level) as an implemented change that temporarily relieved pressure but did not resolve the fundamental capacity-versus-connections relationship. The final step introduced the proposed operational fixes and transitioned to the solution summary.

Solution:

Implemented change recorded: the operator increased VM memory by 100GB at the OS level prior to the engagement, which reduced immediate pressure on running sessions. The support deliverable consisted of a validated configuration report and prioritized recommendations for PostgreSQL, including: introduce a connection-pooling mode that reduces the number of PostgreSQL backends (use PgBouncer in transaction or statement pooling where application-compatible), reduce per-connection memory settings (lower work_mem and tune temp_buffers/maintenance_work_mem to realistic per-query needs), and document huge_pages reservations with headroom for peak backend allocations. These recommendations were provided as the actionable fix set.

Architectural rationale: reducing the number of active PostgreSQL backends (via pooling) and lowering per-backend memory caps reduces aggregate process-local memory consumption, preventing the system-wide memory envelope from being exceeded. Adjusting huge_pages and overcommit settings with explicit capacity planning preserves PostgreSQL shared memory behavior while avoiding allocation failures.

Conclusion:

Outcome: OS-level RAM increase immediately reduced memory pressure events; the configuration validation and recommendations give a path to sustainable stability without lowering max_connections. Expected operational improvements include lower risk of allocation failures under peak load, reduced swap and IO stalls, and more predictable memory headroom during traffic spikes once pooling and per-connection memory tuning are implemented.