PostgreSQL Memory Tuning in Managed Databases: A Practical Deep Dive
A practical guide to PostgreSQL memory tuning for managed databases, covering work_mem, shared buffers, temp files, connection pools, and safe diagnosis.
ArmorDB Engineering
ArmorDB engineering
On this page 8 sections
PostgreSQL memory problems rarely announce themselves as memory problems. A request gets slower because a sort spills to disk. A reporting query makes temporary files. A deploy adds more workers and suddenly a healthy database is close to its limit. Someone raises work_mem because one query improved in staging, then production becomes less predictable because many sessions can use that memory at once.
The practical problem is that PostgreSQL memory is not one bucket. There is shared memory used by the server, per-backend memory used by connections, and per-operation memory used by sorts, hashes, and maintenance tasks. In managed PostgreSQL, you may not tune every server parameter directly, but you still need to read the symptoms correctly and choose whether the fix is a query change, an index, a pool-size change, a maintenance setting, or a larger plan.
Why memory tuning is mostly capacity management
PostgreSQL documentation describes shared_buffers as the amount of memory the database server uses for shared memory buffers. It also documents work_mem as the maximum memory used by a query operation before writing to temporary files, and notes that several operations may run at the same time in one query. That second detail is where many production surprises come from: work_mem is not a global allowance and it is not simply one allocation per connection.
A single analytical query can sort, hash, aggregate, and join in more than one plan node. If it runs in parallel, workers can multiply the effective memory demand. If the application has many database sessions because every web process owns a pool, the same setting can be safe at low concurrency and unsafe during a deploy or traffic burst.
Managed PostgreSQL changes the operating model, not the arithmetic. The provider may choose safe defaults and protect the host, while application teams still control query shape, connection fan-out, background jobs, reporting windows, and whether the workload creates large sorts in the first place.
| Memory area | What it affects | Common symptom | Safer first response |
|---|---|---|---|
| Shared buffers and OS cache | Repeated reads of tables and indexes | Important queries show heavy reads even after warmup | Check indexes, working set, and plan shape before only upgrading memory |
work_mem | Sorts, hashes, aggregates, merge operations | Temporary files, slow sorts, hash batches | Tune the query or session carefully; do not raise it globally as a reflex |
| Connection overhead | One backend process per server connection | Memory pressure rises after scaling app replicas | Reduce pool sizes and use PgBouncer where transaction pooling fits |
| Maintenance memory | Vacuum, index builds, and maintenance commands | Index builds or vacuum work runs slowly | Schedule maintenance and tune the operation, not the whole workload blindly |
| Temporary files | Disk spill from operations that exceed memory | Latency spikes and storage I/O during reports | Add targeted indexes, reduce rows earlier, or move reports off peak |
The table is intentionally operational. Most teams do not need a perfect model of PostgreSQL internals to make a safer decision. They need to avoid turning one slow report into a cluster-wide memory setting that changes the risk profile for every request.
Read temporary files as a symptom, not a verdict
Temporary files are one of the clearest signs that a query needed more working memory than the active plan node could use. PostgreSQL can log temporary files through log_temp_files, and EXPLAIN (ANALYZE, BUFFERS) can show sorting methods, disk usage, and buffer behavior when you run a query in a safe environment. Those are strong diagnostic tools, but they still require interpretation.
A temp file is not always bad. A monthly export that sorts millions of rows may reasonably spill if it runs off peak and does not affect the product. A checkout query that spills under normal traffic is a different problem. The important question is whether the spill is expected for the workload, whether it competes with user-facing I/O, and whether the result set can be narrowed earlier.
Before increasing memory, inspect the plan. If a query sorts a huge intermediate result and then returns twenty rows, an index or predicate change may remove the spill entirely. If a hash join spills because statistics are stale and PostgreSQL underestimated row counts, running ANALYZE or improving statistics may be safer than raising work_mem. If a reporting job legitimately needs memory, set a larger value for that job's session or role rather than making every web request eligible to consume the same amount.
The work_mem trap
The common mistake is to treat work_mem like a RAM slider for the whole database. It is more precise and more dangerous than that. PostgreSQL can apply it to each sort or hash operation, and multiple operations can appear in one query. Concurrent sessions multiply the demand again. A setting that looks small in isolation can become large when a fleet of workers runs the same endpoint at once.
A safer pattern is to keep the global setting conservative and use scoped changes for known exceptional work. For example, an administrative export can run with SET LOCAL work_mem = '128MB' inside a transaction if the role is trusted, the job is serialized, and the runbook documents why that value is safe. The web application should not inherit that setting just because one report benefits from it.
Connection pooling affects the calculation too. If each application instance opens twenty server connections and an autoscaler grows to ten instances, the database may see two hundred sessions before any BI tool, migration, or worker is counted. PgBouncer can reduce server-side connection pressure for short transactions, but it does not make a memory-heavy query cheap. Pooling controls how many sessions can run; query design controls how expensive those sessions are.
Practical diagnosis during an incident
When memory pressure or disk spills show up in production, start by separating workload classes. User-facing OLTP queries, background jobs, migrations, and reporting traffic should not all be diagnosed as one blob. A report that scans a large table may be acceptable if it runs against a replica or during a quiet window. The same plan in a request path needs immediate attention.
Look at active sessions and wait events, then identify the statements producing the largest temporary files or longest runtimes. If the managed platform exposes query insights, use that first. Otherwise, PostgreSQL views such as pg_stat_activity and plan analysis in a staging restore are often enough to identify whether the issue is a long sort, a missing index, stale statistics, too many concurrent workers, or a backfill that is competing with normal traffic.
Do not make three changes at once. If you increase memory, shrink pools, and add an index during the same incident, it becomes hard to know which change helped and which one created later risk. The fastest safe sequence is usually to reduce concurrency for the offending job, cancel or reschedule non-critical reporting, confirm the backup posture before invasive changes, then apply the narrowest query or index fix.
A managed PostgreSQL memory runbook
For a managed database, the runbook should map symptoms to owner-controlled actions. If the symptom is frequent temp files from one endpoint, the application team owns plan review and indexing. If the symptom is memory pressure after deployments, the deployment and pool settings need a connection budget. If maintenance consumes too much I/O, schedule it deliberately and check whether the provider exposes per-operation settings or recommends a larger tier.
Use staging restores for evidence. A tiny development database will not reproduce memory behavior because the planner, row counts, cache hit patterns, and sort sizes are different. A production-shaped restore lets you run EXPLAIN (ANALYZE, BUFFERS) without hurting users, compare index candidates, and verify whether a scoped memory setting changes the plan or simply hides an inefficient access path.
The runbook should include four numbers: expected application server connections, maximum background worker connections, known reporting or export jobs, and the plan limit where a larger ArmorDB tier or provider setting review becomes cheaper than further tuning. ArmorDB users should pair this with the PgBouncer documentation when connection fan-out is part of the problem and the pricing page when the workload clearly needs more memory headroom rather than better query shape.
Common mistakes to avoid
The first mistake is raising work_mem globally after seeing one disk sort. That can improve a single query while increasing peak memory risk across the cluster. Use a scoped setting or a query fix unless the whole workload has been reviewed.
The second mistake is assuming more connections mean more throughput. PostgreSQL can run many sessions, but a database under memory or I/O pressure often improves when excess work waits outside the server. Smaller pools and shorter transactions can be better than letting every request compete inside PostgreSQL.
The third mistake is testing as the wrong workload. A query run once in a console does not represent twenty web workers, a parallel plan, a background import, and an analytics dashboard arriving together. Test with realistic row counts and realistic concurrency before treating a tuning result as production evidence.
Takeaway
PostgreSQL memory tuning is useful when it is specific. Start with the workload and the symptom: temp files, cache misses, connection fan-out, or maintenance pressure. Inspect plans before changing global settings. Keep work_mem conservative for ordinary application traffic, use scoped settings for known heavy jobs, and control connection counts so memory demand stays predictable.
Managed PostgreSQL makes the server easier to operate, but it does not remove the need for workload discipline. The best memory posture combines targeted indexes, realistic staging tests, sensible pool sizes, and clear upgrade thresholds.
Sources and further reading
- PostgreSQL documentation on resource consumption, including
shared_buffers,work_mem, and maintenance memory: https://www.postgresql.org/docs/current/runtime-config-resource.html - PostgreSQL documentation on logging, including
log_temp_files: https://www.postgresql.org/docs/current/runtime-config-logging.html - PostgreSQL
EXPLAINdocumentation for reading plans withANALYZEandBUFFERS: https://www.postgresql.org/docs/current/sql-explain.html - PostgreSQL documentation on connection settings and
max_connections: https://www.postgresql.org/docs/current/runtime-config-connection.html
Written by ArmorDB Engineering
Practical notes on PostgreSQL operations, security, and infrastructure decisions for teams building production applications.
Updated Aug 24, 2026
Keep exploring
Related reading
Deep Dives · 9 min read
PostgreSQL Logical Replication Slots: A Managed Database Deep Dive
A practical guide to PostgreSQL logical replication slots, WAL retention, monitoring, failure modes, and safer managed database operations.
Read articleDeep Dives · 9 min read
PostgreSQL Autovacuum and Bloat in Managed Databases: A Practical Guide
Learn how PostgreSQL autovacuum controls dead rows and table bloat, which signals to watch, and how managed PostgreSQL teams can prevent maintenance surprises.
Read articleDeep Dives · 9 min read
PostgreSQL Connection Pool Sizing: A Practical Guide for Web Apps
Learn how to size PostgreSQL application pools and PgBouncer budgets without exhausting connections or hiding database bottlenecks.
Read article