Slow storage shows up as high load averages, slow database queries and sluggish deployments, even when the CPU is idle. The fix starts with measuring properly: what kind of I/O the workload does, and what the disk can deliver.
Know the numbers that matter
- IOPS — operations per second. Databases and many small files depend on random-read/write IOPS.
- Throughput (MB/s) — matters for large sequential files: backups, media, logs.
- Latency — how long each operation takes. Often the most important number for interactive workloads.
- Queue depth — how many operations are in flight. SSDs and NVMe deliver their best IOPS with parallel requests.
Benchmark with fio
dd only measures one simple pattern. fio lets you test realistic patterns. Run tests in a directory on the disk you’re measuring, with files larger than RAM caching can hide (or use --direct=1):
apt install -y fio
mkdir -p /root/fio && cd /root/fio
# Random 4K reads (database-like)
fio --name=randread --rw=randread --bs=4k --size=2G --iodepth=32 \
--ioengine=libaio --direct=1 --runtime=60 --time_based --group_reporting
# Random 4K writes
fio --name=randwrite --rw=randwrite --bs=4k --size=2G --iodepth=32 \
--ioengine=libaio --direct=1 --runtime=60 --time_based --group_reporting
# Sequential 1M reads (backup/media-like)
fio --name=seqread --rw=read --bs=1M --size=2G --iodepth=8 \
--ioengine=libaio --direct=1 --runtime=60 --time_based --group_reporting
Look at IOPS, BW and the latency percentiles (clat percentiles, especially 99th). Delete the test files afterwards.
On shared infrastructure such as a VPS, results vary over time — run tests a few times and compare averages.
Watch live I/O
iostat -xz 1
| Column | Meaning |
|---|---|
r/s, w/s |
Reads and writes per second |
rkB/s, wkB/s |
Throughput |
r_await, w_await |
Average latency in ms (including queue time) |
aqu-sz |
Average queue length |
%util |
Time the device was busy — on SSDs, 100% doesn’t always mean saturated |
Find the processes responsible:
iotop -oPa # accumulated I/O per process
pidstat -d 1 # from sysstat
Tuning checklist
Mount options
Add noatime to data filesystems in /etc/fstab so reads don’t trigger metadata writes:
UUID=… /var/lib/mysql ext4 defaults,noatime 0 2
I/O scheduler
cat /sys/block/vda/queue/scheduler
For SSD/NVMe and virtual disks, none or mq-deadline is usually best. Change temporarily with echo mq-deadline > /sys/block/vda/queue/scheduler, or permanently with a udev rule.
Filesystem choice
ext4 and XFS are both excellent. XFS handles large files and parallel writes well; ext4 is a safe default. The difference is small compared with application-level changes.
Reduce the writes
The best I/O is the one you don’t do:
- Cache — a page cache in front of your web app, Redis for sessions and object caching.
- Database memory — a large enough InnoDB buffer pool or PostgreSQL
shared_buffersturns disk reads into memory reads. - Log less — disable access logs for static assets, buffer logs, rotate and compress.
- Batch writes — group many small writes into fewer, larger ones in your application.
Writeback tuning
On servers with lots of RAM, limiting dirty memory smooths out write bursts — see Linux disk caching.
When it’s the hardware
If latency is high even with low utilization from your own processes, or %steal CPU time is high in top on a VPS, the bottleneck may be outside your server. Check with your provider, and consider whether your workload needs a larger plan or a dedicated server with its own disks.
Summary
Measure with fio and iostat, find heavy processes with iotop, then reduce I/O through caching and memory before tuning kernel settings. More tuning in Linux server performance. For data-heavy projects, our Storage VPS offers large SSD capacity.