<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Io · XB Field Notes</title><link>https://www.xbcatm.com/tags/io/</link><description>Illustrated field notes on HTTP, DNS, TLS, Caddy, SSH, releases, and recovery.</description><generator>Hugo</generator><language>en-us</language><atom:link href="https://www.xbcatm.com/tags/io/index.xml" rel="self" type="application/rss+xml"/><item><title>iotop shows which process is burning the disks</title><link>https://www.xbcatm.com/posts/linux-iotop-io-waiter/</link><pubDate>Wed, 15 Mar 2028 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/posts/linux-iotop-io-waiter/</guid><description>iotop watches per process IO by reading kernel accounting, so you can blame a specific pid for a busy disk.</description><content:encoded>&lt;p&gt;A disk that spikes with no obvious writer is hard to chase; iotop maps throughput straight back to the task doing the damage. System-wide tools show the symptom — high await, saturated utilization — but never the suspect: fifty processes share the disk and the average blames all of them equally. The question that ends the investigation is always per-process — who, how much, since when — and iotop answers exactly that, live, from kernel counters no application can hide from.&lt;/p&gt;
&lt;p&gt;Keep it beside the system-wide view rather than instead of it. iostat or vmstat says the disk is the bottleneck; iotop names the process making it so. Together they compress a storage mystery into a two-command diagnosis: confirm the resource, identify the consumer.&lt;/p&gt;
&lt;h2 id="per-process-counters"&gt;Per-process counters&lt;/h2&gt;
&lt;p&gt;iotop reads per-process IO from the kernel, so live read and write rates map to a pid rather than a nameless load average. Run it with &lt;code&gt;-o&lt;/code&gt; to show only processes that are actually doing IO. The default view splits reads from writes and shows swap-in percentage alongside throughput, which separates the genuinely disk-hungry from the merely present — a process with high DISK READ but zero writes is scanning, one with writes is producing, and the distinction decides whether you throttle, relocate, or kill.&lt;/p&gt;
&lt;p&gt;Note what the counters actually measure: bytes passing through the IO layer per process, including buffered writes not yet on disk. A process can show huge write rates while the disk looks idle (page cache absorbing) or modest rates while the disk saturates (many small synchronous writes). Read iotop together with device utilization: process-side blame plus device-side saturation is the complete picture, and either half alone misleads.&lt;/p&gt;
&lt;h2 id="see-whatever-sweeps-the-disk"&gt;See whatever sweeps the disk&lt;/h2&gt;
&lt;p&gt;Sort by bandwidth and the offender stands out, whether it is a log hoarder, an indexer, or a runaway backup. Follow the pid to the exact process (and often the file) before acting. The usual lineup: an unrotated log written at debug level, a reindex job scheduled against peak hours, a backup writing to the same spindle it reads from, a container layer unpack looping on a corrupt image. Each has a different fix — rotate, reschedule, separate, prune — and killing the process treats the symptom while the cause reschedules itself for tomorrow.&lt;/p&gt;
&lt;p&gt;Resolve the pid to its full context before touching anything: command line, working directory, open files via lsof, parent process. A java process burning writes might be ten applications behind one PID namespace; the fix belongs to the right one. Attribution first, action second — every storage war story that ends in regret skipped the first step.&lt;/p&gt;
&lt;h2 id="watch-before-you-act"&gt;Watch before you act&lt;/h2&gt;
&lt;p&gt;iotop observes rather than changes anything, so you can investigate the culprit before deciding what to throttle, move, or kill. Pair it with strace or lsof on the pid to pin the file and the syscall. The combination is decisive: iotop says process 18412 writes 400 MB/s, lsof says the writes go to one growing log file, strace shows O_APPEND writes of 4 KB — diagnosis complete, and the fix (log level, rotation, async appender) is obvious instead of debated.&lt;/p&gt;
&lt;p&gt;Record the baseline while healthy so the incident comparison means something. A five-minute iotop batch capture on a normal day — &lt;code&gt;-b -n 60&lt;/code&gt; to a file — gives you &amp;ldquo;normal for this host&amp;rdquo; in numbers: which processes usually lead, what rates are ordinary. Without it, every spike looks unprecedented; with it, most incidents classify in seconds as &amp;ldquo;the indexer, again, an hour early.&amp;rdquo;&lt;/p&gt;
&lt;h2 id="when-it-breaks"&gt;When it breaks&lt;/h2&gt;
&lt;p&gt;The classic meta-failure is permission blindness: run without privileges and iotop shows question marks instead of process details, or hides other users&amp;rsquo; processes entirely — and the actual offender, running as another user, stays invisible. Run with the necessary privileges (root or the right capabilities) from the start, or the tool exonerates exactly the processes you cannot see. An investigation that &amp;ldquo;found nothing&amp;rdquo; with unprivileged iotop found nothing about your own processes only.&lt;/p&gt;
&lt;p&gt;The second failure is sampling illusion on bursty IO: a process doing violent one-second bursts every minute looks innocent in a 5-second average and guilty in a 1-second one, or vice versa. Vary the delay interval when the numbers and the symptoms disagree, and prefer accumulated totals (&lt;code&gt;-a&lt;/code&gt;) over instantaneous rates for &amp;ldquo;who hurt the disk over the last hour&amp;rdquo; questions. The right aggregation answers the question asked; the default answers whichever question its interval assumes.&lt;/p&gt;
&lt;h2 id="check-it-quickly"&gt;Check it quickly&lt;/h2&gt;
&lt;p&gt;Capture who does IO, with context, non-interactively:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;iotop -b -n 5 -o -d 2 | head -n 30
pid=$(iotop -b -n 1 -o | awk &amp;#39;NR&amp;gt;5 {print $1}&amp;#39; | head -n 1); ls -l /proc/$pid/exe; cat /proc/$pid/cmdline | tr &amp;#39;\0&amp;#39; &amp;#39; &amp;#39;
iostat -x 1 3 | grep -E &amp;#34;Device|sd|nvme&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The batch capture ranks offenders without a terminal UI, the pid resolution names the top writer&amp;rsquo;s binary, and iostat confirms the device side is actually saturated.&lt;/p&gt;
&lt;h2 id="sources"&gt;Sources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://man7.org/linux/man-pages/man8/iotop.8.html"&gt;iotop man page&lt;/a&gt;, Official iotop reference, checked 2026-06-01.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://man7.org/linux/man-pages/man8/vmstat.8.html"&gt;vmstat man page&lt;/a&gt;, documents system-wide CPU and IO-wait reporting, checked 2026-09-05.&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Disk schedulers order I/O to move the head efficiently</title><link>https://www.xbcatm.com/posts/disk-scheduling-elevators-serve-fairly/</link><pubDate>Tue, 09 Mar 2027 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/posts/disk-scheduling-elevators-serve-fairly/</guid><description>A disk scheduler reorders and batches requests so the read-write head sweeps efficiently, trading a little latency for a lot of throughput on rotating disks.</description><content:encoded>&lt;p&gt;blk-mq gives the block layer a software staging queue per CPU and a hardware dispatch queue per device queue, with the I/O scheduler between them. A request takes the shortest path — straight to the hardware queue — unless a scheduler is attached or it can be merged, in which case it is staged in software first. That scheduler is the only place in the stack where the kernel decides the order your requests reach the device.&lt;/p&gt;
&lt;p&gt;Ordering is worth a great deal on a rotating disk and almost nothing on flash, but the scheduler has a second job that survives the move to NVMe: deciding who waits. Those jobs pull in different directions — reordering for head efficiency is indifferent to fairness, and fairness costs seeks — which is why the four schedulers are matched to a device and a workload rather than ranked.&lt;/p&gt;
&lt;h2 id="the-head-is-the-budget"&gt;The head is the budget&lt;/h2&gt;
&lt;p&gt;On a 7200 RPM disk, rotation costs 4.17 ms per request on average and transferring a 4 KiB block costs 0.026 ms, so almost all service time is seek and rotation. The fixture replays 2000 random 4 KiB reads across 100,000 tracks: in arrival order the head travels 67,907,411 tracks and the run takes 21,954 ms, or 91 IOPS; in ascending track order it travels 99,977 tracks and takes 8,645 ms, or 231 IOPS. Same requests and device: 679 times less head travel, 2.5 times the throughput.&lt;/p&gt;
&lt;p&gt;Seek is 62 percent of service time in the arrival-order run and 3 percent in the sorted run, while rotation and transfer stay fixed at 4.19 ms either way. On NVMe the same requests take about 40 ms with no seek at all, so sorting buys nothing.&lt;/p&gt;
&lt;h2 id="what-each-scheduler-is-for"&gt;What each scheduler is for&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;none&lt;/code&gt; is first-come-first-served: right for a deep internal queue with no contention, wrong on a rotator, where the seek component stays untouched. &lt;code&gt;mq-deadline&lt;/code&gt; sorts by sector and adds per-direction expiry — reads expire long before writes, each batch capped by &lt;code&gt;fifo_batch&lt;/code&gt; — which bounds the tail &lt;code&gt;none&lt;/code&gt; leaves unbounded. &lt;code&gt;kyber&lt;/code&gt; targets latencies directly, throttling a queue whose requests miss the read or write target (&lt;code&gt;read_lat_nsec&lt;/code&gt;, &lt;code&gt;write_lat_nsec&lt;/code&gt;), so a bulk writer cannot push interactive reads past the target alone.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;bfq&lt;/code&gt; is the fairness answer: a proportional-share scheduler with cgroup support that distributes device throughput — not just device time — among processes and groups in proportion to their weights. It privileges latency over throughput by default and idles the device, which helps on rotational media but can cost throughput on flash. Read the active scheduler with &lt;code&gt;cat /sys/block/&amp;lt;dev&amp;gt;/queue/scheduler&lt;/code&gt;, which prints the list with the current one in brackets, and change it with &lt;code&gt;echo bfq &amp;gt; /sys/block/sda/queue/scheduler&lt;/code&gt;; make that survive reboot with a udev rule, since the attribute is recreated per device.&lt;/p&gt;
&lt;h2 id="fairness-is-a-different-question-from-order"&gt;Fairness is a different question from order&lt;/h2&gt;
&lt;p&gt;With a 90/10 issue mix between a bulk hog and a light interactive client, arrival order gives the light client 0.099 of the service and an average wait position of 1901 requests — not starved, drowned. Under per-group fair queueing it gets 0.5 of the service regardless of the mix, at the hog&amp;rsquo;s expense. Only the arbitration changed.&lt;/p&gt;
&lt;p&gt;So on flash the choice is usually &lt;code&gt;bfq&lt;/code&gt; for isolation or &lt;code&gt;mq-deadline&lt;/code&gt; for bounded tails, and &lt;code&gt;none&lt;/code&gt; only when one workload owns the device. On a rotator the sorted order is the larger win. In cgroup v2 terms, &lt;code&gt;io.weight&lt;/code&gt; gives a group a share and &lt;code&gt;io.max&lt;/code&gt; a ceiling; both only matter if the scheduler enforces the order.&lt;/p&gt;
&lt;h2 id="when-it-breaks"&gt;When it breaks&lt;/h2&gt;
&lt;p&gt;The classic failure is a rotator running &lt;code&gt;[none]&lt;/code&gt;, usually because a host was cloned from an NVMe image or a udev rule matched &lt;code&gt;sd*&lt;/code&gt; while the device is &lt;code&gt;nvme0n1&lt;/code&gt;. The signature is &lt;code&gt;%util&lt;/code&gt; near 100 percent with &lt;code&gt;r/s&lt;/code&gt; low and &lt;code&gt;await&lt;/code&gt; in the tens of milliseconds — the device is busy, not saturated, and it is busy seeking. Confirm with &lt;code&gt;cat /sys/block/&amp;lt;dev&amp;gt;/queue/rotational&lt;/code&gt;: &lt;code&gt;1&lt;/code&gt; with no reordering scheduler is the bug.&lt;/p&gt;
&lt;p&gt;The second failure is a latency collapse with healthy averages. A backup job or a &lt;code&gt;dd&lt;/code&gt; on the same device starves interactive reads, so p99 explodes while the mean barely moves. &lt;code&gt;none&lt;/code&gt; and &lt;code&gt;mq-deadline&lt;/code&gt; will not protect the interactive client; &lt;code&gt;bfq&lt;/code&gt; or a cgroup &lt;code&gt;io.max&lt;/code&gt; will. On flash, watch &lt;code&gt;slice_idle&lt;/code&gt;: idling to preserve sequentiality is a throughput loss when there is no head to preserve.&lt;/p&gt;
&lt;h2 id="check-it-quickly"&gt;Check it quickly&lt;/h2&gt;
&lt;p&gt;Read what the device is and which scheduler it is running:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;for d in /sys/block/sd* /sys/block/nvme*n*; do echo &amp;#34;$d rot=$(cat $d/queue/rotational) sched=$(cat $d/queue/scheduler)&amp;#34;; done
cat /sys/block/sda/queue/iosched/read_expire /sys/block/sda/queue/iosched/fifo_batch
iostat -x 2 5
echo mq-deadline &amp;gt; /sys/block/sda/queue/scheduler
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The first line pairs each device&amp;rsquo;s rotational flag with its active scheduler in brackets; &lt;code&gt;rot=1&lt;/code&gt; next to &lt;code&gt;[none]&lt;/code&gt; is the failure signature, &lt;code&gt;rot=0&lt;/code&gt; next to &lt;code&gt;[none]&lt;/code&gt; is normal. The second shows &lt;code&gt;mq-deadline&lt;/code&gt;&amp;rsquo;s expiry and batch tunables. &lt;code&gt;iostat -x&lt;/code&gt; distinguishes saturated from merely busy: high &lt;code&gt;%util&lt;/code&gt; with low &lt;code&gt;r/s&lt;/code&gt; and high &lt;code&gt;await&lt;/code&gt; means queueing and seeking.&lt;/p&gt;
&lt;h2 id="sources"&gt;Sources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.kernel.org/doc/html/latest/block/switching-sched.html"&gt;Switching Scheduler&lt;/a&gt;, the scheduler names, sysfs paths and how to change them, checked 2026-09-16.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.kernel.org/doc/html/latest/block/blk-mq.html"&gt;Multi-Queue Block IO Queueing Mechanism (blk-mq)&lt;/a&gt;, software staging queues versus hardware dispatch queues, checked 2026-09-16.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.kernel.org/doc/html/latest/block/bfq-iosched.html"&gt;BFQ (Budget Fair Queueing)&lt;/a&gt;, proportional-share behaviour, cgroup support and the idling trade-off, checked 2026-09-16.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.kernel.org/doc/html/latest/block/kyber-iosched.html"&gt;Kyber I/O scheduler tunables&lt;/a&gt;, the read and write latency targets Kyber throttles against, checked 2026-09-16.&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>