Skip to main content
Cloud Computing

What Actually Breaks First When You Outgrow One Server

Syed Taha Rizvi

Scaling advice usually starts with microservices and Kubernetes. The failures that actually take a growing application down are smaller, duller and arrive in a predictable order — and most of them are fixable without re-architecting anything.

Most writing about cloud scalability describes an end state: microservices, containers, orchestration, multi-region failover. It is accurate and it is not very useful, because almost nobody arrives at that end state deliberately. What actually happens is that an application running comfortably on one machine starts failing under load, and somebody has to decide what to fix first with the budget and the downtime window they actually have.

The useful observation is that those failures arrive in a fairly predictable order, and the order is not the one the architecture diagrams imply. Below is what tends to break, roughly in sequence, and what each one is actually asking you to do. Very little of it requires re-architecting anything.

PerceptiaAI

Ready to transform your business with AI?

Schedule a Consultation

The order things break in

1. The database connection pool, well before the CPU

The first hard ceiling most applications hit is not CPU or memory on the application server. It is connections to the database. PostgreSQL's `max_connections` defaults to 100 and each connection carries real per-backend memory cost, which is why the PostgreSQL documentation treats raising it as a decision with consequences rather than a formality.

This is the failure that most often gets misdiagnosed, because the symptom — requests timing out under load — looks like the application being slow. Adding a second application instance makes it worse rather than better, since each new instance opens its own pool. Teams add capacity, watch the error rate climb, and conclude the application cannot scale horizontally.

The fix is usually a connection pooler such as PgBouncer sitting between the application and the database, in transaction mode, so hundreds of application-side connections multiplex onto a much smaller number of real backends. It is unglamorous, it is often a day of work, and it frequently buys more headroom than a year of other optimisation.

2. Session state pinned to one machine

The second failure appears the moment there are two application instances. Anything held in process memory — a logged-in session, an upload in progress, a cached lookup table, a scheduled timer — now exists on one machine and not the other. Users get logged out at random, which is the classic signature.

Sticky sessions at the load balancer make the symptom disappear and are worth understanding as a deferral rather than a fix: they reintroduce the single point of failure you were trying to remove, because losing an instance now loses its users' state. The durable answer is the one the twelve-factor guidance states plainly — processes should be stateless and share nothing, with anything that must persist moved to a backing service.

Horizontal scaling concept with multiple application nodes

Horizontal scaling only works once nothing important lives in a single process's memory.

3. Background work running inside the web process

Report generation, email sending, image processing, third-party syncs — these usually start life inside a request handler because that was the shortest path. Under load, they compete with user-facing requests for the same workers, and a single slow export can consume the pool that serves everyone else.

Moving them to a queue and separate worker processes is not primarily about throughput. It is about isolating failure domains, so that a slow or failing background task degrades one thing rather than the whole application. It also makes the work retryable, which matters more than it sounds: a background job that fails silently inside a web request is usually discovered by a customer.

4. The absence of anything to look at

The fourth failure is the one that makes the first three expensive: no per-request timing, no database query timing, no way to distinguish slow from broken. Teams end up guessing, and guesses about performance are wrong at a rate that would be entertaining if it were not billable.

Before optimising anything, it is worth being able to answer: which endpoint, which query, how often, how long. AWS's distributed monitoring guidance covers the mechanics. The harder discipline is deciding what "acceptable" means numerically before you start, which is what Google's SRE material frames as service level objectives — a target you can be measured against rather than a vague aspiration to be faster.

What horizontal scaling actually requires

"Scale horizontally" is good advice that skips its own preconditions. An application can only be replicated if losing any single instance is uninteresting, which in practice means four things are true: no session or cache state lives in process memory, no scheduled task assumes it is the only one running, database connections are pooled outside the instance, and file uploads go to object storage rather than local disk.

The third of those catches people repeatedly, and the second is the quietest. A cron job baked into the application container runs once per container. Scale to six and it runs six times — which for an invoicing job or a reminder email is a customer-visible incident rather than an inefficiency.

None of this requires microservices. A single well-behaved application running as several identical stateless instances behind a load balancer covers the overwhelming majority of growing businesses, and it is dramatically easier to operate than a distributed system. Splitting into services is a decision about team boundaries and deployment independence far more than it is about performance.

Autoscaling is a cost control before it is a capacity control

Autoscaling is usually sold as a way to survive traffic spikes. In practice, for most businesses, the spikes are predictable and the real benefit is not paying for peak capacity during the eighteen hours a day nobody is using the system.

That distinction matters because it changes how you configure it. Scaling on CPU is the default and is often the wrong signal — a queue-backed worker fleet should usually scale on queue depth, and a request-serving fleet on concurrency or latency. Kubernetes' Horizontal Pod Autoscaler supports custom metrics precisely because CPU is a poor proxy for most real workloads.

The other half is what happens on the way down. Scaling in terminates instances, and an instance handling a long request or a half-finished job needs to be allowed to finish. Kubernetes expresses this through disruption budgets and graceful termination periods; the equivalent exists on every platform. Skipping it produces intermittent failures that only appear during scale-in, which is to say at the least convenient possible time to debug them.

Serverless and managed service architecture diagram

Managed services move operational burden rather than removing it — the tradeoff is worth making deliberately.

The costs that surprise people

Compute is the cost everyone models. The bills that cause genuine surprise are usually elsewhere, and they share a property: they scale with traffic patterns rather than with capacity, so they do not show up in a sizing exercise.

  • Data transfer between availability zones, which a naive multi-AZ deployment can generate constantly by routing every request across a zone boundary.
  • NAT gateway processing charges, billed per gigabyte on top of the hourly rate — see the VPC pricing page — which a chatty application calling external APIs from a private subnet can run up quickly.
  • Egress to the internet, which is cheap per gigabyte and expensive at volume, and which serving images or files directly from your application rather than a CDN maximises.
  • Managed database IOPS, where the provisioned tier is sized for average load and throttles during exactly the peaks you provisioned it for.
  • Log ingestion, which grows with both traffic and verbosity, and which nobody notices until an over-logged deployment doubles the bill.

The general principle is that architecture decisions have cost shapes, not just cost levels. A design that is cheaper at current volume can be more expensive at three times the volume, and the useful question during design is which direction each line moves as traffic grows.

Caching, and the part that is genuinely hard

Caching is the highest-leverage change available and the one most likely to introduce subtle bugs. Adding a cache is easy; deciding what happens when the underlying data changes is the actual work. Microsoft's caching guidance covers the patterns well, but the decision that matters is per-dataset: how stale can this specific thing be before someone is harmed by it?

A pricing table that is five minutes stale is usually fine. An account balance that is five minutes stale is a support ticket, and possibly a regulatory one. Teams get into trouble by choosing a cache TTL globally rather than per-dataset, and then discovering the exception the hard way.

When none of this is worth doing

There is a real category of system where the correct answer is a larger single server. If load is stable, the business has no availability commitment that a few minutes of downtime would breach, and the application is not close to any of the ceilings above, then vertical scaling is faster, cheaper and considerably less operationally demanding than a distributed setup.

The honest framing is that distributed architecture buys availability and independent scaling, and pays for them in operational complexity — more moving parts, more failure modes, more people needed to be on call. AWS's reliability pillar is a good structured way to establish what you actually require, and Google's error budget framing is a good way to notice that you are buying more reliability than the business is asking for.

In our experience the sequence that serves growing companies best is dull: instrument first, pool database connections, remove state from the application processes, move background work to a queue, then scale out. Each step is independently useful, each one is reversible, and none of them commits you to an architecture you cannot walk back. If you want a second opinion on which of those a specific system needs, that is what a paid discovery engagement is for — one to three weeks, a written architecture review, an estimate and a delivery plan you own regardless of whether the work continues. We run these alongside dedicated development teams or embedded engineers when a build turns out to be the right answer, and through cloud solutions and DevOps when the work is infrastructure rather than product.

Frequently asked questions

Usually not, and adopting it to solve a scaling problem you have not diagnosed is a common and expensive mistake. Several identical stateless instances behind a load balancer, with a managed database and a queue, covers most growing businesses. Kubernetes earns its operational cost when you have many services, many teams deploying independently, or genuinely bursty workloads — not merely when you have more traffic than one server can serve.

Per-request timing broken down by where the time went. Without it the question is unanswerable and the usual outcome is optimising the wrong layer. In practice, an application that is slow under load but fine when idle, with database CPU low and connection counts at their ceiling, is a pooling problem rather than a query problem — and those look identical from the outside.

It is a real option and it moves the operational burden rather than removing it. Serverless suits spiky, stateless, short-running work well. It suits long-running requests, heavy per-invocation initialisation and workloads with steady high throughput considerably less well, where the per-request pricing model becomes more expensive than the equivalent reserved capacity. The deciding factor is usually the shape of the traffic, not the technology.

Whatever you cannot currently measure. Every step after that is guesswork otherwise. Once there is per-request and per-query timing, the ordering above tends to reveal itself quite quickly — and in more cases than not, connection pooling is the change that buys the most headroom for the least work.

Enough to survive the growth you can see plus the time it takes to add more, which is a scheduling question rather than an architectural one. Building for ten times current traffic is usually a waste of money and a source of unnecessary complexity; building for zero additional traffic means the next fix happens during an incident. The useful discipline is knowing your current ceiling numerically, so you can tell how close you are to it.

Take Your Next Step

Whether you're looking to integrate AI into your workflow or just want to see more of our industry insights, we're here to help you lead the market.

Written by

Syed Taha Rizvi

Back to all articles