Skip to content
EL
Contents

Buffer cache, I/O and memory

Two caches between you and the disk

ConceptAdvanced3 min read15 min hands-on#storage#query-planning

The idea in 30 seconds

PostgreSQL reads pages through its own cache, shared buffers, which sits on top of the operating system's file cache. In EXPLAIN, "hit" means the page was in shared buffers and "read" means it was fetched from the operating system, which may still be memory, not disk. Large sequential scans are deliberately kept from flooding shared buffers, so "read" there is expected, not a problem.

Helps to know first: How a table is stored on disk

On this page

Two layers of cache

When PostgreSQL needs a page, it looks in two places before the disk:

LayerOwned bySize
Shared buffersPostgreSQL, shared by all its processesshared_buffers, 128 MB by default
Page cacheThe operating system, for every file on the machineWhatever RAM is free

A page fetched from the operating system usually stays in both. That double caching is deliberate: the documentation suggests starting shared_buffers at about 25% of RAM on a dedicated server, and says more than 40% is unlikely to help, because PostgreSQL relies on the operating system’s cache too.

EXPLAIN (ANALYZE, BUFFERS) reports each page as a hit (found in shared buffers) or a read (requested from the operating system). A read is not necessarily a disk read: if the page was in the page cache, it came from memory.

Watch the cache work

We built a 52 MB table, small_t, restarted PostgreSQL to empty shared buffers, and counted its rows three times. (A restart does not empty the operating system’s cache.) PostgreSQL 18.3, shared_buffers at the default 128 MB, on a laptop:

RunTimeHitRead
1, just after restart39 ms06,667
221 ms2826,385
322 ms5646,103

PredictA table that never warms up

The table is 52 MB and shared buffers are 128 MB, with nothing else running. Why did each repeated scan add only 282 hits, instead of the whole table becoming cached?

When a table really should stay in shared buffers, you can load it explicitly with the pg_prewarm extension:

CREATE EXTENSION pg_prewarm;
SELECT pg_prewarm('small_t');   -- 6667 pages

After that, every page of the same count was a hit (6,667 hits, 0 reads), at about 26 ms. On this laptop that was no faster than reading through the warm operating system cache: both are memory. The difference matters when the operating system cache cannot hold everything, and pages would otherwise come from disk.

What effective_cache_size does

effective_cache_size sounds like a cache size and is not one. It allocates nothing. It tells the planner how much data it can assume is cached between shared buffers and the operating system, which makes index scans look cheaper or more expensive in its estimates. Set it to roughly the memory you expect to be available for caching; it never changes how much memory PostgreSQL uses.

Check yourself

  1. EXPLAIN (ANALYZE, BUFFERS) shows read=6385. Did PostgreSQL go to disk 6,385 times?

    Show answer

    Not necessarily. “Read” means the page was not in shared buffers and was requested from the operating system, which may have served it from its own page cache in memory.

  2. Why do repeated scans of a large table not fill shared buffers with it?

    Show answer

    Sequential scans of large tables use a small ring of buffers that they reuse, so a big scan does not evict everything else from shared buffers.

  3. What does raising effective_cache_size change?

    Show answer

    Only the planner’s cost estimates: it assumes more data is cached, so index scans look cheaper. It allocates no memory.

Finished reading?

Saved in this browser only.

Sources

Where each claim on this page comes from.

  1. 1.
    PostgreSQL documentation: 19.4 Resource Consumption
    Primary· §19.4.1 Memory: shared_buffers

    shared_buffers defaults to 128 MB; on a dedicated server, 25% of RAM is a reasonable start; because PostgreSQL also relies on the operating system cache, more than 40% is unlikely to help.

  2. 2.
    PostgreSQL documentation: Appendix M Glossary
    Primary· Buffer Access Strategy

    Operations that touch many pages, including sequential scans of large tables, reuse a small ring of buffers so they do not evict too many pages from shared buffers.

  3. 3.
    PostgreSQL documentation: 19.7 Query Planning (effective_cache_size)
    Primary· §19.7.2 Planner Cost Constants: effective_cache_size

    effective_cache_size is the planner's assumption about cache available to a query; it affects cost estimates, not memory allocation.

  4. 4.
    PostgreSQL documentation: F.30 pg_prewarm
    Primary· F.30

    pg_prewarm loads a relation's pages into the buffer cache.

Go further

What to read next, and why it is worth the time.

  1. 1.
    PostgreSQL documentation: 19.4 Resource Consumption
    Primary· §19.4.1 Memory

    The authoritative advice on shared_buffers, work_mem and their friends. Short, and better than most blog posts on the subject.

Applies to PostgreSQL 18. Draft: not yet fact-checked end to end.