Engineering

What breaks first when you scale a product too fast

Editorial · Reveneau · November 19, 2026

What breaks first when you scale a product too fast

Products that scale faster than their infrastructure break in a consistent order. The failures are not random. The same parts give out first on almost every product, because those parts share the same structural property: they are the gathering points for concurrency pressure, and concurrency is what scale produces.

Knowing this order in advance is useful. It means you can read early signals before a problem becomes a user-facing outage, and it means you can test for the right failure modes before you need the capacity.

Database write contention

The first thing that breaks is almost always the database. When more users take actions simultaneously, the same rows and tables get locked more often. Write operations queue behind each other. Response times on the operations that touch the busiest tables start to increase.

At low load, this is invisible. The queue clears faster than it fills, and each operation completes fast enough that the latency never accumulates. At higher load, the queue grows. The first sign is in the latency distribution, not the average: the p95 and p99 response times on write-heavy operations start to rise while the median stays fast. If you watch only average response time, you miss the early signal.

The structural reason databases break first is that most products are built with a shared database that handles reads and writes for every part of the application. The database sees all the concurrency pressure at once, while the application servers spread the load across multiple instances. The bottleneck is in the wrong place for horizontal scaling.

The fix depends on the specific contention. Row-level locking issues often come from long transactions or from updates that could be replaced with append-only writes. Table-level contention often comes from hot rows that many operations update simultaneously, like counters or status fields. Each has a different solution, but the diagnostic is the same: find the operations with rising tail latency, trace them to the specific database operations they touch, and address the contention at that level.

Synchronous external dependencies

The second failure mode is external service calls that the application makes synchronously and waits for before continuing. Under low load, a third-party API that takes 200 milliseconds to respond is fast enough that the wait is invisible. Under high load, that same API may take 600 milliseconds, or start returning errors, and the application waits for all of it.

While the application waits for an external call, the thread handling the request is occupied. It cannot handle another request. This matters because application servers have a finite number of threads available. A single external service running three times slower than usual can saturate a server's thread pool, turning a 600ms external wait into a user-facing timeout.

The standard fix is to move non-critical external calls off the synchronous request path. If sending a notification email is part of handling a user action, the email does not need to be sent before the response returns to the user. Move it to a background queue, complete the user-facing action immediately, and let the background job handle the email without holding up the response. This decouples the user experience from the speed of the external service.

The calls that cannot be moved off the synchronous path, an authentication check, a fraud check that must complete before an action proceeds, need circuit breakers: a mechanism that returns a fast fallback response when the external service is slow or unavailable, rather than letting every request wait for a timeout.

Background job queues

Background job queues fail in a different way: they fill faster than they drain.

At low load, jobs are queued and picked up almost immediately. At high load, the number of jobs per unit time exceeds the processing capacity of the workers assigned to the queue. Jobs sit waiting. The operations that depend on them, sending emails, updating statuses, triggering downstream processes, complete late or not at all.

The user experience from queue depth problems is different from the user experience of database contention. The user submits an action and receives an immediate response. The confirmation email takes ten minutes instead of ten seconds. The status update that should have happened after the action is not there yet. These failures are hard to trace because they are not visible at the point where the user action happens.

The fix is monitoring queue depth as a metric, not just the latency and error rate of the web tier. Queue depth tells you whether background processing is keeping up. Add more workers when it is not. Prioritize jobs that affect user experience over jobs that are purely operational.

Connection pool exhaustion

Connection pool exhaustion is a failure mode that looks like a database problem but is not. The database is healthy. The application is running. But requests start returning errors because there are no available connections to give them.

A connection pool is a fixed number of database connections maintained by the application. When more requests need database connections simultaneously than the pool has available, the excess requests wait. At scale, the pool exhausts and requests time out waiting for a connection rather than waiting for the database.

The fix is to size the pool to the expected concurrency and to shorten the time each request holds a connection open. Requests that hold connections open during external API calls or during processing that does not require the database are common causes of pool exhaustion. Release the connection before the external call, re-acquire it after.

What to watch and when to act

The practical implication of this order is that monitoring should be tuned to catch problems in the same sequence they appear. Database write latency at the tail end of the distribution, before average latency rises. External service latency and error rate, before the application thread pool fills. Background job queue depth, before the queue length translates into user-facing delays.

Each of these is measurable before it becomes an outage. Running the application under realistic load in a staging environment before you need the production capacity will show you which of these appears first for your specific product, because the order within your product depends on what your users do and how your data model is shaped.

The failures are predictable. The question is whether you find them in a test run or in production.

Common questions

What breaks first when a product grows faster than its infrastructure?

Database write contention is usually the first visible failure. When more users take actions simultaneously, the same rows get locked more often, write operations queue behind each other, and response times rise. The product looks fine under low load because the queue clears faster than it fills. At higher load, the queue grows, latency goes up, and timeouts start appearing in the logs before they appear in user experience.

Why does the database break before other parts of the system?

Because most products are built with a shared database that handles both reads and writes for every part of the application. The database is the natural gathering point for concurrency pressure. When load increases, the database sees all of it at once, while the application servers spread the load across multiple instances. The bottleneck is structural: one database handles the state for many concurrent workers.

What is a synchronous dependency and why does it become a problem at scale?

A synchronous dependency is any call that the application makes to an external service and waits for before continuing. When the external service is fast, the wait is invisible. When load increases, the external service slows down, and the application waits longer. During that wait, the application server thread is occupied, which means it cannot handle another request. Under high load, a single external service that runs three times slower than usual can saturate the application's connection pool.

How does background job processing fail at scale?

Background job queues fill faster than they drain when the number of jobs per unit time exceeds the number of workers that can process them. At low load, jobs are picked up almost immediately. At high load, jobs sit in the queue, and the operations that depend on them, sending a confirmation email, updating a user's status, triggering a downstream process, complete late or not at all. The user experience degrades in ways that are hard to trace because the job queue failure is not visible in the same place as the user action that triggered it.

What is the connection pool and how does it fail?

A connection pool is a fixed number of database connections that the application maintains and reuses. Each request that needs the database borrows a connection, uses it, and returns it. When more requests need database connections simultaneously than the pool has available, the excess requests wait. At scale, the pool exhausts. Requests waiting for a connection time out. The application starts returning errors not because the database is overloaded but because the pool between the application and the database is too small.

What permission logic problems appear at scale?

Permission checks that read from the database on every request become expensive when the database is under load. At low volume, a permission check that runs a small query is fast. At high volume, that same query runs thousands of times per second, adding to the database load and slowing the checks themselves. Products that check permissions inline on every request discover this problem when their database load increases and their application starts spending a large share of its time doing permission reads.

How does session storage become a problem at scale?

Session storage that is handled by one server becomes a problem when the application scales to multiple servers. A user's session is on one server. If the next request goes to a different server, the session is not there, and the user is logged out. Products that move from one application server to multiple, as most products do when they start growing, discover that session handling needs to move to a shared store before they can run more than one instance.

What is the first sign that a product is approaching a database scaling limit?

Increasing p95 or p99 response times on write-heavy operations, with the p50 (median) still fast. The database is handling the average case quickly but taking longer on requests that arrive during moments of high contention. This shows up in latency percentiles before it shows up as errors. If you watch only average response time, you miss the early signal.

How should a team prepare for scale before it arrives?

Run the application under load in a staging environment and look at where the bottleneck appears. Most scaling problems are identifiable before they happen in production because the same operations that slow down under artificial load also slow down under real load. The specific pattern of which operations slow down first tells you where to invest before you need the capacity.

What is the right order to fix scaling problems?

Fix the bottleneck that is closest to the user experience first. Database write contention is usually visible to users as slow responses, so it comes first. Background job queue depth affects operations that happen after the user has moved on, so it can be addressed second. Synchronous dependencies that are not on the critical path can be made asynchronous without immediate urgency. The goal is to restore normal user experience first, then work backward through the system toward the less visible failures.

Start a project