Skip to main content
RunBook Academy

CephLXXI · Client PerformanceClient Performance

Concurrency and where throughput comes from

Advanced⏱ ~17 minfiorbdceph

What you'll learn

  • Relate concurrency, latency, and throughput
  • Identify where concurrency is limited
  • Raise concurrency where it is the constraint
  • Recognise when more concurrency will not help

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

A cluster capable of 200,000 IOPS delivering 8,000 to an application is usually not a cluster problem. Concurrency on the client side is the most common explanation and the least often checked.

The relationship

throughput = concurrency / latency
one client, qd=1, 0.5 ms latency  →   2,000 IOPS
one client, qd=32, 0.5 ms latency →  64,000 IOPS
20 clients, qd=32, 0.5 ms latency → 1,280,000 IOPS (if the cluster can)

The cluster’s capability is a ceiling; the client’s concurrency determines how much of it is reached.

Where concurrency is limited

LimitSymptom
The application issues one I/O at a timelow IOPS, low latency, low utilisation
A single-threaded workloadsame
iodepth in a benchmarkthe benchmark’s own limit
rbd_op_threadsclient-side processing limit
Filesystem or driver queue depththe block layer above RBD
One image, one clienta single connection path
# what the client is achieving
rbd perf image iostat --pool rbd-vms

# what the cluster is doing overall
ceph osd pool stats rbd-vms

A cluster at 5% device utilisation while an application reports poor performance is a concurrency problem essentially every time.

iostat -x 1 5     # on the OSD hosts

Raising concurrency

# client-side threads
rbd config image set rbd-vms/vm-disk-1 rbd_op_threads 8

# more images rather than one large one
rbd create --size 500G rbd-vms/data-{1..4}
# stripe the application across them
# in the application or benchmark
fio --iodepth=64 --numjobs=4 ...

Multiple images is frequently the most effective change: each image has its own client-side state and its own path through the cluster, so four images at qd=32 behave very differently from one image at qd=128.

When more concurrency will not help

# the cluster is already saturated
ceph osd perf | sort -k2 -rn | head -5
iostat -x 1 5   # on OSD hosts: %util near 100

# latency rising with no throughput gain
for qd in 8 16 32 64 128; do
  fio --name=q$qd --ioengine=rbd --pool=rbd-vms --rbdname=bench \
      --rw=randread --bs=4k --iodepth=$qd --runtime=20 --time_based \
      --output-format=json | python3 -c '
import sys,json; d=json.load(sys.stdin)["jobs"][0]["read"]
print(int(d["iops"]), round(d["lat_ns"]["mean"]/1e6,2))'
done

When IOPS stops rising and latency rises proportionally, the saturation point has been passed and more concurrency only adds queueing.

Quiz

Knowledge check · 4 questions

  1. Q1. OSD device utilisation is at 6% while an application reports poor IOPS. What does this indicate?

  2. Q2. Sharding a workload across four images at queue depth 32 can beat a single image at queue depth 128.

  3. Q3. Improve application throughput.

    An application achieves 8,000 IOPS against a cluster benchmarked at 200,000. OSD utilisation is 5%. The application uses a single 4 TB RBD image and is single-threaded per volume.

  4. Q4. How do you recognise that the saturation point has been passed?

Passing score: 75%. Answers are checked in this browser.

Production discipline

Check OSD device utilisation before investigating cluster-side causes of poor application throughput — single-digit utilisation means the constraint is client concurrency and no cluster tuning will help. Shard across several images rather than using one large one; each gives an independent client-side path.

Cross-course references

  • Kubernetes: a single-replica workload cannot use a cluster sized for many
  • Linux: single-threaded I/O against a fast device is the classic underutilisation case