This article provides a visual deep dive into how PostgreSQL physically stores and retrieves data in its heap storage. Understanding this low-level mechanism is crucial for optimizing database performance, designing efficient data models, and diagnosing storage-related issues in system architecture. It covers concepts like page structure, tuple headers, and the impact of VACUUM on data integrity.
Read original on Medium #system-designPostgreSQL doesn't store data in neat, contiguous blocks per row as one might imagine. Instead, it uses a heap file structure where data pages (typically 8KB) contain multiple rows (tuples). A deep understanding of this physical organization is critical for database administrators and system designers to optimize performance and understand the implications of various database operations, especially in high-throughput or storage-sensitive applications.
Each 8KB data page in PostgreSQL's heap storage is divided into several sections: a page header, item identifiers (pointers to tuples), and the actual tuple data, growing from opposite ends of the page. Free space exists between the item identifiers and the tuple data, which is crucial for inserts and updates. The page header contains metadata like the free space pointer and LSN (Log Sequence Number) for recovery.
Impact of Page Structure on Performance
Understanding the page structure helps optimize `FILLFACTOR` settings, which control how much free space is left on pages for future updates, reducing the need for new page allocations and improving write performance by minimizing page splits. It also informs decisions around data type choices and table column order to minimize tuple size and maximize rows per page.
Each row, or tuple, within a page has its own `HeapTupleHeaderData` structure. This header contains essential metadata for PostgreSQL's Multi-Version Concurrency Control (MVCC), such as `xmin` and `xmax` transaction IDs, which denote the transaction that inserted the tuple and the transaction that deleted or updated it, respectively. This mechanism allows readers to see a consistent snapshot of the database without locking.
Because updates and deletes don't immediately remove old data (due to MVCC), "dead tuples" accumulate. The `VACUUM` process reclaims this space. `VACUUM FULL` rewrites the entire table, compacting dead space, while regular `VACUUM` marks dead tuples as reusable without rewriting the file. `autovacuum` is critical for maintaining performance and preventing bloat in production systems.
VACUUM ANALYZE my_table;
-- Reclaims space and updates statistics for query planner
VACUUM FULL my_table;
-- Rewrites the entire table to reclaim all space, requiring an exclusive lock