Skip to main content
RunBook Academy

CephLXIX · Disk PerformanceDisk Performance

Queue depth and where latency accumulates

Advanced⏱ ~17 minfioiostatceph

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

Not yet marked complete on this device.

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]
QueueDepth controlled by
Applicationthe application’s concurrency
Clientrbd_concurrent_management_ops, driver
OSD shardosd_op_num_shards × threads
Block layer/sys/block/<dev>/queue/nr_requests
Devicethe 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

GoalDepth
Measure the latency floor1
Measure maximum throughputhigh, e.g. 64–256
Model a real workloadmatch 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
ObservationMeaning
aqu-sz high, %util 100saturated; queue is accumulating
aqu-sz ~1, high latencynot a depth problem — the device is slow
aqu-sz high, %util lowthe queue is elsewhere, not the device

Quiz

Knowledge check · 4 questions

  1. Q1. A saturated device shows throughput flat and latency rising in direct proportion to queue depth. Which relationship explains this?

  2. 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.

  3. 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.

  4. 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