PostgreSQL on-disk filesystem visualizer

· updated

A single-file visualizer of how PostgreSQL turns SQL into bytes on disk. On the left, a psql terminal pre-loaded with a script that exercises every interesting on-disk structure. On the right, the live PGDATA tree — directories appear, files spring into existence, pages extend, line pointers march forward and the WAL records pile up — all driven by the same op stream the terminal is replaying.

The script walks through:

CREATE DATABASE shop;        →  mkdir base/16384/, clone template1
\c shop                      →  switch session
CREATE TABLE customers ...   →  alloc heap relfilenode + _fsm + _vm + pkey idx
INSERT ×3                    →  page 0 extends; LPs grow; xmin set; WAL append
SELECT * FROM customers      →  Seq Scan via buffer cache
UPDATE id = 1                →  HOT update: t_xmax on old, new ctid chained
DELETE id = 2                →  tuple marked dead, storage NOT yet freed
CREATE INDEX (email)         →  new relfilenode, B-tree build
VACUUM customers             →  reclaim dead tuples, set VM all-visible
CHECKPOINT                   →  fsync all dirty pages, advance pg_control.redo
CREATE TABLE orders ... PARTITION BY RANGE(ts)
  + 2 partitions (q1, q2)    →  parent.relkind='p' has no storage
INSERT INTO orders ...       →  ExecPartitionRouting routes to correct child
SELECT join                  →  Index Scan + Append over partitions

Open fullscreen ↗

Views

ViewWhat it shows
filesystemThe live PGDATA tree. Per-file metadata (pages, free bytes, last LSN, partition bound), with new files green-flashed and currently-written files orange-flashed. Click any node to inspect.
pageA scaled byte-strip of the selected 8 KB heap page — PageHeaderData (24 B), the line-pointer array growing from the bottom, free space, and tuples filling from the top. Below it, the HeapTupleHeaderData field tree (xmin / xmax / ctid / infomask / hoff) for the first live tuple.
WALEvery record appended to pg_wal/ with its LSN, resource manager, tag and length. Click for xl_tot_len / xl_xid / xl_prev / xl_crc plus prose notes on what that record means.
B-treeMeta page → root → leaf pages of every index, with (key → ctid) pairs visible at the leaves.

What’s “precise” about it

Controls

Key / mouseAction
spaceRun / pause the script
sStep one operation
shift+sStep ×10
rReset (re-init PGDATA, clear WAL/CLOG)
vCycle filesystem → page → WAL → B-tree
click on a fileOpen the inspector (path, OID, columns, fork, pages, LSNs)
click on a pageJump to the page view
click on a WAL recordShow its xl_* header + payload

What the data flow looks like

Every command produces a chain of low-level operations. For an INSERT:

psql:        INSERT INTO customers VALUES (1, 'alice@…');

parser:      raw query → parse tree
planner:     Insert on customers (heap)

WAL append:  rmgr=Heap  tag=INSERT  rfn=16385 blk=0 off=1  len=78 B

heap mutate: page 0: line-pointer [1] added · upper -= tup_size · t_xmin = 743

FSM update:  block 0 free space = 8120 B
VM clear:    block 0 no longer all-visible

btree leaf:  rmgr=Btree tag=INSERT_LEAF key=1 tid=(0,1)

commit:      WAL rmgr=Transaction tag=COMMIT xid=743 · CLOG bit ← COMMITTED · fsync

Each of those steps shows up as a notification at the bottom of the simulator, with the affected file flashing in the tree.

← databases