Proxmox VEXVII · Performance EngineeringTroubleshooting
Diagnosing "the VM is slow"
What you'll learn
- Follow a systematic approach to "VM is slow" tickets
- Identify the bottleneck layer guest, host, storage, network, app
- Use the diagnostic tree to ask the right questions
- Distinguish application slowness from infrastructure slowness
- Use pressure stall information to tell saturation apart from utilisation
- Recognise when several affected guests point at a shared resource rather than a guest fault
Prerequisites
Verified against Proxmox VE 9.2.4 · Proxmox Backup Server 4.2.5 · Ceph Squid / Tentacle · Debian 13 (Trixie) · Linux kernel 7.0 (PVE 9.2 default) · 2026-08-12
Why this matters in production
“VM is slow” is the most common production ticket. Operators must triage in minutes, not hours.
The diagnostic flow
flowchart LR
A[VM is slow] --> B[Where is the latency?]
B --> C[Guest CPU/memory]
B --> D[Storage I/O]
B --> E[Network]
B --> F[Application]
B --> G[Database]
B --> H[Other VMs noisy neighbour]
Step 1: Measure inside the guest
top && free -h && iostat -xz 1 3
- High
%steal→ host CPU oversubscribed. - High memory pressure → memory overcommit.
- High disk await → storage bottleneck.
Step 2: Check the host
uptime && top -bn1 | head -20
If all CPUs are saturated, the host is the bottleneck. Identify the noisy neighbour
with pidstat or top.
Step 3: Check storage
iostat -xz 1 3 && zpool iostat 1 2 2>/dev/null || ceph osd perf 2>/dev/nullLook for:
- High await.
- OSD nearfull.
- ZFS fragmentation.
- Ceph recovery in progress.
Step 4: Check network
for nic in $(ls /sys/class/net | grep -v lo); do echo "=== $nic ==="; ethtool -S $nic 2>/dev/null | grep -E 'errors|dropped' | head -3; doneStep 5: Check the application
If the infrastructure looks fine, the problem is the application. Common causes:
- Database query gone wrong (missing index, full table scan).
- Bug introduced by recent deployment.
- External dependency slow (API upstream).
Check application logs.
Utilisation is not saturation
The five steps above all read utilisation, and utilisation cannot answer the question they are being asked. A CPU at 30 % is not a CPU with 70 % of its performance in reserve — it is a CPU that spent 30 % of wall-clock time executing something, at whatever speed it happened to be running, with no statement about how long anything waited to get there.
Pressure Stall Information is the metric that says whether anything is waiting, and PVE 9 exposes it per guest as well as per host.
cat /proc/pressure/cpu /proc/pressure/io /proc/pressure/memory
# per guest, from the cluster API
pvesh get /cluster/resources --type vm --output-format json | jq -r '
.[] | select(.status=="running")
| [.vmid, .name, .pressurecpusome, .pressureiosome] | @tsv'Read it alongside the guest-side numbers rather than instead of them.
High %steal inside the guest plus high host CPU pressure is genuine
contention. High %steal with the host idle and no pressure is a
cpulimit throttling the guest — a different fault with an identical
symptom, covered in xvii-performance-noisy-neighbours.
The diagnostic tree
flowchart TB
A[VM is slow] --> B[VM CPU high?]
B -->|yes| C[App or guest bottleneck]
B -->|no| D[Storage latency high?]
D -->|yes| E[Storage bottleneck]
D -->|no| F[Network errors?]
F -->|yes| G[Network issue]
F -->|no| H[Application/database issue]
A break/fix exercise
Database VM slow after Ceph cluster expansion
Symptoms
- Database VM 100 reports slow queries (5-10s instead of 100ms)
- Other VMs on the same cluster are unaffected
- Ceph health is HEALTH_OK; no OSD issues
Available evidence
- ceph osd df tree shows utilisation across all OSDs increased after expansion
- Database VM uses replication=3; Ceph pool nearfull at 75%
- ceph -s shows active backfill; recovery is consuming bandwidth and IOPS
- ceph config get osd osd_op_queue returns mclock_scheduler, and osd_mclock_profile returns balanced
Show diagnosis & remediation
Root cause
Recent cluster expansion triggered a large backfill operation. Backfill is consuming significant IOPS and network bandwidth, slowing the database VM. The other VMs (web servers) have caching that masks the IOPS degradation.
Safe remediation
Bias the scheduler toward client I/O: ceph config set osd osd_mclock_profile high_client_ops. Do NOT set osd_recovery_sleep or osd_recovery_max_active — mClock has been the default scheduler since Reef and overrides osd_max_backfills and the osd_recovery_max_active family while forcing the osd_recovery_sleep options to zero, so those settings are stored and reported back but have no effect. Once backfill completes (verify via ceph -s), return the profile to balanced.
Verification
ceph config get osd osd_mclock_profile returns high_client_ops. Database VM query latency returns to normal. ceph -s shows recovery still progressing at a lower rate. After backfill completes, the profile is set back to balanced and confirmed.
Prevention
Schedule cluster expansions during maintenance windows. Note that slowing recovery is a durability trade rather than a free win: until backfill completes some placement groups are below the pool's replica count, so a second failure in that window is more expensive. Decide deliberately which side of that trade the situation is on.
Production considerations
Common mistakes
- Optimising storage when the issue is the application. Measure each layer before changing any of it.
- Restarting things instead of measuring. A restart destroys the evidence and frequently fixes the symptom for long enough that the cause is never found.
- Blaming the network without checking.
ethtool -Sfor errors and drops takes seconds. - Concluding the host is fine because utilisation is low. C-state
exit latency and THP compaction stalls consume no measurable resource.
Read
/proc/pressure/*. - Investigating one guest when several are affected. The overlap is the diagnosis.
- Throttling the victim rather than the culprit. High throughput with low pressure identifies the culprit.
- Throttling Ceph recovery with
osd_recovery_sleep. mClock is the default scheduler on Squid and Tentacle and forces it to zero. Setosd_mclock_profileinstead. - Assuming
%stealmeans the host is busy. It also appears when acpulimitthrottles the guest on an idle host.
Key takeaways
- Measure from the guest outward, but establish first how many guests are affected and what they share.
- Utilisation is not saturation.
/proc/pressure/cpu,/proc/pressure/ioand the per-guestpressurecpusomeandpressureiosomefields say whether anything is actually waiting. - Bad latency with fine throughput, on a host showing no strain, points at host-level causes: C-states, THP compaction, frequency ramp.
%stealon an idle host means acpulimit, not contention.- The culprit has high throughput and low pressure; the victims have the reverse.
- A host that is busy with no busy cgroup means the consumer is a host process — backup, scrub, resilver or Ceph recovery.
- On Squid and Tentacle, bias recovery with
osd_mclock_profile; the legacy sleep and max-active settings are overridden and do nothing. - Slowing recovery lengthens the degraded window, so it is a durability trade rather than a free win.
- Build a knowledge base of past slow-VM tickets; any ticket taking more than thirty minutes should produce a runbook entry.
Knowledge check
Knowledge check · 4 questions
Q1. Where do you start diagnosing a slow VM?
Q2. Ceph recovery can cause performance degradation even when HEALTH_OK.
Q3. A guest reports 40% steal time. The host it runs on shows 15% CPU utilisation, zero CPU pressure, and no other guest is complaining. What is the most likely cause?
Q4. Which command shows Ceph OSD-level latency?
Passing score: 75%. Answers are checked in this browser.