A service is slow and you suspect the disk? Before concluding, you have to measure. This guide shows how to assess the three indicators that really matter (throughput, IOPS, latency) with fio and iostat, check the health of the hardware with smartctl, and above all interpret the results without falling into the trap of the cache or of a test that is too short. It is aimed at intermediate to advanced administrators on Linux (Debian, Ubuntu, RHEL).
What you will learn
- Tell throughput, IOPS and latency apart, and know which one to look at
- Measure a disk's real performance with
fio - Observe live activity with
iostatandiotop - Avoid hasty conclusions (cache, test too short, misleading
%util)
Prerequisites
- A Linux machine with a
sudoaccount - The
fio,sysstat(foriostat) andsmartmontoolstools installed - Understand the notion of file system and mounting
sudo apt install fio sysstat smartmontoolssudo dnf install fio sysstat smartmontoolsThroughput, IOPS, latency: the three metrics
The first mistake is to talk about a disk's "speed" as a single figure. Three different quantities describe storage, and a slow service almost never suffers from all three at once.
| Metric | What it measures | When it matters |
|---|---|---|
| Throughput (MB/s) | volume transferred per second | large files, backups, video |
| IOPS | number of operations per second | databases, many small files |
| Latency (await, ms or µs) | response time of one operation | user experience, interactive applications |
The type of disk radically changes the orders of magnitude. A hard disk (HDD) tops out around 100-200 IOPS because of the mechanical head; a SATA SSD reaches tens of thousands of IOPS; an NVMe exceeds a million. Comparing the latency of an NVMe (a few tens of µs) with that of an HDD (several ms) makes no sense: they are different worlds.
Check health before measuring
No point benchmarking a dying disk. smartctl (smartmontools package) reads the S.M.A.R.T. data the disk keeps about itself:
sudo smartctl -a /dev/sdaThree attributes warn of an approaching end of life:
- Reallocated_Sector_Ct: faulty sectors already replaced.
- Current_Pending_Sector: unstable sectors waiting for reallocation.
- Wear_Leveling_Count (SSD): wear of the memory cells.
A counter that climbs is a replacement signal. Also run a non-destructive self-test:
sudo smartctl -t short /dev/sda # short test (a few minutes)sudo smartctl -l selftest /dev/sda # read the resultObserve real activity with iostat
iostat (sysstat package) shows what the disks are doing live. It is the tool to see whether a disk is saturated while the problem is happening:
iostat -dx 1The -d option targets disks, -x adds the extended columns, and 1 refreshes every second. Here is an excerpt of the key columns (the full output has more):
Device r/s rkB/s r_await w/s wkB/s w_await aqu-sz %utildm-0 55.93 1167.49 0.69 30.58 493.59 1.29 0.08 1.55What to read:
r/sandw/s: read and write operations per second (the real IOPS).rkB/sandwkB/s: read and write throughput.r_awaitandw_await: the average latency in milliseconds, split by direction. It is the most telling indicator of a bottleneck.aqu-sz: average queue size; when high, it reveals an overwhelmed disk.%util: percentage of busy time.
To know which process generates the I/O, iotop lists the culprits in real time:
sudo iotop -oPThe -o option hides idle processes, -P groups by process. That is how you spot a log service or a backup hammering the disk.
Benchmark with fio
iostat observes the real load; fio (Flexible I/O tester) provokes it in a controlled way to measure what the disk is capable of. It is the reference tool for storage benchmarking.
To measure random 4 KB IOPS, the critical case for databases:
fio --name=randread --directory=/var/tmp --rw=randread \ --bs=4k --size=1G --runtime=30 --time_based \ --ioengine=libaio --direct=1 --group_reportingread: IOPS=22.3k, BW=87.2MiB/s (91.5MB/s)(2617MiB/30001msec) lat (usec): min=21, max=3954, avg=44.07, stdev=44.74The line gives everything: 22,300 IOPS, a throughput of 87.2 MiB/s, and an average latency of 44 µs, measured with fio 3.36 on the NVMe of a workstation, with no virtualisation. To measure sequential throughput (large transfers), change the pattern and the block size:
fio --name=seqread --directory=/var/tmp --rw=read \ --bs=1M --size=2G --runtime=30 --time_based \ --ioengine=libaio --direct=1 --group_reportingread: IOPS=2850, BW=2850MiB/s (2989MB/s)(83.5GiB/30001msec)The decisive parameters: --rw (randread, randwrite, read, write, randrw), --bs (4k for random, 1M for sequential), and above all --direct=1, explained right after.
Avoid hasty conclusions
This is where most analyses go wrong. Four classic traps, each of which can turn a measurement into a wrong conclusion.
The cache distorts everything. Without --direct=1, you mostly measure RAM, not the disk: Linux serves reads from its page cache. A naive dd shows flattering GB/s that reflect nothing:
# Misleading: reads/writes in the cachedd if=/dev/zero of=/var/tmp/test bs=1M count=1024# More honest: forces the physical writedd if=/dev/zero of=/var/tmp/test bs=1M count=1024 oflag=directWith fio, --direct=1 bypasses the cache: it is non-negotiable to measure the disk itself.
A test that is too short lies. A few seconds measure a peak, not sustained behaviour. An SSD with a good start can collapse once its SLC cache is full. Use --runtime=30 --time_based at the very least.
%util at 100% is not proof on SSD/NVMe, as seen above: look at the latency.
Measuring with no load makes no sense. The only test that counts is under a load close to reality: same type of access (random or sequential), same block size as your application.
Troubleshooting
A disk performance diagnosis rarely picks the wrong tool, it picks the wrong reading. Before any action, cross-check the latency (await) with the S.M.A.R.T. health state and the type of disk:
| Symptom | Likely cause | Lead |
|---|---|---|
High await (> 20 ms on SSD) | disk saturated or failing | check smartctl, spread the I/O load |
%util at 100% but app slow | false positive on parallel SSD/NVMe | look at await and aqu-sz, not %util |
| Very low random IOPS | HDD, or expected cache disabled | compare with the disk type, check --direct |
Unrealistic fio throughput (GB/s) | cache not bypassed | add --direct=1 |
| A single process saturates the disk | badly tuned service (logs, backup) | identify it via iotop -oP (iotop-c on RHEL 10) or pidstat -d, adjust its pace |
Put it into practice
After measurement comes optimisation: a heavily read volume wastes I/O writing access dates. In this lab, you add the noatime option on a read-heavy mount, enable it live and write it into /etc/fstab so that the performance setting stays in place after a reboot.
Key points
- Three distinct metrics: throughput (MB/s), IOPS, latency (
await). A slow database = IOPS/latency, not throughput. fiomeasures what the disk is capable of;iostatobserves the real load.--direct=1is essential: without it, you measure the cache, not the disk.- On SSD/NVMe,
%utilat 100% proves nothing;svctmis gone from sysstat 12. - Check S.M.A.R.T. health before benchmarking, and test on a file, never on a disk in service.
Knowledge check
Check that the essentials of this guide are acquired. The questions only cover what has just been explained here.
Knowledge check
Check what you have learned with this interactive quiz
How it works
- The timer starts when you click Start
- Multiple choice, true/false and short answer questions
- You can move between questions
- Detailed results are shown at the end
Starts the quiz and the timer
Review
(0/0)Score by domain
A domain groups several guides and gets several questions, so its score is more reliable than a per-topic score, which often rests on a single question.
Summary
Strengths
No topic passed at 80% on at least two questions yet.
Weak areas
No topic below the pass mark.
Recommended next
Hints before you try again?
Continue the course: Set up disk quotas
A new full quiz with random questions
Work again only on the questions you missed