Library knowledge map

See how it all connects, and jump straight to what's next

This is the library graph — not the engine schematic. Search finds a page; this map shows what it connects to. For “how is PostgreSQL built as a machine?”, use the Architecture atlas.

405 pages562 real connectionsFirefight: errors → fixes → parametersLearn: lessons ↔ concepts ↔ pathwaysEngine schematic → Architecture
drag to pan · scroll to zoom · click a node to explore

Browse the whole library

The same graph as a plain, linked index — grouped by kind, most-connected first, with each entry's direct connections listed beneath it.

Errors

102· SQLSTATE failures with reproduction + fix
Could not obtain lock on row

A session asked to lock a row with NOWAIT, but another session already had that row locked.

Could not serialize access due to concurrent update

The REPEATABLE READ concurrent-update form of SQLSTATE 40001: a REPEATABLE READ transaction attempted to update a row that a concurrently committed transaction…

Idle in transaction session timeout

A session left an open transaction sitting idle (no query in flight) for longer than idle_in_transaction_session_timeout.

New row violates check constraint

A column or table has a CHECK constraint, and an INSERT or UPDATE produced a row where that check evaluated to false.

Division by zero

An expression divided a number by zero. PostgreSQL raises an error instead of returning infinity or an undefined result.

Undefined data type

Referencing a type that has not been created (or is misspelled or out of the search_path) raises this undefined-object error; 'currency' is not a built-in type.

Wrong object type (DROP INDEX on a table)

Object-specific DDL like DROP INDEX only accepts that object type; running DROP INDEX against a table raises this wrong-object-type error with a hint to use DR…

Column reference is ambiguous

A query referenced a bare column name that exists in more than one table in the FROM clause, so PostgreSQL could not tell which table you meant.

Connection to client lost

A client was abruptly killed mid-query, severing the connection while the server still had output to send.

COPY: extra data after last column

COPY fails when an input row has more fields than the target column list expects; a three-field, tab-separated line loaded into a narrower target raises this b…

Multiple primary keys not allowed

A table can have at most one PRIMARY KEY; declaring PRIMARY KEY on two columns separately raises this invalid-table-definition error.

New row violates exclusion constraint

A table has an EXCLUDE constraint, and a new row conflicts with an existing row under the constraint's comparison — most commonly, two overlapping time ranges…

No data found

A PL/pgSQL SELECT ... INTO STRICT found zero matching rows. PostgreSQL raised an error instead of silently leaving the target null.

PL/pgSQL assertion failure

A PL/pgSQL ASSERT whose condition evaluates to false raises this error with the assertion's message; it signals a violated invariant, not ordinary data validat…

RESTRICT foreign-key violation

Deleting or updating a parent row still referenced by a child fails when the foreign key is ON DELETE/UPDATE RESTRICT.

Active SQL transaction

VACUUM (and a few other commands) refuse to run while a transaction block is still open.

CASE not found (no matching branch)

A PL/pgSQL CASE statement with no matching WHEN and no ELSE raises this at runtime when the selector matches none of the branches.

Cursor does not exist

A CLOSE (or FETCH/MOVE) named a cursor that was never declared, or was already closed.

Datetime field overflow

A date/time value was well-formed text, but named a value that doesn't exist on the calendar or falls outside PostgreSQL's supported range.

Duplicate prepared statement

Preparing a statement with a name that is already prepared in the session raises this error; prepared-statement names must be unique per session.

Duplicate schema

CREATE SCHEMA fails if a schema of that name already exists; IF NOT EXISTS makes the creation idempotent.

Function ended without RETURN

A function declared to return a value must execute a RETURN on every path; a code path that falls off the end without RETURN raises this SQL-routine exception…

No unique constraint for foreign key

A foreign key must reference columns backed by a PRIMARY KEY or UNIQUE constraint on the parent; referencing unconstrained columns raises this invalid-foreign-…

ON CONFLICT DO UPDATE requires inference specification or constraint name

INSERT ... ON CONFLICT DO UPDATE was written without telling PostgreSQL which unique index or constraint should decide the conflict. DO UPDATE always needs an…

Bind message parameter mismatch

A Bind message in the extended query protocol supplied a different number of parameter values than the prepared statement expected.

Collation mismatch (explicit collations)

A comparison cannot combine two different explicit COLLATE clauses; applying 'C' to one side and 'POSIX' to the other raises this collation-mismatch error.

Duplicate function

CREATE FUNCTION fails when a function with the same name and argument types already exists; CREATE OR REPLACE updates it instead of colliding.

Duplicate table alias

Each table or alias in a FROM clause must have a unique name; using the same alias twice raises this duplicate-alias error.

Function does not exist

A function was called with an argument type it doesn't accept.

GET STACKED DIAGNOSTICS outside a handler

GET STACKED DIAGNOSTICS reads details of the exception currently being handled, so it is only valid inside an EXCEPTION handler; using it elsewhere raises this…

Index expression must be immutable

An expression index may only use IMMUTABLE functions, because a changing result would silently corrupt the index; a non-immutable function in the expression ra…

Interval field overflow

An INTERVAL literal whose field value exceeds the storage limits of the interval type raises this data exception; here a months value larger than a signed 32-b…

Invalid catalog name

A connection attempt named a database that doesn't exist.

Invalid datetime format

A text value was cast to a date/time type, but PostgreSQL couldn't parse it as any recognized date/time format at all.

Invalid XML comment

xmlcomment() rejects text that would produce an illegal XML comment — XML comments cannot contain a double hyphen or end with a hyphen.

Invalid XML content (CONTENT)

XMLPARSE(CONTENT ...) still requires well-formed markup; an unclosed tag raises this data exception with a DETAIL about the premature end of data.

Invalid XML document (DOCUMENT)

XMLPARSE(DOCUMENT ...) requires well-formed XML; mismatched opening and closing tags raise this data exception with a DETAIL pointing at the mismatch.

Invalid XML processing instruction

xmlpi() rejects an illegal processing-instruction target; the name 'xml' (in any case) is reserved and cannot be used as a target.

LIMIT must not be negative

A query passed a negative value to LIMIT. PostgreSQL requires LIMIT to be non-negative; use LIMIT ALL or NULL to mean “no limit”.

Logarithm of a negative number

PostgreSQL raises this data exception when ln(), log(), or log(b, x) is asked for the logarithm of a value that is zero or negative, which is undefined for the…

Negative OFFSET in a query

The OFFSET clause must skip a non-negative number of rows; an OFFSET that evaluates to a negative value raises this data exception.

Negative window frame offset

A window frame's PRECEDING/FOLLOWING offset must be non-negative; a negative frame bound raises this data exception.

No active SQL transaction

A command that only makes sense inside an open transaction block was issued with none active.

ntile argument must be positive

The ntile(n) window function divides rows into n ranked buckets and requires n to be a positive integer; zero or a negative bucket count raises this data excep…

ORDER BY position out of range

ORDER BY and GROUP BY can reference output columns by position, but the position must be within the select list; a position beyond the number of selected colum…

Prepared statement does not exist

EXECUTE or DEALLOCATE of a prepared-statement name that was never prepared (or was already deallocated) raises this invalid-statement-name error.

Reserved schema name (pg_ prefix)

Schema names beginning with 'pg_' are reserved for system schemas; creating one raises this reserved-name error.

Sequence reached its maximum value

nextval() fails once a non-cycling sequence hits its MAXVALUE; a sequence capped at 2 raised the limit error on the third call.

Time zone displacement out of range

A timestamptz literal whose explicit UTC offset lies outside the valid range (about -15:59 to +15:59) raises this data exception; +16:00 is out of range.

UNION types cannot be matched

A UNION mixed two branches whose types can't be converted to each other.

Unique constraint on partitioned table

A UNIQUE constraint was added to a partitioned table without including all of the partition key columns.

width_bucket count must be positive

width_bucket(operand, low, high, count) requires the bucket count to be a positive integer; passing zero or a negative count raises this data exception.

WITH CHECK OPTION violation

Writing through a view defined WITH CHECK OPTION fails when the new or updated row would fall outside the view's WHERE condition; the row must remain visible t…

Zero raised to a negative power

power(0, n) with a negative exponent is undefined because it implies division by zero, so PostgreSQL raises this data exception instead of returning infinity.

Column must appear in GROUP BY

A non-aggregated column in the SELECT list of a grouped query must appear in GROUP BY or be wrapped in an aggregate; otherwise PostgreSQL raises this grouping…

COPY FREEZE with prior transaction activity

COPY ... WITH (FREEZE) was attempted while a cursor was still open in the same transaction. PostgreSQL refused because it can no longer guarantee no one else c…

could not accept SSL connection: EOF detected

PostgreSQL started an SSL handshake on a new connection, then the peer closed the TCP socket before the handshake finished.

Duplicate cursor

Declaring a cursor with a name already open in the same transaction raises this error; cursor names must be unique within a transaction.

Duplicate function parameter name

A function's named parameters must be distinct; declaring two parameters with the same name raises this invalid-function-definition error.

Indeterminate collation

When PostgreSQL cannot derive a collation for a string operation, it raises this error and hints to add an explicit COLLATE clause.

Invalid byte sequence for encoding

Interpreting bytes as text in a given encoding fails when the bytes are not valid there; 0xff is never a valid standalone UTF-8 byte.

Invalid LIKE ESCAPE string

The ESCAPE clause of LIKE accepts an empty string or exactly one character; a multi-character escape string raises this data exception.

JSON_VALUE must return a single scalar

JSON_VALUE must resolve to a single scalar; a path that returns an array, object, or multiple items raises this data exception — use JSON_QUERY for non-scalars.

Negative substring length not allowed

substring(... FOR count) was given a negative count. PostgreSQL treats a negative substring length as a data error rather than returning an empty string.

No SQL/JSON item for path (ON EMPTY)

JSON_VALUE raises this when the path matches nothing and the ON EMPTY behavior is ERROR; a DEFAULT ...

nth_value argument must be positive

nth_value(expr, n) returns the value from the n-th row of the window frame and requires n to be a positive integer; zero or a negative position raises this dat…

Null value not allowed

A PL/pgSQL variable declared NOT NULL was assigned a null value.

Read-only SQL transaction

A write was attempted inside a transaction explicitly marked READ ONLY.

ROLLBACK TO an undeclared savepoint

ROLLBACK TO SAVEPOINT (or RELEASE SAVEPOINT) fails if the named savepoint was never established in the current transaction, raising this savepoint exception.

Schema does not exist

A CREATE TABLE statement referenced a schema name that doesn't exist yet.

Syntax error

PostgreSQL's parser rejected the statement because it didn't match valid SQL grammar — here, an unquoted reserved key word used as a table name.

Too many rows

A PL/pgSQL SELECT ... INTO STRICT matched more than one row. PostgreSQL raised an error instead of silently picking one of them.

Untranslatable character between encodings

Converting text to a target encoding fails when a character has no representation there; the euro sign (U+20AC) has no LATIN1 equivalent.

Invalid password

A connection attempt supplied the wrong password for a password-authenticated role.

Invalid regular expression

A regular expression pattern passed to a PostgreSQL function was not valid — usually unbalanced parentheses or brackets.

Permission denied

A role tried to use a table it has not been granted privileges on.

More than one row returned by a subquery used as an expression

A scalar subquery matched more than one row.

No cross-links yet.

String data length mismatch

A bit string value was stored into a bit(n) column whose length didn't exactly match n.

No cross-links yet.

Too many function arguments

A function was called with more positional arguments than PostgreSQL allows in a single call.

No cross-links yet.

Undefined parameter

A query referenced a positional parameter ($1, $2, ...) outside of any context that supplies a value for it.

No cross-links yet.

Runbooks

75· Step-by-step operational recipes
Detect and resolve lock contention

When queries hang on a lock, pg_blocking_pids and pg_locks tell you exactly who is waiting on whom.

Survive a connection storm when there is no pooler

A retry policy opened a fresh backend every time a query timed out, until every non-reserved slot in max_connections was gone.

Build a safe job queue with SKIP LOCKED

FOR UPDATE SKIP LOCKED lets multiple workers pull from the same queue table without stepping on each other — worker two grabs the next batch instead of stallin…

Capture Sev-1 evidence before recovery erases it

pg_stat_activity, pg_locks and the blocking chain describe only this second — restoring service deletes them.

Index the foreign key you forgot

PostgreSQL indexes the parent key of a foreign key but never the child column.

Rebuild a bloated index with REINDEX CONCURRENTLY

Indexes bloat too. REINDEX INDEX CONCURRENTLY rebuilds an index without blocking reads and writes — here a bloated index shrinks from 19 MB to 4,832 kB.

Recover from a failed CREATE INDEX CONCURRENTLY

When CREATE INDEX CONCURRENTLY fails partway, it leaves an INVALID index behind that still costs writes but serves no reads.

Resolve row-level lock contention from SELECT FOR UPDATE

One session held a row with SELECT ... FOR UPDATE and a second sat behind it for 1.5 seconds on a transactionid wait. No table lock was involved, which is why…

Watch CREATE INDEX CONCURRENTLY progress

A CIC that reports 0 blocks done is usually not stuck — it is waiting.

Watch wait events in pg_stat_activity

wait_event_type and wait_event in pg_stat_activity tell you why a session is not running — waiting on a lock, on the client, or sleeping.

Add a NOT NULL column to a huge table safely

Instead of a blocking SET NOT NULL that scans the whole table under lock, add a CHECK (col IS NOT NULL) NOT VALID, VALIDATE it without a heavy lock, then flip…

Assess multixact wraparound risk

Multixact IDs have their own counter, their own freeze thresholds and their own way of shutting a cluster down — and almost nobody monitors them.

Coordinate work with advisory locks

Advisory locks let application code claim a named lock so only one worker runs a job at a time.

Custom plan versus generic plan in prepared statements

After five executions PostgreSQL may freeze a prepared statement onto a generic plan.

Eliminate redundant and duplicate indexes

An index on (courier_id) is redundant when an index on (courier_id, booked_at) already exists — the composite serves both.

Find and drop unused indexes

pg_stat_user_indexes records how often each index is scanned.

Keep ALTER TABLE from blocking your app

An ALTER TABLE waits for an exclusive lock, and every reader that arrives behind it queues too — one migration stalls everyone.

Monitor a CLUSTER or VACUUM FULL rewrite

CLUSTER and VACUUM FULL hold ACCESS EXCLUSIVE for the whole rewrite, so the only useful question is how much longer.

Read pg_locks: relation waits versus tuple waits

pg_locks mixes relation, tuple and transaction ID locks into one flat view.

Reclaim WAL held by an inactive replication slot

A replication slot guarantees WAL is kept until the consumer reads it — but an abandoned, inactive slot keeps that WAL forever and fills the disk.

Replace deep OFFSET paging with keyset pagination

Page 10,000 of an OFFSET query re-reads everything before it.

Right-size synchronous_commit for latency

A real PG16 top-level DO lab committed 300 single-row transactions in each mode.

Right-size work_mem for hash aggregates

When a GROUP BY does not fit in work_mem, the planner falls back to a sort that spills to disk (external merge, 5912kB here).

Stop disk-spilled sorts by tuning work_mem

A sort bigger than work_mem spills to temp files and does an external merge.

Validate a CHECK constraint without a long lock

Add a CHECK as NOT VALID and it enforces new writes immediately while skipping the scan of existing rows; VALIDATE later scans without blocking writes.

Catch queries spilling to temp files

When a sort or hash exceeds work_mem it spills to disk as an 'external merge'.

Find tables autovacuum keeps skipping

A table with autovacuum_enabled=false is never cleaned, no matter how much it bloats.

Find your slowest queries with pg_stat_statements

pg_stat_statements aggregates every query by normalized shape, so you can rank by total time spent — not just per-call time.

Fix random_page_cost for SSD storage

random_page_cost defaults to 4.0, a spinning-disk assumption.

Flatten checkpoint I/O spikes by tuning checkpoints

checkpoint_completion_target changes the pacing window for dirty-buffer writes.

Help the planner with effective_cache_size

effective_cache_size tells the planner how much memory it can assume is available for caching.

Monitor VACUUM progress on a large table

VACUUM prints nothing until it is done. pg_stat_progress_vacuum names the phase and the block counter, so you can tell 3,445 of 19,608 blocks scanned from a jo…

Reduce WAL volume with wal_compression

Turning wal_compression on cut the WAL for a full-table update from 82 MB to 68 MB — 17.1% with pglz, 16.1% with lz4.

Rotate md5 passwords to SCRAM without locking out clients

Flipping pg_hba.conf to scram-sha-256 while roles still store md5 verifiers logs every one of them out on the same reload.

Shrink TOAST bloat from wide rows

Large values live in a separate TOAST table and it bloats independently.

Size WAL with max_wal_size and checkpoint_timeout

Measure WAL bytes and elapsed time for a representative workload, then project that rate across checkpoint_timeout.

Slow query from a sequential scan on a large table

A lookup that used to be instant now reads the whole table.

Speed up COUNT(*) on a large table

SELECT count(*) on a big table scans every row, every time.

Spot checkpoint write pressure before it hurts

Requested checkpoints rising faster than timed checkpoints means WAL volume is ending checkpoint cycles early.

Tame a lossy bitmap heap scan

When a bitmap scan runs out of work_mem it stops tracking individual rows and rechecks whole pages.

Tame autovacuum on a high-churn table

The default autovacuum threshold (20% of the table) is far too lax for a hot table.

Why parallel query did not kick in

A big aggregate runs single-threaded while your cores sit idle.

BRIN indexes for huge append-only tables

On an append-only table whose rows arrive in physical order, a BRIN index stores per-block ranges instead of per-row pointers.

No cross-links yet.

Choose the right join: fix a nested loop on a misestimate

Point-lookup joins want a nested loop over an index; missing that index forces a hash join that scans both tables.

No cross-links yet.

Control CTE materialization fences

A WITH block can be an optimization fence that spills to disk before your WHERE clause runs.

No cross-links yet.

Covering indexes with INCLUDE

Add the columns a query returns as INCLUDE payload and the index can answer it without touching the table.

No cross-links yet.

Cure HOT update degradation with fillfactor

Same table, same 1,200,000 updates. At fillfactor 100 only 319 of them were HOT and the heap grew to 59 MB. At fillfactor 70, 912,015 were HOT — 76.0% — and th…

No cross-links yet.

Diagnose detoasting overhead on wide rows

The same 5,000-row index scan took 0.887 ms selecting three narrow columns and 120.265 ms once it touched the TOASTed body.

No cross-links yet.

Diagnose I/O behavior with pg_stat_io

A single cache hit ratio hides which part of the workload is doing the damage.

No cross-links yet.

Find unlogged tables before a crash empties them

An UNLOGGED table skips the WAL, so PostgreSQL truncates it during crash recovery rather than serve contents it cannot vouch for.

No cross-links yet.

Fix bad row estimates with extended statistics

When two columns are correlated the planner multiplies their selectivities and guesses badly.

No cross-links yet.

Get the multicolumn index column order right

In a composite index, column order decides whether a range query can use it.

No cross-links yet.

GIN indexes for jsonb containment queries

The jsonb containment operator @> cannot use a B-tree.

No cross-links yet.

Make index-only scans actually skip the heap

Without a current visibility map the planner often will not pick a true index-only scan at all.

No cross-links yet.

Measure and raise your buffer cache hit ratio

The cache hit ratio from pg_stat_database tells you how often reads are served from shared_buffers instead of disk.

No cross-links yet.

Measure your WAL generation rate

pg_current_wal_lsn plus pg_wal_lsn_diff lets you measure exactly how much WAL a workload produces.

No cross-links yet.

Partial indexes for hot subsets of data

If queries only ever touch a small slice of a table, index just that slice.

No cross-links yet.

Preempt transaction ID wraparound

PostgreSQL's 32-bit transaction IDs must be frozen before they wrap.

No cross-links yet.

Read EXPLAIN (ANALYZE, BUFFERS) like a pro

The BUFFERS option shows shared hit (found in cache) versus read (fetched from disk).

No cross-links yet.

Reduce high planning time on complex queries

A fourteen-relation star join spent 378.737 ms planning.

No cross-links yet.

Refresh stale statistics that break query plans

After a bulk load the planner's stats lag reality, so it estimates 1 row where there are 312 and picks the wrong plan.

No cross-links yet.

Replace an N+1 pattern with a LATERAL join

Fetching the latest child row per parent by scanning all children and de-duplicating is wasteful.

No cross-links yet.

Stop work_mem from OOM-killing the server

work_mem is a per-operation budget, not a per-server one.

No cross-links yet.

Track ANALYZE progress on large and partitioned tables

ANALYZE on a partitioned table is really one job per partition plus one for the parent.

No cross-links yet.

Track table and index bloat over time

pg_stat_user_tables exposes dead-tuple counts, so you can compute a bloat percentage and watch it.

No cross-links yet.

Parameters

36· Server settings (GUCs) you tune
idle_in_transaction_session_timeout

Terminates a session that has an open transaction but is genuinely idle (no query in flight) for longer than this many milliseconds.

deadlock_timeout

Controls how long a backend waits while blocked on a lock before PostgreSQL runs its deadlock-detection cycle check.

lock_timeout

Aborts any statement that waits longer than this many milliseconds while trying to acquire a lock.

log_lock_waits

Logs long lock waits.

log_min_duration_statement

Sets the minimum execution time above which all statements will be logged.

max_connections

Sets the maximum number of concurrent connections.

statement_timeout

Aborts any statement that runs longer than this many milliseconds.

autovacuum

Starts the autovacuum subprocess.

No cross-links yet.

autovacuum_freeze_max_age

Age at which to autovacuum a table to prevent transaction ID wraparound.

No cross-links yet.

autovacuum_naptime

Time to sleep between autovacuum runs.

No cross-links yet.

autovacuum_vacuum_cost_limit

Vacuum cost amount available before napping, for autovacuum.

No cross-links yet.

autovacuum_vacuum_scale_factor

Number of tuple updates or deletes prior to vacuum as a fraction of reltuples.

No cross-links yet.

checkpoint_completion_target

Time spent flushing dirty buffers during checkpoint, as fraction of checkpoint interval.

No cross-links yet.

checkpoint_timeout

Sets the maximum time between automatic WAL checkpoints.

No cross-links yet.

default_statistics_target

Sets the default statistics target.

No cross-links yet.

effective_cache_size

Sets the planner's assumption about the total size of the data caches.

No cross-links yet.

effective_io_concurrency

Number of simultaneous requests that can be handled efficiently by the disk subsystem.

No cross-links yet.

full_page_writes

Writes full pages to WAL when first modified after a checkpoint.

No cross-links yet.

hash_mem_multiplier

Multiple of "work_mem" to use for hash tables.

No cross-links yet.

hot_standby_feedback

Allows feedback from a hot standby to the primary that will avoid query conflicts.

No cross-links yet.

jit

Allow JIT compilation.

No cross-links yet.

maintenance_work_mem

Sets the maximum memory to be used for maintenance operations.

No cross-links yet.

max_locks_per_transaction

Sets the maximum number of locks per transaction.

No cross-links yet.

max_wal_senders

Sets the maximum number of simultaneously running WAL sender processes.

No cross-links yet.

max_wal_size

Sets the WAL size that triggers a checkpoint.

No cross-links yet.

min_wal_size

Sets the minimum size to shrink the WAL to.

No cross-links yet.

random_page_cost

Sets the planner's estimate of the cost of a nonsequentially fetched disk page.

No cross-links yet.

seq_page_cost

Sets the planner's estimate of the cost of a sequentially fetched disk page.

No cross-links yet.

shared_buffers

Controls the size of PostgreSQL's shared buffer cache and influences cache hit rate, checkpoint behavior, and memory pressure.

No cross-links yet.

synchronous_commit

Sets the current transaction's synchronization level.

No cross-links yet.

temp_buffers

Sets the maximum number of temporary buffers used by each session.

No cross-links yet.

wal_buffers

Sets the number of disk-page buffers in shared memory for WAL.

No cross-links yet.

wal_compression

Compresses full-page writes written in WAL file with specified method.

No cross-links yet.

wal_keep_size

Sets the size of WAL files held for standby servers.

No cross-links yet.

wal_level

Sets the level of information written to the WAL.

No cross-links yet.

work_mem

Sets per-operation memory for sorts, hashes, and similar executor nodes, so one query can consume it many times over.

No cross-links yet.

Concepts

34· Interview-grade internals, four lenses
Extended statistics (CREATE STATISTICS: dependencies, ndistinct, MCV)

The default planner assumes columns are independent: for 'a=x AND b=y' it multiplies the per-column selectivities, P(a)*P(b).

Heavyweight lock manager (lock modes, conflict table, fast-path, deadlock detector)

Eight table-level (heavyweight) lock modes, ordered weak to strong: AccessShare, RowShare, RowExclusive, ShareUpdateExclusive, Share, ShareRowExclusive, Exclus…

Parallel query: the Gather node, and planned vs launched workers

The Gather node is the seam. Below it the work runs in N background worker processes — plus, by default, the leader itself. Above it the leader collects the pa…

Planner join selection: nested loop vs hash vs merge, the cost crossover, and disable_cost

There is no ranking. The planner cost-models all three join strategies for every join pair and keeps whichever comes out cheapest. Nested loop wins when one si…

Executor PlanState tree and ExecProcNode pull

ExecutorStart/standard_ExecutorStart create an EState and call ExecInitNode to build a PlanState tree mirroring the Plan.

Index LP_DEAD pruning (btree kill_prior_tuple)

It is not only VACUUM. When an index scan follows an index entry to its heap tuple and finds that tuple dead to every running transaction, the executor sets sc…

Partition pruning (plan-time, init-time, exec-time)

There are three pruning moments, not one. (1) plan-time (planner) pruning: when the partition-key restriction is a constant the planner knows, prune_append_rel…

ANALYZE from source: sampling rows into pg_statistic

ANALYZE for a table enters analyze_rel() (commands/analyze.c), which locks the relation and calls do_analyze_rel().

Autovacuum thresholds: the dead-tuple trigger formula and what drives it

For each table the autovacuum daemon computes a dead-tuple threshold: vacthresh = autovacuum_vacuum_threshold + autovacuum_vacuum_scale_factor * reltuples (def…

B-tree deduplication & posting-list tuples

In a non-unique btree, a run of tuples that all share the same key value gets merged into one physical posting-list tuple: the key stored once, then a sorted a…

Clock-sweep buffer eviction, usage_count & the BAS_BULKREAD ring

PostgreSQL doesn't keep a true LRU. Each shared buffer has a usage_count (0..5). A 'clock sweep' hand walks the buffer array; it skips any pinned buffer or one…

Multixacts (shared row locks)

A row header has a single t_xmax slot for 'who locks/deletes me'.

The commit log (clog/pg_xact): where a transaction's outcome is recorded

Every transaction's final outcome lives in the commit log — clog, on disk under pg_xact — as exactly two bits per xid.

TOAST (The Oversized-Attribute Storage Technique): compression, out-of-line storage, chunking

PostgreSQL can't span a row across heap pages, so any variable-length value that would make the tuple exceed TOAST_TUPLE_THRESHOLD (~2032 bytes on a default 8K…

Visibility map & index-only scans

The visibility map (VM) is a 2-bits-per-heap-page bitmap (all-visible, all-frozen).

WAL full-page images (FPI) and checkpoints

A full-page image (FPI) is exactly what it sounds like: a complete copy of an 8KB page, written into WAL.

WAL records: what an UPDATE writes, and the WAL-before-data rule

Every change is described by one or more WAL records before the modified data page is allowed to reach disk.

Why sequences leave gaps: caching, WAL log-ahead, and rollback

A sequence guarantees UNIQUE, monotonically increasing values -- not gap-free ones.

Connection scaling and GetSnapshotData (the PG14 dense-array snapshot rewrite)

Before PG14, taking a snapshot meant walking every backend's PGPROC entry and copying out xids and subxids.

Subtransaction SLRU overflow (the 64-subxid cliff)

PostgreSQL caches up to 64 of a transaction's active subxids directly in its shared-memory PGPROC slot.

Glossary

107· Plain-English definitions
MVCC

Multi-Version Concurrency Control lets readers and writers proceed concurrently by keeping row versions instead of blocking every read.

Selectivity

Selectivity is the fraction of rows a condition is expected to keep — a number between 0 and 1.

Access path (Path)

A Path is one candidate way to produce a result for a table or join — a sequential scan, an index scan, a hash join — annotated with a cost estimate but not th…

Checkpoint

A point at which PostgreSQL flushes dirty buffers to disk and records a WAL position, bounding how much WAL must be replayed after a crash.

Plan tree

The planner's output is a plan tree: a tree of operations — sequential scan, index scan, hash join, sort — that, executed bottom-up, produces your result.

Query planner

The cost-based optimizer that turns a parsed query into an execution plan by estimating the cost of alternative scans, joins, and orderings.

Checkpointer

The checkpointer is the background process that performs checkpoints: it flushes all dirty shared buffers to disk and writes a checkpoint WAL record, giving cr…

Executor node (PlanState)

When the executor runs a plan, each plan node becomes a live executor node (a PlanState) that holds the running state for that step — its position, its buffers…

Lock manager (heavyweight locks)

The subsystem that grants and queues table- and object-level locks, tracking what each transaction holds and what it waits for.

Page split (B-tree)

A page split happens when an insert lands on a B-tree page that is already full.

Patroni

Patroni is an open-source agent that runs beside each PostgreSQL node and automates high availability.

pgBackRest

pgBackRest is a widely used open-source backup and restore tool for PostgreSQL.

SLRU (Simple LRU cache)

An SLRU is a small, fixed-size, least-recently-used cache of 8KB pages, implemented once in slru.c and reused for several internal logs.

Startup cost vs total cost

Every plan node has two cost numbers. Startup cost is the work done before the first row can be returned; total cost is the work to return all rows. EXPLAIN sh…

Statistics target

The statistics target is a knob that controls how detailed a column's statistics are — how many most-common values and histogram buckets ANALYZE keeps, and how…

Tuple deforming

Deforming is the process of turning a packed on-disk row (a byte string with a header, optional NULL bitmap, and padded column values) back into usable column…

Equi-depth histogram

For the values that are not on the most-common list, PostgreSQL splits them into buckets that each hold about the same number of rows, and remembers the bounda…

Failover slot

A failover slot is a logical replication slot marked (with failover = true) to survive a failover.

Fast-path locking

Fast-path locking lets a backend record a weak relation lock (AccessShareLock, RowShareLock, or RowExclusiveLock — the kinds ordinary queries take, which never…

Full-page write (FPI)

A full-page write is a copy of an entire 8KB data page written into the WAL the first time that page is modified after a checkpoint — instead of just the small…

Incremental backup

An incremental backup copies only the files changed since the last backup of any type — full, differential, or another incremental.

Leader key (TTL lease)

The leader key is a single entry in the consensus store that marks which node is currently primary.

Most Common Values (MCV list)

When PostgreSQL studies a column with ANALYZE, it records a short list of the values that appear most often, together with how frequent each one is.

MultiXact (pg_multixact)

When several transactions hold a shared lock on the same row at once (for example SELECT ...

n_distinct

n_distinct is PostgreSQL's estimate of how many different values a column holds.

NULL bitmap

The NULL bitmap is a compact run of bits in the tuple header (t_bits[]), one bit per column, recording which columns are NULL.

Pathkeys

Pathkeys are how the planner records the sort order a path produces its rows in.

pg_promote (promotion)

pg_promote() is the PostgreSQL function that tells a standby to stop following the primary, exit recovery, and become a primary in its own right.

Pipelined vs blocking node

A pipelined node can hand up a result row as soon as it has one — a sequential scan, an index scan, a nested loop.

random_page_cost

random_page_cost is the planner's estimate of how expensive a random disk read is, relative to a sequential read fixed at 1.0.

RTO (Recovery Time Objective)

RTO is the maximum time you can be down before service is restored after a disaster.

Subtransaction overflow (pg_subtrans)

Every backend can cache up to 64 of its own active subtransaction ids in shared memory.

AllocSet allocator

The AllocSet (in aset.c) is the default memory-context implementation behind palloc.

attcacheoff (cached attribute offset)

attcacheoff is a cached byte offset for a column within a row.

Autovacuum

The background process that reclaims space from dead tuples and refreshes planner statistics without a human running VACUUM by hand.

Autovacuum launcher

The autovacuum launcher is the background process that schedules vacuuming.

Backend / connection limit

Each client connection is served by its own backend process; max_connections caps how many can exist at once, after which new connections are refused.

Background writer (bgwriter)

The background writer trickles dirty shared buffers out to disk ahead of demand, so that when a backend needs a free buffer it usually finds a clean one instea…

Block incremental backup

A block incremental backup goes finer than whole files: within a changed file it stores only the individual blocks (pages) that changed, rather than re-copying…

catalog_xmin

catalog_xmin is the oldest transaction id whose system catalog row versions must still be retained.

Commit log (clog / pg_xact)

The commit log is a small on-disk ledger (in the pg_xact/ directory) that stores, for every transaction id, just two bits of status: in progress, committed, ab…

Dead tuple

A row version that is no longer visible to any transaction but still occupies space on the heap page until vacuum removes it.

Deduplication (B-tree posting list)

When a B-tree leaf fills up with many rows that share the same key value, deduplication (_bt_dedup_pass() in nbtdedup.c, PG13+) merges them into one posting-li…

Delayed standby

A delayed standby is a replica deliberately held a fixed interval behind the primary.

Demand-pull execution (Volcano model)

Demand-pull execution means rows flow because the top of the plan asks for them.

Differential backup

A differential backup copies only the files that have changed since the last full backup.

DR drill

A DR drill is a deliberate rehearsal of your disaster-recovery procedure — typically a planned switchover or a restore-and-promote in the DR region — run while…

etcd (distributed consensus store)

etcd is a small, strongly-consistent key-value store that uses the Raft consensus algorithm to agree on data across an odd number of nodes (3 or 5).

Extended statistics

Normally PostgreSQL studies each column on its own.

High key (B-tree)

The high key is an upper-bound value stored as the first item on a B-tree page (every page except the rightmost on its level).

Hint bits

To know whether a row is visible, PostgreSQL must know whether the transaction that created or deleted it committed.

Per-tuple memory context

The per-tuple memory context is a tiny arena the executor uses for evaluating expressions on a single row (ExprContext.ecxt_per_tuple_memory).

pg_stat_slru

pg_stat_slru is a system view (PostgreSQL 13+) that reports activity for each internal SLRU cache — one row per cache (CommitTs, MultiXactMember, MultiXactOffs…

pg_statistic and pg_stats

pg_statistic is the system catalog where ANALYZE stores the statistical portrait of every column.

pg_walinspect

pg_walinspect is a contrib extension (PostgreSQL 15+) that lets you decode the write-ahead log from inside SQL.

Planner enable flags

The enable_* settings (enable_seqscan, enable_indexscan, enable_hashjoin, and friends) let you discourage a particular strategy.

Postmaster

The postmaster is the supervisor process that starts a PostgreSQL server.

Projection (ExecProject)

Projection is the step where a node builds the specific columns and expressions its output should contain — the SELECT list.

Query rewriter

Between analysis and planning, PostgreSQL runs the rewriter.

Query tree

After parsing the syntax, PostgreSQL runs parse analysis and produces a query tree: the same statement, but with every name resolved to a real catalog object,…

Resource manager (rmgr)

A resource manager is a subsystem that knows how to write and replay its own kind of WAL record.

Right-link (B-tree)

Each B-tree page stores a pointer to its right sibling at the same level (the btpo_next field).

Row-level lock

A lock taken on individual tuples (for example by UPDATE or SELECT ...

RPO (Recovery Point Objective)

RPO is the maximum amount of data you can afford to lose in a disaster, expressed as a time window ("at most 30 seconds").

Shared buffers

The shared-memory cache PostgreSQL uses to hold heap and index pages, reducing how often it must read from the operating system and disk.

Slot synchronization

Slot synchronization is the PostgreSQL 17 mechanism that copies failover-enabled replication slots from the primary onto a standby.

Suffix truncation (pivot tuple)

When a B-tree splits, the boundary key copied up to the parent (a pivot tuple) only needs to be just distinct enough to separate the left page from the right.

Switchover

A switchover is a planned, controlled reversal of roles: the current primary steps down cleanly and a standby is promoted, with no data loss because everything…

sync_replication_slots

sync_replication_slots is the PostgreSQL 17 standby setting that, when turned on, launches the slot sync worker to keep failover-enabled slots synchronized fro…

synchronized_standby_slots

synchronized_standby_slots is a PostgreSQL 17 setting on the primary.

Transaction ID wraparound

PostgreSQL numbers transactions with a 32-bit counter; if very old rows are never frozen, the counter can appear to wrap and hide committed data.

Tuple header (HeapTupleHeader)

Every heap row begins with a fixed header (HeapTupleHeaderData) of about 23 bytes carrying the MVCC fields xmin/xmax, the row's own location pointer (ctid), an…

TupleTableSlot

A TupleTableSlot is the small holder that carries the current row between executor nodes.

WAL record (XLogRecord)

A WAL record is one entry in the write-ahead log describing a single change PostgreSQL is about to make.

Watchdog (fencing)

A watchdog is a timer (often /dev/watchdog) that reboots a node unless it is regularly "pet" by a healthy process.

Apply lag

Apply lag (replay lag) is the gap between WAL a standby has received and WAL it has actually applied.

Asynchronous replication

Asynchronous replication means the primary commits and returns to the client without waiting for any standby to confirm it received the WAL.

Cascaded standby

A cascaded standby is a replica that streams its WAL from another standby rather than from the primary.

Cascading replication

Cascading replication is when a standby streams WAL onward to other standbys instead of every standby connecting directly to the primary.

Column alignment (padding)

Each PostgreSQL data type has an alignment requirement (attalign in pg_attribute): 8-byte, 4-byte, 2-byte, or 1-byte.

Deadlock detector

A routine that runs after deadlock_timeout on a waiting backend, builds the wait-for graph, and cancels one transaction when it finds a lock cycle.

Heap pruning

Heap pruning is the lightweight, on-the-fly cleanup PostgreSQL does to a single page while a query is already looking at it.

HOT update (heap-only tuple)

An optimization where an UPDATE that does not change any indexed column can store the new row version on the same page without adding index entries.

Line pointer (ItemId)

Every 8 KB page keeps a small array of slots near its top.

LOCALLOCK

A LOCALLOCK is a backend's private record of the locks it currently holds and how many times it has acquired each.

Lock partition

The shared lock table is divided into 16 partitions, each protected by its own lightweight lock and chosen by a hash of the lock tag.

pg_backend_memory_contexts

pg_backend_memory_contexts is a system view (PostgreSQL 14+) listing every memory context in the current backend — name, parent, depth level, and total/used/fr…

pg_locks

pg_locks is a system view exposing every lock currently held or awaited across the server: the object type, the relation, the lock mode, whether it was granted…

Portal

A portal is the container that holds the state of a query while it runs: its plan, its position in the result, and the memory it uses.

Raw parse tree

The first thing PostgreSQL builds from your SQL text is a raw parse tree: a structural representation of the sentence produced purely from grammar.

recovery_min_apply_delay

recovery_min_apply_delay is the standby setting that creates a delayed standby.

Reservoir sampling

Reservoir sampling is a technique for picking a fixed-size random sample from a stream of unknown length in a single pass.

Serializable Snapshot Isolation (SSI)

PostgreSQL's implementation of SERIALIZABLE that detects dangerous read/write dependency cycles between concurrent transactions and aborts one to preserve seri…

Stanza

A stanza is pgBackRest's configuration unit for a single PostgreSQL cluster — it ties together that cluster's data directory, its repository location, and its…

Table statistics (ANALYZE)

Sampled distributions of column values stored in pg_statistic that the planner uses to estimate row counts and choose plans.

TID (ctid)

A TID is the physical address of a row version: a pair of numbers written (block, slot).

TOAST

The Oversized-Attribute Storage Technique that transparently compresses and/or moves large column values into a side table so main rows stay within a page.

Utility statement

A utility statement is any command that is not a planned query — CREATE TABLE, ALTER, VACUUM, SET, GRANT.

Visibility map

A compact structure that records whether a heap page is all-visible or all-frozen, which helps PostgreSQL skip unnecessary heap checks.

WAL writer (walwriter)

The WAL writer flushes the write-ahead log to disk in the background on a regular cadence, so that busy backends and asynchronous commits don't each have to fs…

WAL-before-data rule

The WAL-before-data rule says a modified data page may not be flushed to disk until the WAL describing that modification is already durable.

Write-Ahead Log (WAL)

An append-only record of every change written before the change reaches the data files, so committed work survives a crash and can be replayed during recovery.

xmin horizon (OldestXmin)

The xmin horizon is the age of the oldest snapshot still alive anywhere in the system.

Correlation (statistics)

Correlation measures how closely the physical order of rows on disk matches the sorted order of a column's values, on a scale from -1 to 1.

No cross-links yet.

fillfactor

fillfactor is a per-table (or per-index) setting that tells PostgreSQL how full to pack each page when inserting.

No cross-links yet.

Lessons

37· Source-grounded internals lessons
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.

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.

Autovacuum Internals: How Dead Tuples Are Reclaimed

Because MVCC leaves dead tuples behind (see the MVCC article), something must reclaim that space and, just as importantly, advance the cluster's frozen-XID hor…

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).

Hot Standby and Recovery Conflicts

Hot standby lets a streaming standby serve read-only queries while it continuously replays WAL from the primary.

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…

MVCC Internals: How PostgreSQL Stores Row Versions

For ordinary non-locking SELECT/INSERT/UPDATE/DELETE, PostgreSQL readers do not block writers and writers do not block readers.

Partitioning: How Declarative Partitioning Prunes Work

A partitioned table is one logical table split into many physical child tables by a partition key.

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/).

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.

TOAST: How PostgreSQL Stores Oversized Values

A tuple must fit within a single 8KB page — PostgreSQL does not span a row across blocks.

work_mem and Spills: Where Sorts and Hashes Go to Disk

work_mem is the memory budget for a single sort, hash, or similar operation — not per query and not per connection.

B-Tree Indexes: Ordered Leaves, Right-Links, and Index-Only Scans

PostgreSQL’s default index is nbtree — a Lehman–Yao B-tree in src/backend/access/nbtree/.

Batched Backfills: Updating Millions of Rows Safely

Batched backfills keep transactions short so vacuum and HOT can keep up, and so locks and replication lag stay bounded.

Buffer Cache: How shared_buffers and Clock-Sweep Work

shared_buffers is a fixed-size array of 8KB page frames allocated in shared memory at startup.

CREATE INDEX CONCURRENTLY: Building Indexes Online

An ordinary CREATE INDEX takes a SHARE lock on the table for its entire duration.

DDL Locking: Why ALTER TABLE Can Freeze Your App

Most forms of ALTER TABLE take an ACCESS EXCLUSIVE lock — the strongest table lock, conflicting with every other lock mode including the ACCESS SHARE that a pl…

Online VACUUM, REINDEX, and Table Rewrites

Over time tables and indexes accumulate bloat — dead tuples and empty space left by MVCC.

pg_rewind: Reusing a Diverged Old Primary

pg_rewind is a frontend utility (src/bin/pg_rewind), not backend postmaster code: it reuses an old primary’s data directory by copying blocks that diverged aft…

Query Executor: From Plan Tree to Tuples

After the planner chooses a Plan tree, the executor builds a parallel PlanState tree, initializes nodes (ExecInitNode), and pulls tuples by calling each node's…

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.

WAL Archiving and Point-in-Time Recovery

Replication protects against hardware failure, but it faithfully copies mistakes — a DROP TABLE or bad UPDATE replicates instantly to every standby.

Write-Ahead Logging: How WAL Guarantees Durability

A transaction is durable when, after COMMIT returns, its effects survive a crash.

Blue/Green and Logical Replication Upgrades

Blue/green and major-version cutovers often use logical replication as the data plane.

Expand/Contract: The Pattern Behind Every Safe Migration

Expand/contract is an application deployment pattern (dual-write or dual-read windows), not a single PostgreSQL subsystem.

Adding Columns and Constraints Without Downtime

How PostgreSQL implements ADD COLUMN and constraint changes matters for downtime: some paths are metadata-only; others rewrite the table.

No cross-links yet.

Connection Overhead: Why Pooling Is Mandatory at Scale

PostgreSQL uses a process-per-connection model: the postmaster fork()s a dedicated backend for every client connection (src/backend/postmaster/postmaster.c).

No cross-links yet.

Connection Routing for HA: VIPs, Pooler, and Read Scaling

VIPs, pooler primary/replica routing, and read scaling are topology choices around PostgreSQL, not a single backend module.

No cross-links yet.

Failover and Fencing: Keeping One Writer (Ops Pattern)

PostgreSQL streaming replication can host a physical standby, but **choosing a single primary after failure is an orchestration problem**, not a single backend…

No cross-links yet.

Interview

11· Real interview questions
Explain REPEATABLE READ vs SERIALIZABLE in PostgreSQL and when you would choose each.

A strong answer explains snapshot behavior, anomaly protection, retry cost, and the operational burden of high-contention workloads.

How does VACUUM relate to MVCC, and what happens if it falls behind?

A strong answer connects dead tuples, storage bloat, xid horizon pressure, and degraded read performance.

Take me from that sample to a query plan — what does ANALYZE compute, and how does the planner use it?

They want the chain from raw sample rows to per-column statistics in pg_statistic, and then how the planner turns a WHERE clause into a row estimate.

You've got a 50 TB table. Walk me through what ANALYZE actually does at that size — does it read every page?

The interviewer is checking that you know ANALYZE reads a bounded sample of blocks, not the whole relation — and that the sample size is fixed by default_stati…

Explain transaction ID wraparound the way the committer sees it — how does Postgres keep a row from four billion transactions ago still visible?

They want the mechanism: 32-bit xids compared modulo 2^31, the commit log recording commit status, and freezing as the thing that lets old xids be recycled saf…

Give me a multixact scenario and take it deep — when does one get created, and how does it bite you in production?

They want to hear that a multixact appears when several transactions lock the SAME row in a way a single xid can't represent, that it lives in its own 32-bit s…

n_live_tup and n_dead_tup are estimates. If I need the real number of tuples in a table, what do you reach for?

They're checking that you know the stats-view counters and pg_class.reltuples are estimates, and that the exact physical truth (plus dead-tuple and free-space…

Start with the highest-severity issue you've personally owned — the one that pushed your limits.

This is a signal probe, not a trivia question — they want scale, ownership, and a decision made under pressure.

When we find a bug we send the patch upstream to pgsql-hackers. Have you worked that way — finding something nobody had flagged and driving it to a fix?

They want evidence you can isolate a novel issue to a minimal, defensible reproduction and that you understand how upstream PostgreSQL actually accepts a fix.

When you propose a new observability signal, how do you justify it — why that metric, and what does it cost to collect?

Every signal has to earn its keep: it must map to a decision, and its collection cost and false-positive rate have to be smaller than the pain it prevents.

You want to measure a table's access pattern cleanly after a change. How do you reset just that table's statistics without wiping the cluster?

They're probing whether you know the per-table counter reset exists and, more importantly, that it resets ACTIVITY counters — not the planner's column statisti…

Pathways

3· Guided end-to-end journeys