Build Your Own PostgreSQL
Buffer Pools and the Memory-Disk Border
What You Will Learn
- Understand the border that decides latency in a PostgreSQL-like database
- Understand the life of a page in memory in a PostgreSQL-like database
- Understand buffer path in a PostgreSQL-like database
- Understand a cache with database rules in a PostgreSQL-like database
The border that decides latency
Heap pages live on disk, but a database that touched disk for every row would be unusable. The buffer pool is the shared memory region that holds copies of disk pages while backends read and modify them. It sits at the border between durable storage and fast execution. Every scan, index probe, update, and vacuum pass eventually depends on whether the needed page is already in memory, can be safely loaded, or must wait behind another process.
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
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 it cannot spend too much CPU maintaining a perfect ranking. Postgre-like systems use practical approximations rather than ideal least-recently-used lists. A clock-style sweep can age buffers and find candidates with low recent use. The policy must also skip pinned buffers and handle 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.
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.