Context
Postgres container memory usage was elevated, while access and operations were effectively none. No active queries, no visible application traffic, no meaningful transaction rate, and no obvious maintenance task explaining the high consumption.
The first instinct is to look for a leak. But with databases is easy to confuse useful retained memory with harmful memory pressure. Postgres and the operating system both treat unused RAM as an opportunity to avoid future disk reads.
Troubleshooting
Check the configured memory settings
| |
Check what pg_stat_activity shows as long running operations
| |
Then list the sessions that could plausibly allocate memory
| |
Check memory contexts
| |
Check whether queries are spilling because memory is too small
| |
To see what is occupying shared_buffers, pg_buffercache is helpful:
| |
In container metrics, check what corresponds to a larger share of the total usage
100
* sum(container_memory_cache{namespace="<ns>", pod=~"<name>.*"}) by (pod, container)
/ sum(container_memory_usage_bytes{namespace="<ns>", pod=~"<name>.*"}) by (pod, container)
100
* sum(container_memory_rss_bytes{namespace="<ns>", pod=~"<name>.*"}) by (pod, container)
/ sum(container_memory_usage_bytes{namespace="<ns>", pod=~"<name>.*"}) by (pod, container)
How Postgres uses memory
Postgres has a few different memory categories, and they do not all behave like short-lived application heap.
shared_buffers is the most visible one. It is a shared cache managed by Postgres for table and index pages. Once pages are read and this cache is warmed, the memory is expected to stay useful. A quiet database does not mean Postgres should eagerly empty this cache.
Backend processes also keep memory for connection-local state. Settings such as work_mem and maintenance_work_mem describe how much memory certain operations may use, but they are not all fully reserved at startup. They become relevant when a sort, hash, vacuum, index build, or similar operation actually needs them.
There are also background processes: checkpointer, WAL writer, autovacuum, stats, and friends. Even when application traffic is absent, Postgres is not a single sleeping process.
Practical takeaway
For Postgres, high memory during idle time is not automatically a problem. It becomes a problem when it comes with low MemAvailable (sum(container_memory_available_bytes{namespace="<ns>", pod=~"<name>.*"}) by (pod, container)), cgroup pressure, swap activity, OOM events, continuously growing private RSS, or query behavior that allocates memory without returning to a stable baseline.