The useful part, up front

What you’ll take away

  • Parallel command support does not create independent work that an application has not supplied.
  • More outstanding requests can increase aggregate throughput while leaving each request’s latency unchanged or higher.
  • Request size and byte units belong beside every IOPS or bandwidth comparison.
  • A configured queue limit, observed outstanding count, and lower-level device queue are different quantities.
  • Interpret averages and tail latencies with sample counts, workload conditions, and a stated measurement boundary.
01

A faster total does not necessarily shorten one request

A storage result can show more completed operations per second without showing that each operation finishes sooner. That is not a contradiction. A system can keep several requests in progress and finish more of them overall, while the time each request spends in the system stays the same or increases. SNIA describes this latency-versus-throughput trade-off when discussing queue depth. NVMe is designed to support parallel command submission and completion, but that ability does not tell you how many independent requests your application has available. A large peak result therefore needs its workload conditions before it can explain a shorter wait in your own task. [1][5]

The useful question is what must finish before the application can continue. In an imagined task, each read might reveal where the next read should go. Another task might already know the addresses of many independent reads. Those are different workloads even if they use the same drive. The examples below deliberately separate a request’s completion time, the number of requests in progress, and the total bytes completed per second. They are calculated models, not tests of an SSD. The goal is to identify which measurement explains the work that must finish before the application can continue.

Ask which storage quantity the number describes. Operations, bytes, elapsed time and outstanding work are separate quantities. Name the measurement boundary.
Operations, bytes, elapsed time and outstanding work are separate quantities. Name the measurement boundary. · Ameer Journal — original explanatory diagramOriginal editorial illustration; no third-party artwork reused.
02

Count operations, bytes, time, and outstanding work separately

IOPS counts input/output operations completed per second. Byte throughput counts how much data those operations transfer per second. A request’s latency measures time between stated starting and finishing events, while outstanding work counts requests that have started but have not completed at the chosen boundary. Microsoft’s DiskSpd results keep byte totals, bandwidth, operation counts, and latency statistics as separate outputs. That separation matters: one large transfer and one small transfer each count as an operation, but they do not carry the same amount of data. A result stated only in IOPS leaves the request size unanswered. [3][4]

Queue depth needs a similar label. A program can be configured to allow a certain number of outstanding requests, but the configured limit is not automatically a measurement of how many were present throughout the run. The operating system and device also have their own queues. Linux’s block-layer documentation describes software staging and hardware dispatch queues rather than one universal queue shared by every counter. When someone reports a depth of eight, ask where that number came from: a load-generator setting, an observed application count, or a particular lower-level queue. Those descriptions cannot be substituted for each other without checking their scope. [2][3]

03

Build a one-request baseline with explicit assumptions

Imagine a workload that keeps exactly one read outstanding. Each read takes exactly 200 microseconds from submission to completion, including everything inside our chosen measurement boundary. The next read is submitted immediately when the previous one finishes, with no extra application pause. Since 200 microseconds equals 0.0002 seconds, this ideal stream completes 1/0.0002 = 5,000 reads per second. If the application then pauses for 50 microseconds before issuing its next read, the complete cycle becomes 250 microseconds. Its rate falls to 4,000 reads per second even though each read still completes in the same 200 microseconds. During the pause there is no request outstanding.

Now give every read a size of 4 KiB, meaning 4,096 bytes. The calculated byte throughput is 5,000 × 4,096 = 20,480,000 bytes per second, or 20.48 MB/s using decimal megabytes. A much larger advertised transfer rate would not change this model unless something about the workload or completion time changed. The example contains only one request to work on at a time. NVMe’s ability to support parallel work cannot manufacture additional independent requests for that stream. Keep this distinction between available concurrency and supported concurrency when comparing an application’s behavior with a high-load benchmark. [1][3]

The invented one-request stream has an explicit cycle. Exactly 200 μs per 4 KiB request, immediate replacement, and no extra application pause. Calculations, not a benchmark.
Exactly 200 μs per 4 KiB request, immediate replacement, and no extra application pause. Calculations, not a benchmark. · Ameer Journal — original explanatory diagramOriginal editorial illustration; no third-party artwork reused.
04

Overlap can raise the total without making a request faster

Change the imagined workload so four independent reads start together. Assume the system can complete all four after the same 200 microseconds and immediately start the next four. This grants enough parallel capacity to preserve each request’s completion time, a condition that a queue setting alone cannot establish. Every repeating 0.0002-second cycle now completes four operations, so the rate is 4/0.0002 = 20,000 IOPS. With the same 4 KiB size, the calculated throughput becomes 81.92 MB/s. Each request still waited 200 microseconds. The total increased fourfold, while the individual wait did not decrease.

That is a possible reason for a high-concurrency result to exceed a one-request result, but it is not a promise of fourfold scaling on a real drive. Requests share resources, and the extra load can change their completion times. SNIA explicitly describes that dependence on the storage environment. An application also needs independent work to overlap; changing the benchmark’s request count does not rewrite another application’s dependencies. Read the result as evidence about the tested combination of concurrency and workload. A separate measurement is needed before saying that one interactive operation, file-opening task, or dependent read became quicker. [1][5]

05

A deeper workload can keep throughput unchanged

For a third invented case, keep eight reads outstanding, but suppose each now takes 400 microseconds. Again, all eight finish together and are replaced immediately. Eight completions every 0.0004 seconds still give 20,000 IOPS and 81.92 MB/s for 4 KiB reads. Compared with the four-request case, twice as many operations are in progress and each waits twice as long, while total throughput is unchanged. The extra occupancy represents more unfinished work inside the measurement boundary, while the rate of finished work stays the same. Looking at both numbers reveals that trade-off immediately.

A comparison that celebrated the bigger queue number would miss the lack of extra completed work. A comparison that looked only at MB/s would miss the longer waits. SNIA’s general guidance describes why lower queue depth can trade some throughput for lower latency, depending on the environment. The sensible question is which result matches the task’s goal: a batch transfer may emphasize total completion, while a dependent request can be held up by its own delay. Even then, evaluate the actual distribution of delays and the full workload, because these deliberately constant-latency examples leave out variation. [5]

06

The same byte throughput can require very different IOPS

Suppose two hypothetical workloads each transfer exactly 100 MiB every second. A mebibyte, written MiB, contains 1,048,576 bytes, so both transfer 104,857,600 bytes per second. If every operation transfers 4 KiB, divide that total by 4,096 to obtain 25,600 operations per second. If every operation instead transfers 64 KiB, divide by 65,536 to obtain only 1,600 operations per second. The byte throughput is identical, but the operation counts differ by a factor of sixteen. The operation rate changes because each operation carries a different number of bytes.

The reverse comparison also needs care. Keeping IOPS fixed while increasing the bytes per request increases calculated byte throughput only if that operation rate can actually be maintained. Larger requests may change the workload’s timing and resource demands, so a result for small requests cannot simply be multiplied into a promised large-transfer result. DiskSpd’s separate byte and operation statistics help keep the measurements apart. Record the block size and the unit convention beside any reported rate. Decimal MB/s and binary MiB/s describe different numbers for the same byte total; silently switching units can create a difference that is only a conversion. [3][4]

Equal byte rates do not require equal operation rates. Hypothetical fixed 100 MiB/s at the same counting boundary. This is unit arithmetic, not a device capability claim.
Hypothetical fixed 100 MiB/s at the same counting boundary. This is unit arithmetic, not a device capability claim. · Ameer Journal — original explanatory diagramOriginal editorial illustration; no third-party artwork reused.
07

A queue setting may apply to each thread and each target

Microsoft’s DiskSpd documentation gives a specific meaning to its outstanding-request option: the usual -o setting applies per thread and per target. Its -t option sets threads for each target. Imagine a configuration with one target, two threads per target, and eight outstanding requests per thread. Its configured request allowance is 1 × 2 × 8 = 16, not eight. With two targets and the same per-target settings, it becomes 32. These are configured allowances rather than measured queue occupancy. A target can be a file, drive, or partition, so two targets need not mean two physical SSDs. [3]

The documentation also describes fixed-thread and other outstanding-operation controls, so one shortcut does not cover every configuration. Even the number of completions processed together is controlled separately from the number of outstanding requests. This is why a result described only as “queue depth eight” can be ambiguous. Keep the actual tool version and full configuration with the result, including thread and target counts. DiskSpd’s XML output records system and test configuration for this purpose. That record makes it possible to understand what load was offered instead of assuming the label names one universal drive setting. [3][4]

08

There is more than one place for a request to wait

An application’s outstanding work is not necessarily sitting in one device queue. Linux’s multi-queue block layer describes software staging queues, hardware dispatch queues, and requests held until hardware resources become available. It can also combine adjacent requests before dispatch. Those documented mechanisms show why operation counts can depend on where you look. If two higher-level pieces of work are combined lower down, a counter at one boundary need not agree one-for-one with a counter at another. Naming the layer gives that disagreement a concrete explanation to investigate. [2]

Queues and data buffers are different things as well. SNIA distinguishes command/response queues from buffers holding data in flight. A configured count of requests does not by itself tell you the number of bytes in a buffer or the amount of drive cache. Likewise, a queue does not guarantee that completions return in the same order as submissions: Linux explicitly says higher layers must handle completion ordering. For the reader comparing results, the consequence is practical. Preserve whether the reported quantity concerns application requests, block-layer requests, or device commands, rather than treating all three as interchangeable descriptions of a physical line of waiting reads. [2][5]

A queue count needs its place in the storage path. Conceptual Linux block-layer example. Requests may merge or wait; submission order does not guarantee completion order.
Conceptual Linux block-layer example. Requests may merge or wait; submission order does not guarantee completion order. · Ameer Journal — original explanatory diagramOriginal editorial illustration; no third-party artwork reused.
09

An average wait can hide a small group of long waits

Consider two invented sets of 1,000 completed requests. In the first set, every request takes 0.2 milliseconds. In the second, 990 requests take 0.1 milliseconds and ten take 10.1 milliseconds. Both sets have an average of 0.2 milliseconds: the second total is 990 × 0.1 + 10 × 10.1 = 200 milliseconds, divided by 1,000. Yet one percent of the second set waits much longer than any request in the first. These are constructed latency lists, separate from the earlier repeating-workload models. The sum of their response times is not necessarily the elapsed run time because operations can overlap.

Microsoft’s DiskSpd results can report average and percentile latency statistics rather than reducing the run to one mean. Its documentation also warns that high percentiles need enough completed operations to be meaningful: an extreme tail can otherwise depend on only a handful of observations. Keep the sample count, operation type, and duration beside the latency result. A read/write mixture should not be described as though its combined average proved identical behavior for both kinds of request. This does not create a universal acceptable latency target. It gives you enough context to judge whether the measurement answers the application’s actual concern. [4]

10

Match the result to the task that must finish

Before using a storage result to explain a real wait, identify the task: one dependent operation, many independent reads, or a large transfer. Then keep the result’s request size, read/write mix, thread and target counts, outstanding-request controls, tool version, and measurement boundary together. Record both the completed work and the delay statistics. Microsoft’s configuration and results documentation shows why these details belong with the numbers. A result with that context can be matched to the task; a headline number with the context removed leaves the relevant comparison unresolved. [3][4]

For the next comparison, ask which assumption changed. Did the system expose more independent requests, shorten their measured completion times, transfer more bytes per operation, or simply use a different unit? If throughput stops improving while waits increase, a larger outstanding count is not automatically progress toward the task’s goal. If a one-request workload remains serial, a result produced with many overlapping requests may answer a different question. NVMe’s parallel design makes concurrency relevant; it does not remove the need to describe it. The useful conclusion is the one supported by the actual workload and its measured completion behavior. [1][3][5]

Read a performance result with its workload attached. A proposed evidence-reading sequence. No storage benchmark was executed for this article.
A proposed evidence-reading sequence. No storage benchmark was executed for this article. · Ameer Journal — original explanatory diagramOriginal editorial illustration; no third-party artwork reused.
Follow the evidence

Sources & further reading

Explore the references behind this guide. External pages may change after our review.

  1. What is NVMe? | SNIA | Experts on Data
    SNIA · Accessed 6 Nov 2026
  2. Multi-Queue Block IO Queueing Mechanism (blk-mq) — The Linux Kernel documentation
    The Linux Kernel · Accessed 6 Nov 2026
  3. Threading and concurrency · microsoft/diskspd Wiki · GitHub
    Microsoft · Accessed 6 Nov 2026
  4. Analyzing test results · microsoft/diskspd Wiki · GitHub
    Microsoft · Accessed 6 Nov 2026
  5. Buffers, Queues and Caches Explained | SNIA | Experts on Data
    SNIA · Accessed 6 Nov 2026

Article updated 6 Nov 2026.

Found something we should revisit?

Good explanations get better with thoughtful feedback.

Suggest a correction