Build Your Own PostgreSQL
Buffer Pools and the Memory-Disk Border
What You Will Learn
- Trace a page through buffer lookup, pinning, modification, and eviction
- Distinguish a buffer pin from synchronization of page contents or metadata
- Explain why a dirty page may flush only after its required WAL
The border that decides latency
Two bookstore sessions request the same orders page. Reading separate private copies seems simple, until one session updates its copy while the other continues from stale bytes. Reading from disk for every tuple avoids stale private caches but makes ordinary scans unusably slow. PostgreSQL instead gives one shared buffer frame a current in-memory identity for a particular relation block.
A buffer frame is a slot in shared memory that can hold one page from a relation. Around that page are control fields: which file and block it represents, whether it is dirty, how many processes have it pinned, whether I/O is in progress, and how recently it has been used. The page bytes are only part of what matters. The metadata decides whether the page can be evicted, flushed, modified, or trusted.
The life of a page in memory
When a backend needs a page, it asks the buffer manager. If the page is already present, the backend pins the buffer so it cannot be evicted while in use. If the page is absent, the buffer manager chooses a victim frame, writes it first if it is dirty and safe to flush, reads the requested page into that frame, and returns a pinned buffer. This process is routine, but it rests on several safety rules.
Buffer path
Trace one miss and one hit
Backend A asks for orders block 42. Lookup misses, so the manager chooses an unpinned victim. If that victim is dirty, its required WAL must first be durable and the page must be written. The requested block is then read into the frame, tagged with its identity, and pinned for A. Backend B soon requests the same block, finds the tag, and pins the same frame rather than loading another copy.
A pin protects lifetime: the frame cannot be reassigned while a backend holds a reference. It does not by itself serialize changes to page contents or shared metadata; short internal locks handle those critical sections. The invariant is one coherent cached identity plus controlled lifetime. That combination lets backends share page state without making every read a disk operation or every reference vulnerable to eviction.
A cache with database rules
The most important rule is that a dirty data page cannot be written before the log records needed to redo its changes are durable. This is the write-ahead part of write-ahead logging. The buffer manager is therefore more than a simple cache. It takes part in the durability contract. Flushing a page is safe only when recovery would know how to reconstruct that page if a crash happened immediately after the flush.
The operating system page cache is useful, but too general to own this job alone. It does not know database transaction rules, page identities, tuple visibility, checkpoints, or WAL dependencies. A database can rely partly on OS caching, but it still needs its own buffer manager to coordinate shared access, dirty state, and eviction under database-specific invariants. Without that, the engine loses control over the boundary where correctness meets performance.
Another contrast is private per-process caching. If each backend cached pages independently, repeated reads within one session would be fast, but memory would duplicate heavily across sessions. Worse, one backend might modify a page while another holds a stale copy. Shared buffers make page state common. That forces synchronization, but it also gives the database a single place to reason about dirty data and page identity.
Pins, eviction, and background pressure
Pins and locks solve different problems. A pin says the buffer frame cannot be reused because some process is actively referencing it. A lock or latch protects the page or buffer metadata while it is inspected or changed. Holding a pin for a long scan is normal, but holding a short internal latch for a long operation is harmful. Good buffer discipline keeps critical sections small while preserving the lifetime of page references.
Eviction policy is an economic choice. The system wants to keep hot pages and discard cold ones, but cannot spend too much CPU maintaining a perfect ranking. PostgreSQL uses a practical clock-sweep approximation rather than an ideal least-recently-used list. The sweep must skip pinned buffers and account for dirty buffers whose flushing may be expensive.
Dirty pages create background work. If backends alone flushed pages when they needed victim frames, user queries would suffer unpredictable stalls. Background writers and checkpointers spread I/O across time. The checkpointer makes sure enough dirty pages reach disk to limit crash recovery. The background writer can reduce the chance that ordinary backends have to perform writes under pressure. These processes do not remove the cost, but they move it to calmer moments.
The buffer pool also shows why scans can disturb other workloads. A large sequential scan may touch more pages than the buffer pool can hold, pushing out pages that many small queries need. Databases use strategies to reduce this damage, such as special access patterns for bulk reads. Cache behavior is not an implementation footnote. It can decide whether a workload feels smooth or erratic.
Three buffer questions
For any page access, ask which buffer tag establishes identity, which pin or ownership rule protects the frame's lifetime, and which synchronization protects the bytes being inspected. For any eviction, add a fourth question: which WAL position must be durable first? These questions separate cache policy from correctness and make a slow path easier to diagnose without weakening the storage contract.
Into WAL
At this layer, a page is both a memory object and a future disk object. It can be read, pinned, dirtied, flushed, evicted, and recovered. But the buffer pool itself does not explain how a committed change survives a crash. For that, the database needs a separate chronological record of change: the write-ahead log.