PostgreSQL uses lazy pointer swizzling to speed up buffer access, keeping on-disk and in-memory data in sync without costly conversions. Here’s how it works…
When PostgreSQL stores data structures like B-tree index pages on disk, it can’t use actual memory pointers - disk addresses and memory addresses are fundamentally different. But once data is loaded into memory, we want fast pointer-based traversal, not slow indirect lookups.
PostgreSQL uses block numbers and offsets on disk. When a page is read into the shared buffer pool, these logical references remain as-is initially. Here’s where it gets interesting.
For Buffer Pool Pages:
- Pages are identified by a
BufferTag(database, relation, block number). - In memory, PostgreSQL maintains a buffer descriptor array.
- When code needs to access a page, it goes through the buffer manager, which maps the block number to the actual buffer location.
- This is effectively lazy swizzling - the translation happens on each access instead of converting all references upfront.
Here’s a simplified version of what a BufferTag looks like in PostgreSQL source code
typedef struct
{
Oid spcOid; /* tablespace */
Oid dbOid; /* database */
Oid relNumber; /* relation (table or index) */
ForkNumber forkNum; /* main, fsm, vm, init forks */
BlockNumber blockNum; /* block number within the relation */
} BufferTag;
This approach has several advantages. Pages in the buffer pool keep their on-disk format. Multiple processes can reference the same page through block numbers, hence concurrent access is easy and seamless. When a page is evicted, there’s no need for “un-swizzling.”
PostgreSQL’s design trades a small amount of indirection overhead for much simpler buffer management and better concurrency.
Hope you also find this interesting.