mailwal

How to Scale Your Database

Nikki Siapno from Level Up Coding

How to Scale Your Database

More replicas? Bigger machines? Sharding? Here's what to consider first.


Go Beyond AI Tutorials. Learn to Build.

Presented by MongoDB

Understanding AI concepts is one thing. Building applications with them is another. MongoDB’s AI Learning Hub brings together self-paced learning tracks, hands-on technical resources, and Skill Badges to help developers build practical skills in vector search, RAG, and AI application development.

Explore the AI Learning Hub


How to Approach Scaling Your Database

What should you do when your database starts struggling under load?

The tempting answer is to start adding replicas, shards, caches, and bigger machines.

But that can create another problem.

Every scaling technique adds complexity. And if you introduce that complexity before you actually need it, you may end up making the system harder to build and operate without solving a real bottleneck.

So database scaling is less about finding the most advanced architecture and more about knowing which problem to solve next.

A useful progression looks something like this: Measure → reduce unnecessary work → optimize queries → distribute reads → partition data.

Let’s walk through it.

Scaling has a cost

Growing pains are usually a good problem to have.

As your user base expands, database queries may become slower, network requests may start timing out, and parts of the application may begin to lag.

That tells you something in the system is approaching its limit.

But it doesn’t automatically mean you need a more complicated architecture.

Scaling techniques introduce their own costs:

  • More infrastructure → More servers, databases, and services to operate

  • More moving pieces → More components that can fail or interact unexpectedly

  • Slower development → Engineers need to account for a more complex architecture

  • Harder testing → More distributed behavior needs to be reproduced and verified

  • Harder debugging → Problems can span multiple systems instead of one database

So the goal isn’t to build for enormous scale as early as possible.

The goal is to keep the system simple until the current design can no longer handle the workload.

And before changing the architecture, you need to know what is actually reaching its limit.

Start by finding the bottleneck

You can’t scale effectively if you don’t know what’s slow.

Start with your monitoring.

Look at database CPU, memory, connections, query latency, network usage, and other resources involved in serving requests. Cloud providers such as AWS and Azure already provide monitoring tools that can help surface these signals.

You’re looking for resources that consistently spike or hit their limits.

Maybe the database CPU is saturated.

Maybe one query suddenly takes several seconds.

Maybe connection pools are exhausted.

Or perhaps the database isn’t the bottleneck at all.

If the problem still isn’t obvious, instrumentation and logs around heavily used operations can help reveal where requests are spending their time.

This is important because different bottlenecks require very different solutions.

Adding read replicas won’t fix an inefficient query. Sharding won’t help if most of your latency comes from another service.

Measure first. Scale second.

Once the database really is the bottleneck, the next question becomes: Can we make it do less work?

Make the database do less work

A surprising amount of scaling comes down to avoiding unnecessary computation.

Instead of immediately adding more database servers, ask whether the application can avoid repeating work it has already done.

That usually means techniques such as:

  • Reuse existing data → Avoid fetching the same information repeatedly

  • Remove unnecessary requests → Stop queries that don’t need to reach the database

  • Cache expensive results → Store frequently reused results instead of recomputing them

  • Move work out of the request path → Process expensive operations asynchronously when possible

And one of the simplest places to start is caching.

Cache frequently requested data

Many applications have a relatively small group of queries that are executed repeatedly. If the result doesn’t change often, asking the database for the same answer every time is wasted work.

Instead, the application can cache the result.

For smaller systems, that might mean storing it in memory on the application server.

As the system grows, a dedicated in-memory store such as Redis can provide a shared cache across multiple application servers.

Now repeated requests can often skip the database entirely. That can remove a significant amount of read traffic.

But cached data can become stale.

So you need to decide:

  • What should be cached? → Prioritize frequently requested data that changes relatively slowly

  • How long should it stay cached? → Choose a lifetime based on how fresh the data needs to be

  • When should it be invalidated? → Refresh or remove cached data when the underlying value changes

Caching reduces load, but only when you’re comfortable with the freshness tradeoff.

And sometimes the problem isn’t that you’re running too many queries.

The queries themselves are simply too expensive.

Make queries cheaper with indexes

Imagine searching through a book without an index. To find one topic, you’d potentially have to scan every page.

Databases face a similar problem when querying unindexed data.

Indexes give the database a faster way to locate the rows it needs without scanning the entire table.

For frequently queried columns, the right index can dramatically reduce query time. That’s why indexing is often one of the highest-leverage optimizations available before changing the overall architecture.

But indexes aren’t free.

Every time data is inserted, updated, or deleted, the database may also need to update its indexes.

More indexes therefore mean:

  • More storage → Each index consumes additional disk space

  • More write work → Inserts, updates, and deletes may also need to update indexes

  • More maintenance → Unused or poorly designed indexes add overhead without improving useful queries

So the goal isn’t to index everything. It’s to index the data paths your application actually uses.

Caching and indexing can take you a long way.

But eventually you may reach a different problem.

Even efficient queries still have to be handled by the same database server.

Spread reads across replicas

Let’s say your database is handling far more reads than writes.

Caching helps, and your queries are properly indexed, but the primary database is still struggling to serve all the remaining read traffic.

At that point, you can distribute the workload.

With read replication, writes go to a primary database while copies of that database (called read replicas) serve read queries. Instead of one server answering every request, read traffic can be spread across several machines.

Replicas can also be placed closer to users in different regions, potentially reducing read latency.

But a write made to the primary database needs time to propagate to every replica. That means a replica may temporarily contain older data.

For operations that require immediate read-after-write consistency (for example, updating a profile and immediately displaying the new information) the application may need to read from the primary instead.

So while replication gives you more read capacity, the application now has to think about consistency.

And there’s another limit: read replicas help distribute reads, but writes still need to go through the primary database.

What happens when the primary itself becomes the bottleneck?

That’s where things get much more complicated.

Partition the Database With Sharding

Most of the techniques so far try to reduce or distribute database work while keeping one primary database at the center.

Sharding provides a different approach.

Instead of storing the entire dataset on one database server, the data is divided across multiple databases called shards.

For example, users could be split across shards based on their user_id. Now each shard stores only part of the dataset and handles only part of the workload.

This allows both data and traffic to be distributed across multiple machines.

There are two ways data can be divided.

Horizontal sharding

Horizontal sharding splits rows from the same table across databases.

For example:

  • Users 1–1,000,000 → Shard A

  • Users 1,000,001–2,000,000 → Shard B

Each shard contains the same table structure, but different records.

Vertical sharding

Vertical sharding splits data differently.

Instead of dividing rows, it splits data by tables, groups of tables, or even columns within the same table.

For example:

  • User data → Database A

  • Orders → Database B

  • Analytics → Database C

This can reduce the amount of work handled by any one database.

But sharding introduces far more complexity than caching, indexing, or replication.

Now the application needs to know where data lives. Queries that span multiple shards become harder. Moving data between shards can be difficult. And a poor sharding strategy can simply move the original bottleneck somewhere else.

So sharding can unlock another level of scale.

But it should usually come after you’ve exhausted simpler options.

Managed databases can hide some of the complexity

Fortunately, you don’t always need to implement every scaling mechanism yourself.

Database-as-a-Service platforms such as Amazon RDS, Google Cloud SQL, and Microsoft Azure SQL Database can automate parts of database provisioning, replication, backups, monitoring, and scaling.

That doesn’t remove the architectural decisions.

You still need to understand whether your bottleneck is caused by queries, reads, writes, storage, or something else.

But managed services can reduce the operational work required to implement the solution.

Wrapping up

Database scaling isn’t about jumping straight to replicas or sharding.

It’s a progression.

Start by measuring the system. Then make the existing database do less work. Cache repeated reads. Optimize expensive queries with indexes. If one machine can no longer handle the read traffic, distribute those reads with replicas. And if a single primary database eventually becomes the limit, partition the workload with sharding.

Each step gives you more capacity.

But each step also adds complexity.

So the best scaling architecture isn’t the one with the most infrastructure.

It’s the simplest architecture that can reliably handle the workload you actually have.


👋 If you found this useful → Like + Restack to help others learn system design.


Subscribe to get high-signal and clear system design deep dives, case studies, and breakdowns straight to your inbox:

© 2026 Level Up Coding
548 Market Street PMB 72296, San Francisco, CA 94104