Skip to main content
RunBook Academy

Proxmox VEXVII · Performance EngineeringCPU and memory

CPU and memory performance engineering

Intermediate⏱ ~26 minmpstatnumactl

What you'll learn

  • Diagnose CPU contention vs memory pressure
  • Understand steal time, ballooning, NUMA effects
  • Apply CPU pinning, hugepages, and overcommit tuning with the correct option values
  • Distinguish transparent hugepages from explicit hugepages and their costs
  • Measure before optimising

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

Not yet marked complete on this device.

Why this matters in production

“The VM is slow” tickets are the most common in production. Most are diagnosed by measuring CPU, memory, storage, or network — and fixing the wrong one wastes hours.

CPU and memory are where the wrong fix is most tempting, because both have obvious-looking levers — pin the vCPUs, add hugepages, raise the memory — that feel like progress and frequently make things worse.

CPU contention

Inside the guest, look for:

  • %steal: time the vCPU wanted to run but the host did not schedule it.
  • %user vs %system: user = application; system = kernel. Heavy system time usually means I/O or memory issues.

On the host, look for:

  • top / htop: per-CPU utilisation. If all CPUs are 100 %, the host is saturated.
  • mpstat: per-CPU breakdown. Useful for detecting single-core hotspots.
  • pidstat: per-process CPU. Useful for finding noisy neighbours.
Read-only / Safeper-CPU breakdown and whether anything is waiting
mpstat -P ALL 1 5

cat /proc/pressure/cpu
uptime
nproc

Memory pressure

Inside the guest:

  • free -h: total and available memory.
  • vmstat 1: si/so (swap in/out) columns; non-zero = pressure.
  • /proc/pressure/memory: kernel-side stall metrics.

On the host:

  • Per-VM RSS via ps or top.
  • Host free memory (free -h).
  • Host swap usage (should be near zero).
flowchart LR
  A[Guest VM] -->|balloon| B[Host memory]
  B --> C[Physical RAM?]
  C -->|no| D[Swap / OOM]

NUMA effects

On multi-socket hosts, a vCPU running on socket 0 accessing memory attached to socket 1 incurs a penalty — typically 1.5 to 2 times the local latency.

Read-only / Safetopology, and whether allocations are landing off-node
numactl --hardware

numastat
numastat -c qemu

For latency-sensitive VMs:

  • Pin vCPUs to specific cores.
  • Enable NUMA awareness in the VM config (qm set VMID --numa 1).
  • Ensure VMs and their memory fit in one NUMA node.

A VM larger than one NUMA node cannot avoid remote access; the best you can do is give it a matching virtual topology with --numa 1 so the guest’s own scheduler knows about the split and can place its processes accordingly.

CPU pinning

Configuration changepin a guest's vCPUs to specific host cores
VMID=100

qm set "$VMID" --affinity 0-3

qm config "$VMID" | grep -E 'affinity|numa|cores'

Pinning reduces context switches and improves cache locality. Trade-off: less flexibility for the scheduler.

Hugepages

Hugepages reduce TLB pressure for large-memory workloads. There are two different mechanisms with the same name, and conflating them is the usual source of error.

Transparent hugepages (THP)Explicit hugepages
ConfiguredHost-wide, /sys/kernel/mm/transparent_hugepage/Per VM, qm set --hugepages
AllocationOpportunistic, by khugepagedReserved from a pool
Guest memoryStill balloonable, still KSM-mergeablePinned; ballooning disabled
CostEffectively freeMemory reserved whether used or not

THP is on by default and is generally a win on a KVM host; xvii-performance-host-tuning covers the defrag setting that decides whether it also causes latency spikes.

Explicit hugepages are a deliberate per-guest choice.

Service impact possiblegive a VM explicit hugepages
VMID=100

# 2 MiB pages
qm set "$VMID" --hugepages 2

# or 1 GiB pages, if the host has them reserved
qm set "$VMID" --hugepages 1024

qm config "$VMID" | grep -E 'hugepages|balloon'
Read-only / Safewhat hugepages exist, at what size, and how many are free?
grep -iE 'huge|AnonHugePages' /proc/meminfo

for D in /sys/kernel/mm/hugepages/*/; do
echo "== $D"
cat "$D/nr_hugepages" "$D/free_hugepages"
done
Read-only / Safea host with no explicit hugepages, but THP in heavy use
# grep -iE 'huge|AnonHugePages' /proc/meminfo
AnonHugePages:  193273528 kB
HugePages_Total:       0
HugePages_Free:        0
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:       2048 kB
Hugetlb:               0 kB

Illustrative output

Overcommit tuning

A host with 256 GB RAM running 12 VMs configured with 32 GB each is committing 384 GB to 256 GB of physical. This works if ballooning is effective and guests are not all saturated simultaneously.

Measure first:

Read-only / Safewhat is committed against what exists
grep -E 'MemTotal|MemAvailable|CommitLimit|Committed_AS' /proc/meminfo

pvesh get /cluster/resources --type vm --output-format json | jq '
[ .[] | select(.status=="running") ] | { guests: length, maxmem_gib: ( map(.maxmem) | add / 1073741824 | floor ) }'

Production considerations

Common mistakes

  • Reading %steal as proof the host is oversubscribed. A cpulimit produces identical symptoms on an idle host; check nr_throttled.
  • Tuning without measuring. On a hypervisor an unmeasured change helps one guest at every other guest’s expense.
  • Pinning everything. Pinning restricts, it does not reserve; without also keeping other work off those cores it makes the guest worse.
  • Using --hugepages 1. The value is a page size in MiB — 2, 1024 or any.
  • Expecting 1 GiB hugepages to be allocated on demand. They need reserving at boot on the kernel command line, and the reservation is unavailable to everything else.
  • Forgetting hugepages disable ballooning and pin the guest’s memory.
  • Confusing THP with explicit hugepages. One is host-wide and free; the other is per-guest and costs reserved memory.
  • Believing free -h on a ZFS node. The ARC is not reclaimable at page-cache speed.
  • Counting ballooning and KSM savings as capacity. Both stop working during a failover.

Key takeaways

  • Measure first: mpstat -P ALL, vmstat, numactl --hardware, and /proc/pressure/cpu for whether anything is actually waiting.
  • High %steal means the vCPU was not scheduled — by a busy host, or by a cpulimit on an idle one. nr_throttled distinguishes them.
  • Memory pressure shows as swap activity; on a ZFS node also read arcstat size against MemAvailable.
  • NUMA locality matters on multi-socket hosts; a guest larger than one node cannot avoid remote access, so give it --numa 1 and let the guest schedule around it.
  • Pinning constrains rather than reserves. Pin only latency-sensitive guests, on nodes where you also control what else runs there.
  • --hugepages takes 2, 1024 or any — a page size in MiB. 1 GiB pages must be reserved at boot and are unavailable to anything else.
  • Explicit hugepages pin guest memory and disable ballooning; THP does neither.
  • Overcommit that works in steady state fails during a failover, because ballooning and KSM both stop helping at the same moment.

Knowledge check

Knowledge check · 5 questions

  1. Q1. What does high %steal in a VM indicate?

  2. Q2. CPU pinning always improves performance.

  3. Q3. Which is a valid value for the qm --hugepages option?

  4. Q4. A pinned, latency-sensitive VM becomes slower after an HA failover, with an identical configuration. Which explanations are plausible? Select all that apply.

  5. Q5. Which /proc file reports whether tasks are stalled waiting for CPU, as opposed to how busy the CPUs are?

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