Load-Testing an App to 10,000 Users: Here's What Breaks First

This walkthrough is an illustrative load test of a typical MVP setup, based on the patterns we see most often when reviewing growing products. The numbers show typical ranges, not the results of a specific client project.
Ask most founders how many users their product can handle at once, and you'll get a confident guess. "Probably a few thousand." "It's on the cloud, so it scales." "We've never had a problem."
Then ask whether they've tested it. Usually, they haven't.
That's not a criticism. When you're building an MVP, load testing is rarely the priority, and it shouldn't be. But at some point, a launch, a campaign or a big customer is going to send far more traffic than you've ever seen. When that happens, the only question that matters is: what breaks first?
In this article, we'll walk through what a load test to 10,000 concurrent users looks like on a typical MVP, stage by stage. You'll see the order in which things usually fail, why each one fails, and what it takes to get the same product through 10,000 users comfortably, without rewriting it. Load testing like this is at the core of our System Design & Scalability service.
The app: a typical MVP stack
Our example is a two-sided marketplace, the kind many startups build: users search listings, view profiles with photos, message each other and book services with online payment. It's a common, sensible MVP setup:
A web application running on one cloud server, with a few app processes
A relational database (PostgreSQL) on its own small instance
Photos uploaded to and served from the app server
Confirmation emails sent directly when a booking is made
A payment provider called during checkout
No caching layer, no CDN, and no autoscaling
Basic server logs, but no performance monitoring
In beta, with a few hundred active users, it feels fast. Nobody has complained. The team is confident.
How the test works
A useful load test doesn't just hammer one URL. It simulates what real users actually do. We'd script several realistic journeys, mixed in proportions that match real usage:
User journey | Share of simulated traffic |
Browse the homepage and popular listings | 40% |
Search with filters | 25% |
View a provider profile with photos | 20% |
Send a message | 10% |
Complete a booking with payment | 5% |
The test runs against a staging environment that mirrors production, never the live product. Traffic increases in stages: 100, 500, 1,000, 2,500, 5,000 and 10,000 concurrent users, holding at each stage long enough to see how the system settles. At every stage, we'd watch four things: response times (especially the slowest 5% of requests), error rates, resource use on every server and the database, and which component is under the most strain.
Tools like k6, Locust or JMeter generate the traffic. The value isn't the tool. It's knowing what to measure and how to read the results.
Stage 1: 100 users. Everything looks fine.
At 100 concurrent users, pages load quickly, error rates are zero, and the server is barely working. This is roughly the traffic the product sees on a busy day in beta.
This stage matters more than it seems: it's the baseline. Every later stage is compared against it. It's also where teams get falsely reassured. A product that's comfortable at 100 users can still be minutes away from failing at 1,000.
Stage 2: 500 users. The database runs out of connections.
What breaks first: Somewhere between a few hundred and a thousand users, errors suddenly appear. Not slowdowns at first, but outright failures. The logs fill with messages that the database has refused new connections.
Why: Each app process opens its own connections to the database, and under load, it opens more. The database has a limit on how many connections it can accept at once, and on a small instance, that limit is low. With no connection pooling to share and reuse connections, the limit is reached quickly. Once it is, new requests simply fail.
This is one of the most common first failures, and one of the most surprising to teams, because the database isn't even busy yet. It's just out of connections.
Stage 3: 1,000 users. Search grinds to a halt.
Once connection limits are raised temporarily to continue the test, the next problem appears quickly.
What breaks next: Search and listing pages, which loaded in well under a second at 100 users, now take five, eight, ten seconds. Database CPU sits at 100%. Every other page slows down too, because they all share the same overloaded database.
Why: Two classic problems, working together:
Missing indexes. Search filters by location, category, price and availability, but the database has no indexes for those combinations. Every search scans the entire listings table. With a few thousand listings, that was fast. Under hundreds of simultaneous searches, it's crippling.
The "N+1" query problem. Each listing page loads 20 listings, then runs a separate query for each listing's photos, ratings and provider details. One page view becomes 60 or more database queries. Multiply that by hundreds of users, and the database is drowning in small queries.
In our experience, the database is the first real bottleneck in most MVPs. And it's usually the most fixable.
Stage 4: 2,500 users. Uploads freeze the site.
What breaks next: Even with simulated database fixes, the app servers start struggling. Response times jump whenever a burst of photo uploads arrives. Booking confirmations slow down unpredictably.
Why: Heavy work is happening inside user requests:
When a provider uploads photos, the server resizes each image while the user waits, using a lot of CPU and tying up an app process for several seconds.
Every photo on every page is served from the app server itself, so the same machine handling bookings is also delivering megabytes of images.
When a booking is made, the app sends the confirmation email directly and waits for the email provider to respond. When that provider slows down, so does every checkout.
The app server isn't running out of capacity because of real application logic. It's running out because it's doing jobs that shouldn't be happening there at all.
Stage 5: 5,000 users. The single server hits its ceiling.
What breaks next: Response times climb steadily, and then sharply. The server's CPU and memory are maxed out. Requests queue up, some time out, and the error rate rises past 10%. Eventually the app processes start crashing and restarting, and for a few minutes, the site is effectively down.
Why: There's only one app server, and no amount of tuning makes a single machine infinitely large. Without a load balancer and more servers, there's nowhere for extra traffic to go. Without autoscaling, nobody adds capacity until someone notices, logs in and does it by hand, by which time the spike may already be over and the damage done.
This is also where a less obvious problem shows up: user sessions are stored in the app server's memory. That works with one server, but it means simply adding a second server would log users out randomly as requests bounce between machines.
Stage 6: 10,000 users. The original setup can't get here.
In our example, the original setup never reaches 10,000 users. It fails somewhere between a few hundred and a few thousand, depending on which problems hit first. That's the honest, typical result for an unoptimized MVP, and it's not a sign of bad engineering. The product was built to prove an idea, not to handle a crowd.
Here's a summary of the order things fail in:
Order | What breaks | Typical point | Symptom |
1st | Database connections | A few hundred users | Sudden errors, refused connections |
2nd | Slow, unindexed queries | Around 1,000 users | Search and listings take many seconds, database CPU maxed out |
3rd | Heavy work in requests | 1,000–2,500 users | Uploads and checkouts slow the whole site |
4th | Single server capacity | 2,500–5,000 users | Rising timeouts, crashes, effective downtime |
5th | Server-bound sessions | When adding servers | Users logged out at random |
The exact numbers vary from product to product. The order is remarkably consistent.
Not sure whether your product has these problems? Start with 7 Signs Your MVP Won't Survive Its First Traffic Spike
Do you know where your product would break? A load test answers that in days, before your users do. Get a free 30-minute review →
Fixing it, in the order it broke
The good news: none of these problems require a rewrite. Each one has a targeted fix, and the order of the failures tells you the order of the fixes.
1. Pool database connections
Adding a connection pooler lets hundreds of app requests share a smaller number of database connections efficiently. This removes the first failure point almost entirely, and usually takes very little time.
2. Fix the slow queries
Profiling shows exactly which queries are slow. Adding the right indexes for common search filters, and rewriting listing pages to load related data in a few queries instead of dozens, often cuts database load dramatically. For read-heavy traffic like browsing and search, a read replica takes even more pressure off the main database.
3. Cache what's popular
In most products, a small share of pages and searches makes up most of the traffic. Caching popular listings, search results and profiles in Redis means most requests are answered in milliseconds, without touching the database at all.
4. Move heavy work out of the request
Photo processing, confirmation emails and payment webhooks move to a background queue with separate workers. Users get an instant response, and the work happens moments later, with automatic retries if something fails. Every call to an outside service also gets a timeout, so a slow provider can't freeze the app.
5. Serve files from the right place
Photos move to cloud object storage, are resized once into the sizes each page needs, and are delivered through a CDN. Pages get lighter, especially on mobile, and the app servers stop doing a job they were never meant for.
6. Scale out, automatically
User sessions move out of server memory into a shared store, so any server can handle any request. Then the app runs on several servers behind a load balancer, with autoscaling that adds servers as traffic rises and removes them as it falls.
Load balancing, autoscaling and high availability are set up as part of our Deployment service.
The second test
With the fixes in place, the same test runs again, with the same journeys, the same stages and the same staging environment. These are the kinds of targets a well-executed round of fixes typically aims for:
Measure | Original setup | Target after fixes |
Max concurrent users before errors | A few hundred to a few thousand | 10,000+, with headroom |
Search and listing pages (slowest 5%) | 5–10 seconds under load | Under 1.5 seconds at 10,000 users |
Error rate at peak | Above 10%, then downtime | Under 0.5% |
Database CPU at peak | 100% | Comfortably below its limit |
Effect of a slow email or payment provider | Whole checkout slows down | Isolated, retried in the background |
Server failure during the test | Site goes down | Load balancer routes around it |
Cost per 1,000 users | Rising with traffic | Flat or lower, thanks to caching and autoscaling |
The exact results depend on the product, but the pattern holds: the same codebase, with targeted changes, can handle many times the traffic it started with.
Five lessons from load testing MVPs
1. The database almost always breaks first. Connection limits, missing indexes and inefficient queries are the most common first bottlenecks. Start there.
2. "It's on the cloud" doesn't mean it scales. Cloud hosting makes scaling possible. It doesn't make it automatic. A single cloud server fails just like a single physical one.
3. Fixing one bottleneck reveals the next. Load testing is iterative. Each fix moves the breaking point further out, and shows you what to tackle next.
4. Most fixes are targeted, not rewrites. Pooling, indexing, caching, queues, a CDN and a load balancer can take an MVP a very long way.
5. Test before the spike, not during it. A load test on staging costs days. Discovering the same problems on launch day can cost your launch.
Planning a load test for your own product
If you want to find your own product's limits, a good load test needs:
A staging environment that mirrors production, with realistic data volumes
Realistic user journeys, mixed in the proportions your real users follow
Gradual stages, so you can see exactly where performance starts to slip
Monitoring on every layer, so you know which component is struggling, not just that something is slow
A clear target, usually your expected peak traffic, plus generous headroom
A plan to re-test after every round of fixes
The hardest part usually isn't generating traffic. It's reading the results correctly and knowing which fix will have the biggest effect.
Our Monitoring & Observability service sets up the per-layer visibility that makes load test results easy to read, and keeps it running after launch.
Quick self-check
Do you know how many concurrent users your product can handle today?
Does your database use connection pooling?
Are your most common searches and pages backed by the right indexes?
Is heavy work, like image processing and emails, done in the background?
Are images and files served through a CDN?
Could you add a second app server today without breaking user sessions?
Would your product scale up automatically during a spike?
If you answered "no" or "not sure" to several of these, your product's real limit is probably lower than you think.
Find your breaking point before your users do
Every product has a breaking point. The only question is whether you find it in a planned test or on your busiest day.
At Codersarts, load testing is at the heart of our System Design & Scalability service. We test your product with realistic traffic, find exactly what breaks first, fix it in the right order, and prove the result with before-and-after tests at your expected peak and beyond. Not sure where to start? Our Launch Readiness Audit includes a load test as part of a full review of your system.
Related reading




Comments