Skip to content

Scaling PostgreSQL: Vertical, Read Replicas, and Sharding

Aman Kumar Singh5 min read
Part 39 of 40From the PostgreSQL Masterclass series
Scaling PostgreSQL: Vertical, Read Replicas, and Sharding — article by Aman Kumar Singh

Most scaling advice jumps straight to the exotic options, sharding, distributed SQL, exotic architectures, when the honest truth is that the large majority of applications never need them. PostgreSQL on a single well-tuned server handles far more than people expect, and the right scaling strategy is almost always the least complex one that solves your actual bottleneck. This article lays out the scaling ladder in order, so you reach for each rung only when the one below it runs out, rather than adding distributed-systems complexity you don't need.

This is part 39 of the PostgreSQL Masterclass, following monitoring.

First: make the single server efficient

Before scaling out or up, exhaust the cheap wins, because they're often the whole answer. Everything earlier in this series is a scaling technique:

  • Index the queries that need it and fix the ones doing sequential scans, from the indexing module.
  • Kill N+1 patterns and bad queries, from query optimization; a lot of "we need to scale" is really "one endpoint runs 500 queries."
  • Add connection pooling so the server isn't drowning in connections.
  • Tune autovacuum so bloat isn't quietly slowing everything.

It's common to "need scaling" and discover the database is doing 10x the work it should because of a missing index or an N+1 loop. Fix the efficiency problems first. They're cheaper than any infrastructure change and often buy years of headroom.

Rung 1: scale vertically

The simplest scaling is a bigger machine: more CPU, more RAM, faster disk. This is underrated because it's unglamorous, but modern hardware is enormous, and a single Postgres instance on a large server handles very serious workloads. More RAM means more of your data fits in cache (raising that cache hit ratio), faster disk means quicker reads and writes, more cores mean more concurrent queries.

Vertical scaling has real advantages: no application changes, no new failure modes, no distributed-systems complexity. Its limits are that you eventually hit the biggest available machine, and that a single server is a single point of failure (which HA addresses separately). But you should ride vertical scaling further than instinct suggests. It's often the correct answer well past the point where people start reaching for something fancier.

Rung 2: scale reads with replicas

When read traffic is the bottleneck (and for most applications reads dominate), add read replicas, from the replication article. Route read-heavy, lag-tolerant queries to replicas and keep writes on the primary. This scales reads horizontally: need more read capacity, add another replica.

Replicas handle a large class of scaling needs, because so many workloads are read-heavy. Reporting, dashboards, search, browsing, most of it can move to replicas, leaving the primary to handle writes and consistency-critical reads. For the majority of applications that outgrow a single server, "vertical scaling plus read replicas" is the entire scaling story, and they never need anything more.

Rung 3: reduce write load before sharding

If writes (not reads) are the bottleneck, there are moderate steps before the big one:

  • Offload work that doesn't belong in the primary datastore. Move caching to Redis, full-text search to a dedicated engine if you've outgrown Postgres search, and time-series or analytics to a system built for them. This takes load off the primary without sharding it.
  • Partition large tables (from the partitioning article) so writes and maintenance work on smaller pieces.
  • Batch writes and reduce write amplification from over-indexing.

These reduce write pressure meaningfully and are far simpler than sharding. Exhaust them before splitting your data across servers.

Rung 4: shard, when you genuinely must

Sharding splits your data across multiple independent databases, each holding a subset (by tenant, by user id, by hash), so writes spread across servers. It's how you scale writes beyond one machine, and it's the last rung because it's by far the most complex:

  • Queries that span shards become hard (cross-shard joins, aggregates, and transactions are painful or impossible).
  • Every application query needs to know which shard to hit.
  • Rebalancing data as you add shards is a project in itself.

You do this when a single primary genuinely can't absorb your write volume even after everything above, which is a threshold most applications never reach. When you do reach it, you have options short of building sharding yourself: a natural shard key like tenant lets you shard at the application level, and distributed-Postgres systems like Citus handle sharding within Postgres, spreading a table across nodes while keeping much of the SQL interface. Managed distributed SQL databases are another route. All of them trade operational and query complexity for write scalability, which is why you only pay that price when you must.

The ladder, in order

The scaling decision is really just "what's my bottleneck, and what's the simplest rung that fixes it":

  1. Make the single server efficient (indexes, query fixes, pooling, vacuum). Fixes most "scaling" problems.
  2. Scale vertically (bigger machine). Handles serious load with zero complexity.
  3. Add read replicas if reads dominate. Covers most remaining cases.
  4. Offload and partition if writes grow. Simpler than sharding.
  5. Shard only when a single primary truly can't keep up with writes.

The mistake is skipping rungs, adding sharding when a bigger box and an index would do, and inheriting distributed-systems complexity for a problem you didn't have. Climb the ladder in order, and you'll almost certainly find that a well-tuned Postgres, a generous server, and a couple of read replicas carry you further than you expected.

That leaves one thing: turning everything in this series into a checklist you can run against a real deployment. Next and last, the PostgreSQL production checklist.

Key takeaways

  • Most "we need to scale" problems are efficiency problems; fix indexes, N+1 queries, pooling, and vacuum first.
  • Scale vertically (a bigger machine) further than instinct suggests; it adds serious capacity with zero complexity or new failure modes.
  • Add read replicas for read-heavy workloads, which covers most applications that outgrow a single server.
  • Before sharding, reduce write load by offloading caching/search/analytics elsewhere and partitioning large tables.
  • Shard only when a single primary genuinely can't absorb write volume; consider Citus or a natural shard key rather than building it yourself.

Frequently asked questions

How do I scale PostgreSQL?

Climb a ladder from simplest to most complex: first make the single server efficient (indexes, query fixes, pooling, vacuum), then scale vertically with a bigger machine, then add read replicas for read-heavy load, then offload and partition to reduce writes, and only shard as a last resort.

When should I add read replicas?

When read traffic is your bottleneck, which is common since most applications read far more than they write. Route reports, dashboards, search, and other lag-tolerant reads to replicas while keeping writes and read-your-own-writes on the primary.

Is vertical scaling (a bigger server) a real strategy?

Yes, and an underrated one. Modern hardware is large, and a single well-tuned Postgres instance handles serious workloads with no application changes or distributed-systems complexity. Ride vertical scaling further before reaching for anything more complex.

When do I actually need to shard PostgreSQL?

Only when a single primary can't absorb your write volume even after tuning, vertical scaling, read replicas, offloading, and partitioning. Most applications never reach this point. When you do, consider Citus or a natural shard key rather than hand-building sharding.

What's the alternative to sharding for write-heavy load?

Reduce write pressure first: move caching to Redis, search to a dedicated engine, and analytics to a purpose-built system; partition large tables; and batch writes. These are far simpler than sharding and often enough.

Related articles

Streaming Replication and Read Replicas in PostgreSQL — Aman Kumar Singh
Connection Pooling with PgBouncer in PostgreSQL — Aman Kumar Singh
Table Partitioning in PostgreSQL — Aman Kumar Singh
Aman Kumar Singh

About the author

I'm Aman Kumar Singh, a software engineer in Noida, India building scalable full-stack products with React, Next.js, Node.js, NestJS, PostgreSQL, Redis, and AWS. I write about backend engineering, distributed systems, and system design.