PostgreSQL 1,289 stores a table as a heap of 8 kB pages: a 24-byte header, then 4-byte line pointers growing forward, while tuples fill from the end backward. The bundled pageinspect extension shows it for mart.sales:
CREATE EXTENSION IF NOT EXISTS pageinspect;
SELECT lower, upper FROM page_header(get_raw_page('mart.sales', 0));
SELECT lp, lp_off, lp_len, t_hoff FROM heap_page_items(get_raw_page('mart.sales', 0)) LIMIT 3;
SELECT count(*) AS rows_on_page, pg_relation_size('mart.sales') / 8192 AS pages
FROM heap_page_items(get_raw_page('mart.sales', 0));Output
lower | upper
-------+-------
256 | 328
lp | lp_off | lp_len | t_hoff
----+--------+--------+--------
1 | 8056 | 136 | 32
2 | 7928 | 126 | 32
3 | 7784 | 144 | 32
rows_on_page | pages
--------------+-------
58 | 2432Line pointers end at byte 256 (24 + 58 x 4) and the lowest tuple starts at 328: the page is full. Each tuple spends 32 bytes (t_hoff) on visibility data, a null bitmap and padding, then stores all 15 columns side by side. Fetching one order line is one page read; summing gross_amount reads all 2,432 pages (19.9 MB) and deforms every tuple, walking its columns in order to reach the wanted one.