Tune Memory Settings
Learn how to tune PostgreSQL memory settings to boost performance. This lesson covers key parameters, practical configuration steps, and troubleshooting tips for better database efficiency.
Focus: tune memory settings for performance
You’ve been running PostgreSQL for a while, and performance is starting to crawl. Queries that used to return in milliseconds now take seconds, and you suspect it’s not the SQL itself—it’s the memory settings. PostgreSQL’s default configuration is deliberately conservative, designed to run on a laptop, not to squeeze every bit of performance from your production server. In this lesson, you’ll learn how to safely and effectively tune memory settings for performance, turning your database from a sluggish default into a tuned machine.
The problem this lesson solves
Default PostgreSQL settings are like a car with the parking brake on. The shared_buffers default is a meager 128MB, work_mem is just 4MB, and effective_cache_size is a guess that’s often too low. When your database grows and workloads intensify, these defaults cause:
- Excessive disk I/O: Every query that doesn’t fit in cache has to hit the disk, which is orders of magnitude slower than memory.
- Slow sorting and joining: Operations like
ORDER BYorJOINspill to disk whenwork_memis too small. - Poor index scans: The planner underestimates available cache, leading to inefficient index usage.
- Cache thrashing: Multiple backends contend for the same small shared buffer pool, causing frequent evictions.
If you’re seeing high disk read rates or slow queries despite good indexes, your memory settings are likely the bottleneck. This lesson gives you a practical, methodical approach to tuning the three most impactful parameters: shared_buffers, work_mem, and effective_cache_size.
Core concept / mental model
Think of PostgreSQL’s memory as a multi-layered cache system, like a kitchen with different storage areas:
- Shared buffers are the pantry—a common stash all backends (queries) can access, holding recently used data pages.
- Work memory is the chef’s prep counter—a private workspace for sorting and joining, allocated per operation.
- Effective cache size is the estimate of the entire kitchen’s storage, including the pantry and the refrigerator (OS cache).
(In words: shared_buffers → OS cache → disk)
When you tune memory settings for performance, you’re deciding how much of the meal (data) you want on-hand in each layer. Too little, and you’re constantly running to the store (disk). Too much, and you’re blocking the kitchen with bulky items that rarely get used.
The operating system also caches file reads, so PostgreSQL’s shared_buffers and the OS cache work together. This is why effective_cache_size matters: it tells the planner how much total cache is available for index scans.
How it works step by step
Tuning memory isn’t about setting one value—it’s a sequence of informed decisions. Here’s a step-by-step approach:
- Check your current settings with
SHOWorpg_settings. Know your baseline. - Set
shared_buffersto about 25% of your total server RAM (on dedicated DB servers), capped at safe upper limits. - Set
work_membased on expected operation complexity—bigger for complex queries, but beware of high concurrency. - Set
effective_cache_sizeto estimate OS cache plus shared_buffers, usually 50-75% of total RAM. - Adjust
maintenance_work_memfor vacuum and index maintenance operations—higher is better during maintenance windows. - Apply changes and test using
EXPLAIN (ANALYZE, BUFFERS)to observe buffer usage and query plans. - Iterate—monitor performance, watch for problems, and fine-tune values.
Important parameter details
shared_buffers: The main cache for data pages. Set to 25% of RAM. On Linux, values above 32GB require a restart and special shared memory settings. In a container, it must be lower than the container’s memory limit.work_mem: Per-operation memory for sorts and joins. Starts at 4MB; increase for warehouses or complex reports, but consider the product ofwork_mem× concurrent sorts.effective_cache_size: A planner hint, not allocated memory. Set to 50-75% of RAM to encourage index scans.maintenance_work_mem: Memory forVACUUM,CREATE INDEX, etc. Set to 50-100MB for small DBs, up to 1GB for large ones. Applies to autovacuum as well.
Pro tip: Always reason in terms of total RAM and workload. A tiny VM with 1GB RAM shouldn’t use 4GB settings—you’ll OOM or cripple the OS.
Hands-on walkthrough
Let’s put this into practice on a server with 8GB of RAM. We’ll start by checking current settings, then change them and observe the impact.
1. View current settings
Run this as a superuser:
SHOW shared_buffers;
SHOW work_mem;
SHOW effective_cache_size;
SHOW maintenance_work_mem;
Example output:
shared_buffers | 128MB
work_mem | 4MB
effective_cache_size | 524288
maintenance_work_mem | 64MB
Note that effective_cache_size is displayed in pages (8KB each by default). 524288 pages = 4GB.
2. Set recommended values
Use ALTER SYSTEM to persist changes, then reload:
ALTER SYSTEM SET shared_buffers = '2GB'; -- 25% of 8GB
ALTER SYSTEM SET work_mem = '32MB'; -- 8× default for complex queries
ALTER SYSTEM SET effective_cache_size = '6GB'; -- 75% of RAM
ALTER SYSTEM SET maintenance_work_mem = '256MB'; -- for larger maintenance
SELECT pg_reload_conf();
3. Verify and observe the plan
Restart if the change requires it (shared_buffers on some platforms). Then, run an EXPLAIN on a heavy query:
EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM orders WHERE customer_id = 123 ORDER BY created_at;
Before and after tuning, notice the Buffers: line—it shows shared hit vs shared read. Fewer reads and more hits mean better cache usage.
4. Test with a realistic workload
If you have pgbench installed, run a quick benchmark:
pgbench -i -s 20 mydb
time pgbench -c 8 -j 8 -T 60 mydb
Run it before and after tuning. When you tune memory settings for performance, you should see higher transactions per second (tps) and lower latency.
Compare options / when to choose what
Not every setting suits every scenario. Here’s a comparison to guide your choices:
| Parameter | Low value (e.g., default) | High value (e.g., 25-75% RAM) | When to prefer low | When to prefer high |
|---|---|---|---|---|
shared_buffers |
128MB | 2-8GB+ | Small RAM, low concurrency | Heavy OLTP, large datasets |
work_mem |
4MB | 32-256MB | Many concurrent connections | Complex sorts/joins, few connections |
effective_cache_size |
512MB | 50-75% RAM | OS cache is small | OS cache is large, mostly reads |
maintenance_work_mem |
64MB | 256MB-1GB | Frequent vacuums, small DB | Large indexes, infrequent maintenance |
Key trade-off: Increasing work_mem per operation can multiply quickly. With 100 concurrent queries, even 32MB each means 3.2GB of memory pressure. Balance is critical.
Troubleshooting & edge cases
- PostgreSQL won’t start after increasing
shared_buffers: On Linux, you may need to raise kernelshmmax/shmall. Runsysctl -w kernel.shmmax=<bytes>and add to/etc/sysctl.conf. In Docker, increase the container’s memory limit. - Out of memory (OOM): If you see “out of memory” errors, your
work_memis too high relative to connection count. Reduce it or tune connection limits. Usemax_connections×work_memas a worst-case estimate. - No performance improvement: Check if the database is actually hitting memory limits. Use
pg_stat_database(seeblks_readvsblks_hit) andEXPLAIN (BUFFERS). Maybe your workload is I/O-bound elsewhere (e.g., disk slow, not cache miss). - Autovacuum runs too often or too slow:
maintenance_work_memaffects vacuum speed and dead-tuple cleanup. If it’s too low, vacuum takes longer and can lag behind workload.
Pro tip: Always benchmark before and after using a realistic workload. Don’t guess—measure the changes.
What you learned & what's next
You now know how to tune memory settings for performance by adjusting shared_buffers, work_mem, effective_cache_size, and maintenance_work_mem. You can check current values, set them via ALTER SYSTEM, and observe the effect with EXPLAIN (BUFFERS). You understand the trade-offs between memory per operation and concurrency, and you know how to troubleshoot common issues like OOM and startup failures.
Your next step is to learn about checkpoint tuning or max_connections, which build on this memory foundation. With memory tuned, you’ll want to look at query parallelization to further cut latency. Keep those metrics in mind as you move forward—consistent performance demands both correct settings and ongoing monitoring.
Practice recap
As a quick exercise, pick one of your slow queries and run EXPLAIN (ANALYZE, BUFFERS). Check the Buffers: output for high shared read counts, then tune shared_buffers and work_mem as described. Re-run the query and note the drop in reads and improved execution time — you've just tuned memory settings for performance.
Common mistakes
- Setting shared_buffers too high (e.g., >50% of RAM) can starve the OS of cache and cause double caching, actually hurting performance.
- Assuming work_mem is a global pool—it’s per operation, and high values under heavy concurrency can lead to memory exhaustion and OOM.
- Changing shared_buffers without restarting the server on platforms that require it, leading to the new value not taking effect.
- Ignoring effective_cache_size because it’s just a planner hint—if it’s too low, the planner may skip index scans and choose slower sequential scans.
- Applying the same settings to every database regardless of RAM or workload, causing under- or over-allocation.
Variations
- Use
pgbounceror connection pooling to reduce concurrent backends, allowing you to safely increasework_memper query. - For workloads with mixed large analytical queries and small OLTP, use
SET work_memper session or transaction to tailor memory usage. - Consider
autovacuum_work_memseparately frommaintenance_work_memto give VACUUM its own memory segment without starving maintenance operations.
Real-world use cases
- Tune memory in an e-commerce database to handle Black Friday traffic spikes without adding hardware.
- Speed up nightly report generation in a data warehouse by increasing
work_memfor complex joins and sorts. - Reduce disk I/O and render times on a web app’s user-facing queries by enlarging
shared_buffersandeffective_cache_size.
Key takeaways
- Default PostgreSQL memory settings are conservative; tuning can dramatically improve performance.
- The three main parameters are
shared_buffers,work_mem, andeffective_cache_size. - Set
shared_buffersto about 25% of RAM,effective_cache_sizeto 50-75%, and adjustwork_membased on query complexity and concurrency. - Always use
EXPLAIN (ANALYZE, BUFFERS)to measure the actual effect of your changes. - Monitor and test after each change; tuning is an iterative, workload-specific process.
- Balance
work_memagainst the number of concurrent connections to avoid OOM.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.