An application that ran smoothly for 6 months is suddenly slowing down. A page that used to load in 800 ms now takes 4 seconds. The database occasionally times out. Users complain. The team scratches its head and asks, “do we need Kubernetes?”
Almost certainly not. Most scaling problems aren’t solved with complicated infrastructure — they’re solved with a better database setup, smarter caching, and optimisations that have been well known for years. This post breaks down how to scale properly: what order to do things in, where the real bottleneck actually is, and how to avoid the classic mistake of over-engineering for a scaling problem you don’t yet have.
What “scaling” actually means
“We need scalable architecture” is one of the most misused phrases in software. Scaling doesn’t mean “more servers” or “microservices.” Scaling means growth without proportional growth in cost or degradation of experience.
Three kinds of growth an application has to be ready for:
- More users: 100 → 10,000 → 100,000
- More data: 10,000 → 10 million → a billion records
- More traffic: 1,000 requests/day → 1,000,000
Each kind has a different solution. An application with 50 users working with massive data has a different problem from an application with 50,000 users each clicking around lightly.
The key principle: don’t scale for traffic you don’t have. Optimise for actual traffic, not theoretical traffic.
The order rule: 7 levels of optimisation
There’s a clear order in which performance problems get solved. You move from one level to the next only when the previous one is exhausted.
| Level | What gets done | When |
|---|---|---|
| 1. Measurement | Identify the actual bottleneck | Always first |
| 2. Database | Indexes, query optimisation | Most common diagnosis |
| 3. Caching | Redis, browser cache, CDN | If the database can’t keep up |
| 4. Async processing | Background jobs, queues | For slower tasks |
| 5. Vertical scaling | Bigger server | Quick temporary fix |
| 6. Horizontal scaling | More servers, load balancer | For higher numbers |
| 7. Distributed | Microservices, sharding, multi-region | Only for very large systems |
Most applications never need to go past level 4. Applications that “need” level 7 are often the ones that misdiagnosed the problem at level 2.
Level 1: Measure before you optimise
The biggest mistake in scaling: optimising something that isn’t the problem. Without measurement, your assumptions about why the system is slow are almost always wrong.
What to measure:
- Response time per endpoint: which pages / API calls are slowest?
- Database query time: which queries put the most load on the database?
- CPU and memory usage during peaks
- Requests per second and how it shifts through the day
- Errors and timeouts: when and why do they happen?
Tools:
- Application Performance Monitoring (APM): New Relic, Datadog, Sentry Performance — gives you a live view
- Database query analysis: PostgreSQL EXPLAIN, MySQL slow query log
- Front-end performance: Lighthouse, WebPageTest
Typical findings after first measurement:
- 80 % of time is lost on 3 to 5 specific queries
- One API route fires 50 separate queries instead of 5 (the N+1 problem)
- Images load uncompressed (5 MB instead of 200 KB)
- Every click hits the server, when it could be cached
Without measurement, optimisation is guesswork. With measurement, optimisation becomes a precise intervention.
Level 2: The database — where 80 % of scaling lives
The most common cause of slow web applications isn’t the server, isn’t the code — it’s the database. And the good news: fixing the database is relatively cheap.
Indexes
The simplest optimisation. A database index is like the index in a book — without it, the database has to read the whole table to find what you’re looking for.
Typical symptoms of missing indexes: a simple query takes 2 seconds instead of 5 ms. A table with 100,000 rows runs slowly instead of instantly.
The fix: add an index on columns most often filtered or sorted by. Cost: 30 minutes of work. Result: often a 10 to 100 times faster query.
The N+1 problem
A classic mistake. The application fetches a list of 50 orders, then separately fetches the customer for each one — that’s 1 + 50 queries instead of one with a JOIN. Result: 51 queries instead of 1, slow and unnecessary load.
The fix: use eager loading in your ORM (Laravel with(), Rails includes, Prisma include). Cost: a few hours to find and fix. Result: dramatic speedup.
Optimising slow queries
Some queries are structurally slow — JOINs across multiple tables without indexes, aggregations over millions of rows. Here you really have to look at the query level.
Tools: PostgreSQL EXPLAIN ANALYZE, MySQL EXPLAIN. They show you exactly how the database executes the query and where time is lost.
Database connection pooling
Applications that open a new database connection for every request quickly fall over under load. Connection pooling reuses existing connections.
Tools: PgBouncer for PostgreSQL, ProxySQL for MySQL.
Level 3: Caching — the second biggest win
After database optimisation, the next biggest jump comes from caching. The principle: if data doesn’t have to be fresh on every request, don’t fetch it on every request.
Layers of caching:
Browser and CDN caching
Static files (CSS, JS, images) don’t need to load every time. Set cache headers so browsers keep the files longer — and use a CDN (Cloudflare, BunnyCDN) that serves them close to your users.
Cost: $12 – $60/mo for a CDN, 1 day of work for configuration. Result: 30 to 70 % less load on your server, dramatically faster loading for users far from your origin.
Application caching (Redis)
Most commonly: caching results of expensive queries or API calls. Users seeing the same dashboard don’t need to re-trigger the same calculation — cache it for 60 seconds, return it almost instantly.
Cost: $12 – $60/mo for managed Redis (Upstash, Redis Cloud), a few days of work to set up the right invalidation strategy. Result: 5 to 50 times faster loading on the affected endpoints.
Full-page caching
Pages that don’t change often (homepage, blog) can be fully cached as static HTML. The server doesn’t have to generate them every time — it just serves the file.
Tools: Cloudflare Page Rules, Varnish, Nginx caching, Next.js Static Generation.
Level 4: Async processing
Some actions don’t have to happen while the user waits. Sending emails, generating reports, processing images — all of those can go to a queue and execute in the background.
The user clicks “Send 500 emails,” and instead of waiting 30 seconds while they all send, gets a “Sending” response in 100 ms. The actual sending happens asynchronously.
Tools:
- Background job queues: Sidekiq (Ruby), Bull (Node), Laravel Queues, Celery (Python)
- Message brokers: Redis, RabbitMQ, Amazon SQS
Cost: a few days of development to set up the queue infrastructure. Result: the application feels dramatically faster without any change to the actual processing speed.
Level 5: Vertical scaling
The simplest form of “scaling”: give the server more resources. More CPU, more memory, faster disk.
When to use it: as a temporary measure while you fix the actual problem. A server that’s overloaded can move from 4 GB to 16 GB and buy you weeks of breathing room for proper optimisation.
When not to use it as a permanent solution: if your server keeps needing more, you haven’t scaled — you’ve thrown money at the problem. An application that struggles under 1,000 users with 16 GB of RAM has a structural problem a bigger server doesn’t solve.
Cost: depending on the provider, moving from a smaller to a bigger server is usually $60 – $400/mo more.
Level 6: Horizontal scaling
When one server isn’t enough, add more. A load balancer distributes traffic between them.
What it looks like:
- Load balancer (Cloudflare, AWS ALB, Nginx) in front
- 2 to 10+ identical application servers behind
- Central database + Redis for state
- File storage off the servers (S3, R2)
What needs preparing:
- The application has to be stateless — no state in server memory, everything in the database or Redis
- User files must not live on local disk (need S3 or similar storage)
- Sessions must be stored centrally (Redis)
Cost: nearly double the infrastructure, 1 to 2 weeks of engineering to prepare. When to do it: when vertical scaling becomes too expensive or hits a limit (every individual server has a ceiling).
Level 7: Distributed (microservices, sharding) — almost never
This is the level everyone talks about and almost nobody actually needs. Microservices, database sharding, multi-region setups — all of those are solutions for very large systems (millions of daily users, billions of records).
When it’s actually needed:
- The application processes 10,000+ requests per second consistently
- Different parts of the system have radically different scaling needs
- The team is large enough (50+ engineers) to operate a distributed system
- There’s a clear business reason (e.g. global presence with low latency in every region)
An application with 5 to 20 engineers and 10,000 users almost certainly doesn’t need microservices. A monolith with proper modularity is faster to develop, easier to maintain, and much easier to scale at levels 2 to 6.
Practical advice: when someone proposes microservices, ask “what specific problem does this solve?”. If the answer sounds like “it’s more modern” or “it’s more scalable” without a concrete problem behind it, the decision isn’t technical — it’s a trend.
The most common scaling mistakes
Optimising too early. An application with 200 users doesn’t need a multi-region setup. Optimising for problems you don’t have wastes resources that could be solving the problems you do have.
Jumping to “modern” solutions too soon. Microservices, Kubernetes, event sourcing — all have their place, but they’re over-engineering for 90 % of applications. Boring technology (monolith, PostgreSQL, Redis) scales to millions of users when used wisely.
Not measuring. Optimisation without measurement is guessing. You spend weeks “speeding up” things that aren’t bottlenecks at all.
Treating the symptom instead of the cause. The application is slow, we add a bigger server. The server helps for a month, then it slows down again. The real problem was an unoptimised query — the server was just smothering it with raw power.
Not planning for data growth. An application with 100,000 rows runs fast; with 50 million it might not. A schema design that works at small scale may not work at large. Tables need pagination, indexes, eventually partitioning.
A realistic timeline for resolving scaling issues
Typical flow for “the application has gotten slow”:
| Week | Activity |
|---|---|
| 1 | Measurement, identification of bottlenecks |
| 2 – 3 | Database optimisation (indexes, queries, N+1) |
| 4 | Caching implementation (Redis, CDN) |
| 5 | Async processing for slower tasks |
| 6 | Verification, load testing |
Cost: $5,000 – $15,000 of work (less for smaller applications). Result: for most applications, 5 to 20 times performance improvement without any infrastructure change.
The bottom line
Scaling a web application isn’t magic — it’s methodical work through seven levels, almost always in the same order: measure, fix the database, add caching, async slower tasks, eventually scale up the server, eventually add more servers, distribute only if you really have to.
90 % of applications never need to go past level 4. Most “scaling” problems are solved by better indexes, smarter queries, and good caching — not microservices.
The biggest trap is over-engineering for problems you don’t have. Boring technology (PostgreSQL, Redis, monolith) scales much further than most founders think. Microservices and distributed solutions are expensive, complicate development, and are only justified for systems with tens of thousands of requests per second.
The smartest approach: start with a well-structured monolith, measure from launch, optimise when the data shows what’s needed — not when someone mentions that “we should.”
Is your application starting to struggle under load? We can run a performance audit — identify the actual bottlenecks and propose the fastest interventions with the largest impact. Often 80 % of the improvement comes from a few days of work. Get in touch. More about our approach on the Web Application Development page.
Bojan Zlatanović
Founder at Norvaris. Building digital products and writing about what actually works in product, engineering, and growth.
More from this author