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.