LinuxXLI · Storage Performancefio
fio and safe benchmarking - measuring storage baseline
What you'll learn
- Use fio for storage benchmarking
- Choose the right parameters for the workload
- Run fio safely (without affecting production)
- Interpret fio output
Prerequisites
Verified against Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL 9.x · Rocky Linux 9.x · AlmaLinux 9.x · Linux kernel 6.1 LTS / 6.6 LTS · systemd 255+ · OpenSSH 8.7p1 (RHEL 9) / 9.6p1 (Ubuntu 24.04) · nftables 1.0.x · chrony 4.x · Pacemaker 2.1.x · Corosync 3.1.x · 2026-08-09
fio (Flexible I/O Tester) is the standard tool for storage benchmarking. It measures latency, throughput, and IOPS for specific workload patterns. This lesson covers safe and useful benchmarking.
Install fio
sudo apt install fio
# or
sudo dnf install fio
A basic test
fio --name=randread \
--filename=/mnt/test/fio-test.bin \
--size=1G \
--bs=4k \
--rw=randread \
--ioengine=libaio \
--direct=1 \
--iodepth=32 \
--runtime=30 \
--time_based
Parameters:
--name: name of the test.--filename: file to read/write. Use a separate file, not production data.--size: file size. 1G is a reasonable test size.--bs: block size. 4k for database, 64k for streaming.--rw: read/write pattern.randread,randwrite,read,write,randrw.--ioengine:libaio(kernel async) orpsync(slower, simpler).--direct:1bypasses the page cache. It defaults to 0, so omitting it is the single most common way to produce a wrong baseline: you measure RAM, not storage. It also costs you the queue depth — fio documents thatlibaio“may only support queued behavior with non-buffered I/O”, so a buffered job runs synchronously and--iodepth=32means nothing. Always pass it.--iodepth: queue depth. 32 is typical.--runtime: time-based test duration.--time_based: run for--runtimeeven if--sizeis written.
Output
read: IOPS=12,345, BW=48.2MiB/s (50.5MB/s)(1475MiB/30001msec)
slat (usec): min=2, max=125, avg=4.50
clat (usec): min=15, max=8500, avg=78.50
lat (usec): min=18, max=8505, avg=83.00
bw (MiB/s): min=45, max=52, per=100.00%
Reading:
- IOPS: 12,345 read operations per second.
- BW: bandwidth, 48.2 MiB/s.
- clat: completion latency.
avg=78.50us,max=8500us (8.5ms). High max = inconsistent performance. - slat: submission latency. Submission to driver.
- lat: total latency.
For production baseline, capture IOPS, BW, and latency at p50, p95, p99, p99.9.
Safe benchmarking
fio can destroy data. To benchmark safely:
- Use a separate test file (not production data).
- Use a separate test directory (not /, not /var).
- Never benchmark the root filesystem.
- Check free space before you start. fio lays out the whole
--sizefile up front, so a 10G test needs 10G free plus headroom. - Stop the test if it interferes with production.
- Document the test parameters.
Never place a fio test file on a database data directory, a
log directory, or the root filesystem. fio creates the file
if it does not exist and fills it to --size. Ten gigabytes
appearing in /var/lib/mysql fills the datadir, InnoDB can
no longer extend the tablespace or write the redo log, and
the server aborts writes. If binlogs live on the same
filesystem, replication breaks with it. The recovery is a
crash restart, not a rm.
For read tests, use a file that has been pre-filled:
# Create the test file
dd if=/dev/zero of=/mnt/test/fio-test.bin bs=1M count=1024
# Run read test (does not modify the file)
fio --name=randread --filename=/mnt/test/fio-test.bin ...
For write tests, use a separate test directory that you can wipe.
Common workload patterns
# Database: random 4K reads
fio --rw=randread --bs=4k --iodepth=32
# Database writes: random 4K writes
fio --rw=randwrite --bs=4k --iodepth=32
# Streaming read: sequential 1M
fio --rw=read --bs=1M --iodepth=4
# Streaming write: sequential 1M
fio --rw=write --bs=1M --iodepth=4
# Mixed: 70% read, 30% write
fio --rw=randrw --rwmixread=70 --bs=4k --iodepth=32
Each pattern tests a different aspect of storage performance.
Capture baseline
For a database host, benchmark the same storage class the database uses, but never inside the data directory itself. Use a scratch path you can wipe.
# 1. Scratch path: not the datadir, not /var, not /
sudo mkdir -p /mnt/fio-test
# 2. Confirm room. A 10G test needs 10G plus headroom.
df -h /mnt/fio-test
# 3. Confirm the scratch path is not on the datadir filesystem
findmnt -no SOURCE,TARGET -T /mnt/fio-test
findmnt -no SOURCE,TARGET -T /var/lib/mysql
Run the benchmark only if df reports at least 20G
available and the two findmnt lines name different
filesystems. If they match, stop and pick another path.
# Random 4K reads at queue depth 32
fio --name=baseline-randread \
--filename=/mnt/fio-test/test.bin \
--size=10G \
--bs=4k \
--rw=randread \
--ioengine=libaio \
--direct=1 \
--iodepth=32 \
--runtime=60 \
--time_based \
--output=baseline-randread.json \
--output-format=json
Cleanup. fio does not remove the test file:
rm -f /mnt/fio-test/test.bin
df -h /mnt/fio-test
Save the JSON result. Compare future benchmarks against the baseline.
To characterise the device that holds the datadir, do it before the database is in service, or on an identical host that is out of rotation. A live datadir is never a benchmark target.
Interpret results
Key metrics:
- IOPS: operations per second.
- BW: throughput.
- clat p50, p99, p99.9: latency percentiles. p99.9 is the “tail” - rare slow operations.
- clat stddev: standard deviation. Low = consistent.
A healthy storage has:
- High IOPS for the workload.
- Low latency at all percentiles.
- Low standard deviation (consistent performance).
Storage with issues:
- High average IOPS but high p99.9 latency = inconsistent (queue contention, GC, etc.).
- Low IOPS = slow storage.
Knowledge check
Knowledge check · 3 questions
Q1. What does --iodepth=32 do in fio?
Q2. It is safe to run fio on the production filesystem.
Q3. Which of the following are valid fio read/write modes? Select all that apply.
Passing score: 75%. Answers are checked in this browser.