Read Replicas vs Sharding Explained Simply: When to Scale Reads vs When to Split Data
#Backend

Read Replicas vs Sharding Explained Simply: When to Scale Reads vs When to Split Data

Backend Reporter
7 min read

Read replicas help when one database cannot answer enough reads. Sharding helps when one database should no longer own all the data.

Featured image

Problem

Most database scaling mistakes start with a vague diagnosis: the database is slow. That phrase hides the detail that actually matters. A system with too many profile lookups, dashboard refreshes, feed reads, and search result fetches has a different failure mode than a system whose largest table no longer fits comfortably on one machine. Treating both as the same problem is how teams add complexity without removing the bottleneck.

A single primary database usually works longer than people expect. It keeps the data model simple, transactions are local, joins are cheap, migrations are easier to reason about, and operational failure modes are familiar. The trouble begins when growth stops being one-dimensional. Traffic grows, data grows, query shapes change, indexes become larger, cache hit rates shift, and background jobs start competing with user requests. At that point, the first useful question is not whether to scale horizontally. The first useful question is what is being exhausted.

Read replicas address read pressure. They copy data from a primary database and serve queries that do not need to mutate state. PostgreSQL documents this under warm standby and streaming replication, while MySQL covers similar mechanics in its replication documentation. The application still writes to one primary, but read traffic can be spread across multiple machines.

Sharding addresses data ownership and per-node limits. Instead of every database node storing the full dataset, each shard stores a subset. A routing layer decides where a row belongs, often by user ID, tenant ID, account ID, region, or another partition key. Sharding is less about making more copies and more about changing the shape of the system. Once data is split, every cross-shard query, transaction, migration, backup, and incident response procedure has to respect that split.

The distinction is simple, but the consequences are not. Replicas ask, how many copies can safely answer reads. Shards ask, which machine owns this data.

Solution Approach

A practical scaling plan starts by separating read volume from data volume. If CPU and I/O spike during repeated SELECT queries, and the write path is still manageable, read replicas are usually the first tool to evaluate. They reduce load on the primary by moving read-only work elsewhere. A common API pattern is command-query separation at the application boundary: writes, state transitions, payments, inventory changes, and user edits go to the primary path, while profile pages, analytics panels, timelines, and reporting screens go to a read path.

That split sounds clean until consistency shows up. Replication is usually asynchronous in production because synchronous replication can make write latency depend on another machine. Asynchronous replicas can lag behind the primary. A user may update their profile and then immediately read from a replica that has not received the change yet. That is not a theoretical edge case. It appears in account settings pages, order confirmation flows, access-control checks, and support tools.

Good systems make that consistency model explicit. For read-after-write flows, the API can route the next read to the primary, pin a session to the primary for a short period, include a version token, or make the client tolerate pending state. For feeds and counters, eventual consistency is often acceptable. For permissions, billing, and inventory, stale reads can become correctness bugs. A replica is not just a faster database endpoint. It is a promise that some reads can tolerate being slightly behind.

Sharding needs a different design process. The core decision is the shard key. Pick well, and most requests touch one shard. Pick poorly, and the system pays a permanent coordination tax. User ID works well for user-scoped data because profiles, settings, posts, and messages often cluster around a user. Tenant ID works well for business applications where each customer account is mostly isolated. Region can help with data residency and latency. A timestamp can work for append-heavy event systems, but it can also create hot shards if all new writes target the same range.

A shard router sits between the API and the databases. It may live in application code, a service layer, or infrastructure. Its job is to map a request to the correct shard. For example, an endpoint like GET /users/{id}/posts can route by user_id. An endpoint like GET /search?term=x may not have a natural shard key, which means it either fans out to many shards or relies on a separate search index such as OpenSearch or Elasticsearch. This is why API design matters. APIs that carry the partition key are easier to scale. APIs that hide it often force expensive distributed queries later.

In replicated systems, the query router asks whether a request is a read or a write. In sharded systems, it asks which data partition owns the request. In mature systems, it asks both. A large application may have many shards, and each shard may have its own replicas. Writes go to the primary for that shard. Reads go to a replica for that shard when the consistency requirements allow it.

Google article image

Trade-offs

Read replicas are operationally attractive because they preserve a mostly familiar data model. The primary still owns all writes. Foreign keys, local transactions, and joins still work in the primary database. Many applications can add replicas with limited code changes, especially if the data access layer already distinguishes reads from writes. Managed databases such as Amazon RDS read replicas, Google Cloud SQL read replicas, and Azure SQL replicas make this pattern accessible without building replication machinery by hand.

The cost is that replicas do not reduce total data size on each node. Every replica still stores the same dataset, or close to it. If the main table is too large, indexes no longer fit, vacuum or compaction work is falling behind, or storage I/O is saturated by the working set itself, adding replicas may only move the pain around. Replicas also do not increase write capacity on the primary. A single write leader remains a single write leader.

Sharding changes those limits. Each shard owns less data, so indexes can shrink, cache locality can improve, backups can become smaller per node, and write load can be spread across machines. For very large multi-tenant SaaS platforms, social networks, event stores, and messaging systems, this is often the path from one overloaded database to a fleet that can grow with the product.

The price is complexity that does not stay contained. Joins across shards become application workflows or precomputed views. Transactions across shards require careful coordination, often through sagas, outbox patterns, idempotency keys, or a distributed transaction protocol. Rebalancing data after one shard gets too large is risky work. Hot tenants can overload a shard even when the global average looks fine. Schema migrations must be coordinated across many databases. Incident response becomes harder because the failure may affect only one partition, one tenant group, or one routing range.

API behavior also changes under sharding. A simple endpoint that once returned all recent activity may need pagination by shard key, fan-out limits, or an asynchronous export path. Aggregations become approximate, delayed, or delegated to analytical systems. A dashboard that queries COUNT(*) across a single table may become a pipeline into Apache Kafka, ClickHouse, BigQuery, or another analytics store. That is not failure. It is the normal result of separating transactional storage from global read models.

The pragmatic rule is to add read replicas when the primary is healthy enough to keep owning the dataset but too busy to serve every read. Use sharding when one database should no longer be responsible for all rows, all writes, or all storage growth. Most systems should not shard early. Sharding before the data model has settled creates permanent routing decisions around assumptions that may age badly. At the same time, waiting too long can make the migration painful because every API, job, and report has learned to assume one database can answer everything.

A sane evolution often looks like this: start with one primary database, add indexes and query discipline, introduce caching where the access pattern is clear, add read replicas for read-heavy workloads, isolate analytical queries from transactional traffic, then shard only when data size, write volume, tenant isolation, or operational limits justify it. Large systems frequently end up using both replicas and shards, but they usually arrive there through measured pressure, not architectural ambition.

The failure I have seen more than once is using the right tool for the wrong bottleneck. Replicas will not save a system whose real problem is unbounded data growth on one write leader. Sharding will not fix sloppy queries, missing indexes, or a read path that could have been moved off the primary. The engineering work is diagnosis first, topology second.

Comments

Loading comments...