shared_buffers is Postgres’s primary memory cache, and getting it right can be the single biggest performance win, but most people get it wrong by making it too small.
Let’s watch it in action. Imagine you have a PostgreSQL database with a table users that’s 100MB.
-- Create a dummy table
CREATE TABLE users (
id serial PRIMARY KEY,
name varchar(255),
email varchar(255)
);
-- Populate it to ~100MB
INSERT INTO users (name, email)
SELECT 'User ' || i, 'user' || i || '@example.com'
FROM generate_series(1, 5000000) AS i;
-- Check the size
SELECT pg_size_pretty(pg_total_relation_size('users'));
-- Output will be around 100MB
Now, let’s say your shared_buffers is set to a default of 128MB.
-- Check current setting
SHOW shared_buffers;
-- Output: 128MB
When you query this table, PostgreSQL will try to load as much of it as possible into shared_buffers.
-- First query might be slow
SELECT * FROM users WHERE id = 1;
The first time you read data from users, it’s on disk. PostgreSQL reads it from disk and places it into shared_buffers. Subsequent reads of the same data will be lightning fast because they come directly from RAM.
If shared_buffers is too small, say 128MB for our 100MB users table, PostgreSQL can only hold a portion of it. When you access different parts of the users table, PostgreSQL might have to evict older data from shared_buffers to make room for new data, leading to more disk reads.
The problem PostgreSQL solves is that disk I/O is orders of magnitude slower than memory access. By caching frequently used data in RAM, PostgreSQL dramatically reduces the need to go to disk. shared_buffers is the dedicated area in RAM for this caching. It’s shared by all backend processes, hence the name.
The core levers you control are the size of shared_buffers and how PostgreSQL manages its replacement policy (which blocks to keep and which to evict). The default replacement policy is a variant of LRU (Least Recently Used), which is generally effective. The key is simply giving it enough space.
Here’s how you tune it:
- Determine your RAM: Know how much total RAM your database server has.
- Allocate for OS and other processes: Don’t give all RAM to PostgreSQL. The OS needs RAM, and other PostgreSQL components (like
work_mem, WAL buffers, connection overhead) also consume memory. A common starting point is to dedicate 25% of total RAM toshared_buffersif you have ample RAM (e.g., 64GB+), or up to 40% if RAM is more constrained or if your workload is heavily read-bound. - Set
shared_buffers: Edit yourpostgresql.conffile. For a server with 64GB of RAM, you might set:
If you have 32GB, you might try:shared_buffers = 16GB
Or even:shared_buffers = 8GB
The value must be a multiple ofshared_buffers = 12GBwal_segment_size(typically 16MB) or 1MB ifwal_segment_sizeis not set. A common mistake is setting it to a non-standard value that Postgres rejects. - Restart PostgreSQL: This is a "hot" parameter, but for
shared_buffers, it requires a full restart to allocate the memory.sudo systemctl restart postgresql - Monitor: Observe cache hit ratios and query performance. A good hit ratio (above 95%) indicates
shared_buffersis effectively caching data. You can check this with:SELECT sum(blks_hit) AS hits, sum(blks_read) AS reads, sum(blks_hit) / (sum(blks_hit) + sum(blks_read)) AS hit_ratio FROM pg_stat_database WHERE datname = current_database();
The most impactful shared_buffers size is often the one that can hold your entire working set of data. If your most frequently accessed tables and indexes fit entirely within shared_buffers, you’ll see a dramatic performance improvement because almost all reads will be cache hits. Many systems with 64GB or more RAM can afford to set shared_buffers to 16GB, 32GB, or even more, which is far beyond the typical 128MB or 256MB defaults.
After tuning shared_buffers, you’ll likely start thinking about how PostgreSQL manages writes to disk, which leads to tuning wal_buffers and max_wal_size.