Skip to content
Rivl
29 August 2026Performance9 min

How to scale a web app for more users, in the right order

Adding servers is the most visible response to load and usually the least effective one. The bottleneck is rarely where the conversation starts.

A scaling conversation almost always opens at the infrastructure layer: bigger machines, more of them, a load balancer, maybe a move to a different host. That is the most visible lever and, in the systems we get called into, almost never the binding one.

The order below is not a best-practice list. It is the sequence that stops you from spending money on capacity you already have and were wasting.

The five steps of this article listed in order, from measuring in production through to adding machines last.
The order this piece argues for, with adding hardware deliberately placed fifth

1. Measure in production, not on your laptop

The first mistake is optimising against a local benchmark. Google puts the reason bluntly in its guidance on field measurement: "Without field data, it's impossible to know for sure whether the changes you're making to your site are actually achieving their desired results." Your machine is fast, near the server, and running one session. None of those describe your users under load.

There is a detail in how Core Web Vitals are assessed that matters more for scaling than for anything else. The thresholds, 2.5 seconds for Largest Contentful Paint, 200 milliseconds for Interaction to Next Paint and 0.1 for Cumulative Layout Shift, are judged at the 75th percentile of page loads rather than the average. That is deliberate, and it is the right instinct for capacity work too. Load problems appear in the slowest quarter of requests long before they move the average, so a team watching averages finds out late, from users.

2. Find the actual bottleneck before touching anything

Slowness under load has a small number of usual causes and they are not equally likely. In order of how often we find them:

  • Database queries that were fine at small data. A query with no index is invisible at ten thousand rows and fatal at two million. Nothing about it changed; the data grew past it.
  • The N+1 pattern. One query to fetch a list, then one more per item. Twenty items is unnoticeable, two thousand is an outage. This is the single most common cause we see and it is usually a framework convenience being used honestly.
  • Work done per request that could be done once. Recomputing something on every page load that changes daily.
  • Blocking calls to somebody else's API inside the request path, where their bad afternoon becomes your downtime.
  • Connection exhaustion. The app scales, the database connection pool does not, and adding app servers makes it worse rather than better.
The causes of slowness under load from this article, drawn as a list of key points.
The usual causes, in the order we find them. No measurements implied, only frequency in our own work

Note what that last one implies. Horizontal scaling can actively harm a system whose limit is the database, because every new instance opens more connections to the same constrained resource. That is why adding machines comes fourth in this list and not first.

3. Fix the layer that is actually binding

Almost always the database. Indexes on the columns you filter and join on, the N+1 collapsed into one query, and any aggregate that gets read far more often than it changes computed ahead of time rather than on demand.

The payoff here is routinely an order of magnitude, which no amount of hardware will match at any sane price. A missing index is not a capacity problem wearing a disguise; it is a bug that only shows up once you succeed.

4. Cache the expensive and unchanging

Caching is powerful and it is where teams create their next problem, because a cache is a second copy of the truth and now you own two. Cache things that are expensive to produce and tolerant of being slightly stale: rendered pages for anonymous visitors, reference data, aggregate counts on a dashboard.

Do not cache anything a user must see change the instant they change it. And be honest about how fresh the numbers really need to be, because that assumption is usually inherited rather than decided. Our note on what "real time" actually means in a dashboard exists because most teams are paying for a freshness nobody asked for.

5. Only now, add machines

Once the queries are sane and the expensive work is cached, horizontal scaling does what people expected it to do in step one, and it does it far more cheaply, because each instance now serves several times the traffic it did before. This is also the point at which the monthly bill becomes predictable rather than alarming, which is the subject of what a web app costs per month to run.

Doing this step first is not merely inefficient. It buries the underlying problem under enough hardware that nobody finds it until the next growth step, when it costs more to fix and you have a larger bill to keep paying in the meantime.

The front end is a separate problem with the same shape

Everything above concerns the server. A page can be served in eighty milliseconds and still feel slow because of what happens after it arrives: oversized images, render-blocking scripts, layout that moves while the user is reading. That is a different diagnosis with a different fix, laid out in the mobile slowness sequence, and it is worth ruling in or out early because it is usually cheaper to fix than anything in the backend list.

It also has a direct commercial consequence rather than an aesthetic one, which is the argument in why a slow site is a revenue problem. If traffic is growing, the fraction of it you lose to slowness grows with it.

The honest limits

Most applications never need any of steps four or five. If you are serving a few thousand users a day, a single well configured machine with correct database indexes will carry you comfortably, and a team that builds for imagined scale ships later and spends more for a capacity that never arrives.

There is also a real case for doing nothing yet. If load is growing but the system is still comfortably inside its limits, the right move is instrumentation and a threshold to act on, not a rewrite. Knowing the number at which you will start work is most of the value; the work itself can wait until the number arrives.

And if the answer turns out to be that the architecture cannot get there from here, that is worth establishing deliberately rather than discovering across three quarters of incremental patching. Why software projects take so long covers what that conversation usually looks like from the inside.

Describe it. We build it.

Seven or twelve days, pay on delivery, a year of maintenance included. Bring the problem, not a spec.

Book a meeting

Read next