Skip to main content

The Four Bottlenecks: CPU, Memory, Disk, Network

Introduction

For a whole section, we've been living inside a single server — its CPU cores and caches, its memory hierarchy, its disks, the way it holds thousands of connections, juggles work with threads and event loops, and protects shared state with locks. Now we close the loop with the question all of that was quietly building toward: when a server finally falls over under load, what exactly ran out?

The answer is beautifully small. A server has only four things it can run out of — CPU, Memory, Disk, and Network — and under enough load, one of them saturates first. That one is the bottleneck, and it single-handedly decides how much your server can do. The other three could be sitting nearly idle; it doesn't matter. A chain is only as strong as its weakest link, and a server is only as fast as its most saturated resource.

This is one of the highest-leverage mental models in all of systems work, because it turns a vague panic — "the site is slow!" — into a precise, answerable question: "which of the four is maxed?" Get the answer right and the fix is obvious. Get it wrong — throw a bigger CPU at a server that's actually starving on disk — and you'll spend a fortune and change nothing.

By the end you'll be able to look at any struggling server, name the resource that's dying, know its tell-tale signature, and — the part that separates seniors from juniors — know that the moment you fix it, the bottleneck will simply move to the next-weakest resource. Let's learn to read the four deaths.

(Two smaller-but-deadly limits ride along — running out of file descriptors and drowning in context switches — and we cover those too, because they kill servers that have CPU and memory to spare.)

Illustration of a server as four finite resources under load, each drawn as a gauge: CPU, Memory, Disk, and Network. A workload is thrown at the server and one gauge — the disk — redlines past a 70 percent knee where a queue forms and the latency readout spikes, while CPU, Memory, and Network sit comfortably green and half-idle; the disk is the bottleneck, the narrow neck of the bottle that caps the whole system, and a chain-with-one-weak-link motif reinforces that a server is only as fast as its weakest resource. A signature panel lists how to tell each death apart: CPU-bound shows near-100 percent utilization and a growing run queue; memory-bound shows RAM exhausted and swapping to disk; disk-bound shows high I/O wait while the CPU looks deceptively idle because it is waiting, not computing; network-bound shows the link saturated with rising dropped packets. The USE method is named as the checklist — for every resource check utilization, saturation, and errors — with the key insight that a resource at 60 percent with a queue is worse than one at 90 percent without, because saturation, not utilization, is what makes latency explode past the roughly 70 percent knee. Two sneaky extra limits are flagged that end a server with CPU and memory to spare: file-descriptor exhaustion, the too-many-open-files error where the server cannot accept another connection, and context-switch thrashing from too many threads. A final note states the Theory of Constraints — you never eliminate a bottleneck, you move it, so fixing the wrong resource, like buying a bigger CPU for a disk-bound box, buys nothing.

A Server Is Four Finite Resources

Strip away the software and a server is just four physical things doing work, each with a hard ceiling. You've met all four already in this section; here they are as a set:

  • CPU — the cores that compute. Ceiling: how many instructions per second across all cores. Runs out when you ask it to think too hard (crunching, hashing, parsing, rendering).
  • Memory (RAM) — the fast working space that holds your live data. Ceiling: total gigabytes. Runs out when your working set doesn't fit.
  • Disk (I/O) — the durable storage you read and write. Ceiling: reads/writes per second and MB/s of throughput. Runs out when you move too much data to or from storage.
  • Network — the NIC and the link to the world. Ceiling: the link's bandwidth (and packets per second). Runs out when you push too many bytes in or out.

Every request your server handles draws down some mix of these four. A thumbnail resize is almost pure CPU. A full-table analytics scan is almost pure Disk. Serving a cached value is almost pure Memory and Network. Fanning out to twenty microservices is Network (and, as we'll see, file descriptors). The shape of your workload decides which resource it leans on — and therefore which one it will exhaust first.

Hold this picture: four tanks, each draining at its own rate depending on the work. Whichever empties first stops the whole show.

The Bottleneck: Only as Fast as the Narrowest Point

Picture an assembly line with four stations. Three of them can process 100 units an hour; one — say, the disk station — can only manage 60. What's the line's output? 60 units an hour. Not the average, not something in between — exactly 60, the rate of the slowest station. The fast stations just pile up work in front of the slow one and then sit idle waiting.

That slow station is the bottleneck, and your server works exactly the same way. It's the literal image the word comes from: the narrow neck of a bottle decides how fast liquid pours out, no matter how big the bottle is. One resource saturates first, and that single resource caps your entire server's throughput. The other three could be at 20% — irrelevant. You are limited by your weakest link, full stop.

This has a sharp, money-saving consequence that most people get backwards: making a non-bottleneck resource faster does nothing. Add cores to a server that's starving on disk and throughput doesn't budge — the extra cores just wait on the disk alongside the old ones. It's like widening three lanes of a highway when the jam is at a single toll booth. The only change that helps is relieving the actual bottleneck. So the entire game of performance is: find the one resource that's saturated, and fix that one.

Which raises the practical question — how do you tell which of the four is the bottleneck? They each leave a different fingerprint.

Reading the Signature: Utilization, Saturation, Errors

There's a wonderfully simple checklist for this, popularized by the performance engineer Brendan Gregg, called the USE method. For every resource, you look at three numbers:

  • Utilization — how busy is it? (CPU: percent not-idle. Disk: percent of time doing I/O. Network: bandwidth used vs. the link. Memory: how full.)
  • Saturation — how much work is queued up waiting because the resource can't keep up? (A CPU run-queue of hungry threads; a disk with a deep I/O queue; a network dropping packets because its buffers overflowed.)
  • Errors — is anything actually failing? (Dropped packets, failed writes.) Any non-zero count here demands attention on its own.

Of the three, saturation is the one that matters most — and the one people miss. Here's the counterintuitive heart of it: a resource at 60% utilization with a growing queue is a bigger emergency than one at 90% utilization with no queue. Why? Because the queue is the pain. Utilization tells you how busy something is; saturation tells you that requests are now waiting in line — and waiting is what your users feel as "the site is slow."

So the discipline isn't "find the resource near 100%." It's "find the resource with a queue forming behind it." That's your bottleneck. Watch the line, not just the meter.

See It: Throw a Workload, Find the Bottleneck

Time to build the instinct by doing it. Below is a server shown as four live gauges — CPU, Memory, Disk, Network. You pick a workload (a CPU-heavy video encode, a disk-heavy analytics scan, a network-heavy API fan-out, a memory-heavy cache, a context-switch-heavy thread storm), turn up the load, and — before you run it — predict which gauge will redline first.

Then hit Run and watch: requests stream in, the matching gauge climbs, and right around 70% a queue forms and the latency number spikes into the red — while the other three gauges stay calm and green. That's the bottleneck, caught in the act. Now the payoff: press "upgrade the bottleneck" and watch it relax — and the bottleneck jump to the next-weakest resource. For the real lesson, try upgrading a green resource instead, and watch nothing whatsoever improve.

Find the Bottleneck: pick a workload — a CPU-heavy video encode, a disk-heavy analytics scan, a network + file-descriptor heavy API fan-out, a memory-heavy cache, or a context-switch-heavy thread storm — turn up the load, and predict which of the four resource gauges redlines first. Run it and watch that gauge hit the 70% knee where a queue forms and latency spikes, while the others stay green. Then hit 'upgrade the bottleneck' and watch it jump to the next-weakest resource — and see that upgrading the wrong (green) resource does nothing at all.

The Four Deaths, One by One

Each resource fails with its own unmistakable fingerprint. Learn these four and you can diagnose most struggling servers in under a minute:

CPU-bound. Utilization pinned near 100%, the run-queue growing (threads waiting for a core), load average climbing past your core count. The CPU is genuinely working — this is your video-encoders, your report-crunchers, your crypto. The honest fix: faster or more cores, or less computation.

Memory-bound. RAM fills up, and then the operating system does the worst possible thing to survive: it starts swapping — shoving memory out to disk to make room. Now every access that used to take nanoseconds takes milliseconds, and the whole machine grinds. (Or the OOM killer wakes up and starts killing your processes outright.) The tell: swap activity climbing.

Disk-bound. The most misdiagnosed one, so pay attention. The signature is high I/O wait and a deep disk queue — but here's the trap: the CPU can look nearly idle. A junior sees 15% CPU and concludes the server is fine. It is not fine. It's on its knees, and the CPU is idle because it's waiting on the disk, which (from earlier lessons) is about 100,000× slower than memory. Low CPU with a slow server almost always means: look at the disk.

Network-bound. The NIC's throughput sits right at the link's ceiling, retransmits and dropped packets start rising, buffers overflow. This is your file servers, your video streamers, your chatty fan-out services moving more bytes than the pipe can carry.

Four resources, four fingerprints. The whole skill is matching the symptom to the resource — and not being fooled by the disk-bound case where the CPU plays dead.

Two Sneaky Killers: File Descriptors and Context Switches

Two more limits deserve a seat at this table, because they take down servers that have plenty of CPU, memory, disk, and bandwidth to spare — which makes them baffling until you know to look.

Running out of file descriptors. A file descriptor (fd) is a small number the operating system uses to track every open file — and, crucially, every open socket. Every single connection your server accepts consumes one. There's a limit on how many a process may hold (often a laughably low default of 1024), and when you hit it, your server throws "Too many open files" and simply cannot accept another connection — even though its CPU and memory are barely warm. This is the direct sequel to the 10,000-connections lesson: 10,000 connections means 10,000 file descriptors, and production servers raise the limit to tens of thousands. And if the real cause is a leak — connections opened and never closed (hello again, the pooling lesson) — raising the limit only postpones the crash.

Drowning in context switches. From the threads lesson: every time the CPU switches from one thread to another, it pays a small tax. Spawn thousands of busy threads and the CPU starts spending more of its time switching between them than running your actual code. The gauge says "CPU busy," but it's busy doing overhead, not work — the tell is a sky-high system-CPU percentage and an enormous context-switch rate. The fix isn't a bigger CPU; it's fewer threads (an event loop, a right-sized pool — everything this section has been teaching).

Both are reminders that "what ran out?" isn't always one of the big four. Sometimes a server dies rich in resources, strangled by a limit you forgot existed.

Two limits that do not show up on the CPU, memory, disk, or network gauges but still crash a half-idle server. File descriptors: every open socket, file, or pipe consumes one descriptor, and when open descriptors approach the operating system ulimit the process hits a too-many-open-files error and every new connection is refused instantly; fixes are raising the ulimit, closing what you open, and pooling connections. Context switches: the CPU's time splits between real work and switching between threads, so a few hundred threads is mostly work but ten thousand threads is mostly switching — busy on overhead rather than your code; fixes are bounding threads, using async I/O, and preferring fewer busier workers. Both redline a box with CPU and RAM to spare.

The Bottleneck Always Moves

Here is the idea that turns bottleneck-hunting from a one-time fix into a career-long instinct: you never eliminate a bottleneck — you relocate it.

Say your server is disk-bound, so you install faster SSDs. The disk gauge relaxes... and almost immediately, some other gauge starts climbing, because now that data flows in faster, the CPU has to work harder to process it, or the network has to ship more of it out. You didn't remove the constraint; you moved it to whatever was the next-weakest link. Fix that one, and it moves again. This is a genuine law — in operations research it's called the Theory of Constraints — and it's why performance work is a cycle, not a checklist: find the current bottleneck, relieve it, find the new one, repeat.

This reframes two things. First, a moving bottleneck is not failure — it's progress. Each time it moves, your system got faster up to the new limit. Second, and more useful: it tells you to always fix the current bottleneck and nothing else. Optimizing a resource that isn't the bottleneck is pure waste — you make one station on the assembly line faster and the output doesn't move a hair, because the slow station is somewhere else entirely. The discipline of the whole field, in one line: measure, find the one constraint, fix only that, then measure again.

The Trade-off

Everything in this lesson converges on a single, expensive truth: the costliest performance mistake is optimizing the wrong resource. It's not a subtle error — it's a company writing a check for a beefier machine that fixes nothing.

The classic disaster: the site is slow, someone declares "we need more CPU," the company pays for a machine with twice the cores — and the site is exactly as slow, because it was disk-bound the whole time and the CPU was never the problem. Money spent, zero improvement, and now everyone's confused. The reverse happens too: teams pour weeks into shaving milliseconds off CPU code while the real bottleneck is a saturated network link nobody measured.

The trade-off, then, isn't really about resources — it's about discipline over instinct. The instinct is to optimize the thing you understand or the thing that's easy to change. The discipline is to measure first, identify the actual saturated resource (with a queue behind it), and fix only that — even when it's the boring, hard-to-change one. And to remember the 70% rule while you're at it: don't run any resource flat-out to 100%, because past the knee, queues and latency explode. Leave every tank some headroom, and it can absorb the spikes that would otherwise take you down.

"Which of the four is the bottleneck?" is the cheapest question in engineering and the most expensive one to skip.

Mental-Model Corrections

"CPU is at 100%, so we need a bigger CPU." Maybe — but if the server is actually disk-bound, the CPU is idle in I/O wait and a bigger CPU changes nothing. Diagnose which resource is saturated before you spend.

"CPU is low, so the server is healthy." No. A server can be completely on its knees with a nearly-idle CPU — because it's waiting on disk or network. Low utilization is not the same as healthy.

"To go faster, make everything faster." Only the bottleneck matters. Speeding up a non-bottleneck resource improves throughput by exactly zero (and can make things worse by piling up work at the real constraint).

"Run resources near 100% — that's efficient." Past the ~70% knee, queues form and latency explodes (a server at 80% can already be failing). Headroom isn't waste; it's how you survive traffic spikes and failovers.

"Fix the bottleneck and you're done." You're never done — fixing it just moves it to the next-weakest resource. Performance is a cycle.

"Only CPU, memory, disk, and network can run out." File descriptors and context-switch capacity run out too, and kill a server that has all four big resources to spare.

Try It Yourself: Read Your Own Machine's Four Gauges

Five minutes turns this into muscle memory. Open a terminal and read your machine's four resources live — then create a bottleneck on purpose and watch its signature appear:

# The one-screen overview (press it and read all four at a glance):
top          # CPU % and load average (CPU) · memory + swap used (Memory)
#            watch 'load average' vs your core count, and 'wa' = I/O-wait

# Per-resource, the classic tools:
vmstat 1     # 'r' = run-queue (CPU saturation) · 'si/so' = swap (Memory) · 'wa' = I/O wait (Disk)
iostat -x 1  # '%util' and 'aqu-sz' (average queue) per disk (Disk saturation)
ss -s        # socket / connection counts (heading toward the fd limit?)
ulimit -n    # your file-descriptor ceiling (often a tiny 1024)

Now make one gauge move: run yes > /dev/null in a couple of terminals and watch CPU pin and the run-queue grow; or dd if=/dev/zero of=./bigfile bs=1M count=20000 and watch I/O-wait climb in vmstat while the CPU stays low — the disk-bound signature, live. Once you've seen a queue form behind a saturated resource, "which of the four?" stops being theory and becomes a reflex.

Recap & What's Next

The diagnostic instinct, in five lines:

  • A server is four finite resources — CPU, Memory, Disk, Network — and under load one saturates first: the bottleneck. It alone caps throughput; the other three are irrelevant. You're only as fast as your weakest link.
  • Read the signature with the USE method (utilization, saturation, errors) — and watch saturation, the queue, not just the busy %. CPU pins the run-queue; memory swaps; disk shows I/O-wait with a deceptively idle CPU; network drops packets.
  • Two sneaky extras — file descriptors ("too many open files") and context-switch thrash — kill servers with CPU and RAM to spare.
  • Respect the 70% knee: past it, queues form and latency explodes. Headroom is reliability.
  • The bottleneck moves. Fix one, the next appears — so diagnose, fix only the current constraint, and never pay to optimize the wrong resource.

And with that, a whole section closes. Look back at what you can now do: you can trace a request through the machine, reason about the memory hierarchy and access patterns, choose between processes, threads, event loops and pools, tell concurrency from parallelism, defend shared state from races, and name the exact resource a server dies on. You understand one computer the way a systems engineer does.

There's a checkpoint next to make sure it stuck. And then — the leap the entire course has been building toward. Everything so far has lived inside a single machine. But no real system runs on one machine. The moment you add a second, a dozen, a thousand, a new universe of problems opens up: they fail independently, they disagree, they can't even agree on what time it is. We leave the single server behind and step into distributed systems — where every hard-won idea from this section gets tested at a scale that changes everything.