The one thing to understand first
MVCC stores many versions of each row; a snapshot is the rule that selects which versions a statement or transaction may see. Conceptually it captures “which transactions had committed at the instant I started looking.” The struct is SnapshotData in src/include/utils/snapshot.h, built by GetSnapshotData() in src/backend/storage/ipc/procarray.c.
An isolation level is not a locking strategy — it is simply a decision about when you take your snapshot. Once you see that READ COMMITTED re-snapshots every statement while REPEATABLE READ freezes one snapshot for the whole transaction, the visibility “surprises” and the link between an idle transaction and runaway bloat both become obvious.
The three pieces that define a snapshot
xmin — the oldest transaction ID still running. Anything older is definitely committed or aborted (resolved).
xmax — the first not-yet-assigned XID. Anything at or above this had not started and is invisible.
xip[] — the list of XIDs that were in progress when the snapshot was taken.
With just these, visibility for any tuple is decidable: a tuple inserted by XID x is visible if x < xmax, x is not in xip[], and x committed. The deleting XID (t_xmax) is checked the same way to decide whether the version has been superseded.