Increasing work_mem and shared_buffers on Postgres 9.2 significantly slows down queries I have a PostgreSQL 9.2 instance running on RHEL 6.3, 8-core machine with 16GB of RAM. The server is dedicated to this database. Given that the default postgresql.conf is quite conservative regarding memory settings, I thought it might be a good idea to allow Postgres to use more memory. To my surprise, following advice on wiki.postgresql.org/wiki/Tuning_Your_PostgreSQL_Server significantly slowed down practically every query I run but it's obviously more noticeable on the more complex queries.

I also tried running pgtune which gave the following recommendation with more parameters tuned, but that didn't change anything. It suggests shared_buffers of 1/4 of RAM size which seems to in line with advice elsewhere (and on PG wiki in particular).

default_statistics_target = 50
maintenance_work_mem = 960MB
constraint_exclusion = on
checkpoint_completion_target = 0.9
effective_cache_size = 11GB
work_mem = 96MB
wal_buffers = 8MB
checkpoint_segments = 16
shared_buffers = 3840MB
max_connections = 80


I tried reindexing the whole database after changing the settings (using reindex database), but that didn't help either. I played around with shared_buffers and work_mem. Gradually changing them from the very conservative default values (128k / 1MB) gradually decreased performance.

I ran EXPLAIN (ANALYZE,BUFFERS) on a few queries and the culprit seems to be that Hash Join is significantly slower. It's not clear to me why.

To give some specific example, I have the following query. It runs in ~2100ms on the default configuration and ~3300ms on the configuration with increased buffer sizes:

select count(*) from contest c
left outer join contestparticipant cp on c.id=cp.contestId
left outer join teammember tm on tm.contestparticipantid=cp.id
left outer join staffmember sm on cp.id=sm.contestparticipantid
left outer join person p on p.id=cp.personid
left outer join personinfo pi on pi.id=cp.personinfoid
where pi.lastname like '%b%' or pi.firstname like '%a%';


EXPLAIN (ANALYZE,BUFFERS) for the query above:


Default buffers: http://explain.depesz.com/s/xaHJ
Bigger buffers: http://explain.depesz.com/s/Plk


The question is why am I observing decreased performance when I increase buffer sizes? The machine is definitely not running out of memory. Allocation if shared memory in OS is (shmmax and shmall) is set to very large values, that should not be a problem. I'm not getting any errors in the Postgres log either. I'm running autovacuum in the default configuration but I don't expect that has anything to do with it. All queries were run on the same machine few seconds apart, just with changed configuration (and restarted PG).

Edit:
I just found one particularly interesting fact: when I perform the same test on my mid-2010 iMac (OSX 10.7.5) also with Postgres 9.2.1 and 16GB RAM, I don't experience the slow down.
Specifically:

set work_mem='1MB';
select ...; // running time is ~1800 ms
set work_mem='96MB';
select ...' // running time is ~1500 ms


When I do exactly the same query (the one above) with exactly the same data on the server I get 2100 ms with work_mem=1MB and 3200 ms with 96 MB.

The Mac has SSD so it's understandably faster, but it exhibits a behavior I would expect. 

See also the follow-up discussion on pgsql-performance.
First of all, keep in mind that work_mem is per operation and so it can get excessive pretty quickly.  In general if you are not having trouble with sorts being slow I would leave work_mem alone until you need it.

Looking at your query plans, one thing that strikes me is that the buffer hits are very different looking at the two plans, and that even the sequential scans are slower.  I suspect that the issue has to do with read-ahead caching and having less space for that.  What this means is you are biasing memory for re-use of indexes and against reading tables on disk.



My understanding is that PostgreSQL will look to the cache for a page before reading it from disk because it doesn't know really whether the OS cache will contain that page.  Because the pages are then staying in the cache and because that cache is slower than the OS cache, this changes the sorts of queries which are fast vs the sorts that are slow.  In fact reading the plans, aside from work_mem issues, it looks like all of your query info comes from the cache but it is a question of which cache.

work_mem: how much memory we can allocate for a sort or related join operation.  This is per operation, not per statement or per back-end, so a single complex query can use many times this amount of memory.  It isn't clear you are hitting this limit but it is worth noting and being aware of.  if you increase this too far, you lose memory that might be available for the read cache and the shared buffers.

shared_buffers: how much memory to allocate to the actual PostgreSQL page queue.  Now, ideally the interesting set of your database will stay in memory cached here and in the read buffers.  However, what this does is ensure that the most frequently used information across all backends gets cached and not flushed to disk.  On Linux this cache is significantly slower than the OS disk cache, but it offers guarantees that the OS disk cache dos not and is transparent to PostgreSQL.  This is pretty clearly where your problem is.

So what happens is that when we have a request, we check the shared buffers first since PostgreSQL has deep knowledge of this cache, and look for the pages.  If they are not there we ask the OS to open them from the file, and if the OS has cached the result it returns the cached copy (this is faster than the shared buffers, but Pg can't tell whether it is cached or on disk, and disk is much slower so PostgreSQL typically will not take that chance).  Keep in mind this affects random vs sequential page access as well.  So you may get better performance with lower shared_buffers settings.

My gut sense is that you probably get better, or at least more consistent, performance in high concurrency environments with larger shared_buffer settings.  Also keep in mind that PostgreSQL grabs this memory and holds it so if you have other things running on the system, the read buffers will hold files read by other processes.  It's a very large and complex topic.   Larger shared buffer settings provide better guarantees of performance but may deliver less performance in some cases.