Data structures and storage engines
Pages and row layout
A database page is a fixed-size block with a header, slots and rows packed from both ends. How that layout drives row IDs, updates and wasted space.
The storage engine never reads a single row from disk. It reads a page, a fixed-size block, usually 4 KB to 16 KB, and then finds the row inside it. That is why row layout matters: it decides how many rows fit per read, how updates move data, and how much space is wasted.
The common design is the slotted page. A small header sits at the front. A slot array at the top records where each row starts. Rows grow in from the end of the page. Free space sits in the middle.
+--------------------------------------------------------------+
| header: page id, row count, free space pointer |
| slot 0 -> offset 4000 | slot 1 -> offset 3920 | slot 2 -> ... |
| free space |
| row 2 (offset 3920) | row 1 (offset 3960) | row 0 (4000) |
+--------------------------------------------------------------+Why slots
A row can be stored at offset 4000, then moved within the page, for example after an update makes it bigger. Other structures only point at the slot number, not the byte offset. So the engine can compact the page without updating every index that points to the row. The index stores (page, slot), a stable row ID.
Without slots, every compaction would rewrite pointers across the database. The extra indirection is cheap, and it keeps the layout flexible.
The cost of a wide row
Count how many rows fit in one page. With a 4 KB page and 200-byte rows, you get about 20 rows per page. Drop the row to 100 bytes and you get about 40. A full table scan then reads half as many pages.
Estimates like these tell you whether a query can stay in cache. If a table is 40 GB and your buffer pool is 16 GB, a full scan will evict hot pages. This is why teams split large text and JSON columns into a separate table, and why SELECT * costs more than people expect.
Updates and page fill
Updates can grow a row. If it no longer fits in its page, some engines move it and leave a forwarding pointer behind. PostgreSQL uses multiversion rows, so every update writes a new row version and a later vacuum pass reclaims the old ones. Either way, updates can leave free space scattered across pages. Pages that are only partly full read as more pages than the data needs.
Tip
If a table seems much larger than its row count justifies, check for bloat before adding hardware. Vacuum, OPTIMIZE TABLE and index rebuilds exist for this reason.
Common mistakes
- Designing wide rows that store large blobs next to frequently read columns.
- Treating row IDs as stable across all engine operations. They are stable, but only within the storage engine's rules.
- Ignoring page fill when a table grows faster than its data does.
Check yourself
- Why does an index store
(page, slot)instead of a byte offset? Answer: slots let the engine move and compact rows inside a page without updating every index that points to them. - Why is a 200-byte row slower to scan than a 100-byte row with the same count? Answer: it fills twice as many pages, so a full scan reads twice as many pages.
Key takeaways
- The storage engine reads pages, not rows, and the page size sets how much each read fetches.
- Slotted pages keep a stable row address while the bytes inside move.
- Row width sets how many rows fit per read, so keep hot columns narrow.
- Updates and deletes leave free space behind, which bloats tables over time.
Further learning
Next, see how a B+tree uses these pages to find a row in a few reads.
Related
Storage engines: pages, B-trees and the WAL
How a relational database stores rows in fixed-size pages, finds them in a B+tree, caches them in a buffer pool, and survives crashes with a write-ahead log.
The memory hierarchy
From registers to SSDs to the network, the latency numbers that dominate performance, and why the tier your data sits in matters more than the code you write.
Buffer pool
The buffer pool caches database pages in RAM. How eviction chooses which page to drop, why sequential scans can wipe out the cache, and how midpoint insertion protects hot pages.