DEV Community

Mustafa ERBAY
Mustafa ERBAY

Posted on • Originally published at mustafaerbay.com.tr

How High‑Traffic Systems Fail

In my twenty‑year career as a systems and network administrator, the clearest lesson I've learned is this: the answer to how high‑traffic systems fail is never “insufficient server resources.” What kills systems is usually not the massive machines handling hundreds of thousands of requests per second, but a tiny timeout parameter forgotten behind those machines. Once, on a large Turkish e‑commerce infrastructure, we experienced a full 45‑minute disaster for exactly this reason.

That day’s problem was neither a database server shortage nor a saturated network bandwidth. Everything started when the default (default) timeout value for a request from one microservice to another remained at 30 seconds. When a single service slowed down, the entire system toppled like a line of dominoes.

Cascading Reaction: How Health Checks Become Triggers?

When designing a high‑traffic system we place health check mechanisms behind the load balancer to monitor the status of every node. But if you configure this mechanism incorrectly, you’re shooting yourself in the foot. In a project I was involved in, the /health endpoint that ran every 5 seconds was issuing a simple SELECT 1 query against the database in the background.

The system ran fine under normal conditions. However, a heavy reporting query in the database (I was quite angry at the teammate who wrote it that day) didn’t use an index, so it began to stress PostgreSQL’s disk I/O limits. This slowdown immediately affected the health check queries as well.

The process unfolded exactly as follows:

  1. The database slowed down, and the /health endpoints started timing out.
  2. The load balancer, not receiving responses, marked each of the 10 healthy application servers as “unhealthy” and removed them from traffic.
  3. All traffic (15 k requests per second) was suddenly routed to the remaining two servers.
  4. Those two servers instantly ran out of memory (OOM) and crashed, plunging the entire system into darkness.

⚠️ Important Takeaway

Your health‑check endpoints should never query the database or any external dependency directly. The status check should be static or only examine local system resources (disk, memory). Instead of killing the server when the database connection drops, you should opt to throttle traffic (rate limiting).

Connection Pools and a False Sense of Security

Another popular answer to why high‑traffic systems fail is database connection limits. Many developers want to keep the connection pool limit as high as possible to boost application performance. They start with the mindset “Postgres is a powerful machine, let’s give it 500 connections.”

What they forget is this: in PostgreSQL each active connection (backend process) consumes RAM and CPU. When thousands of requests arrive per second, if your pool limit is too high and queries start taking seconds instead of milliseconds, the database server can freeze while trying to manage hundreds of active connections.

The table below summarizes the behavior of connection pool strategies I tested in my own projects under high traffic:

Strategy Behavior Under Load Risk Level Suggested Remedy
Unlimited / Very High Limit CPU spike, OOM, lockup Very High Use a connection pooler such as PgBouncer
Narrow / Small Limit Queue waiting, request timeouts Medium Optimize queue timeouts
Dynamic Scaling Connection open/close overhead, latency High Define a fixed, optimized pool size

I made this mistake while building a real‑time monitoring dashboard for a production ERP system. I kept the PostgreSQL connection limit high and scaled the FastAPI applications behind an Nginx reverse proxy without control. The result? The database CPU hit 100 % and the entire factory production halted for 15 minutes. Since that day I never leave PgBouncer out of the picture.

What Should We Do to Prevent System Failures?

Managing high traffic isn’t solved by simply renting bigger servers. When designing the infrastructure you need to ask, “This system will inevitably fail—how will it fail?” Minimizing damage (graceful degradation) should be our primary goal.

Here are three core rules I apply in my systems that have saved lives:

  • Apply the Circuit Breaker Pattern: If an external service you depend on (e.g., a payment gateway) slows down, stop sending requests to it and avoid consuming your own resources. Open the circuit, return the error immediately, and rescue the rest of the system.
  • Use Aggressive Timeout Values: Default timeout settings are a system’s biggest enemy. A 30‑second timeout is unacceptable. Inter‑service communication timeouts should be measured in milliseconds.
  • Rate Limiting and Shedding: You don’t have to accept all incoming traffic. Quickly reject requests that exceed your capacity with a 429 (Too Many Requests) response so that in‑flight operations can complete successfully.

System architecture is as much an art of organization and limit management as it is about writing code. If you see an engineer who says “Our system will never fail,” they probably haven’t encountered enough traffic yet.

So, when did your system last crash and which tiny parameter caused it? Share in the comments and let’s discuss.

Top comments (0)