STORAGE ENGINEERING

Linux disk performance benchmarking

Category: PerformanceTechnologies: fio, dd, iostat, iotop, hdparm, biolatency, ioping

Storage benchmarking measures three fundamental metrics: IOPS (random access), bandwidth (sequential throughput), and latency (response time). The right tool and parameters depend on what you are trying to measure and why.

IOPS vs bandwidth vs latency

MetricMeasuresCritical forTypical values
IOPSRandom access operations/secDatabases, virtualization, transactional workloadsHDD: 80-200, SATA SSD: 80k-100k, NVMe: 200k-500k+
BandwidthSequential data transfer rateBackups, media streaming, large file transfersHDD: 150-250 MB/s, SATA SSD: 500-560, NVMe: 3-7 GB/s
LatencyTime per I/O operationDatabases, interactive apps, real-time systemsHDD: 5-15ms, SATA SSD: 0.1-0.5ms, NVMe: 0.05-0.2ms

fio (Flexible I/O Tester)

fio is the standard for storage benchmarking. It simulates configurable I/O workloads with precise control over block size, queue depth, I/O engine, and read/write patterns.

Command-line examples

# Random read IOPS (4K, queue depth 256, 4 jobs, 60s)
fio --name=randread --filename=/dev/sda --rw=randread --bs=4k \
    --direct=1 --ioengine=libaio --iodepth=256 --numjobs=4 \
    --time_based --runtime=60 --group_reporting

# Sequential write bandwidth (1M blocks)
fio --name=seqwrite --filename=/dev/sda --rw=write --bs=1M \
    --direct=1 --ioengine=libaio --iodepth=64 --size=5G \
    --numjobs=4 --group_reporting

# Mixed 70/30 read/write
fio --name=mixed --filename=/dev/sda --rw=randrw --rwmixread=70 \
    --bs=4k --direct=1 --ioengine=libaio --iodepth=128 \
    --size=10G --numjobs=4 --time_based --runtime=60 --group_reporting

Job file format

[global]
ioengine=libaio
direct=1
iodepth=16
bs=4k
size=1G
numjobs=4
time_based
runtime=60
group_reporting

[rand-read]
rw=randread
filename=/dev/sda

[seq-write]
rw=write
bs=1M
iodepth=64
filename=/dev/sda

Key parameters

ParameterEffect
rwread, write, randread, randwrite, randrw
bsBlock size (4k for databases, 1M for sequential)
direct=1Use O_DIRECT, bypass page cache (essential for hardware benchmarks)
ioenginelibaio (Linux async), io_uring (modern, recommended for NVMe)
iodepthQueue depth, outstanding I/O requests (SSD: 32-256, HDD: 1-8)
numjobsNumber of parallel threads/processes
time_based + runtimeRun for a duration rather than a data size
group_reportingAggregate stats across all jobs

Interpreting fio output

dd (basic sequential test)

# Write 10 GB with direct I/O
dd if=/dev/zero of=testfile bs=1M count=10000 oflag=direct conv=fdatasync

# Read test with direct I/O
dd if=testfile of=/dev/null bs=1M iflag=direct
dd is not a real benchmark

dd is sequential only, single-threaded, with no IOPS measurement or queue depth control. It gives a rough sequential throughput number but does not represent real workloads. Use fio for any serious benchmarking.

iostat (extended statistics)

# Extended stats every 1 second
iostat -x 1

# Every 5 seconds, 10 times, specific device
iostat -x 5 10 -p sda

# Human-readable
iostat -h 1
ColumnMeaning
r/s, w/sRead/write requests per second
rkB/s, wkB/sKilobytes read/written per second
awaitAverage time (ms) per I/O, includes queue + service time
r_await, w_awaitRead/write wait time separately
aqu-szAverage queue length
%utilPercentage of time device was busy (saturation indicator)

Identifying bottlenecks: high %util + high await = device saturated. High await with low %util may indicate controller or network bottleneck.

iotop (per-process I/O)

# Show only processes doing I/O
iotop -o

# Batch mode for logging
iotop -b -n 10 > iotop.log

# Accumulated I/O since start
iotop -a

Requires kernel options CONFIG_TASK_DELAY_ACCT, CONFIG_TASK_IO_ACCOUNTING, CONFIG_TASKSTATS.

Other tools

hdparm (quick read test)

# Buffered disk reads
hdparm -t /dev/sda

# Cache reads (shows RAM speed)
hdparm -T /dev/sda

# Direct I/O (bypass cache)
hdparm -t --direct /dev/sda

# Both
hdparm -tT /dev/sda

biolatency (eBPF, kernel latency histogram)

# Block I/O latency histogram
biolatency

# Per-device histograms
biolatency -D

# Include OS queued time
biolatency -Q

# 1-second summaries, 10 times
biolatency 1 10

ioping (latency test)

# Basic latency
ioping .

# Disk seek rate (IOPS)
ioping -R /dev/sda

# Sequential speed (bandwidth)
ioping -RL /dev/sda

# Direct I/O
ioping -D .

Best practices

  1. Always bypass cache. Use direct=1 in fio or oflag=direct in dd. Without it you are benchmarking RAM, not storage.
  2. Warm up: run tests multiple times, since SSDs may behave differently after initial writes.
  3. Match block size to workload: 4k for databases, 64k-256k for virtualization, 1M for backups.
  4. Use realistic queue depths. HDDs: 1-8, SATA SSDs: 32, NVMe: 128-256.
  5. Multiple runs: run 3-5 times, take the average, discard outliers.
  6. Watch for thermal throttling. NVMe drives can exceed 80°C under sustained load and throttle performance, so monitor temperature during long tests.
  7. Test on the target device, not a file. Testing on a file in another filesystem adds overhead and may not reflect raw device performance.

Matching benchmarks to workloads

WorkloadBlock sizePatternQueue depth
Database (OLTP)4k-8krandrw (70/30)64-256
Virtualization64k-256krandrw32-64
Backup / archive1Msequential32-64
Web servermixedrandread16-64
File servermixedmixed16-32