How to Tune the Linux I/O Scheduler for Database Workloads on a VPS

The Linux I/O scheduler decides the order in which block-device requests are processed, and on a VPS it is one of the most misunderstood performance levers in the kernel. Database workloads are especially sensitive to it: the difference between the wrong scheduler and the right one can show up as 20–40% swings in random-read latency, which translates directly into slower queries and higher p99 response times. This article explains how to pick, apply, and verify the I/O scheduler for database workloads on a VPS.

First, Know What You Are Running On

The modern kernel ships with four schedulers: mq-deadline (default on many distros), bfq, kyber, and none (a no-op pass-through). Critically, on NVMe and most cloud block storage the hardware already performs its own reordering and queueing, so a software scheduler can add overhead without adding benefit. Check what is active with:

cat /sys/block/vda/queue/scheduler
lsblk -d -o NAME,ROTA

ROTA=0 means the device is non-rotational (SSD/NVMe); ROTA=1 means a spinning disk. That single value determines your starting point. On cloud VPS instances backed by network storage, the physical device is often a remote SAN or NVMe array, which changes the calculus again.

Which Scheduler for Which Workload

SchedulerBest forWhy
noneNVMe, cloud block storage, single-queue devicesZero overhead; hardware already optimizes ordering
mq-deadlineSSD with mixed read/write workloadsGuarantees read and write deadlines, limits starvation
bfqHDD, shared disks, desktop-class latency fairnessFairness-oriented; strong for many concurrent processes
kyberLow-latency latency-sensitive apps on fast storageTargets read/write latency directly, minimal tuning

For a typical PostgreSQL or MySQL workload on an SSD-backed VPS, the practical choice is none or mq-deadline. On NVMe, none is almost always right. On a shared or spinning-disk VPS (increasingly rare), bfq prevents one noisy process from monopolizing the disk.

Applying the Scheduler at Runtime

You can switch schedulers live, which makes A/B testing easy:

echo none > /sys/block/vda/queue/scheduler

To make it permanent, add a udev rule so it survives reboots:

# /etc/udev/rules.d/60-iosched.rules
ACTION=="add|change", KERNEL=="vd*", ATTR{queue/scheduler}="none"

If you use a kernel command-line parameter instead (elevator=none), remember that it only applies to the boot device and is ignored for devices probed later. The udev approach covers all block devices consistently.

Verify with Real Benchmarks, Not Opinions

Scheduler changes are workload-dependent, so benchmark before and after. fio with a database-shaped profile is the right tool:

fio --name=db-sim --rw=randrw --rwmixread=70 --bs=8k \
    --iodepth=16 --numjobs=4 --size=2G --runtime=60 \
    --time_based --group_reporting



70% reads at 8 KB blocks approximates OLTP traffic. Record IOPS and latency percentiles under each scheduler. A meaningful difference is one you can reproduce across multiple runs — a 5% IOPS swing is noise; a 30% p99 latency change is real. For an even more realistic test, run your actual database's workload replay (pgbench for PostgreSQL, sysbench oltp for MySQL) and watch query latency directly.

Interaction with Other I/O Settings

The scheduler is only one layer of the I/O stack. On a VPS, three adjacent knobs change the outcome:

  • Queue depth (nr_requests) — for databases, a deeper queue helps absorb bursty OLTP traffic; on NVMe the device handles depth internally.
  • Filesystem mount optionsnoatime eliminates metadata writes on every read; for databases consider nobarrier only on battery-backed storage, never on a VPS without guarantees.
  • VM dirty ratiosvm.dirty_ratio and vm.dirty_background_ratio control writeback bursts. If you see periodic latency spikes, tune these before blaming the scheduler.

Remember that on most cloud VPSes the "disk" is remote network storage. The host-side scheduler on your instance is often less important than the storage vendor's own queueing, which is exactly why none wins so often in benchmarks — there is nothing left for the guest kernel to optimize.

When Scheduler Tuning Is Not the Answer

If your database is slow and the disk is an SSD-backed cloud volume, the scheduler is rarely the bottleneck. Check the more common culprits first: insufficient innodb_buffer_pool_size (MySQL) or shared_buffers (PostgreSQL), missing connection pooling, missing indexes, and CPU steal from an oversold host. If iostat -x shows low utilization and %util near 100%, you may be looking at queue saturation on the remote storage side — a provider-level constraint no scheduler fixes. In that case, compare VPS providers on our comparison table, because storage quality varies far more between providers than between schedulers.

Also keep the rest of the memory subsystem consistent with your I/O changes. Swap behavior, the page cache, and the I/O scheduler all interact under pressure — tuning one in isolation while the others fight it is a common failure mode. A good monitoring setup (iostat, iotop, and a metrics dashboard) turns scheduler tuning from guesswork into a measured decision. If you are evaluating hosts for a database-heavy VPS, see the full storage specs on our VPS comparison page, and InterServer's NVMe VPS plans are a reasonable baseline for this kind of workload.

Summary

For database workloads on modern VPS hardware, start with none on NVMe and mq-deadline on SSD, then verify with fio and a real database workload before committing. Apply changes through udev so they survive reboots, benchmark with percentile-aware tools, and keep the scheduler in context — storage vendor queueing and database memory settings usually matter more. Measure, change one variable, measure again. That discipline is worth more than any scheduler default.

Leave a Reply