Every row version in a heap carries two hidden system columns: xmin, the transaction id that created it, and xmax, the transaction id that deleted or superseded it. Postgres never overwrites a row in place: an UPDATE writes a new version and stamps the old one’s xmax. A transaction sees a version only if its xmin is committed and visible to that transaction’s snapshot, and its xmax is not. Old versions stay on disk until vacuum proves no live snapshot can still need them; that backlog is what n_dead_tup counts.
UPDATE writes a new version and stamps the old row’s xmax. Open a snapshot first to see vacuum blocked.