PostgreSQL In Depth
Why Postgres is the de-facto warehouse and OLTP engine
PostgreSQL is an extensible, MVCC-based relational database. Its snapshots, unlogged indexes, WAL archiving, TOASTed values, and background VACUUM define how it behaves under load.
What you will learn
- Explain MVCC snapshots and snapshot age in a few sentences
- Describe the role of TOAST, WAL, and VACUUM
- Install a useful extension and run psql routines
What is this?
PostgreSQL gives each transaction a snapshot, stores row versions so readers and writers do not block each other, keeps large values out of line with TOAST, and journals every change to the WAL before touching data files. Background VACUUM reclaims obsolete row versions and keeps the engine honest.
Why do you need it?
- It is the default engine for warehouses, backend services, and self-managed analytics in the modern stack.
- Extensibility lets you turn a relational store into geospatial, JSON, or analytics infrastructure.
- Batch workloads die on bloat and checkpoint storms; knowing VACUUM and WAL keeps them steady.
Mental model
A git repo where every commit keeps a full snapshot
Postgres MVCC
Reading at an old snapshot is like checking out an old commit: it never sees later commits (uncommitted writes) yet stays perfectly consistent. Obsolete blobs are garbage-collected by VACUUM like git gc.
How it works
- Every query reads under a snapshot; dirty rows are ignored based on their xmin/xmax visibility flags.
- Each row's old versions accumulate until VACUUM removes ones no transaction can see anymore.
- Values over ~2KB are stored inline in TOAST tables and only fetched when selected, so big JSON payloads stay cheap.
- WAL is appended before page changes; checkpoints flush dirty buffers and recycle WAL segments.
- extensions (pg_stat_statements, postgis, timescaledb) hook into catalogs and planner hooks without forking.
MVCC visibility and the VACUUM lifecycle.
Key commands
SELECT pid, age(clock_timestamp(), state_change), query FROM pg_stat_activity;List open transactions and their age — find long-running snapshots.
SELECT pg_relation_size('orders') AS bytes, pg_total_relation_size('orders') AS with_indexes;Check table vs total size including indexes and TOAST.
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;Enable top-query statistics, the fastest way to find hot SQL.
VACUUM ANALYZE orders;Reclaim dead tuples and refresh planner statistics.
Examples
Seeing the versioning in action
BEGIN;CREATE TABLE t(id int);INSERT INTO t VALUES (1);-- in another session:-- SELECT count(*) FROM t; -> 0 (uncommitted invisible)COMMIT;-- now count(*) -> 1 SELECT relname, n_live_tup, n_dead_tup, last_vacuumFROM pg_stat_user_tables WHERE relname = 't';Uncommitted rows stay invisible to other sessions; the stats view shows how many dead versions are waiting for VACUUM.
In production
- A team hooks pg_stat_statements to its APM to catch 'top ten slow queries' weekly.
- A CDC pipeline relies on logical replication slots; unused slots are detected via pg_stat_replication.
- Long-running analytics queries that touch JSONB benefit because TOAST keeps the compact columns in cache.
Best practices
- Set autovacuum thresholds generously and watch n_dead_tup growth on hot tables.
- Use prepared statements and bind parameters so constant-inlining does not defeat cached plans.
- Drop unused replication slots after reading them from pg_stat_replication.
Common mistakes
- Never running VACUUM and letting bloat slow every scan or, worse, let XID wraparound threaten the cluster.
- Treating the checkpoint as a spare 'backup': it is a crash-recovery boundary, not a copy of your data.
- Using lowercase or per-message connection churn, which piles connection setup and planner startup onto every request.
Common issues
Table grows 10% daily but deletes are frequentDead tuples (old MVCC versions) not yet vacuumed
Tune autovacuum or run VACUUM ANALYZE manually during a maintenance window.
Every write pauses for 15 seconds before resumingUncontrolled checkpoint with low max_wal_size
Raise max_wal_size and check checkpoint_timeout so the flush is smooth instead of a spike.
Debugging tips
- When a query is slow, run EXPLAIN (ANALYZE, BUFFERS) and compare reads vs cached hits.
- When writes stall, inspect pg_stat_activity for 'waiting' and look for lock waits, fsync pressure, or checkpoint spikes.
- When a replica lags, check the WAL replay position and consumed slots rather than CPU.
Interview checkpoint
Hands-on lab
- Run PostgreSQL locally in Docker
- Demonstrate that uncommitted writes are invisible to other sessions
- Observe snapshot age and dead tuple counts via pg_stat_*
- Show VACUUM reclaiming dead tuples
Prove MVCC Snapshot Semantics in Postgres
35 min · intermediate
Quick quiz
What does VACUUM remove from a table?
A replication slot that is never consumed can stop WAL recycling and fill the disk.