DEV Community

Cover image for How Do Big Backend Applications Scale?
Tanu Priya
Tanu Priya

Posted on

How Do Big Backend Applications Scale?

A backend rarely starts as a complicated distributed system.

It usually starts with something much simpler:

User → API Server → Database
Enter fullscreen mode Exit fullscreen mode

A few users send requests. The server processes them. The database stores the data. Everything is easy to understand.

Then the application becomes popular.

The number of users increases. Requests arrive faster. Some database queries become expensive. Large files need processing. Emails and notifications have to be sent. One server starts running out of capacity.

So you add another server.

Then another.

Then a cache.

Then a queue.

Then database replicas.

Eventually, you might have dozens or hundreds of machines working together.

This raises a fundamental question:

How does a simple backend gradually evolve into a system capable of serving millions of requests?

The answer isn't simply "add more servers."

Backend scaling is a process of identifying bottlenecks, separating workloads, distributing work, and accepting carefully chosen trade-offs.

The architecture becomes more complicated because the problems become more complicated.


What Does It Actually Mean to Scale?

Before looking at specific technologies, it helps to define what we're trying to improve.

A backend typically has several important characteristics.

Latency

How long does one request take?

For example:

GET /products/123
→ 80 ms
Enter fullscreen mode Exit fullscreen mode

Lower latency generally means the individual request completes faster.

Throughput

How much work can the system handle over a period of time?

For example:

10,000 requests / second
Enter fullscreen mode Exit fullscreen mode

Throughput is about the amount of work the system can process.

Concurrency

How many operations are happening at the same time?

A server might have thousands of active connections even if only a fraction of them are actively consuming CPU at a particular moment.

Availability

How often is the system operational and able to serve requests?

A system that works extremely quickly but frequently goes offline isn't very useful.

Scalability

Scalability is about how well the system continues to handle increasing workload as demand grows.

These concepts are related, but they aren't the same.

You can improve latency without increasing throughput.

You can increase throughput while making individual requests slower.

You can increase capacity while accidentally making the system harder to operate.

That's why system design is fundamentally about trade-offs.


The First Version: Keep It Simple

Imagine you're building a small e-commerce application.

The first version might look like:

Users
  ↓
API Server
  ↓
Database
Enter fullscreen mode Exit fullscreen mode

The API server might handle:

  • Authentication
  • Validation
  • Business logic
  • Database queries
  • API responses

The database handles persistent state.

For a small application, this architecture can work extremely well.

There is a tendency in system design discussions to jump immediately to technologies such as Kubernetes, Kafka, Redis, microservices, and distributed databases.

But complexity has a cost.

Every additional component means another system that must be:

  • Deployed
  • Monitored
  • Secured
  • Debugged
  • Upgraded
  • Paid for

If your application has 100 users, introducing twenty distributed components probably creates more problems than it solves.

So the first scaling principle is:

Start with the simplest architecture that satisfies the current requirements.

The architecture should evolve when the workload demands it.


What Happens When Traffic Increases?

Suppose the application becomes popular.

More users mean more requests.

Initially, the server may handle everything comfortably.

Eventually, resources begin to approach their limits.

You might observe:

CPU       → 90%
Memory    → 85%
Database  → High load
Latency   → Increasing
Errors    → Increasing
Enter fullscreen mode Exit fullscreen mode

But these numbers don't tell you what to change.

A server can be slow because of CPU-intensive work.

Or because it is waiting for the database.

Or because it is making too many network calls.

Or because a particular query is poorly optimized.

Or because a downstream service is slow.

This is why scaling should begin with measurement, not infrastructure.

You want to answer:

Where is the time and capacity actually going?

That leads to one of the most important ideas in system design:

Don't scale everything. Scale the bottleneck.


Step 1: Optimize Before You Scale

Imagine one endpoint is taking 2 seconds to respond.

The first instinct might be:

"We need more servers."

But suppose the request contains a database query that scans millions of rows.

Adding more API servers won't fix that query.

You might instead need:

  • A better index
  • A more efficient query
  • Pagination
  • Smaller result sets
  • Connection pooling
  • Reduced database round trips

This is an important distinction.

There are two broad ways to handle more workload:

Do less work

Examples:

  • Cache repeated results
  • Optimize database queries
  • Avoid unnecessary computation
  • Reduce payload sizes

Do more work in parallel

Examples:

  • Add application servers
  • Add database replicas
  • Add workers
  • Partition workloads

Good architectures often use both.


Step 2: Add Caching

One of the most common forms of unnecessary work is repeatedly calculating or retrieving the same information.

Imagine a product page:

GET /api/products/123
Enter fullscreen mode Exit fullscreen mode

Suppose thousands of users request the same product.

Without caching, every request may reach the database.

Request
   ↓
API
   ↓
Database
   ↓
Response
Enter fullscreen mode Exit fullscreen mode

If the product doesn't change frequently, doing this thousands of times is wasteful.

A cache allows frequently accessed data to be stored temporarily.

Request
   ↓
Cache
   ↓
Database if needed
Enter fullscreen mode Exit fullscreen mode

If the data exists in the cache, that's a cache hit.

If it doesn't, that's a cache miss.

A common approach is called cache-aside:

  1. Application checks the cache.
  2. If data exists, return it.
  3. Otherwise, query the database.
  4. Store the result in the cache.
  5. Return the result.

Redis is commonly used for this type of workload.

Why Is Caching So Powerful?

Imagine a database receives 100,000 identical reads.

If 90,000 of those requests can be served from a cache, the database only needs to handle a fraction of the original workload.

That can improve:

  • Database capacity
  • Response latency
  • Application throughput

But caching introduces a difficult problem:

Stale Data

Suppose the database says:

Price = ₹999
Enter fullscreen mode Exit fullscreen mode

while the cache still contains:

Price = ₹899
Enter fullscreen mode Exit fullscreen mode

Which value should the user receive?

This is why cache design involves concepts such as:

  • TTL
  • Cache invalidation
  • Cache-aside
  • Write-through caching
  • Cache consistency

Caching is therefore not simply a performance feature.

It is also a data-consistency decision.

And this is a recurring pattern in system design:

Performance improvements often introduce consistency or operational trade-offs.


Step 3: One Server Is No Longer Enough

Eventually, the application server itself may become the bottleneck.

Perhaps CPU usage is consistently high.

Perhaps memory usage is approaching the machine's limit.

Perhaps there are simply too many concurrent requests.

At this point, horizontal scaling becomes useful.

Instead of one server:

Users
  ↓
Server
Enter fullscreen mode Exit fullscreen mode

we run multiple instances:

Users
  ↓
Load Balancer
  ↓
+--------+--------+
|        |        |
Server   Server   Server
Enter fullscreen mode Exit fullscreen mode

The load balancer distributes requests among the available instances.

Now the application can increase capacity by adding instances.

This is horizontal scaling.


Why Stateless Servers Matter

Multiple servers create an important question:

Where does application state live?

Suppose Server 1 stores a user's session in its local memory.

The user's next request goes to Server 3.

Server 3 doesn't have that session.

Now the system has a problem.

This is why scalable application servers are generally designed to be stateless.

Instead of keeping important shared state only in local memory, that state can be stored in an external system.

For example:

Server 1 ─┐
Server 2 ─┼──→ Shared State
Server 3 ─┘
Enter fullscreen mode Exit fullscreen mode

The shared system might be a database, Redis, or another appropriate storage mechanism.

Now any application instance can process the request.

This has an important operational benefit.

If a server disappears, another server can replace it without losing application state.

Servers become relatively disposable compute units.

That property is extremely useful in horizontally scaled environments.


Step 4: The Database Becomes the Bottleneck

Now imagine we have five application servers.

Users
  ↓
Load Balancer
  ↓
Server Server Server Server Server
  ↓
Database
Enter fullscreen mode Exit fullscreen mode

The application layer has scaled.

But there is still one database.

Every server is sending queries to it.

Eventually, the database may become the limiting component.

Before introducing database replication or sharding, investigate the fundamentals.

Indexes

Indexes can dramatically reduce the amount of data a database needs to inspect for supported query patterns.

Query Optimization

A query that scans unnecessary rows or performs expensive joins can consume significant resources.

Connection Pooling

Creating database connections repeatedly can be expensive.

Connection pools allow applications to reuse connections.

Pagination

Returning 1 million rows when the user only needs 20 is unnecessary work.

Avoiding N+1 Queries

Fetching one record and then performing another query for every related record can create hundreds or thousands of database calls.

These optimizations are often more valuable than immediately adding infrastructure.

The lesson is simple:

A slow database query is still a slow query after you add more API servers.


Step 5: Database Replication

After optimizing the database, the workload may still be too large.

Many applications have a read-heavy workload.

Think about an online store.

A product might be viewed thousands of times while its price or description changes relatively infrequently.

In such cases, database replication can distribute read traffic.

A simplified architecture looks like:

             Application
                 |
          +------+------+
          |             |
          ↓             ↓
       Primary      Read Replica
       Writes          Reads
Enter fullscreen mode Exit fullscreen mode

The primary handles writes.

Replicas can serve appropriate read queries.

This can reduce pressure on the primary database.

But there is an important trade-off.

Replication Lag

Suppose a user updates their profile.

The write reaches the primary database immediately.

The replica may receive that change slightly later.

If the next request reads from the replica, the user might temporarily see the old information.

This creates a consistency question:

Does this operation require the latest data, or is slightly stale data acceptable?

For some applications, a small amount of replication lag is acceptable.

For others, it may not be.

Therefore, replication isn't simply about making a database faster.

It also requires designing how reads and writes interact.


Step 6: Move Slow Work Out of the Request

Now consider a different problem.

Suppose a user requests a report.

Generating that report requires:

  1. Querying millions of records
  2. Processing the data
  3. Creating a file
  4. Uploading the file
  5. Sending an email

If all of this happens inside the HTTP request, the user may be waiting for a very long time.

Instead, the API can create a background job.

User
 ↓
API
 ↓
Queue
 ↓
Worker
Enter fullscreen mode Exit fullscreen mode

The API can respond quickly while the worker performs the expensive operation asynchronously.

This pattern is useful for:

  • Email sending
  • Video processing
  • Image processing
  • Report generation
  • Notifications
  • Data exports
  • Scheduled processing

The queue creates a boundary between request processing and background processing.


Why Queues Help During Traffic Spikes

Queues are particularly useful when work arrives in bursts.

Imagine a system normally receives:

100 jobs/minute
Enter fullscreen mode Exit fullscreen mode

Then a marketing campaign causes:

10,000 jobs
Enter fullscreen mode Exit fullscreen mode

If the system tries to process all 10,000 immediately, downstream resources may become overwhelmed.

A queue lets the system absorb the burst.

Workers can process jobs at a sustainable rate.

This is closely related to backpressure.

Instead of forcing every component to operate at the speed of the fastest producer, the queue provides a buffer.

But now we have another set of engineering problems.

What if a worker crashes halfway through a job?

What if the job is delivered twice?

What if the job keeps failing?

These lead to concepts such as:

  • Retries
  • Acknowledgements
  • Idempotency
  • Dead-letter queues
  • Visibility timeouts
  • Retry backoff

Idempotency: An Important Property of Background Jobs

Suppose a worker processes:

Send ₹1,000 payment
Enter fullscreen mode Exit fullscreen mode

The worker completes the payment but crashes before acknowledging the message.

The queue may deliver the same job again.

If processing the job twice creates two payments, you have a serious problem.

An idempotent operation can safely be repeated without producing an incorrect additional effect.

For example, instead of blindly creating a payment every time, the system might associate the operation with a unique transaction ID and ensure that the same transaction cannot be applied twice.

This is an example of why distributed systems require more than just adding components.

Once work can be retried, duplicated, delayed, or reordered, the application has to account for those possibilities.


Step 7: Scale Workers Independently

Once background processing has been separated from API requests, the two workloads can scale independently.

Suppose you have:

10 API servers
3 workers
Enter fullscreen mode Exit fullscreen mode

Later, video processing becomes much more popular.

You might need:

10 API servers
30 video workers
Enter fullscreen mode Exit fullscreen mode

There is no reason to increase API capacity simply because video processing became expensive.

Different workloads have different resource requirements.

An API may be mostly network and database bound.

A video encoder may be CPU intensive.

A machine-learning worker may require large amounts of memory or specialized hardware.

Independent scaling allows each workload to receive the resources it actually needs.


Step 8: When Does a Monolith Become a Problem?

As the application grows, another kind of scaling problem can appear.

The problem isn't necessarily CPU or memory.

It is organizational and architectural complexity.

Imagine one huge application containing:

Users
Orders
Payments
Search
Notifications
Analytics
Files
Enter fullscreen mode Exit fullscreen mode

The application may still run perfectly well.

But as the codebase grows, different teams may need to deploy different areas independently.

One module may require very different scaling characteristics from another.

This is where modular architecture becomes important.

A good first step is often a modular monolith.

Keep the application as one deployable unit, but clearly separate its internal domains.

For example:

Application
├── Users
├── Orders
├── Payments
├── Search
└── Notifications
Enter fullscreen mode Exit fullscreen mode

This gives the codebase clear boundaries without immediately introducing distributed-system complexity.

Only when those boundaries need independent deployment, scaling, ownership, or infrastructure should you consider separating them into services.


Step 9: Microservices and Their Cost

A microservices architecture might eventually look like:

                 API Gateway
                      |
        +-------------+-------------+
        |             |             |
      Users         Orders       Payments
      Service       Service       Service
Enter fullscreen mode Exit fullscreen mode

Each service can potentially have its own:

  • Deployment lifecycle
  • Scaling strategy
  • Team ownership
  • Data storage
  • Technology choices

This can be useful.

But there is a major trade-off.

Inside a monolith:

Orders → Payment Module
Enter fullscreen mode Exit fullscreen mode

might be a local function call.

With microservices:

Orders Service
      ↓
Network
      ↓
Payment Service
Enter fullscreen mode Exit fullscreen mode

Now you have network latency, timeouts, retries, authentication, serialization, and partial failures.

The system can fail in ways that never existed inside the monolith.

For example:

Orders Service → Working
Payment Service → Working
Network → Failed
Enter fullscreen mode Exit fullscreen mode

Both services are healthy, but the request still fails.

This is the fundamental cost of distribution.

Microservices don't remove complexity. They move complexity into the network and operations.

That's why microservices should solve a specific problem rather than being treated as the default architecture for every large application.


Step 10: Observability Becomes Essential

As the architecture becomes distributed, debugging becomes harder.

Imagine a request travels through:

Load Balancer
      ↓
API
      ↓
Cache
      ↓
Orders Service
      ↓
Payment Service
      ↓
Database
Enter fullscreen mode Exit fullscreen mode

The request takes 4 seconds.

Where did the delay happen?

Was the cache slow?

Was the database slow?

Did the payment service take 3 seconds?

Did a network request time out and retry?

Without observability, answering these questions becomes difficult.

This is why larger systems rely heavily on:

Logs

Detailed records of events.

Metrics

Numerical measurements such as:

  • Request rate
  • Error rate
  • CPU
  • Memory
  • Latency
  • Database connections
  • Queue depth

Traces

A way to follow one request as it moves through multiple components.

Observability is therefore not just something added after the system is built.

For distributed systems, it becomes part of the system itself.


Step 11: Design for Failure

A system with one server has a relatively small number of failure modes.

A distributed system has many more.

A cache can fail.

A database replica can fall behind.

A queue can become full.

A worker can crash.

A service can time out.

A network connection can fail.

A deployment can introduce an error.

So as the architecture grows, a new question becomes increasingly important:

What happens when something fails?

Consider a cache.

If the cache disappears, can the application still read from the database?

Consider a worker.

If it crashes halfway through a job, can another worker safely retry it?

Consider a database replica.

If it becomes unavailable, can the application temporarily route reads elsewhere?

Consider a service.

If it becomes slow, should the caller wait indefinitely?

These questions lead to concepts such as:

  • Timeouts
  • Retries
  • Circuit breakers
  • Failover
  • Health checks
  • Rate limiting
  • Graceful degradation

Reliability is not the absence of failure.

It is the ability of the system to behave sensibly when failures occur.


A Realistic Scaling Journey

Let's put everything together with an e-commerce example.

Stage 1: Small Application

You start with:

Users
 ↓
API
 ↓
Database
Enter fullscreen mode Exit fullscreen mode

Simple, inexpensive, easy to operate.

Stage 2: Repeated Reads

Product pages become popular.

The database receives the same reads repeatedly.

You introduce caching.

Users
 ↓
API
 ↓
Cache
 ↓
Database
Enter fullscreen mode Exit fullscreen mode

Stage 3: More Traffic

The API server becomes overloaded.

You add multiple instances behind a load balancer.

Users
 ↓
Load Balancer
 ↓
API API API
Enter fullscreen mode Exit fullscreen mode

Stage 4: Database Pressure

The database becomes the next bottleneck.

You optimize queries, add indexes, and improve connection management.

If read traffic is still high, you introduce replicas.

Stage 5: Background Work

The application starts generating invoices, processing images, and sending notifications.

You move those tasks into a queue.

API
 ↓
Queue
 ↓
Workers
Enter fullscreen mode Exit fullscreen mode

Stage 6: Independent Workloads

Image processing becomes much heavier than normal API traffic.

You scale image workers independently.

Stage 7: Organizational Growth

The engineering organization becomes larger.

Payments, orders, search, and notifications have different requirements.

You introduce stronger module boundaries.

Some modules may eventually become independent services.

Notice what happened.

The final architecture wasn't designed on day one.

It evolved because each new problem required a different solution.

That is how many real systems grow.


The Architecture Is a Set of Trade-Offs

There is no architecture that is simultaneously:

  • Cheapest
  • Fastest
  • Simplest
  • Most available
  • Most consistent
  • Most scalable
  • Easiest to operate

Every decision changes something else.

Caching can improve latency but introduce stale data.

Replication can increase read capacity but introduce replication lag.

Queues can protect the API from expensive background work but introduce asynchronous behavior.

Microservices can provide independent deployment and scaling but introduce distributed-system complexity.

Horizontal scaling can increase capacity but requires stateless application design and shared infrastructure.

This is why system design is fundamentally about trade-offs.

You aren't searching for the perfect architecture.

You're choosing the architecture whose trade-offs make sense for the problem.


A Better Way to Think About Scaling

Instead of memorizing:

Caching
Load Balancer
Replication
Queue
Microservices
Enter fullscreen mode Exit fullscreen mode

think in terms of the problems they solve.

Problem Possible Solution
Repeated expensive reads Caching
One server cannot handle traffic Horizontal scaling
Requests depend on local state Externalize shared state
Database queries are slow Indexing and query optimization
Too many database reads Replication / caching
Long-running requests Asynchronous processing
Background workload increases Worker scaling
Large codebase becomes difficult to manage Modular architecture
Independent deployment is required Service boundaries
Distributed system is difficult to debug Observability
Components fail independently Resilience mechanisms

This is much more useful than memorizing architecture diagrams.

When you encounter a new system-design problem, ask:

What is the bottleneck?

Then:

What is the smallest architectural change that can remove it?


The Complete Mental Model

A mature backend might eventually look something like this:

                         Users
                           |
                           v
                     Load Balancer
                           |
                  +--------+--------+
                  |        |        |
                  v        v        v
               Server   Server   Server
                  |
             +----+----+
             |         |
             v         v
           Cache    Database
                       |
                 +-----+-----+
                 |           |
                 v           v
              Primary    Read Replica

                       |
                       v
                     Queue
                    / | \
                   /  |  \
                  v   v   v
              Worker Worker Worker
Enter fullscreen mode Exit fullscreen mode

And parts of the application may eventually become independent services.

But this diagram is not a blueprint.

It is a collection of patterns.

You might use only three of them.

You might use ten.

You might replace some entirely.

The right architecture depends on:

  • Traffic
  • Data volume
  • Latency requirements
  • Consistency requirements
  • Availability requirements
  • Team structure
  • Budget
  • Operational maturity
  • Failure tolerance

The Most Important Lesson

The biggest mistake in system design is thinking that a large architecture is automatically a better architecture.

It isn't.

A simple architecture that reliably handles 10,000 users is better than an unnecessarily complicated architecture that creates operational problems.

And a simple architecture that works today may not be sufficient tomorrow.

That's okay.

Architecture is allowed to evolve.

The goal isn't to predict the future perfectly.

The goal is to build a system that can evolve when the future arrives.

So the backend scaling journey can be summarized as:

More Users
     ↓
More Work
     ↓
Measure
     ↓
Find the Bottleneck
     ↓
Reduce Work OR Add Capacity
     ↓
Measure Again
     ↓
Repeat
Enter fullscreen mode Exit fullscreen mode

Sometimes reducing work means caching.

Sometimes it means optimizing a query.

Sometimes adding capacity means more servers.

Sometimes it means read replicas.

Sometimes it means more workers.

And sometimes the problem isn't performance at all—it is code ownership or organizational complexity.

That's when modularity and service boundaries become important.


Final Takeaway

You don't start with a distributed system.

You grow into one.

A backend might begin as:

Server + Database
Enter fullscreen mode Exit fullscreen mode

Then evolve into:

Load Balancer
+ Multiple Servers
+ Cache
+ Database Replication
+ Queue
+ Workers
Enter fullscreen mode Exit fullscreen mode

And eventually, parts of the system may become independent services.

But every step should have a reason.

Before adding a new component, ask two questions:

What problem does this solve?

and:

What new complexity does this introduce?

If you can answer both, you're not just adding technologies.

You're designing a system.

And that is the real skill behind scaling big backend applications.

Top comments (0)