Disk I/O Performance and Optimization

Use iostat, iotop, and fio to tell whether your disk is the bottleneck, then tune the filesystem, mount options, and write patterns to get the most out of it.

When a server feels sluggish, the first guess is usually "not enough CPU" or "out of memory." But the real culprit is often disk I/O. A stalling database, request latency caused by log flushing, a box that crawls during backups: behind all of these is usually a disk that can't keep up. This article shows you a handful of commands to see what your I/O is doing, plus optimizations you can apply right away. Commands target Ubuntu/Debian.

Start with utilization and wait time

iostat ships in the sysstat package and is the go-to tool for watching I/O:

sudo apt install sysstat
iostat -x 2

-x prints extended metrics; 2 refreshes every 2 seconds. Focus on these columns:

  • %util: how busy the device is. Near 100% means the disk is almost never idle.
  • await: average time per I/O in milliseconds. SSDs are typically single digits; sustained tens of milliseconds is a red flag.
  • r/s, w/s: reads and writes per second (IOPS).
  • aqu-sz: average queue depth. Consistently above 1 means requests are lining up.

To find which process is hammering the disk, use iotop:

sudo apt install iotop
sudo iotop -oPa

-o shows only processes doing real I/O and -a accumulates totals, so the heavy writer stands out immediately.

Measure real throughput with fio

To learn how many IOPS and how much bandwidth your disk can actually deliver, benchmark it with fio:

sudo apt install fio
# 4K random read/write, measures IOPS
fio --name=randrw --ioengine=libaio --direct=1 \
    --rw=randrw --bs=4k --size=1G --numjobs=1 \
    --runtime=30 --group_reporting

# Sequential read, measures throughput (MB/s)
fio --name=seqread --ioengine=libaio --direct=1 \
    --rw=read --bs=1M --size=1G --runtime=30 --group_reporting

Run it inside a directory on your data disk, and delete the generated test file afterward so it doesn't waste space or disturb production traffic.

SSD versus HDD

A spinning disk (HDD) relies on a moving head to seek, so random IOPS top out at a couple hundred. An SSD has no moving parts and easily reaches tens of thousands of random IOPS, with await an order of magnitude lower. Check which one you have:

lsblk -d -o name,rota   # ROTA=1 is HDD, ROTA=0 is SSD/NVMe

The takeaway is simple: put workloads that are sensitive to small random I/O, such as databases and message queues, on SSD whenever you can.

Common optimizations

  • Keep databases on SSD: MySQL, PostgreSQL, and Redis persistence are all heavy random-I/O users, so an SSD pays off directly.
  • Mount with noatime: by default, every file read updates its access time, generating pointless writes. Add noatime to the partition in /etc/fstab (e.g. defaults,noatime), then run sudo mount -o remount /. Read-heavy workloads notice the difference right away.
  • Pick the right filesystem: ext4 is a safe default for general use; xfs suits large files and snapshots. Avoid dated or mismatched filesystems.
  • Cut down on tiny writes: batch many small writes into larger ones, buffer your logs, and add a write queue at the application layer to relieve IOPS pressure.
  • Add caching: with spare memory, the Linux page cache automatically holds hot data; layer Redis or Memcached on top to keep read requests off the disk entirely.

How to confirm an I/O bottleneck

A quick rule of thumb: if %util sits near 100% with a high await in iostat -x, while top shows a large CPU wa (iowait) share, you're almost certainly I/O-bound. If %util is low but the system is still slow, the problem is more likely CPU or network, so don't waste effort chasing the disk.

Summary

Watch I/O with iostat -x and iotop, benchmark capacity with fio, and tell SSD from HDD with rota. The heart of optimization is to put random I/O on SSD, cut unnecessary writes, and let caches absorb reads. Locate the bottleneck with data first, then treat the actual cause, and your server's disk will finally perform the way it should.