Skip to main content
RunBook Academy

CephLXXI · Client PerformanceClient Performance

Client-side queueing and threading

Advanced⏱ ~17 minrbdcephfio

What you'll learn

  • Identify the client-side queues and threads
  • Recognise a client-side bottleneck
  • Tune the client for concurrency
  • Balance client CPU against throughput

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

At high concurrency the client itself becomes the bottleneck, and the symptom — high client CPU with the cluster idle — is distinctive once recognised.

The client-side path

flowchart TD
  A[Application I/O] --> B[librbd: striping to objects]
  B --> C[librbd cache]
  C --> D[librados objecter]
  D --> E[messenger threads]
  E --> F[TCP connections to OSDs]
StageLimited by
librbd processingrbd_op_threads
Objecterin-flight operation limits
Messengerms_async_op_threads
Connectionsone per OSD contacted

The settings

rbd config global ls | grep -E 'op_threads|concurrent'
ceph config get client ms_async_op_threads
ceph config get client objecter_inflight_ops
ceph config get client objecter_inflight_op_bytes
SettingEffect
rbd_op_threadslibrbd worker threads per image
ms_async_op_threadsmessenger worker threads
objecter_inflight_opsoutstanding operations allowed
objecter_inflight_op_bytesoutstanding bytes allowed
rbd_concurrent_management_opsconcurrent management operations

The objecter limits are throttles: reaching them blocks new operations until outstanding ones complete, which caps concurrency regardless of what the application requests.

Recognising a client-side bottleneck

# on the client
top -H -p $(pidof -s qemu-system-x86_64)
mpstat -P ALL 1 5

# on the cluster
ceph osd perf | sort -k2 -rn | head -3
ObservationConclusion
Client CPU saturated, cluster idleclient-side bottleneck
Client CPU idle, cluster saturatedcluster-side
Both moderate, throughput flatcheck the objecter throttles
One client thread at 100%single-threaded path
# objecter state on a running client
ceph --admin-daemon /var/run/ceph/ceph-client.*.asok perf dump objecter | \
  python3 -c '
import sys,json; d=json.load(sys.stdin)["objecter"]
for k in ("op_active","op_laggy","osdop_read","osdop_write"):
    print(k, d.get(k))'

op_active sitting at the objecter_inflight_ops value means the throttle is binding.

Tuning

ceph config set client objecter_inflight_ops 4096
ceph config set client objecter_inflight_op_bytes 1073741824
ceph config set client ms_async_op_threads 5
rbd config image set rbd-vms/busy-image rbd_op_threads 8

Every increase costs client CPU and memory. The balance point is where client CPU is comfortably used but not saturated:

mpstat -P ALL 1 10 | tail -3

Quiz

Knowledge check · 4 questions

  1. Q1. What do `objecter_inflight_ops` and `objecter_inflight_op_bytes` protect against?

  2. Q2. One client thread at 100% CPU while others are idle indicates the cluster is the bottleneck.

  3. Q3. Diagnose flat throughput at high concurrency.

    An application raised its queue depth from 32 to 256 and saw no throughput improvement. Cluster OSD utilisation is 30%. Client CPU is at 40%.

  4. Q4. What does client CPU saturation with an idle cluster tell you?

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

Production discipline

Run top -H on the client and check OSD utilisation together before tuning anything — client CPU saturated with an idle cluster and the reverse call for opposite fixes. When neither is saturated and throughput is flat, check op_active against objecter_inflight_ops; the throttle is likely binding.

Cross-course references

  • Kubernetes: client-side connection pool limits cap throughput identically
  • Linux: application thread pool sizing bounds what any fast backend can deliver