Scalingscalingconnection poolread replicacachepartitioning

Scaling from One User to Millions

Scale in the cheapest order — indexes, connection pool, read replicas, cache, partitioning, sharding — because each step buys capacity by adding a problem, and skipping to sharding is adding the biggest problem first.

▶ InteractiveInterview questionSee how this works internally →
Progress

The ladder

One box serves the first thousand users. The first wall is almost always a missing index, not a missing server — one composite index can be a 100× win that no hardware matches. Next, a connection pool: each Postgres connection is a process, so twenty app instances opening fifty connections each is a dead server, and a pool of twenty reused connections serves far more. Next, read replicas: most workloads are 90%+ reads, and replicas add read capacity almost linearly plus a failover target — at the cost of replication lag. Next, a cache for the hot, repeated reads — at the cost of invalidation. Next, partitioning to keep a huge table operable. Finally sharding, the only step past a single machine’s write ceiling, and by far the most expensive.

The order matters because each rung is cheaper and less invasive than the next, and each solves a problem the previous rung created. Teams that jump straight to sharding or microservice-per-database are adding the hardest problem before exhausting the easy ones — and usually before they have the traffic to justify any of it.

Cheapest next step first
reads pile upread-boundhot readstable hugewrite ceilingIndexesPoolReplicasCachePartitionShard
UserLLMAgentToolDataDecisionHumanGuardrail

Vertical scaling is underrated

Before any of the distributed rungs, buy a bigger machine. A modern server has dozens of cores and hundreds of gigabytes of RAM; a working set that fits in RAM runs from memory. Doubling the instance is a config change and a restart, with no new failure modes, no consistency questions, and no application changes. It runs out eventually, but "eventually" is further away than most teams assume, and every rung you postpone is a class of bug you do not yet own.

What each rung costs

Indexes cost write speed and storage. A pool costs one component and breaks session state in transaction mode. Replicas cost money per replica and hand you replication lag — the "I saved it and it shows the old value" bug; see Replication and Read Scaling. A cache costs a whole class of staleness bug and a component whose loss must be survivable; see Caching Patterns. Partitioning costs schema complexity and a maintenance job; see Partitioning and Sharding. Sharding costs joins, cross-shard transactions, global uniqueness, and rebalancing — the largest step change on the ladder.

The discipline: at each rung, measure that the previous one is genuinely exhausted before climbing. "The database is slow" is not a reason to add a replica if the real cause is one unindexed query.

Key points

  • Indexes → pool → replicas → cache → partition → shard. Cheapest and least invasive first.
  • The first wall is usually a missing index, not missing hardware.
  • Vertical scaling buys a lot with no new failure modes; use it before distributing.
  • Each rung buys capacity by adding a problem; climb only when the previous rung is measurably exhausted.

From one user to millions

From one user to millions
Each rung is the cheapest next step, and each one buys capacity by adding a problem. Read the “what breaks next” line — that is what pushes you to the following rung.
ApplicationDatabase
Scale
1 – 1,000
What you add

Nothing. One application, one database, one machine.

Why it works

A single Postgres instance on modest hardware serves thousands of users. Most systems never truthfully need more than this plus indexes.

What breaks next: Nothing yet. The usual failure at this stage is a missing index, not a missing server.
What it costs

One instance. Backups are a cron job.

Rules for this rung
  • Get the schema and the indexes right. Everything below is harder if these are wrong.
1/7 · One box

When to use — and when not

Use it when
  • Any growing system, at the point a specific metric shows a specific limit.
Avoid it when
  • Pre-emptively, on projected traffic. Build for 10× current, not 1000×.

Failure modes

  • Sharding before indexing.
  • Adding replicas to fix a problem one index would solve.
  • A cache added without an invalidation plan.

See how this works internally →

Descend one layer: the same topic explained from the machinery up.

Don't delegate understanding
The manifesto →