Storage fairness: count operations and bytes separately
Calculate request-size-dependent ceilings and trace buffered I/O attribution before interpreting a device limit as a tenant service guarantee.
On this page
The question for today
What you’ll understand
- Convert fixed-size I/O rates between operations and bytes.
- Explain why application requests and device operations need not match.
- Separate storage authorization from performance isolation.
A checkpoint writer issuing large sequential writes and a retrieval worker issuing small random reads can collide on the same storage path while reporting similar application request counts. Their byte volumes, queue occupancy, and durability costs differ. Linux 6.12 cgroup v2 provides per-device I/O controls, but those controls operate within a block-I/O accounting and enforcement path. They do not automatically create a reservation in a remote object store or prove that tenants cannot read each other’s files.
Operation rate and byte rate impose different ceilings
An IOPS limit counts operations in the relevant accounting domain; a bytes-per-second limit counts data volume. For fixed-size operations, multiplying operation rate by size relates the two. Small requests often reach an operation ceiling before a byte ceiling, while large requests can reach the byte ceiling first. This is an accounting model, not a performance prediction: device latency, read/write asymmetry, queue depth, random versus sequential access, and competing work can keep achieved throughput below either configured ceiling.
Distinguish an application-level lookup or checkpoint call from block requests. A lookup can be satisfied by a page cache without device I/O, or trigger several metadata and data reads. Filesystem merging, splitting, compression, copy-up, and writeback can change counts and timing. A “thousand requests” application metric therefore cannot be compared directly to a thousand block operations without tracing the path. For AI workloads, report request-size distributions and durability requirements alongside totals; a buffered write accepted into memory has not necessarily reached the persistence boundary the application needs.
Trace the device and the charge owner
Linux io.max identifies limits for a particular block device and resource direction. Inspect the effective hierarchy and underlying storage layout: a filesystem can sit above multiple layers, and the bottleneck may be elsewhere. Buffered writeback attribution depends on filesystem and kernel support and on which cgroup owns the writeback work. Shared inode patterns can complicate that ownership. A leaf’s application byte counter alone does not establish that the intended cgroup receives every relevant device charge. Compare application, filesystem, and device evidence before drawing a tenant-level conclusion.
A ceiling is also different from a minimum service entitlement. Throttling one writer can reduce interference without ensuring that a reader meets a tail-latency objective under every queue state. Remote storage adds server-side quotas, authentication, replication, and network bottlenecks beyond the local block controller. Define whether the claim covers local block traffic, a storage service account, a volume, or an object namespace. Enforce object and file permissions separately; traffic shaping cannot prevent an authorized path with an incorrect tenant check from exposing another customer’s checkpoint.
Construct an aggressor that tests the promised boundary
Keep the victim’s query distribution and cache state controlled, then vary aggressor request size, read/write mix, queue depth, and persistence behavior. A large buffered write test can expose writeback bursts that a direct-read test misses. Include a quiet baseline, a saturated aggressor, and a recovery phase while recording errors and unfinished work. If the victim appears faster when requests time out, completed-request latency alone is a misleading oracle. Match the measurement to the promised useful service and identify which device or remote dependency actually saturated.
Use the queue lab only for average normalized pressure. Reset, explicitly set arrival four and service three, predict growth from four pending items, then Play or Step. For an independent comparison Reset and explicitly set arrival two and service three before predicting drainage. Both rate values must be restored after Reset; an edit at nonzero time recomputes from the initial backlog. There are no blocks, page cache, request sizes, durability barriers, or I/O schedulers here. The real storage experiment supplies those missing mechanisms.
Work it through
One pair of limits produces two different request ceilings
A hypothetical device-accounting domain has ceilings of 1,000 operations per second and eight mebibytes per second. Compare fixed four-kibibyte operations with fixed 64-kibibyte operations, ignoring all other bottlenecks.
Eight mebibytes equals 8,388,608 bytes. Dividing by 4,096 bytes yields a byte-derived ceiling of 2,048 operations per second. The smaller configured ceiling is therefore 1,000 operations per second for four-kibibyte operations.
At that operation ceiling, four-kibibyte requests move 4,096,000 bytes per second, or 3.90625 mebibytes per second. Unused byte allowance cannot bypass the independent operation ceiling.
For 65,536-byte operations, the byte-derived ceiling is 128 operations per second. That is below 1,000, so the byte ceiling binds. The modeled traffic is then eight mebibytes per second, although achieved throughput may still be lower.
Equal IOPS settings do not imply equal achievable request rates across sizes, and neither ceiling promises latency or minimum service.
The living notebook
Explore the model
Interactive lab · queue
Normalized queue pressure is not a block-I/O trace
Use independent Reset → set BOTH rates → predict → Play/Step experiments for (λ,μ)=(4,3) and (2,3). Each recomputes q=max(0,4+(λ−μ)t). Rates are abstract items per model second, not measured storage IOPS.
- Fluid backlog
- 4
- Net rate · λ − μ
- −1 /s
- M/M/1 mean residence
- 1 s
Fluid backlog starts at 4 and follows max(0, 4 + (λ − μ)t). The separate M/M/1 residence-time formula assumes Poisson arrivals, exponential service and λ < μ. Parameter changes pause playback and recompute the model at the current elapsed time. Reset restores both parameters and returns time to zero. Playback starts paused. Adjust either parameter, then choose Play or Step.
Look forBacklog arithmetic motivates an admission margin, while real attribution and request sizes determine storage isolation.
Pause & reason
Check your understanding
Your turn
Try it yourself
A hypothetical fixed-size path has ceilings of 500 operations per second and four mebibytes per second. For 16-kibibyte operations, calculate the configured rate bound. Does achieving it prove fsync latency isolation?
Take a moment to make a prediction or work it out on paper. Open a hint when you need one.
Give me a hint
Convert both byte prefixes consistently and keep rate ceilings separate from persistence and latency.
Compare with the solution
Four mebibytes divided by 16 kibibytes is 256 operations per second. The minimum of 500 and 256 is 256, so the byte ceiling binds in this simplified matching domain. This is an upper bound, not promised achieved service. Reaching that average does not establish an fsync deadline: writeback, device queues, barriers, other tenants, and the actual durability path still matter. Test the required persistence operation and count deadline failures and unfinished requests instead of replacing that evidence with an average bandwidth observation.
Keep the idea
Take it with you
- IOPS and byte ceilings bind differently as request size changes.
- Trace application work through cache, filesystem, writeback, and device accounting.
- Validate useful durable service and storage authorization separately.
Read further
Original sources and deeper explanations to follow at your own pace.
Optional progress, saved in this browser when available. Revisit any lesson at any time.
Lesson not marked complete.