PostgreSQL
The most-extensible open-source RDBMS: MVCC, a cost-based planner, and 30+ years of catalog evolution.
PostgreSQL is a client-server object-relational database with a process-based architecture, multi-version concurrency control, a cost-based query optimizer, and a deeply extensible type system. It supports user-defined types, operators, index methods, procedural languages, and foreign data wrappers, all cataloged in system tables.
MVCC without undo logs
Every row version is written in-place with xmin/xmax system columns. Old versions become dead tuples and are reclaimed by VACUUM.
Cost-based planner
The planner enumerates access paths, joins, and aggregations, scores each by an estimated cost, and picks the lowest. Statistics from ANALYZE feed the estimator.
Extensible catalogs
Tables, types, functions, and operators are first-class rows in pg_catalog. Extensions install new rows at runtime, not via recompilation.
Logical and physical replication
Streaming replication ships WAL to replicas; logical replication decodes WAL into row-level change streams. Both can run in parallel.
MVCC and tuple visibility
PostgreSQL never overwrites a row in place. An UPDATE writes a new tuple version and marks the old one as deleted. Visibility is decided per-query by comparing each tuple’s xmin and xmax against the transaction snapshot taken at statement (or transaction) start. This is what makes readers never block writers and writers never block readers.
Dead tuples accumulate and consume space until VACUUM reclaims them. Autovacuum is a background launcher that schedules vacuum workers when the dead-tuple fraction in a table crosses a threshold.
Query lifecycle
A client sends SQL over the wire protocol. The backend parses it into a parse tree, rewrites it against the rule system, analyzes it (binding names to catalog entries), and hands it to the planner. The planner produces a tree of plan nodes: Seq Scan, Index Scan, Hash Join, Aggregate, Sort. The executor pulls tuples top-down through the plan.
Each backend is a separate OS process. A connection-pooling proxy (pgBouncer) is the usual way to keep thousands of clients from spawning thousands of backends.
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
EXPLAIN (ANALYZE, BUFFERS)
SELECT relname, n_live_tup
FROM pg_stat_user_tables
ORDER BY n_live_tup DESC
LIMIT 10;
Indexes beyond B-tree
PostgreSQL ships with five built-in index access methods. B-tree handles the 80% case. GIN indexes an inverted list per key (great for full-text, JSONB, arrays). BRIN summarizes a range of pages into a tiny min/max (great for time-series with monotonic keys). GiST and SP-GiST are extensible balanced-tree frameworks for spatial, range, and custom types.
Because indexes are plug-in, the community has built hundreds of them: PostGIS for geometry, pg_trgm for fuzzy text, vector for embeddings.
Diagrams
A walwriter appends to the WAL; a checkpointer flushes dirty buffers and recycles WAL segments. Crash recovery replays from the last checkpoint.
Each 8 KiB heap page carries a page header (lsn, tli, free space), an item-pointer array, and tuple headers with xmin/xmax for MVCC.
A prepared statement goes through parse, analyze, rewrite, plan, execute. The executor walks heap pages and index pages and applies MVCC visibility per snapshot.
Writes append to the WAL, then update heap tuples in place; dead tuples are reclaimed by autovacuum.
| visible after recovery | v1 (balance 100) |
| v2 (250) | aborted — never committed |
| WAL replay stop point | last committed record |
| durability rule | commit waits for WAL flush |
All steps
- Tx A: UPDATE balance 100 → 250 — A new version (v2, xmin = 100) is written and chained; v1 is marked dead-upon-A’s-commit. Nothing in place is overwritten — writers never block readers.
- Reader B (old snapshot) still sees 100 — B’s snapshot was taken before A ran. xmin = 100 is in progress ⇒ v2 is invisible to B; it follows the chain to v1 (100). Repeatable reads in action.
- Crash before the commit record — A’s WAL records exist, but the commit record was never written or flushed. Durability is defined by that record — without it, A never happened.
- Recovery replays WAL to the last commit — On restart, WAL is replayed up to the last committed record. A’s half-written v2 has no commit ⇒ treated as aborted; v1 (100) is the visible state.
- Reader C sees 100 again — Aborted xids are invisible to every snapshot. Atomicity came from the commit boundary, not from undoing pages.
Transaction visibility, the WAL commit boundary, and crash recovery — PostgreSQL-flavored. Simplified: xmin-only version chains, no xmax/clog details; the durable rule shown here is exact: a transaction exists only once its commit record is flushed to WAL.
Simplified xmin-only model of PostgreSQL MVCC — the durable rule shown (commit waits for WAL flush) is exact; xmax/clog details are in the MVCC chapter.
[caption] Simplified xmin-only model of PostgreSQL MVCC — the durable rule shown (commit waits for WAL flush) is exact; xmax/clog details are in the MVCC chapter.
Stepwise: a new version is invisible until its transaction commits; old-snapshot readers keep seeing v1; crash recovery replays WAL to the last committed record.
Primary streams WAL; one replica streams further to a cascading replica.
A representative plan tree from EXPLAIN (ANALYZE): a nested loop joins an index scan on users to a filtered scan on events; costs are planner units.
- 1Limit10 rows · cost 42.5
- 2Sort96 rows · cost 41.9
- 3Nested Loop96 rows · cost 38.2
- 4Index Scan using users_pkey12 rows · cost 8.3
- 5Seq Scan on events (filter: kind = 2)8 rows · cost 28.1
[caption] Costs are planner units: 1.0 is one sequential page fetch.
A representative plan tree from EXPLAIN (ANALYZE): a nested loop joins an index scan on users to a filtered scan on events; costs are planner units.
Three levels at the default 90% fill factor: one root, ~32 internal routing pages, and a leaf level that dominates the page count.
[caption] Upper levels are pure routing pages; leaves carry heap TIDs.
Three levels at the default 90% fill factor: one root, ~32 internal routing pages, and a leaf level that dominates the page count.