Increasing shared_buffers only helps when it keeps hot table and index pages in PostgreSQL without starving the operating system page cache. A value that is written but never restarted stays pending, while a value that is too large can push query workers, connections, and other services into memory pressure.

shared_buffers sets the size of PostgreSQL's shared buffer cache, a shared memory area used by all backend processes to cache data pages. ALTER SYSTEM writes the setting to postgresql.auto.conf, which is read in addition to postgresql.conf, so it is a cluster-wide change rather than a session setting.

Changes to shared_buffers apply only after a server restart because the shared memory segment is allocated at startup. A dedicated database server often starts near 25% of RAM, but values above roughly 40% rarely help because PostgreSQL still relies on the operating system cache; large increases may also require a higher max_wal_size so checkpoints do not become too frequent. Keep headroom for connections, work_mem, parallel queries, and maintenance tasks before choosing the final value.

Steps to tune shared_buffers in PostgreSQL:

  1. Check the current shared_buffers value.
    $ sudo -u postgres psql -Atc "SHOW shared_buffers"
    128MB
  2. Record the current buffer cache hit rate for a baseline.
    $ sudo -u postgres psql -c "SELECT datname, blks_hit, blks_read, round(blks_hit * 100.0 / nullif(blks_hit + blks_read, 0), 2) AS hit_pct FROM pg_stat_database WHERE datname = current_database()"
     datname  | blks_hit | blks_read | hit_pct
    ----------+----------+-----------+---------
     postgres |     4170 |       148 |   96.57
    (1 row)

    Use psql -d <db> to measure a specific database.

  3. Check the host memory size to budget shared_buffers.
    $ free -h
                   total        used        free      shared  buff/cache   available
    Mem:           7.7Gi       1.5Gi       1.7Gi        85Mi       4.7Gi       6.1Gi
    Swap:          1.1Gi       4.0Ki       1.1Gi
  4. Select a new shared_buffers value with headroom for OS page cache plus PostgreSQL per-query memory.

    A conservative starting point on Linux is around 25% of RAM, adjusted for workload, connection count, and the need to retain OS cache for sequential reads.

    Oversizing can trigger swapping or shared memory allocation errors during startup.

  5. Set the new value using ALTER SYSTEM.
    $ sudo -u postgres psql -c "ALTER SYSTEM SET shared_buffers = '512MB'"
    ALTER SYSTEM

    ALTER SYSTEM writes to postgresql.auto.conf in the data directory and requires superuser privileges.

    Rollback uses ALTER SYSTEM RESET shared_buffers plus a restart.

  6. Reload the configuration so PostgreSQL rereads postgresql.auto.conf and marks the startup-only change as pending.
    $ sudo -u postgres psql -Atc "SELECT pg_reload_conf()"
    t

    This reload does not activate shared_buffers. It only lets PostgreSQL detect that the changed value requires a restart.

  7. Confirm the setting is marked as pending restart.
    $ sudo -u postgres psql -Atc "SELECT pending_restart FROM pg_settings WHERE name = 'shared_buffers'"
    t
  8. Restart the PostgreSQL service to activate the new shared_buffers value.
    $ sudo systemctl restart postgresql

    Restarting terminates active connections and may extend downtime if crash recovery runs on startup.

    The unit name may be versioned on some distributions (for example postgresql@15-main or postgresql-15).

  9. Verify the active shared_buffers value after restart.
    $ sudo -u postgres psql -Atc "SHOW shared_buffers"
    512MB
  10. Check for swapping after the change.
    $ swapon --show
    NAME          TYPE SIZE USED PRIO
    /var/lib/swap file   4G   0B   -2

    Sustained swap activity usually indicates memory pressure and can negate the benefits of a larger shared_buffers value.

  11. Re-run the cache hit query after representative workload completes.
    $ sudo -u postgres psql -c "SELECT datname, blks_hit, blks_read, round(blks_hit * 100.0 / nullif(blks_hit + blks_read, 0), 2) AS hit_pct FROM pg_stat_database WHERE datname = current_database()"
     datname  | blks_hit | blks_read | hit_pct
    ----------+----------+-----------+---------
     postgres |     6399 |       248 |   96.27
    (1 row)

    Statistics counters reset on restart, so compare after enough traffic to represent normal load.