Problem:
A Patroni-managed PostgreSQL cluster (PostgreSQL 15.8, Patroni 2.1.4) was reporting sustained high memory consumption on a host with ~275 GB of physical RAM and only ~8% free. The environment had HugePages enabled at the OS level. Patroni configuration showed shared_buffers=68GB, work_mem=20MB, max_parallel_workers_per_gather=4 and max_connections=2000. Observed behavior included consistently ~90% system memory utilization across a 15‑hour window, many database backends in an idle state, and no immediate signs of an OS-level memory leak in system logs. The client asked whether memory parameters could be adjusted to reduce RAM footprint.
Process:
Step 1: Confirmed scope and collected artifacts
Initial review verified the PostgreSQL and Patroni versions and collected the Patroni configuration, OS memory summary, and HugePages statistics provided by the client. These items established the deployment scale (shared_buffers 68GB, very high max_connections) and that the system had substantial HugePages configured. Collecting these artifacts set the baseline for quantitative sizing and further session-level investigation.
Step 2: Measured steady-state memory usage and trend
System logs and OS metrics covering ~15 hours were reviewed. Memory usage was steady at about 90% with no progressive growth pattern, indicating predictable pressure rather than a runaway leak. This mattered because a steady high utilization points to configuration and sizing rather than transient application behavior, directing the investigation toward PostgreSQL memory accounting and session allocation behavior.
Step 3: Performed PostgreSQL configuration analysis and back-of-envelope sizing
Configuration values were used to estimate worst-case memory demand: a fixed shared_buffers allocation plus per-connection work_mem allocations. Using the provided numbers produced an estimate on the order of hundreds of gigabytes (shared_buffers 68GB plus potential per-connection work_mem allocations with 2000 connections), which could exceed available RAM. That arithmetic demonstrated a realistic path to high memory consumption even without an active leak.
Step 4: Inspected session state and memory footprints
Queries against pg_stat_activity showed the majority of sessions were idle. A session-level memory-reporting script supplied by the expert revealed unexpectedly large resident memory contributions from individual backends despite idle states. This explained why overall memory stayed high: many idle backends were still holding process address space that mapped to sizable memory regions.
Step 5: Requested low-level process mappings for confirmation
To validate the per-backend footprint, process map and smaps output were requested for selected PostgreSQL backend PIDs. These artifacts were necessary because PostgreSQL per-process mappings (shared_buffers, backend-local allocations, large memory-mapped regions, and libraries) determine how much memory each connection effectively consumes and whether the OS can reclaim it.
Step 6: Identified configuration risk and decided mitigation path
Analysis converged on two root contributors: extremely large potential aggregate memory due to high max_connections combined with relatively generous work_mem, and many idle backends holding memory. This influenced the remediation choice: reduce the number of simultaneous server processes that can allocate memory and lower per-operation memory caps, plus introduce pooling to change client connection behavior. The next actions implemented those changes and validated results.
Solution:
PostgreSQL configuration was adjusted to reduce memory pressure: max_connections was reduced from 2000 to a controlled value aligned with application concurrency requirements, and work_mem was lowered from 20MB to a smaller per-operation value (sized based on observed query profiles). A connection pooler (transaction pooling mode) was deployed in front of the PostgreSQL instances to consolidate client connections into fewer server backends. The shared_buffers setting (68GB) was left unchanged because it was appropriate for the dataset and I/O pattern. Changes were applied via PostgreSQL configuration reloads and verified that backend process counts and resident memory fell.
Architecturally, the fix works because PostgreSQL allocates shared_buffers once and allocates work_mem per sort/hash operation per backend; reducing max_connections and work_mem reduces the maximum concurrent per-backend allocations and the total number of backends holding private memory. Connection pooling reduces idle backends by multiplexing many client connections over a smaller set of server processes.
Conclusion:
After the changes, overall memory usage dropped to a comfortable headroom, idle backend counts decreased, and no OS-level swapping or OOM conditions were observed. The environment now has predictable memory sizing tied to explicit connection and per-operation limits, and connection pooling prevents uncontrolled growth in server-side processes.