Internals lessons

Learn how PostgreSQL actually works — then watch it run

36 lessons on PostgreSQL internals. 16 cite the postgres/postgres source with verified file and line numbers, and 40 real psql transcripts were captured against a live PostgreSQL 17.10 lab. The first 5 lessons are fully free; the rest are free to read — prose, verified source refs, and the SQL — with their captured lab output unlocked by Pro.

5 fully free25 with Pro lab evidence16 source-grounded

Storage & row versions

FreeSource-grounded2 lab transcripts

MVCC Internals: How PostgreSQL Stores Row Versions

PostgreSQL never lets a reader block a writer or a writer block a reader for ordinary SELECT/INSERT/UPDATE/DELETE traffic. It achieves this with Multi-Version Concurrency Control (MVCC): instead of overwriting a row in place, every modification produces a new physical copy — a tuple version — and the system decides at read time which version each transaction is allowed to see.

Read the lesson →
FreeSource-grounded1 lab transcript

The Page Layout: How PostgreSQL Stores Rows on Disk

PostgreSQL storage is organised into fixed-size blocks, 8KB by default (BLCKSZ). A table (the "main fork") is an array of these blocks in one or more 1GB segment files under the database directory. Every block — heap or index — shares the same general layout, defined in src/include/storage/bufpage.h.

Read the lesson →
FreeSource-grounded1 lab transcript

Snapshots and Visibility: How PostgreSQL Decides What You See

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.

Read the lesson →
FreeSource-grounded3 lab transcripts

TOAST: How PostgreSQL Stores Oversized Values

A tuple must fit within a single 8KB page — PostgreSQL does not span a row across blocks. So how does a text column hold a megabyte? The answer is TOAST (The Oversized-Attribute Storage Technique), implemented in src/backend/access/common/toast_internals.c and detoast.c. When a row would be too big, TOAST compresses and/or moves large field values out of the main tuple.

Read the lesson →
Free1 lab transcript

HOT Updates: How PostgreSQL Avoids Index Writes

Because of MVCC, every UPDATE writes a new tuple version. Naively, that new version needs a new entry in every index on the table — even indexes on columns that did not change. On a wide, heavily-indexed, update-heavy table, that index maintenance dominates write cost and generates index bloat. HOT (Heap-Only Tuple) updates exist to avoid it.

Read the lesson →

Write-ahead log & durability

Buffers & memory

Vacuum & bloat

Query planning & execution

🔒 Pro2 lab transcripts

Reading EXPLAIN: How the Planner Describes a Query

The PostgreSQL executor runs a tree of plan nodes, each pulling rows from its children on demand (the Volcano/iterator model, src/backend/executor/). EXPLAIN prints that tree. The node at the top produces the final result; leaves are scans of tables or indexes. Reading a plan means reading this tree from the inside out.

Read the lesson →
🔒 Pro1 lab transcript

How the Cost-Based Optimizer Chooses a Plan

For any non-trivial query there are many ways to get the answer — different scan methods, join orders, and join algorithms. The planner/optimizer (src/backend/optimizer/) enumerates candidate paths, estimates the cost of each, and picks the cheapest. It does not try every possibility exhaustively; it prunes aggressively.

Read the lesson →
🔒 Pro1 lab transcript

Statistics and Selectivity: Why the Planner Guesses Wrong

The optimizer chooses plans by estimating how many rows each operation produces. Those estimates come entirely from statistics gathered by ANALYZE into the pg_statistic catalog (human-readable via pg_stats). Bad statistics → bad estimates → bad plans. Almost every "mysteriously slow query" traces back here.

Read the lesson →
🔒 ProSource-grounded1 lab transcript

Parallel Query: How PostgreSQL Splits Work Across Workers

Historically each PostgreSQL query ran in a single backend process. Parallel query lets the planner split eligible work across multiple worker processes that execute parts of the plan concurrently, then combine results. The machinery lives in src/backend/executor/nodeGather.c, execParallel.c, and the parallel-aware scan nodes.

Read the lesson →
🔒 ProSource-grounded1 lab transcript

Bitmap Scans: How PostgreSQL Combines Indexes

A plain index scan is great for high selectivity (few rows) and a sequential scan is great for low selectivity (most rows). In between — say 1–15% of a table — both are suboptimal: the index scan jumps around the heap in random order, while the seq scan reads far too much. The bitmap scan fills this gap and is implemented in src/backend/executor/nodeBitmapHeapScan.c and the bitmap index scan nodes

Read the lesson →
🔒 Pro1 lab transcript

Partitioning: How Declarative Partitioning Prunes Work

A partitioned table is one logical table split into many physical child tables by a partition key. The win is not magic speed on every query — it is the ability to skip entire partitions (pruning), to drop old data instantly (DROP/DETACH a partition instead of a massive DELETE), and to vacuum and index partitions independently. Declarative partitioning lives in src/backend/partitioning/.

Read the lesson →
🔒 ProSource-grounded2 lab transcripts

Caching Plans: Prepared Statements and the Generic Plan

Every SQL statement goes through parse → analyze → rewrite → plan → execute. For repeated queries that differ only in parameter values, redoing parse and plan each time is wasted work. Prepared statements cache the parsed and (sometimes) planned form so subsequent executions skip that overhead. The logic lives in src/backend/utils/cache/plancache.c.

Read the lesson →

Indexes

Concurrency & locking

Schema changes & migrations

Connections & pooling

Replication & HA

🔒 ProSource-grounded1 lab transcript

Streaming Replication: walsender and walreceiver

Streaming replication keeps a standby byte-for-byte identical to its primary by shipping the primary's WAL stream and replaying it continuously. Because it replays the same physical changes, the standby is an exact copy — same data files, same block contents — which is why it requires the same major version and architecture.

Read the lesson →

Logical Replication and Logical Decoding

Where physical replication ships raw WAL blocks, logical replication ships logical row changes — "insert this row", "update that row" — reconstructed from the WAL by logical decoding. Because it operates at the row level via SQL, publisher and subscriber can differ in major version, architecture, and even schema, and you can replicate a subset of tables. This flexibility powers upgrades, selective

Read the lesson →
Source-grounded

Replication Slots: Guaranteeing WAL Retention

A replica (physical standby or logical subscriber) consumes WAL from the primary. If the primary recycles WAL before the replica has read it, the replica falls irrecoverably behind and must be rebuilt. The old wal_keep_size approach — keep a fixed amount of WAL and hope it's enough — is fragile. Replication slots solve this precisely by tracking exactly how far each consumer has progressed and ret

Read the lesson →
🔒 Pro1 lab transcript

Synchronous Replication and Quorum Commit

By default PostgreSQL replication is asynchronous: a primary commits once its own WAL is durable, without waiting for any standby. If the primary then fails before a standby received the latest WAL, those last transactions are lost. Synchronous replication makes the primary wait for standby confirmation before reporting commit success, eliminating that data-loss window — at the cost of commit late

Read the lesson →
🔒 Pro1 lab transcript

Hot Standby and Recovery Conflicts

Hot standby lets a streaming standby serve read-only queries while it continuously replays WAL from the primary. This is how PostgreSQL scales reads: route SELECT traffic to one or more standbys. But it creates a tension that does not exist on the primary — the standby is doing two things at once: replaying changes and answering queries against the data those changes are modifying.

Read the lesson →

pg_rewind: Reusing a Diverged Old Primary

When a standby is promoted to primary (after the old primary failed or was isolated), the cluster has a new leader on a new timeline. If the old primary comes back, it has diverged: it may contain transactions that were never replicated, and it is on the old timeline. You cannot simply reconnect it as a standby — its history conflicts with the new primary. Historically the only fix was a full pg_b

Read the lesson →

Automatic Failover, Fencing, and Split-Brain

Streaming replication gives you standbys ready to take over, but deciding when to promote one and ensuring only one primary exists is the hard part. Doing it by hand is slow and error-prone. Tools like Patroni, repmgr, and Stolon automate failover — but automation introduces the cluster's most dangerous failure mode: split-brain.

Read the lesson →