Module A-0·26 min read

Why most engineers have a shallow model of Postgres — and what it costs them in production.

JJS
Written by Jatin Jain Saraf · Senior Software Engineer

Module 0 — Before You Proceed: Mental Model Reset

Who this is for: Mid-to-senior backend engineers who have used Postgres in production but have never looked under the hood. You know how to write queries. You know how to add indexes. You may have even tuned work_mem. But when your query inexplicably regresses after a bulk insert, or your replica falls six hours behind on a Thursday morning, or VACUUM takes longer than your deployment window — you don't yet have the mental model to diagnose it from first principles.

That is what this module builds.


The Problem With How Most Engineers Learn Postgres

Most engineers learn Postgres through three channels:

  1. The official documentation — comprehensive, accurate, and almost entirely organised around syntax rather than mechanics.
  2. Tutorial sites — which teach you to SELECT, JOIN, and INDEX using tables named users and orders with 20 rows.
  3. Stack Overflow — which tells you what to do but almost never why it works.

The result is an engineer who can write correct SQL but who treats the database as a black box. When that black box behaves unexpectedly under production load, they reach for EXPLAIN ANALYZE, see a sequential scan where they expected an index scan, and have no model for why the planner made that choice.

This course is the model they were never given.


What Postgres Actually Is

Postgres is not "just a reliable RDBMS." That framing is technically correct and practically useless.

Here is a more accurate description:

PostgreSQL is a multi-process, heap-based, MVCC-driven database engine with a cost-based query planner, a Write-Ahead Log for crash recovery and replication, and a background worker system for maintenance — all of which interact in ways that are not visible from the SQL interface.

Every word in that sentence is load-bearing. Let's unpack each one.

Multi-process architecture

When you start a Postgres instance, you start a supervisor process called the postmaster. The postmaster listens for connections and forks a new backend process for each one. Each backend is an independent OS process with its own memory — there is no thread pool, no shared execution context between connections.

This has direct consequences:

  • work_mem is allocated per sort operation per backend. If you set work_mem = 256MB and 100 clients run a query with two sort operations each, you have potentially allocated 51GB of RAM.
  • There is no shared query plan cache between connections in the way Oracle or SQL Server have one. Postgres caches plans per prepared statement per backend.
  • Connection overhead is real. Forking a process is expensive. This is why connection pooling (PgBouncer) is not optional above a few hundred connections.

The background workers running alongside backends include:

  • WAL writer — flushes Write-Ahead Log buffers to disk
  • Background writer — writes dirty shared_buffers pages to disk
  • Checkpointer — performs periodic checkpoints
  • Autovacuum workers — reclaim dead tuple space
  • WAL sender / WAL receiver — handle replication
  • Stats collector — populates pg_stat_* views

When your database feels "slow," it is almost always one of these background processes that is either overloaded or misconfigured.

Heap-based storage

Postgres stores table data in heap files — unordered collections of 8KB pages. There is no concept of a clustered index where the table data is physically sorted by a key (unlike InnoDB in MySQL). When you insert a row, it goes into the first available page with enough free space. Period.

This has consequences:

  • Sequential scans are efficient because pages are read in order from disk.
  • Random-access queries on non-indexed columns scan the entire heap.
  • Table data has no inherent ordering. SELECT * FROM orders LIMIT 10 does not return the 10 most recent orders. It returns 10 rows from whatever pages happen to be read first.
  • Physical correlation between data and indexes degrades over time as rows are inserted, updated, and deleted in non-sequential patterns.

You will encounter the heap file again in Module 1, where we look at the exact byte layout of a page and what happens to that layout under UPDATE workloads.

MVCC-driven

Multi-Version Concurrency Control is the mechanism by which Postgres allows concurrent readers and writers without one blocking the other. Instead of acquiring read locks, Postgres creates a new version of every modified row — the old version remains visible to transactions that started before the modification, while the new version is visible to transactions that started after.

The consequence that most engineers miss: dead tuples accumulate on disk. When you UPDATE a row, Postgres does not overwrite the old value. It marks the old tuple as dead and writes a new one. The old tuple sits in the heap, occupying space, until VACUUM reclaims it.

On a table with a high update rate, dead tuple accumulation is continuous. Without properly tuned autovacuum, tables grow without bound even if the number of live rows stays constant.

We will spend all of Module 2 on MVCC mechanics, including the snapshot model, transaction visibility, and the transaction ID wraparound problem that will shut your database down if you ignore it long enough.

Cost-based query planner

When you execute a query, Postgres does not simply find the "obvious" way to execute it. It runs a cost-based optimizer that considers multiple execution plans — different join orders, different index choices, parallel vs sequential — and estimates the cost of each using statistics it collects about your data distribution.

The keyword is estimates. The planner does not know the true cost of a plan. It uses statistics from the last ANALYZE run. If those statistics are stale (because you just loaded 50 million rows without running ANALYZE), the planner will make wrong choices based on outdated data.

This is why "the query got slower after the data load" is such a common production incident. Nothing changed about the query or the schema. The statistics changed — or failed to change — and the planner chose a different plan.

Module 6 covers the planner in depth, including how to read plan output, what statistics Postgres collects, and how to diagnose plan regressions.


How a Single SELECT Travels Through the System

Understanding the pipeline a query goes through demystifies most "why is this slow?" questions.

sql

Here is what happens when this hits the server:

1. Connection & authentication

The postmaster forks a backend process for your connection. SSL handshake, authentication, and session setup happen here. This is the cost you amortize by using a connection pool.

2. Parser

Sign in to keep reading

The rest of this module is free — sign in with Google to unlock it and track your progress.

Sign in & Register

Discussion

0

Join the discussion

Loading comments...

© 2026 Jatin Jain Saraf (JJS). All rights reserved.