postgres/postgres
MVCC and transactions
PostgreSQL implements multi-version concurrency control (MVCC): writers create new row versions instead of overwriting, and readers see the version visible at the start of their snapshot. The combination of MVCC and a separate WAL system is the project's defining architectural choice. This page covers transaction state, snapshot computation, visibility rules, and the supporting catalogs.
Directory layout
src/backend/access/transam/
├── clog.c # commit log: per-XID commit/abort status (SLRU)
├── commit_ts.c # per-XID commit timestamp (SLRU, optional)
├── multixact.c # XID groupings for shared row locks (SLRU)
├── parallel.c # parallel-worker transaction state setup
├── slru.c # simple LRU page cache used by clog/multixact/etc.
├── subtrans.c # subtransaction parent pointers (SLRU)
├── timeline.c # WAL timelines (used by recovery)
├── transam.c # XID-status helpers (TransactionIdDidCommit, ...)
├── twophase.c # PREPARE TRANSACTION
├── varsup.c # XID and OID counters; wraparound
├── xact.c # top-level transaction state machine
├── xlog.c # WAL infrastructure
├── xloginsert.c # XLogInsert API
└── xlogreader.c # WAL parser used by recoveryThe visibility logic itself lives next to the heap: src/backend/access/heap/heapam_visibility.c.
The proc array — the runtime state of which XIDs are in progress — lives at src/backend/storage/ipc/procarray.c.
XIDs
A transaction ID (XID) is a 32-bit counter. Every transaction that writes gets one, allocated lazily (read-only transactions get a virtual XID instead — BackendId, LocalTransactionId). Source: src/backend/access/transam/varsup.c::GetNewTransactionId.
XIDs wrap every ~4 billion. Wraparound is a real problem because a row's xmin of "transaction 5" is supposed to predate "transaction 4 billion + 5." The mitigation is freezing: VACUUM rewrites old rows to mark them with the special XID FrozenTransactionId (= 2), declaring "this is unconditionally visible to everyone." Once a row is frozen, its actual XID can be safely recycled. The autovacuum launcher monitors pg_class.relfrozenxid and forces an emergency vacuum if a relation's age approaches the wraparound limit.
64-bit XIDs have been a long-discussed feature; the project has continued to refine the freezing path instead.
Transaction state machine
Source: src/backend/access/transam/xact.c. Each backend maintains a TransactionState stack:
TopTransactionState (per top-level transaction)
└── CurrentTransactionState (top, plus a stack frame per active subtransaction)Top-level entry points:
| Function | Role |
|---|---|
StartTransaction |
Begin a top-level transaction. Allocates TopTransactionContext, sets state. |
StartTransactionCommand |
Called at the start of every protocol command; opens an implicit transaction if none exists. |
CommitTransactionCommand |
At the end of a command; commits if it was an implicit transaction. |
BeginInternalSubTransaction |
Open a subtransaction (used for EXCEPTION blocks in PL/pgSQL). |
CommitTransaction |
End-of-COMMIT. Releases locks, runs deferred triggers, writes the commit record to WAL, marks the XID committed in CLOG. |
AbortTransaction |
End-of-ROLLBACK or post-error. Releases resources via the resource owner, runs aborts, writes abort record to WAL. |
PrepareTransaction |
PREPARE TRANSACTION; durably records the transaction state to a 2PC file. |
The state machine values themselves (TBLOCK_DEFAULT, TBLOCK_STARTED, TBLOCK_INPROGRESS, TBLOCK_END, TBLOCK_ABORT, TBLOCK_SUBINPROGRESS, ...) are enumerated in xact.c and handled in giant switches; touching them requires care.
Snapshots
A snapshot is the set of XIDs visible to a transaction. PostgreSQL's snapshot is essentially:
typedef struct SnapshotData
{
SnapshotType snapshot_type;
TransactionId xmin; // oldest XID still potentially visible
TransactionId xmax; // first XID not yet visible
TransactionId *xip; // in-progress XIDs in [xmin, xmax)
uint32 xcnt;
/* ... */
} SnapshotData;Visibility rule: a transaction with XID t is visible iff:
t < snapshot.xmin(committed before this snapshot was taken), ORt >= snapshot.xmin && t < snapshot.xmax && t not in xip(committed before the snapshot).
Sources:
GetSnapshotDatainsrc/backend/storage/ipc/procarray.c— builds a snapshot by walking the proc array underProcArrayLock.src/backend/utils/time/snapmgr.c— snapshot manager, snapshot stack, "active snapshot" vs "transaction snapshot."
There are several specialized snapshot types: MVCC (default), SelfMVCC (same-tx changes are visible), Dirty (catalog scans), NonVacuumable (used during VACUUM), Historic (logical decoding).
Tuple visibility
The actual test is HeapTupleSatisfiesVisibility(tuple, snapshot, buffer) in src/backend/access/heap/heapam_visibility.c. The function dispatches on the snapshot type. For an MVCC snapshot, the rough algorithm is:
graph TD
Start[heap tuple] --> CheckXmin{Was xmin committed and visible to snapshot?}
CheckXmin -- "no" --> Invisible
CheckXmin -- "yes" --> CheckXmax{Is xmax set?}
CheckXmax -- "no" --> Visible
CheckXmax -- "yes" --> CheckXmaxState{Was xmax committed and visible?}
CheckXmaxState -- "yes" --> Invisible[invisible<br/>(deleted row)]
CheckXmaxState -- "no" --> VisibleIn practice the function is a careful sequence of t_infomask flag checks, CLOG lookups (via TransactionIdDidCommit / TransactionIdDidAbort), and proc-array consultation — each step caching the result back into t_infomask hints to avoid future repeated lookups.
HeapTupleSatisfiesVacuum is the parallel function used by VACUUM to decide if a tuple can be removed.
CLOG
The commit log stores 2 bits per XID: in progress (00), committed (01), aborted (10), sub-committed (11). Backed by SLRU. Source: src/backend/access/transam/clog.c. Truncated periodically as XIDs are frozen.
TransactionIdDidCommit(xid) reads CLOG, with hint-bit caching in the proc array to amortize cost.
Multixact
When multiple transactions hold a shared lock on a row, the heap tuple's xmax slot can hold only one XID — so PostgreSQL invents a MultiXactId. A multixact is an ordered list of XIDs and their lock modes, stored in another SLRU (pg_multixact/offsets, pg_multixact/members). Source: src/backend/access/transam/multixact.c.
Multixacts have their own freeze counter (relminmxid), tracked alongside relfrozenxid to ensure they get cleaned up.
Subtransactions
Each SAVEPOINT (or PL/pgSQL EXCEPTION block, internally) opens a subtransaction. Subtransactions get their own XID; on commit, the parent inherits all of their effects. On rollback, the subtransaction's XID is marked aborted in CLOG.
A subtransaction's parent is recorded in pg_subtrans, an SLRU-backed lookup table (src/backend/access/transam/subtrans.c). The visibility code consults it when determining whether a sub-XID is "really" visible.
Two-phase commit
PREPARE TRANSACTION 'gid' durably records a transaction's state to disk in pg_twophase/<xid> and detaches it from the issuing backend. A later COMMIT PREPARED 'gid' (potentially by a different backend) finishes it. Used for distributed transactions coordinated by an external transaction manager. Source: src/backend/access/transam/twophase.c.
Isolation levels
PostgreSQL implements:
- Read Committed (default) — each query sees its own fresh snapshot. Writes can race; row-level locking and
SELECT FOR UPDATEprovide additional ordering when needed. - Repeatable Read — one snapshot per transaction; serialization anomalies prevented at the row-version level by
xmin/xmax+ the proc array. Implements PostgreSQL's "snapshot isolation." - Serializable — adds Serializable Snapshot Isolation (SSI) on top: a runtime predicate-locking system that detects dangerous read-write dependency cycles and aborts the offending transaction. Source:
src/backend/storage/lmgr/predicate.c.
The choice of level affects which Snapshot is "active": Read Committed re-fetches GetTransactionSnapshot each statement; Repeatable Read freezes one for the whole transaction; Serializable adds SSI tracking on top of the RR snapshot.
VACUUM
Source: src/backend/access/heap/vacuumlazy.c, src/backend/commands/vacuum.c. Reclaims dead row versions by:
- Heap scan: identify tuples whose
xmaxis committed and older than the global horizon (OldestXmin). - Index vacuum: for each affected page, remove index entries pointing to dead tuples.
- Heap second pass: mark line pointers
LP_DEAD/LP_UNUSED. - Possibly truncate trailing all-dead pages.
Autovacuum (src/backend/postmaster/autovacuum.c) reads pg_stat_all_tables (now in shared memory), uses thresholds (autovacuum_vacuum_threshold, _scale_factor, _max_workers) to decide which tables need vacuum/analyze, and dispatches per-table workers.
OldestXmin is computed from the proc array — the smallest xmin of any active backend, replication slot, or prepared transaction. Long-running transactions and unused replication slots are common causes of "VACUUM can't clean up dead rows."
Entry points for modification
- New isolation semantic: usually starts in
xact.c(transaction state) andsnapmgr.c(snapshot acquisition); SSI tracking lives inpredicate.c. - New visibility check:
heapam_visibility.c::HeapTupleSatisfiesVisibilityis the dispatch site; specialized functions live in the same file. - New transaction-end side effect:
xact.c::CommitTransactionandAbortTransactionare the choke points. Many subsystems already register here (AtCommit_*,AtAbort_*). - Tracking a new per-XID datum: there is precedent in
commit_ts.cfor adding an SLRU-backed XID-keyed array.
For the WAL and recovery side of transactions, see WAL and recovery. For the proc array internals, see Storage.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.