Skip to main content
RunBook Academy

← All labs in Proxmox VE

Lab · intermediate · ~75 min

Right-size a VM using perf, ballooning, and CPU pinning

A · Physical hardwareB · Nested virtualisation

Objectives

  • Measure current memory and CPU usage inside a guest
  • Adjust ballooning and shares for fair allocation
  • Pin a VM to specific cores for cache locality
  • Document before/after performance numbers

Prerequisites

  • A PVE cluster with at least one busy VM
  • fio and sysbench installed inside the guest
  • pve-top or similar host-side tool for visibility

Right-sizing and CPU pinning lab

This lab takes an over-provisioned or under-provisioned VM and tunes it to match its actual workload — without buying new hardware.

Steps

1. Capture baseline

# Inside the VM
fio --name=baseline --ioengine=libaio --iodepth=32 \
  --rw=randread --bs=4k --direct=1 --size=2G --filename=/tmp/baseline

sysbench cpu --cpu-max-prime=20000 run
sysbench memory --memory-block-size=1M --memory-total-size=10G run

Note the IOPS, latency, events/sec, and bandwidth numbers.

2. Inspect host-side allocation

# On the host
pvesh get /cluster/resources --type vm
# Look at balloon, cpus, mem

3. Adjust the memory config

If the VM is ballooning heavily:

qm set <vmid> --balloon 1024   # 1 GB target balloon size
qm set <vmid> --shares 1000     # default; lower = less priority

If the VM has unused RAM, shrink it:

qm set <vmid> --memory 4096 --balloon 2048

4. Enable CPU pinning

Identify the NUMA node and free cores:

lscpu | grep NUMA
# NUMA node0 CPU(s): 0-15
# NUMA node1 CPU(s): 16-31

# Reserve cores 0-1 for system tasks; assign cores 2-7 to the VM
qm set <vmid> --cpuunits 1024
qm set <vmid> --affinity 2-7
qm set <vmid> --numa 1

For NUMA-aware pinning:

qm set <vmid> --numa0 cpu=0-3,memory=2048,hostnode=0 \
             --numa1 cpu=4-7,memory=2048,hostnode=1

Restart the VM to apply pinning.

5. Re-measure

# Inside the VM
fio --name=after --ioengine=libaio --iodepth=32 \
  --rw=randread --bs=4k --direct=1 --size=2G --filename=/tmp/after

sysbench cpu --cpu-max-prime=20000 run

Compare:

# Approximate comparison
echo "IOPS before: $(grep -oP 'IOPS=\K[0-9.]+' /tmp/baseline.log)"
echo "IOPS after:  $(grep -oP 'IOPS=\K[0-9.]+' /tmp/after.log)"

6. Verify isolation

# On the host, watch the pinned VM
ps -eLo pid,psr,comm | grep kvm
# Expected: vCPU threads stay on the assigned cores

Run a CPU-bound workload in another VM and confirm no contention.

7. Document the change

# Snapshot the config before/after
qm config <vmid> > /tmp/vm-final-config.txt

Verification

  • The VM’s measured performance matches or exceeds baseline
  • Pinning is visible in ps -eLo
  • NUMA allocation matches the host’s topology
  • The host scheduler is not oversubscribed

Cleanup

# Revert to defaults if needed
qm set <vmid> --balloon 0
qm set <vmid> --affinity 0-0  # back to "any CPU"
qm set <vmid> --numa 0

Notes

  • CPU pinning is best for latency-sensitive workloads (databases, real-time).
  • For throughput workloads, sharing CPUs is usually fine and gives better overall utilisation.
  • Test with your actual workload, not synthetic benchmarks.

Deliverables

  • · Baseline performance numbers (fio + sysbench)
  • · A right-sized VM config with documented changes
  • · After numbers showing equal-or-better performance

Verification status

Executed end to end
not yet run on hardware

The commands and configuration here have been reviewed against the verified software versions, but nobody has run this lab start to finish on a system meeting its prerequisites. Treat the Expected Outcome as the intended result rather than an observed one, and keep the Cleanup section to hand.