Reference · Glossary
Glossary
Canonical definitions for this course. When a term is defined here, lessons use it exactly this way.
Living documentGrows with each lessonContext: PostgreSQL (current, 16/17+)
- heap
- Postgres's name for a table's main data area: an unordered
collection of pages holding row versions. Unlike an InnoDB table (which is
a B+tree clustered by primary key), a heap has no inherent order, and indexes are
stored separately from it.
— intro'd in lesson 0001
- tuple (row version)
- One physical version of a row inside a heap page. A single logical row can have
several tuples at once (a live one plus expired ones) because MVCC keeps old
versions until they're reclaimed.
— intro'd in lesson 0001
- MVCC (Multiversion Concurrency Control)
- Postgres's concurrency model: it keeps multiple versions of a row so each SQL
statement sees a consistent snapshot of the data as of some point in time.
Its headline guarantee: reading never blocks writing and writing never blocks
reading.
— intro'd in lesson 0001
- dead tuple
- A row version that is expired (superseded by an
UPDATE or removed by
a DELETE) and no longer visible to any transaction, but still occupying
space in the heap. Reclaimed by VACUUM.
— intro'd in lesson 0001
- bloat
- Heap (or index) space consumed by dead tuples that have not yet been reclaimed.
A direct consequence of "never update in place": repeated
UPDATEs grow
a table well beyond its logical size until VACUUM catches up.
— intro'd in lesson 0001
- VACUUM
- The maintenance operation that reclaims space from dead tuples so it can be reused
by new rows. Normally run automatically by autovacuum. The
Postgres counterpart to InnoDB's undo-log purge. Plain
VACUUM marks
space reusable inside the table but does not return it to the OS.
— intro'd in lesson 0001, expanded in 0002
- autovacuum
- The background feature that automates
VACUUM and ANALYZE.
Vacuums a table once dead tuples exceed
autovacuum_vacuum_threshold + autovacuum_vacuum_scale_factor × reltuples
(defaults 50 and 0.2 → roughly 20% of rows dead). Lower the scale factor for large,
hot tables so they vacuum sooner.
— intro'd in lesson 0002
- VACUUM FULL
- A heavier operation that rewrites the entire table with no dead space, shrinking
the file on disk — but it takes an
ACCESS EXCLUSIVE lock (blocks all
reads and writes). A last resort for badly bloated tables, not routine maintenance.
— intro'd in lesson 0002
- HOT update (Heap-Only Tuple)
- An
UPDATE optimization: when no indexed column changes and
there is room on the same heap page, the new version is chained in-page and
no new index entries are created. Avoids index write amplification,
and the chain can be pruned during ordinary reads. Encouraged by a lower
fillfactor.
— intro'd in lesson 0002
- fillfactor
- A per-table storage setting (percentage) that reserves free space on each page at
insert time. Below 100 it leaves room for future in-page row versions, raising the
chance an
UPDATE can be a HOT update.
— intro'd in lesson 0002
- secondary index
- In Postgres, every index: a structure stored separately from the heap
that points into it. There is no clustered index — contrast InnoDB, where the table
itself is the one clustered index and only the rest are secondary.
— intro'd in lesson 0001
- ctid / TID
- A tuple's physical address as
(page, slot) — how an index entry
points into the heap. It is not stable: an UPDATE
creates a new tuple with a new ctid, which is why you can watch the
keystone happen by selecting ctid before and after an update.
— intro'd in lesson 0001
- visibility map
- A bit per heap page marking it "all-visible" (every tuple on it is visible to all
transactions). Because visibility lives in the heap, not the index, this map is what
lets an index-only scan skip the heap fetch when the bit is set.
Maintained by
VACUUM — so index-only scans are only
fast on a well-vacuumed table.
— intro'd in lesson 0001, full treatment in 0003
- heap fetch
- The second step of an index scan: after the index yields a
ctid,
Postgres reads the actual tuple from the heap page. An index-only scan tries to avoid
it via the visibility map. In EXPLAIN (ANALYZE) the Heap Fetches:
line counts how many were still needed (0 = fully index-only).
— intro'd in lesson 0003
- index-only scan
- An access path that answers a query from the index alone, skipping the heap fetch —
but only for heap pages whose visibility-map bit is set (all-visible). Falls back to a
normal index scan on pages that aren't. Requires the index to contain every column the
query needs.
— intro'd in lesson 0003
- covering index / INCLUDE
- An index that contains every column a query needs, so it can be served by an
index-only scan. Postgres lets you add non-key payload columns with
INCLUDE (…): they ride along in the index but aren't part of the search
key. The Postgres analogue of an InnoDB covering index — but subject to the
visibility-map condition.
— intro'd in lesson 0003
- XID (transaction ID) · xmin / xmax
- A 32-bit counter Postgres assigns to transactions. Each heap tuple's header stores
xmin (the XID that inserted it) and xmax (the XID that
expired it). MVCC decides visibility by comparing these against the reader's snapshot:
an insertion XID "in the future" is not visible.
— intro'd in lesson 0004
- transaction ID wraparound
- Because XIDs are 32-bit and compared with modulo-2³² (circular) arithmetic, from any
point there are ~2 billion "past" and ~2 billion "future" XIDs. An unmaintained old row
that drifts more than 2 billion transactions behind re-enters the "future" half and
becomes invisible — silent data loss. Prevented by freezing.
— intro'd in lesson 0004
- freezing / FrozenTransactionId
- VACUUM's third job: marking sufficiently-old, all-visible rows as frozen so
they are treated as inserted by the special
FrozenTransactionId, always
older than every normal XID and thus immune to wraparound. Why every table must be
vacuumed at least once per two billion transactions.
— intro'd in lesson 0004
- anti-wraparound autovacuum
- A forced autovacuum that freezes a table once its oldest unfrozen XID reaches
autovacuum_freeze_max_age (default 200 million) — it runs even if
autovacuum is disabled. If freezing keeps failing, Postgres warns, then refuses to
assign new XIDs (writes fail, reads continue) to avoid data loss.
— intro'd in lesson 0004
- snapshot
- A captured record of which transactions had committed at a chosen instant —
effectively a set of XIDs. MVCC compares each tuple's
xmin/xmax
against it to decide visibility. An isolation level is simply a choice of when
this snapshot is taken and how long it is held.
— intro'd in lesson 0005 (mechanism from 0004)
- Read Committed
- Postgres's default isolation level: each statement takes a fresh
snapshot at the instant it begins, so two statements in one transaction can see
different data. Conflicting writes re-read the latest committed row rather than erroring.
— intro'd in lesson 0005
- Repeatable Read (snapshot isolation)
- One snapshot is taken at the transaction's first statement and held to commit, giving
stable reads and no phantoms. A write to a row changed by a concurrent transaction after
the snapshot fails with "could not serialize access due to concurrent update"
and must be retried. Postgres's RR is full snapshot isolation (stronger than the SQL
standard's RR).
— intro'd in lesson 0005
- Serializable (SSI)
- Repeatable Read plus a watchdog: Serializable Snapshot Isolation monitors read/write
dependencies (via predicate locking) and aborts a transaction whose interleaving has no
equivalent serial order — catching write skew, which RR allows. Aborts with
"could not serialize access due to read/write dependencies among transactions."
— intro'd in lesson 0005
- write skew
- A serialization anomaly snapshot isolation permits: two transactions each read an
overlapping set, each verify a constraint that currently holds, and each write based on
that read — individually valid, jointly impossible in any serial order. Prevented only at
Serializable.
— intro'd in lesson 0005
- serialization failure (SQLSTATE 40001)
- The error class both Repeatable Read and Serializable raise on a conflict they can't
resolve. Applications at those levels must catch it and retry the whole transaction; a
single 40001 retry loop covers both.
— intro'd in lesson 0005
- cost (in EXPLAIN)
- A plan node's
cost=startup..total, in arbitrary units where one sequential
page fetch is conventionally 1.0. It is for comparing plans, not a time in
seconds, and a node's cost includes all its children.
— intro'd in lesson 0006
- rows / width (in EXPLAIN)
rows is the planner's estimate of rows a node emits
(after its own filtering) — not rows scanned, and not actual. width is the
estimated average output row size in bytes. Actual counts appear only with
ANALYZE.
— intro'd in lesson 0006
- EXPLAIN ANALYZE
- Runs
EXPLAIN and actually executes the query, printing real
time, row counts, and loops beside the estimates. Because it executes, a data-modifying
statement really runs — wrap it in BEGIN … ROLLBACK to inspect safely. The
top diagnostic is comparing estimated vs actual rows.
— intro'd in lesson 0006
- BUFFERS
- An
EXPLAIN option (implicitly on with ANALYZE) reporting blocks
hit (served from the cache/buffer pool) vs read (fetched from disk). The
read count is what makes an otherwise-cheap-looking plan slow on a cold cache.
— intro'd in lesson 0006
- Bitmap Heap Scan
- An access path that builds a bitmap of matching tuple locations from an index, then reads
the heap in physical page order. Postgres's middle gear for "many matches, scattered" —
between a single Index Scan and a full Seq Scan.
— intro'd in lesson 0006
- cost-based planner
- Postgres's query planner estimates the cost of every candidate plan (seq scan, index scan,
bitmap scan, join orders) and executes the cheapest. An index is never "ignored" — it is
chosen only when its estimated cost is lowest.
— intro'd in lesson 0008
- selectivity
- The fraction of a table's rows a condition matches. It drives the plan: few rows → index
scan; a medium/scattered set → bitmap heap scan; a large fraction → sequential scan (one
sweep beats many random heap jumps).
— intro'd in lesson 0008
- seq_page_cost / random_page_cost
- Planner cost constants: a sequential page fetch is
1.0, a random (out-of-order)
page fetch defaults to 4.0 — the ratio that makes index heap-jumps look
expensive. On SSDs or a heavily-cached database, lower random_page_cost (toward
~1.1) so index scans win their fair comparisons.
— intro'd in lesson 0008
- effective_cache_size
- The planner's assumption about how much OS + Postgres cache is available to a query
(default 4GB). It doesn't allocate memory — it's an estimate; a higher value makes index
scans more likely, a lower value makes sequential scans more likely.
— intro'd in lesson 0008
- expression index
- An index built on an expression rather than a bare column, e.g.
CREATE INDEX ON t (lower(email)). Needed because a predicate that wraps a column
in a function (WHERE lower(email) = …) cannot use a plain index on the raw
column. The Postgres answer to MySQL's "wrapped column" trap.
— intro'd in lesson 0008
- enable_seqscan (diagnostic)
- A session setting that, when turned
off, penalizes sequential scans so you can
see what an index plan would cost via EXPLAIN ANALYZE. A diagnostic to
confirm mis-set costs or stale stats — not a production setting.
— intro'd in lesson 0008
- WAL (Write-Ahead Log)
- The durability log. Its central rule: a change's WAL record must be flushed to durable
storage before the data page it changes is written. So at commit only the (sequential)
WAL must be flushed, not every scattered data page. Postgres's WAL is the equivalent of
InnoDB's redo log — pure roll-forward; there is no undo log.
— intro'd in lesson 0009
- REDO / roll-forward recovery
- Crash recovery replays WAL records to re-apply any committed change that hadn't yet reached
the data files. It starts from the last checkpoint's redo record, not the beginning of time.
— intro'd in lesson 0009
- checkpoint
- A point at which all dirty data pages are flushed to disk and a checkpoint record is written
to the WAL. Recovery replays WAL only from the latest checkpoint, and WAL segments before it
can be recycled. Frequent checkpoints shorten recovery but add I/O; infrequent ones do the
reverse.
— intro'd in lesson 0009
- synchronous_commit
- The durability dial.
on (default) makes COMMIT wait for the local
WAL flush before returning. off returns earlier for speed, risking loss of a small
window of recent commits on a crash — but not corruption; the database stays
consistent (unlike fsync=off). Analogue of InnoDB's
innodb_flush_log_at_trx_commit.
— intro'd in lesson 0009
- LSN (log sequence number)
- A position in the WAL stream (e.g. from
pg_current_wal_lsn()), advancing as
changes are logged. The ordering handle behind recovery and replication.
— intro'd in lesson 0009
- streaming (physical) replication
- The default replication: a standby continuously receives the primary's WAL records and
replays them, producing a byte-for-byte copy of the whole cluster (same
version), serving read-only queries. The basis of high-availability failover.
— intro'd in lesson 0010
- asynchronous vs synchronous replication
- Async (default): WAL is shipped after commit, so a standby lags and a
primary crash loses transactions not yet shipped (failover data-loss window).
Sync (set
synchronous_standby_names): commit waits until the
standby has the WAL on disk — no loss, but a standby outage can stall commits. It's
synchronous_commit extended over the network.
— intro'd in lesson 0010
- logical replication
- Replication of row-level changes by replication identity (usually the
primary key) via a publish/subscribe model — "copy the meaning," in contrast to physical
byte-by-byte copying. Enables replicating a subset of tables, across major versions or
platforms, into a writable target. The closer cousin of MySQL's binlog replication.
— intro'd in lesson 0010