How to Limit and Control Disk I/O Resources on a KVM VPS

Estimated reading: 5 minutes 78 views

Introduction

Unregulated disk read and write operations can easily saturate virtio storage block devices on virtualized platforms, introducing catastrophic I/O wait times (iowait) that degrade the responsiveness of the entire operating system. Applications like unoptimized database servers, heavy logging agents, or bulk file backups can quickly exhaust disk queue depths. This guide details how to implement robust block device bandwidth and IOPS (Input/Output Operations Per Second) rate-limiting using the cgroup v2 io controller on Debian 11/12 KVM virtual servers.

Step 1: Identify Target Virtio Block Devices

Unlike CPU limits, cgroup v2 I/O limitations must be applied to the underlying physical block device mapped by the hypervisor (typically /dev/vda on KVM instances), rather than individual filesystem partitions (e.g., vda1 or vda3).

Run the device mapping utility to inspect major and minor device identifiers:

lsblk -o NAME,MAJ:MIN,SIZE,MOUNTPOINTS

Note the Major:Minor values corresponding to your root disk. For example, if vda reports 254:0, this exact identifier (254:0) will be referenced in cgroup configuration rules.

Step 2: Configure Persistent I/O Limits for Storage-Heavy Services

To protect system responsiveness from storage-heavy applications (such as MariaDB/MySQL database engines executing massive write transactions or backups):

Open the systemd override configuration for the database service:

sudo systemctl edit mariadb.service

Append the storage throttling directives within the [Service] block, targeting your virtio disk (/dev/vda) with explicit bandwidth and IOPS ceilings:

Ini, TOML

[Service]
IOReadBandwidthMax=/dev/vda 50M
IOWriteBandwidthMax=/dev/vda 20M
IOReadIOPSMax=/dev/vda 1000
IOWriteIOPSMax=/dev/vda 500

Save the editor buffer, reload systemd, and restart the service:

sudo systemctl daemon-reload
sudo systemctl restart mariadb.service

Step 3: Implement Dynamic cgroup Throttling for Unmanaged Processes

If a rogue background script or transient data processing job begins abusing disk bandwidth outside of a managed systemd unit, administrators can throttle it dynamically using manual cgroup manipulation without terminating the process.

Create a dedicated control group directory:

sudo mkdir -p /sys/fs/cgroup/limited

Define the kernel rule enforcing byte-per-second and IOPS caps against the disk’s major:minor (254:0):

echo "254:0 rbps=20971520 wbps=10485760 riops=500 wiops=200" | sudo tee /sys/fs/cgroup/limited/io.max

(Where rbps=20971520 is 20MB/s read, wbps=10485760 is 10MB/s write).

Migrate the offending target Process ID (PID, e.g., 1234) into the constrained control group:

echo 1234 | sudo tee /sys/fs/cgroup/limited/cgroup.procs

Step 4: Testing & Performance Verification

To benchmark the hypervisor’s block storage layer and confirm that kernel-enforced rate-limiting functions accurately, utilize fio (Flexible I/O Tester).

Security Note: fio is a powerful storage benchmarking engine. Install it exclusively for testing sessions and purge it immediately afterward.

Install the testing utility:

apt-get update && apt-get install -y fio

Execute a sandboxed direct-write test restricted to a 10 MB/s ceiling using a transient systemd scope:

sudo systemd-run --scope -p IOWriteBandwidthMax="/dev/vda 10M" \
  fio --name=capped --directory=/root --rw=write --bs=1M --size=200M \
  --numjobs=1 --direct=1 --ioengine=libaio --group_reporting

Verify results: Examine the output metrics summary. The reported write bandwidth line (WRITE: bw=…) will precisely match the 10.0 MB/s parameter cap, proving the kernel-level rule is active.

Cleanup test artifacts and purge the package:

rm -f /root/capped.0.0
apt-get purge -y fio

Step 5: Cleanup, Reversion & Rollback

If you need to remove applied storage restrictions, return services to an unthrottled state, or clean up manual testing artifacts, follow the specific rollback procedures below depending on how the limit was implemented:

Scenario A: Reverting Persistent systemd Service Overrides When you configure limits via systemctl edit, systemd creates an override drop-in file (typically under /etc/systemd/system/mariadb.service.d/override.conf). To completely wipe out these custom I/O limits and restore native performance:

Revert the unit overrides automatically:

sudo systemctl revert mariadb.service

(Alternatively, you can manually delete the override directory using sudo rm -rf /etc/systemd/system/mariadb.service.d/)

Reload the systemd daemon and restart the service:

sudo systemctl daemon-reload
sudo systemctl restart mariadb.service

Verify that the limits have been successfully purged:

systemctl show mariadb.service -p IOReadBandwidthMax -p IOWriteBandwidthMax

(Expected output should return empty/unlimited values).

Scenario B: Deleting Manual cgroups & Releasing PIDs

Manual cgroups created directly under /sys/fs/cgroup/ are volatile and do not survive a system reboot. However, if you need to tear down a manual control group immediately while the system is running:

Check which active PIDs are still trapped inside the control group:

cat /sys/fs/cgroup/limited/cgroup.procs

Relocate active PIDs back to the system root slice (or safely terminate them if they are rogue background processes):

# Move the process back to root cgroup (replace 1234 with your active PID)
echo 1234 | sudo tee /sys/fs/cgroup/cgroup.procs

Remove the custom control group directory:

sudo rmdir /sys/fs/cgroup/limited 2>/dev/null

Scenario C: Transient Scopes (systemd-run –scope)

If you applied temporary limits using systemd-run –scope (such as during the fio testing phase in Step 4), no manual cleanup is required. Transient scopes automatically destroy their underlying cgroup and release all system hooks the exact moment the command execution completes or times out.

Scenario D: Resetting Process Scheduling Priority (ionice)

If you adjusted process I/O scheduling classes using ionice and want to restore the process back to standard default parameters (Best-Effort class, Priority 0):

Reapply standard default scheduling parameters to the target PID:

ionice -c2 -n0 -p 1234

Verify the reset priority status:

ionice -p 1234

Conclusion

Proper governance of disk I/O prevents heavy write amplification from paralyzing system responsiveness. By setting clear bandwidth and IOPS ceilings on database backends or transient helper scripts via cgroup v2, administrators ensure stable storage latency across all shared virtual infrastructure blocks, with clean rollback paths available whenever operational requirements change.

Share this Doc

How to Limit and Control Disk I/O Resources on a KVM VPS

Or copy link

CONTENTS