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— Read-only. mpstat -P ALL exposes single-core hotspots that an aggregate figure hides. The pressure file answers a different question — whether tasks are stalled — which utilisation cannot.
mpstat -P ALL 1 5
cat /proc/pressure/cpu
uptime
nproc
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— Read-only. numa_miss and numa_foreign climbing steadily means memory is being allocated on the wrong node for the task using it. numastat -c qemu attributes it per QEMU process.
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— Applies live. The value is a taskset-style host CPU list; the documentation gives 0,5,8-11 as an example. Note the Proxmox documentation's warning that CPU affinity is not a security feature — it constrains scheduling, not access.
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
Configured
Host-wide, /sys/kernel/mm/transparent_hugepage/
Per VM, qm set --hugepages
Allocation
Opportunistic, by khugepaged
Reserved from a pool
Guest memory
Still balloonable, still KSM-mergeable
Pinned; ballooning disabled
Cost
Effectively free
Memory 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— Takes effect at the next guest start, not live. The documented values are 2 (2 MiB), 1024 (1 GiB) or any. There is no --hugepages 1: the number is the page size in MiB, not a boolean. This pins the guest's memory and disables ballooning for it.
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?— Read-only. Hugepagesize is the default size on this host. The per-size directories under /sys show nr_hugepages (reserved) and free_hugepages (available) for each supported size independently.
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— Illustrative. HugePages_Total 0 means no explicit pool is reserved. AnonHugePages of 184 GiB means transparent hugepages are backing most of the guest memory, which is the desirable state on a KVM host.
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— Read-only. Committed_AS is what the kernel has promised; MemTotal is what exists. On a hypervisor the more useful figure is the sum of guest maximum memory, since a guest can claim its maximum at any time.
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 arcstatsize
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
Q1. What does high %steal in a VM indicate?
Q2. CPU pinning always improves performance.
Q3. Which is a valid value for the qm --hugepages option?
Q4. A pinned, latency-sensitive VM becomes slower after an HA failover, with an identical configuration. Which explanations are plausible? Select all that apply.
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.