CephLXIX · Disk PerformanceDisk Performance
Queue depth and where latency accumulates
What you'll learn
- Identify the queues in the I/O path
- Explain the latency-throughput trade in queue depth
- Choose a queue depth for a workload
- Recognise a queue depth problem
Prerequisites
None — start here.
Verified against Ceph Tentacle 20.2.x · Ceph Squid 19.2.x (supported previous) · cephadm matches the verified Ceph release · podman 4.x · csi-rbd and csi-cephfs current · RBD / CephFS / RGW current (matches Ceph release) · Linux kernel 5.15+ (5.10 minimum) · Ubuntu 24.04 LTS (Ceph host baseline) · Debian 12 (Bookworm) (Ceph host baseline) · Rocky Linux / RHEL / AlmaLinux 9.x (Ceph host baseline) · Proxmox VE 9.x (cross-course integration) · Kubernetes 1.31+ (cross-course integration) · 2026-08-18
Why this matters in production
Queue depth is the setting that most changes a benchmark result and is most often left unstated. Understanding it prevents both bad benchmarks and bad conclusions from good ones.
The queues
flowchart TD
A[Application queue] --> B[Client library / kernel]
B --> C[Network]
C --> D[OSD shard queue]
D --> E[BlueStore]
E --> F[Block layer queue]
F --> G[Device queue]
| Queue | Depth controlled by |
|---|---|
| Application | the application’s concurrency |
| Client | rbd_concurrent_management_ops, driver |
| OSD shard | osd_op_num_shards × threads |
| Block layer | /sys/block/<dev>/queue/nr_requests |
| Device | the device’s own command queue (NCQ/NVMe) |
The trade
low queue depth → low latency per operation, low throughput
high queue depth → high throughput, higher latency per operation
# the same device, different depths
for qd in 1 4 16 64; do
fio --name=qd$qd --filename=/dev/sdX --direct=1 --rw=randread \
--bs=4k --iodepth=$qd --runtime=20 --time_based --readonly \
--output-format=json | python3 -c '
import sys,json; d=json.load(sys.stdin)["jobs"][0]["read"]
print("qd", d["iodepth_level"] if "iodepth_level" in d else "",
int(d["iops"]), round(d["lat_ns"]["mean"]/1e6,2), "ms")'
done
Typical HDD result:
qd1 150 IOPS 6.6 ms
qd4 170 IOPS 23.5 ms
qd16 180 IOPS 88.0 ms
qd64 182 IOPS 351.0 ms
Throughput barely improves past qd4 while latency rises linearly — the device is saturated and the queue is just accumulating.
NVMe behaves differently:
qd1 18k IOPS 0.055 ms
qd16 240k IOPS 0.067 ms
qd64 610k IOPS 0.105 ms
Here depth buys real throughput because the device has internal parallelism to use.
Choosing a depth
| Goal | Depth |
|---|---|
| Measure the latency floor | 1 |
| Measure maximum throughput | high, e.g. 64–256 |
| Model a real workload | match the application’s actual concurrency |
A benchmark at qd=1 and one at qd=256 measure different properties of the same hardware, and quoting one as “the performance” is the most common benchmarking error.
Recognising a queue depth problem
iostat -x 1 5
| Observation | Meaning |
|---|---|
aqu-sz high, %util 100 | saturated; queue is accumulating |
aqu-sz ~1, high latency | not a depth problem — the device is slow |
aqu-sz high, %util low | the queue is elsewhere, not the device |
Quiz
Knowledge check · 4 questions
Q1. A saturated device shows throughput flat and latency rising in direct proportion to queue depth. Which relationship explains this?
Q2. One HDD can honestly be quoted at both 150 IOPS and 182 IOPS, and the more impressive figure is the one with fifty times the latency.
Q3. Reconcile conflicting benchmark results.
Two teams benchmarked the same cluster. One reports 8,000 IOPS at 4 ms; the other reports 45,000 IOPS at 45 ms. Both used fio against the same pool.
Q4. How do you tell from `iostat -x` whether high latency is a queue depth problem or a slow device?
Passing score: 75%. Answers are checked in this browser.
Production discipline
State the queue depth with every latency and IOPS figure; the same device produces wildly different numbers at different depths and both are true. Benchmark at the application’s actual concurrency rather than at the depth that produces the best headline number.
Cross-course references
- Kubernetes: request concurrency changes latency and throughput identically
- Linux: nr_requests and scheduler depth present the same trade at the block layer