Server

I/O-bound

I/O-bound work spends most of its time waiting on something outside the processor: a database, a disk, a remote API. The CPU is mostly idle while it waits.

How it is measured

The signal is low CPU with long request times. Check I/O wait in `top`, the time spent in database calls in your tracing, and the breakdown of a request into compute versus waiting. If 520 of 600 ms are spent awaiting responses, the work is I/O-bound.

The remedy is concurrency: handle other work during the wait. Async runtimes, thread pools and connection pools exist for this reason, and a bigger CPU adds nothing.

Worked example

A product page on a Rails app makes 7 sequential calls: database, Redis, inventory API, review API, and so on. Each waits 70 to 110 ms, so the page takes about 640 ms while the CPU shows 9 percent use.

Running the four independent calls in parallel and caching reviews for 5 minutes cut the page to 230 ms. CPU use barely moved, and the same machine held twice the traffic.

How it differs

I/O-bound work waits outside the CPU. CPU-bound work keeps the CPU busy. I/O-bound excludes heavy local computation, CPU-bound excludes long idle waiting. Event-loop runtimes like Node suit the first and struggle with the second. Knowing which you have decides whether you add concurrency or cut computation.

Common errors

Adding CPU cores to a service that waits on a database. Making calls one after another that could run together. Opening a new database connection per request. Forgetting to set timeouts on remote calls. Reading low CPU as proof that the server is healthy.

In practice

Trace one slow page and list every wait in order. Parallelise independent calls, cache the ones that rarely change, and put a timeout on each so a single slow dependency cannot hold the page.

See also

CPU-bound, Event loop

Sources

Count this on a real site.

Watch my website