Pinchas vCPUs porque quieres “determinismo”. Luego la VM de base de datos empieza a dar vuelcos cada pocos minutos, la latencia p95 se duplica,
y alguien inevitablemente dice: “Pero la fijamos, así que no puede ser la CPU.”
El pinning de CPU en Proxmox puede ser una herramienta de rendimiento. También puede ser un autoataque de denegación de servicio al propio planificador.
La clave es saber qué tipo de “pinning” hiciste realmente, qué le quitaste al resto del sistema y qué latencia acabas de empeorar.
El mito: fijar CPUs equivale a menor latencia
El pinning se vende como “evita que la VM salte entre núcleos”. Eso no es incorrecto. Tampoco es el punto.
En Linux moderno con KVM, el planificador ya es bastante bueno manteniendo hilos calientes en los mismos núcleos.
Donde es aún mejor es en pedir prestada CPU ociosa de cualquier lado cuando llega un pico.
La mayoría fija CPUs porque tienen un problema que no ven: vecinos ruidosos, contención en el host, tormentas de interrupciones,
penalizaciones por faltas de NUMA o sobremultiplicación. Fijar da sensación de control. Y a veces lo es.
Pero si fijas sin entender hacia dónde va el tiempo, conviertes un sistema flexible y adaptativo en carga en un conjunto
de jaulas pequeñas. La latencia empeora por dos razones:
- Eliminas las vías de escape del planificador. Cuando una CPU está ocupada (o preemptada por una interrupción), la VM no puede ejecutarse en otro lado.
- Concentras las colisiones. Los hilos vCPU de la VM, la emulación de QEMU, vhost-net y las tareas de mantenimiento del host pueden pelear por los mismos núcleos.
El pinning no es “modo rendimiento”. Es un contrato. Le prometes esas CPUs a la VM. A cambio, debes mantener esas CPUs limpias:
pocas interrupciones, frecuencia consistente, localidad de memoria predecible y suficiente holgura. La mayoría de entornos firma el contrato y luego
se olvida de pagarlo.
Qué es realmente el pinning de CPU (y qué cambia Proxmox)
Tres cosas distintas que la gente llama “pinning”
En el mundo Proxmox, “pinning” se usa para al menos tres mecanismos distintos. Confundirlos es donde nace la tradición oral.
- Afinidad de hilos QEMU/KVM (estilo taskset). Vinculas los hilos vCPU de la VM a CPUs del host. Este es el clásico “pinning de vCPU”.
- cpuset cgroups (particionamiento fuerte). Restringes el árbol de procesos de la VM a un conjunto de CPU. Más fuerte que la afinidad; la estás cercando.
-
Aislamiento de CPU para el host (parámetros de arranque del kernel). Reservas CPUs para cargas específicas relegando el trabajo general del host
(callbacks RCU, kworkers, muchas interrupciones). Esta es la parte que la gente suele omitir.
Lo que Proxmox expone vs lo que Linux realmente programa
Proxmox te da perillas como unidades de CPU, límite de CPU, NUMA y a veces “afinidad” vía args o hooks. Bajo el capó,
tu VM es un proceso QEMU con múltiples hilos: uno por vCPU, más hilos de E/S, más emulación, más hilos vhost según el modelo de dispositivo.
Linux programa hilos, no “VMs”.
Cuando fijas “la VM”, en realidad estás fijando algún subconjunto de esos hilos.
Si fijas solo hilos vCPU y te olvidas del hilo de E/S, aún puedes bloquearte en el hilo no fijado que corre en un núcleo concurrido.
Si fijas todo en el mismo conjunto pequeño, puedes crear una olla a presión de latencia.
El pinning cambia la equidad, no la física
El pinning no hace las CPUs más rápidas. Cambia quién puede correr dónde.
Eso importa porque la tarea del planificador de Linux es distribuir trabajo ejecutable entre CPUs disponibles manteniendo equidad
y localidad de caché. Si lo restringes, debes garantizar que tu conjunto restringido tenga:
- Suficientes ciclos de CPU bajo carga máxima
- Frecuencia estable (sin reducciones agresivas durante un pico)
- Comportamiento de interrupciones aceptable
- Localidad NUMA con la que puedas convivir
La opción que empeora la latencia: restringir al planificador sin aislar el host
Aquí está el patrón de configuración que silenciosamente arruina la latencia: fijar vCPUs (o usar restricciones cpuset) en un host Proxmox
que aún programa mantenimiento del host e interrupciones en esos mismos núcleos.
Quisiste “núcleos dedicados”. Obt uviste “núcleos compartidos con menos opciones”. La VM no puede escapar de un núcleo ocupado, pero el núcleo está ocupado
porque el host todavía realiza tareas ahí: manejo de interrupciones, kworkers del kernel, mantenimiento de ZFS, softirqs de red
y cualquier otra VM que no fue tan “especial” como la fijada.
El resultado es la clásica latencia cola larga. La mediana parece bien. p95/p99 se vuelve feo. Y la fealdad a menudo se correlaciona con:
- Brot es de red (picos en tiempo de softirq)
- Brot es de almacenamiento (kworker, sincronización txg de ZFS, completado de IO)
- Actividad periódica del kernel (RCU, ticks de temporizador en sistemas no sin-tick)
- Transiciones de escalado de frecuencia (cambios de comportamiento de boost por restricciones térmicas/potencia)
Por qué este patrón es peor que no hacer nada
Sin pinning, los hilos vCPU de la VM pueden migrar fuera de un núcleo temporalmente malo. El planificador puede repartir trabajo.
Cuando fijas, conviertes la interferencia transitoria del host en un bloqueo duro: la vCPU es ejecutable, pero no puede programarse en una CPU no bloqueada.
Eso no es “determinismo”. Eso es “la línea más corta en la tienda, pero es la única que puedes usar”.
Broma #1: El pinning de CPU es como asignar a cada persona de la oficina un ascensor. Muy ordenado hasta que alguien trae un carrito.
Qué hacer en su lugar (la mayoría de las veces)
Si tu objetivo es menor latencia, no empieces con pinning. Comienza con:
- Capacidad y holgura: deja de sobremultiplicar CPUs para VMs sensibles a latencia
- Corrección NUMA: mantiene vCPUs y memoria locales, o acepta la penalización conscientemente
- Higiene de interrupciones: asegúrate de que los “núcleos dedicados” de la VM no reciban las peores interrupciones del host
- Política de frecuencia de CPU: elige un governor que coincida con tu carga (y verifica)
El pinning tiene sentido cuando es parte de un paquete: aislamiento de CPU + afinidad de IRQ + alineación NUMA + dimensionamiento sensato de vCPU.
Pinning solo es como comprar un neumático de carrera y ponérselo a un carrito de compras.
Hechos interesantes y breve historia (por qué sigue ocurriendo)
- Hecho 1: La afinidad de CPU existe en Linux desde hace décadas, pero se volvió popular con sistemas SMP y luego explotó con la virtualización.
- Hecho 2: KVM no es un scheduler de hypervisor separado; es un módulo del kernel. Tu “planificación del hypervisor” es en gran parte el planificador de Linux.
- Hecho 3: NUMA ha sido una mina de rendimiento desde que los servidores multi-socket se volvieron comunes; el acceso remoto a memoria puede parecer picos de latencia aleatorios.
- Hecho 4: Los kernels “sin tick” (NO_HZ) redujeron interrupciones periódicas de temporizador, lo que importa cuando persigues micro-bloqueos en CPUs aisladas.
- Hecho 5: irqbalance se creó para distribuir interrupciones entre CPUs para throughput, no para proteger tus núcleos de baja latencia de IRQ ruidosas.
- Hecho 6: En virtualización, la vCPU es un hilo del host. Si es preemptado por un softirq largo, tu guest lo experimenta como “steal” de CPU o simplemente “lento”.
- Hecho 7: El auge de NVMe redujo la latencia de almacenamiento lo suficiente como para que la planificación de CPU y las interrupciones se convirtieran en el nuevo cuello de botella en muchas pilas.
- Hecho 8: SMT/Hyper-Threading complica el pinning porque dos “CPUs” comparten recursos de ejecución; fijar a hermanos puede aumentar la jitter bajo contención.
- Hecho 9: cgroups evolucionaron de CPU shares a controladores más precisos; cpuset es potente y fácil de usar incorrectamente como un mazo en una relojería.
Cómo falla el pinning: los principales modos de fallo de latencia
1) Fijaste vCPUs en CPUs que son puntos calientes de interrupciones
Una VM sensible a latencia fijada a un núcleo que recibe interrupciones del adaptador de red es una forma especial de daño autoinfligido.
Bajo carga, el procesamiento de softirq puede dominar. Tu hilo vCPU está ejecutable, pero la CPU está ocupada procesando red para el host.
El guest ve pausas aleatorias y jitter.
2) Fijaste a través de nodos NUMA sin controlar la localidad de la memoria
Puedes fijar vCPUs a CPUs en dos sockets mientras la memoria del guest acaba asignada mayoritariamente en un nodo. Ahora la mitad de tus vCPUs
hacen accesos remotos a memoria. La memoria remota no siempre es desastrosa, pero rara vez es estable. Puedes obtener picos de cola cuando se satura el ancho de banda remoto.
3) Fijaste, y aún así sobremultiplicaste
El pinning no arregla la sobremultiplicación; la vuelve más rígida. Si fijaste varias VMs ocupadas en conjuntos de CPU solapados,
creaste una contención de la que no puedes programar alrededor. Así obtienes “todo está bien hasta las 10:02, luego todo arde.”
4) Olvidaste los “otros hilos” (hilo de E/S, hilos vhost, emulador)
Una VM que hace almacenamiento o red intensa puede estar limitada por un hilo de E/S de QEMU o por hilos vhost.
Si fijas vCPUs pero no la ruta de E/S, puedes acabar con vCPUs esperando a un hilo no fijado y sobrecargado.
Eso no es un problema de CPU en el guest. Es un problema de arquitectura en el host.
5) Colisiones entre hermanos SMT
Fijar una VM a “dos núcleos” que en realidad son dos hilos en el mismo núcleo físico puede reducir el rendimiento y aumentar la jitter.
Para trabajo sensible a latencia, normalmente quieres núcleos completos, no hermanos, a menos que tengas una buena razón y evidencia medida.
6) Escalado de frecuencia y límites de potencia
El pinning reduce la movilidad del planificador, lo que puede interactuar mal con el comportamiento de boost.
Si tus CPUs fijadas son las que primero reducen frecuencia por restricciones térmicas o de potencia,
verás ralentizaciones que parecen “picos de latencia misteriosos”. No son aleatorios; son política.
Cita (idea parafraseada), atribuida a: Werner Vogels repite a menudo un principio de fiabilidad: “Todo falla; diseña para que sea seguro y recuperable cuando falle.”
El pinning también es una decisión de fiabilidad: hazla recuperable, no frágil.
Guion de diagnóstico rápido
Cuando la latencia aumenta en un host Proxmox con VMs “fijadas”, quieres respuestas en minutos, no después de una semana de arte interpretativo de rendimiento.
Aquí está el orden que suele encontrar al culpable más rápido.
Primero: demuestra que es planificación/ interrupciones de CPU, no almacenamiento
- Revisa saturación de CPU del host y síntomas tipo steal: busca acumulado de ejecutables y utilización por CPU.
- Revisa tiempo de softirq/irq: si softirq es alto en las CPUs fijadas, ese es el villano.
- Revisa latencia de almacenamiento: si los discos están bien y la CPU no, deja de culpar a ZFS por costumbre.
Segundo: verifica que el pinning sea real y completo
- ¿Qué hilos están fijados (solo vCPU? ¿todos los hilos de QEMU?)
- ¿Las CPUs fijadas se comparten con otras VMs o trabajo del host?
- ¿Estás fijando a hermanos SMT por accidente?
Tercero: comprueba la localidad NUMA
- ¿Las vCPUs están repartidas entre nodos?
- ¿La memoria está asignada en los mismos nodos?
- ¿La VM es lo bastante grande como para que las penalizaciones de memoria remota sean inevitables?
Cuarto: confirma comportamiento de potencia/frecuencia
- Gobernador y frecuencias actuales bajo carga
- Eventos de límites de potencia/estrangulamiento térmico
Quinto: solo entonces ajusta el pinning
El pinning es el último paso porque es fácil de cambiar y difícil de razonar después del hecho.
Diagnostica primero; luego aprieta las restricciones en movimientos pequeños y reversibles.
Tareas prácticas: comandos, salidas y decisiones (12+)
Estos son los chequeos que hago en un host Proxmox cuando alguien me dice, “Fijamos CPUs y la latencia empeoró.”
Cada tarea incluye: un comando, qué significa la salida y la decisión que tomas.
Task 1: Identificar el proceso QEMU y sus hilos de la VM
cr0x@server:~$ pgrep -a qemu-system
21433 /usr/bin/kvm -id 101 -name vm-db01 -m 32768 -smp 8,sockets=1,cores=8,threads=1 ...
Significado: Tienes el PID de la VM. El pinning afecta a este proceso y sus hilos.
Decisión: Usa el PID 21433 para las comprobaciones posteriores de afinidad y planificación.
Task 2: Ver uso de CPU por hilo (encuentra hilos vCPU, iothread, vhost)
cr0x@server:~$ top -H -p 21433 -b -n 1 | head -n 20
top - 10:12:41 up 32 days, 2:10, 2 users, load average: 6.12, 5.88, 5.77
Threads: 36 total, 6 running, 30 sleeping, 0 stopped, 0 zombie
%Cpu(s): 62.3 us, 7.2 sy, 0.0 ni, 28.9 id, 1.1 wa, 0.0 hi, 0.5 si, 0.0 st
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
21433 root 20 0 12.3g 1.1g 19772 S 10.0 3.6 2:41.33 kvm
21445 root 20 0 12.3g 1.1g 19772 R 98.7 3.6 22:18.91 CPU 0/KVM
21446 root 20 0 12.3g 1.1g 19772 R 95.4 3.6 21:55.07 CPU 1/KVM
21447 root 20 0 12.3g 1.1g 19772 S 12.3 3.6 1:10.10 IOThread
Significado: Se ven hilos vCPU (CPU 0/KVM, CPU 1/KVM, etc.) y un IOThread está presente. IOThread también está trabajando.
Decisión: Si fijas solo vCPUs pero IOThread está ocupado, debes considerar fijar IOThread (o darle una CPU limpia) también.
Task 3: Comprobar afinidad de CPU actual del proceso QEMU
cr0x@server:~$ taskset -cp 21433
pid 21433's current affinity list: 2-5
Significado: El proceso QEMU está restringido a CPUs 2-5. Eso son solo cuatro CPUs de espacio de planificación.
Decisión: Confirma que esto coincide con el número de vCPU previsto y que las CPUs 2-5 no están cargadas de interrupciones ni compartidas con otro trabajo crítico.
Task 4: Comprobar afinidad por hilo (aquí es donde a menudo falla el “lo fijamos”)
cr0x@server:~$ for t in $(ls /proc/21433/task); do echo -n "$t "; taskset -cp $t | sed 's/^.*: //'; done | head
21433 2-5
21445 2-5
21446 2-5
21447 0-31
21448 0-31
Significado: Los hilos vCPU están fijados a 2-5, pero IOThread (21447) y otro hilo auxiliar no están fijados.
Decisión: O fijas intencionalmente todos los hilos relevantes de QEMU, o aceptas que los hilos no fijados puedan convertirse en cuello de botella e introducir jitter.
Task 5: Encontrar qué CPUs son hermanos SMT (evitar fijar a medio núcleo por accidente)
cr0x@server:~$ lscpu -e=CPU,CORE,SOCKET,NODE | head
CPU CORE SOCKET NODE
0 0 0 0
1 0 0 0
2 1 0 0
3 1 0 0
4 2 0 0
5 2 0 0
Significado: Las CPUs 2 y 3 comparten CORE 1, las CPUs 4 y 5 comparten CORE 2. Si fijas 2-5, estás usando dos núcleos físicos (con SMT).
Decisión: Para VMs sensibles a latencia, prefiere fijar a núcleos completos (por ejemplo, 2,4,6,8…) en lugar de pares de hermanos, salvo que lo hayas medido y aprobado.
Task 6: Comprobar interrupciones por CPU (capturar puntos calientes de IRQ)
cr0x@server:~$ egrep -i 'CPU|eth0|nvme|mlx|ixgbe|virtio' /proc/interrupts | head -n 15
CPU0 CPU1 CPU2 CPU3 CPU4 CPU5
45: 0 0 9123456 0 0 0 IR-PCI-MSI 524288-edge eth0-TxRx-0
46: 0 0 0 8876543 0 0 IR-PCI-MSI 524289-edge eth0-TxRx-1
97: 11234 11876 10987 11022 11301 11450 IR-PCI-MSI 0000:01:00.0 nvme0q0
Significado: CPU2 y CPU3 reciben la mayor parte de las interrupciones de la NIC. Si tu VM está fijada a 2-5, la mitad de su capacidad “dedicada” está manejando paquetes.
Decisión: Mueve el pinning fuera de CPUs con muchas interrupciones, o mueve la afinidad de IRQ fuera de las CPUs de la VM. No compartas por accidente.
Task 7: Comprobar presión de softirq por CPU (los picos de red aparecen aquí)
cr0x@server:~$ awk 'NR==1||/NET_RX|NET_TX|SCHED|RCU/ {print}' /proc/softirqs
CPU0 CPU1 CPU2 CPU3 CPU4 CPU5
NET_RX 123456 234567 9988776 8877665 345678 456789
NET_TX 98765 87654 5432109 4321098 76543 65432
SCHED 456789 567890 678901 789012 890123 901234
RCU 34567 45678 56789 67890 78901 89012
Significado: NET_RX/NET_TX son mucho más altos en CPU2/CPU3. Eso es consistente con el mapeo de IRQ.
Decisión: Si los picos de latencia se correlacionan con actividad de red, trata la colocación de IRQ/softirq como un elemento de afinado de primera clase antes de tocar más pinning.
Task 8: Comprobar carga vs hilos ejecutables (¿estás falto de CPU en las CPUs fijadas?)
cr0x@server:~$ vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
7 0 0 812344 90212 4231120 0 0 2 31 8123 19022 64 9 26 1 0
9 0 0 811920 90212 4231400 0 0 0 0 9100 22011 71 11 17 1 0
Significado: La columna “r” (ejecutable) es alta. Si la VM está fijada a un conjunto pequeño de CPUs, el backlog ejecutable puede concentrarse allí.
Decisión: Si los ejecutables superan las CPUs disponibles persistentemente, deshaz el pinning o amplía el conjunto de CPU, o reduce el número de vCPU para ajustar la capacidad real.
Task 9: Ver utilización por CPU y stalls tipo steal
cr0x@server:~$ mpstat -P ALL 1 3
Linux 6.2.16-xx (pve01) 02/04/2026 _x86_64_ (32 CPU)
12:14:01 PM CPU %usr %nice %sys %iowait %irq %soft %steal %idle
12:14:02 PM 2 68.0 0.0 9.0 0.0 2.0 21.0 0.0 0.0
12:14:02 PM 3 61.0 0.0 8.0 0.0 1.0 29.0 0.0 1.0
12:14:02 PM 4 55.0 0.0 6.0 0.0 0.0 4.0 0.0 35.0
Significado: CPU2/CPU3 tienen %soft enorme. Esas CPUs están haciendo softirq de red en lugar de ejecutar tus vCPUs fijadas.
Decisión: Rebalancea IRQs o mueve la VM. Fijar en CPUs con mucho softirq es como fabricar jitter.
Task 10: Confirmar topología NUMA y si la VM abarca nodos
cr0x@server:~$ numactl -H | head -n 20
available: 2 nodes (0-1)
node 0 cpus: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
node 0 size: 128755 MB
node 0 free: 42110 MB
node 1 cpus: 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
node 1 size: 128705 MB
node 1 free: 39876 MB
Significado: Dos nodos NUMA. Si fijas una VM a CPUs en ambos nodos sin asegurar localidad de memoria, ocurrirán accesos remotos.
Decisión: Mantén VMs sensibles a latencia dentro de un nodo NUMA cuando sea posible, o habilita/verifica configuración aware de NUMA y mide.
Task 11: Inspeccionar la política NUMA de memoria del proceso QEMU (¿es la memoria local?)
cr0x@server:~$ grep -E 'Mems_allowed_list|Cpus_allowed_list' /proc/21433/status
Cpus_allowed_list: 2-5
Mems_allowed_list: 0-1
Significado: El conjunto de CPU es 2-5 (nodo 0), pero la memoria está permitida en ambos nodos. Eso puede causar asignaciones cruzadas según la presión.
Decisión: Si fijas estrictamente, controla también la localidad de memoria. Si no, obtendrás la peor combinación: CPUs fijas, memoria errante.
Task 12: Comprobar el governor de CPU y comportamiento de frecuencia actual
cr0x@server:~$ cat /sys/devices/system/cpu/cpu2/cpufreq/scaling_governor
powersave
Significado: El governor es “powersave”. Eso puede estar bien en algunas plataformas, pero a menudo es malo para cargas latentes y con ráfagas.
Decisión: Considera cambiar a “performance” (o una política ajustada) para hosts que ejecutan VMs críticas por latencia, luego verifica la frecuencia bajo carga.
Task 13: Comprobar pistas de throttling / límites de potencia (cuando estén disponibles)
cr0x@server:~$ dmesg -T | egrep -i 'thrott|powercap|thermal' | tail -n 5
[Mon Feb 3 22:11:52 2026] thermal thermal_zone0: critical temperature reached (97 C), shutting down
[Mon Feb 3 22:11:05 2026] CPU0: Package temperature above threshold, cpu clock throttled
Significado: Si ves throttling, tu “afinamiento de pinning” está rearrangando sillas. La CPU literalmente se está yendo más lenta.
Decisión: Arregla refrigeración/límites de potencia antes de micro-optimizar comportamiento del planificador.
Task 14: Comprobar latencia de ZFS y comportamiento de txg (para que el almacenamiento no sea culpado incorrectamente)
cr0x@server:~$ zpool iostat -v 1 3
capacity operations bandwidth
pool alloc free read write read write
rpool 1.12T 2.38T 120 310 8.2M 21.1M
nvme0n1 1.12T 2.38T 120 310 8.2M 21.1M
Significado: No hay una saturación obvia de almacenamiento en este snapshot. Si la latencia de la VM sube mientras ZFS está quieto, vuelve a la CPU/interrupciones.
Decisión: No “arregles ZFS” solo porque es ZFS. Arregla el cuello de botella que puedas medir.
Task 15: Confirmar la configuración de CPU de la VM en Proxmox (conteo de vCPU, sockets/cores)
cr0x@server:~$ qm config 101 | egrep 'name|cores|sockets|cpu|numa|balloon'
name: vm-db01
cores: 8
sockets: 1
cpu: x86-64-v2-AES
numa: 1
balloon: 0
Significado: NUMA está habilitado, configuradas 8 cores. Si fijaste el proceso a 4 CPUs host (Task 3), creaste una discrepancia obvia.
Decisión: Alinea el tamaño de vCPU y el pinning. Si quieres 8 vCPUs, dale espacio de planificación para 8, o reduce el conteo de vCPU para ajustarlo a la capacidad real.
Tres mini-historias corporativas desde las trincheras del pinning
Mini-historia 1: El incidente causado por una suposición errónea (“Fijado significa dedicado”)
Una empresa SaaS mediana ejecutaba Proxmox para servicios internos: runners de CI, almacenamiento de artefactos, monitorización y algunos clústeres de bases de datos.
Un día la VM primaria de base de datos empezó a mostrar paradas periódicas en las consultas. No consultas lentas: paradas. Las conexiones se colgaban un par de segundos,
luego se recuperaban. Los gráficos parecían una sierra: calma, pico, calma, pico.
El equipo había “endurecido el rendimiento” recientemente fijando las vCPUs de la VM de base de datos a cuatro CPUs del host. La suposición fue sencilla:
fijado equivale a dedicado, dedicado equivale a estable. También asumieron que si la VM tenía cuatro CPUs fijadas, no podía verse afectada por otras VMs.
Esa suposición es la forma de pasar la tarde leyendo /proc como si fuera una novela de misterio.
La causa raíz no fue la base de datos, ni siquiera la configuración de la VM. Fue la colocación de interrupciones.
Las colas más ocupadas de RX/TX de la NIC estaban aterrizando en las mismas CPUs a las que la VM estaba fijada. Bajo tráfico de backups y subidas de artefactos de CI,
el tiempo de softirq se disparó en esos núcleos. Los hilos vCPU estaban ejecutables, pero las CPUs “suas” estaban ocupadas procesando paquetes.
El guest lo experimentó como pausas aleatorias.
Arreglarlo fue aburrido: mover la afinidad de IRQ fuera de las CPUs de la VM y dejar de fingir que el pinning es aislamiento.
También ampliaron el conjunto de CPUs permitido para la VM para incluir un par de núcleos limpios adicionales, intercambiando algo de localidad de caché por la posibilidad de escapar.
Las paradas desaparecieron. Lección del postmortem: el pinning es un compromiso con el host, no una protección contra él.
Mini-historia 2: La optimización que salió mal (“Fijamos por calor de caché”)
Otra organización ejecutaba brokers de mensajes sensibles a latencia en VMs. Querían menor latencia cola larga durante picos de tráfico.
Alguien propuso fijar cada VM broker a un pequeño conjunto de CPUs “para calor de caché”. El plan se veía elegante en la pizarra:
dos VMs por socket, cada una con vCPUs fijadas, sin vagabundeo.
Funcionó en una prueba sintética. Luego llegó la producción. El tráfico real no es un benchmark; es una serie de pequeños desastres con marca de tiempo.
Durante picos, una VM broker saturaba sus CPUs fijadas y formaba una cola. El planificador no podía pedir tiempo ocioso prestado de otras CPUs
porque el proceso estaba restringido. La latencia subió y se mantuvo alta más tiempo del esperado, porque la cola necesitaba tiempo para drenarse.
Mientras tanto, otras CPUs del host estaban parcialmente ociosas. El host tenía capacidad; la VM no tenía permiso para usarla.
La optimización cambió “cachés calientes” por “muros duros”. Las cachés calientes son agradables. Mensajes perdidos y timeouts, no tanto.
El rollback fue instructivo: eliminaron el pinning estricto, mantuvieron shares de CPU sensatos y se enfocaron en reducir la contención del host:
menos vecinos ruidosos por nodo, mejor distribución de IRQ y colocación consciente de NUMA.
Su mediana cambió poco. Su latencia cola mejoró. El resultado no fue heroico; fue predecible.
Mini-historia 3: La práctica aburrida pero correcta que salvó el día (“Medir, luego aislar solo lo necesario”)
Una compañía relacionada con finanzas ejecutaba un clúster Proxmox para cargas mixtas: trabajos por lotes, apps web internas y algunos servicios sensibles a latencia.
Tenían una práctica que sonaba aburrida en las reuniones: cada “cambio de rendimiento” requería una captura antes/después de CPU, interrupciones
y latencia de almacenamiento a nivel host, más un plan de rollback ejecutable en minutos.
Durante un pico, una VM crítica empezó a mostrar jitter. La reacción instintiva de un equipo de aplicaciones fue exigir pinning de CPU.
El equipo de infraestructura no se negó—con educación—pero insistió en ejecutar su captura primero.
En 15 minutos hallaron el problema: la VM estaba bien; el host tenía un pico periódico de softirq causado por un cambio en la configuración de colas de la NIC.
Corregieron la distribución NIC/IRQ, verificaron que el tiempo de softirq se normalizó y el jitter de la VM desapareció sin tocar la afinidad de vCPU.
Nadie pudo presumir de “optimización”. El sistema simplemente volvió a comportarse, que es todo el punto.
La práctica aburrida que les salvó fue procedimental: medir la contención del host primero, tratar el pinning como una herramienta quirúrgica
y mantener los cambios reversibles. Evitó una semana de experimentos supersticiosos con pinning y el tipo de deriva “funciona en un host” que complica operar clústeres.
Broma #2: Fijar sin medir es como ajustar tu reloj mirando fijamente. Te sientes productivo; el tiempo sigue sin impresionarse.
Errores comunes: síntomas → causa raíz → solución
1) Síntoma: p99 se dispara después de fijar, mientras la CPU media parece bien
Causa raíz: Fijaste en CPUs con IRQ/softirq intensos, o restringiste la VM de modo que no puede escapar de la interferencia transitoria del host.
Solución: Mueve la afinidad de IRQ fuera de esas CPUs y/o amplía el conjunto de CPU permitido. Si realmente necesitas núcleos “dedicados”, implementa el aislamiento de CPU correctamente.
2) Síntoma: la VM se siente “entrecortada” durante picos de tráfico de red
Causa raíz: El procesamiento de softirq (NET_RX/NET_TX) está robando tiempo a tus vCPUs fijadas.
Solución: Rebalancea colas/IRQs de NIC, considera ajustes RSS y mantén VMs sensibles a latencia fuera de esos núcleos.
3) Síntoma: fijaste una VM de 8 vCPU pero se comporta como de 4 vCPU
Causa raíz: La afinidad del proceso QEMU está limitada a menos CPUs host que el conteo de vCPU, o hermanos SMT se hacen pasar por “cores”.
Solución: Alinea el conteo de vCPU con núcleos físicos (no hilos) y con el conjunto de CPU permitido. Reduce vCPU si no puedes proveer capacidad real.
4) Síntoma: el rendimiento es inconsistente entre hosts del mismo clúster
Causa raíz: Diferentes ajustes de BIOS, governors de CPU, microcode o layouts de IRQ. El pinning magnifica esas diferencias.
Solución: Estandariza firmware, parámetros del kernel y política de rendimiento entre nodos. Valida con los mismos comandos a nivel host.
5) Síntoma: se culpa al almacenamiento pero los discos están inactivos
Causa raíz: Contención de CPU en la ruta de completado de I/O (kworkers, softirq, vhost), no latencia del medio.
Solución: Mide %soft y utilización por hilo; asegúrate de que los hilos de I/O no estén atascados en CPUs concurridas; revisa colocación de IRQ para NVMe y NIC.
6) Síntoma: “Fijar ayudó una vez, luego empeoró después de una actualización del kernel”
Causa raíz: Cambió el planificador, los valores por defecto de IRQ o el comportamiento del driver; tu pinning dependía de supuestos frágiles.
Solución: Revalida interrupciones, softirqs y topología de CPU tras actualizaciones. Trata las configuraciones de pinning como artefactos por kernel/plataforma, no verdades eternas.
7) Síntoma: la VM va rápida hasta que otra VM en el host se pone ocupada
Causa raíz: Conjuntos de pin solapados o hermanos SMT compartidos. Has incorporado vecinos ruidosos en los mismos recursos físicos.
Solución: Asegura que conjuntos de CPU no se solapen para VMs críticas y evita contención de hermanos. Si no puedes evitar solapamiento, no fijes; confía en shares y holgura.
Listas de verificación / plan paso a paso
Checklist A: Decide si el pinning está justificado
- ¿Está el host con CPU sobremultiplicada? Si sí, arregla eso primero. El pinning no creará capacidad.
- ¿Tienes interferencia demostrada de IRQ/softirq? Si sí, arregla la colocación de IRQ antes de fijar.
- ¿La localidad NUMA está rota actualmente? Si sí, arregla colocación y localidad de memoria antes de fijar.
- ¿La carga es sensible a latencia o a throughput? Si es throughput, el pinning a menudo reduce el pico de throughput al limitar la flexibilidad de planificación.
- ¿Puedes revertir rápido? Si no, no lo hagas en producción. Los cambios de pinning son de riesgo engañosamente alto porque “más o menos funcionan” mientras están equivocados.
Checklist B: Si fijas, hazlo como un paquete (el plan “haz que sea verdad”)
- Elige núcleos físicos completos (evita hermanos SMT para el conjunto primario salvo que lo hayas medido).
- Manténlo dentro de un nodo NUMA cuando sea posible: vCPUs y memoria deben vivir juntos.
- Mueve interrupciones fuera de esos núcleos (NIC y NVMe son sospechosos habituales).
- Cuenta los hilos no vCPU: IOThread, hilos vhost, hilos del emulador. No dejes sin recursos la ruta de I/O.
- Establece una política de frecuencia que coincida con las necesidades de latencia y verifica bajo carga real.
- Deja salidas de escape si puedes: un conjunto de CPU ligeramente mayor puede reducir la latencia cola larga en entornos con ráfagas.
Checklist C: Despliegue y validación (no confíes en tu primer resultado)
- Captura la línea base: mpstat, /proc/interrupts, /proc/softirqs, utilización de hilos de la VM, iostat de almacenamiento.
- Cambia una cosa: pinning o afinidad de IRQ o política NUMA—no todo a la vez.
- Mide p95/p99 en el guest y %soft/%irq en el host.
- Mantén el comando de rollback listo y probado.
- Vuelve a comprobar tras un reinicio: la distribución de IRQ y la topología de CPU pueden cambiar.
Preguntas frecuentes
1) ¿Debo fijar CPUs para cada VM en Proxmox?
No. La mayoría de VMs se benefician de la flexibilidad del planificador. Fija solo cuando tengas una razón medida: necesidades de aislamiento predecible, restricciones de licencia
o una carga sensible a latencia donde también puedas controlar interrupciones y localidad NUMA.
2) ¿Por qué el pinning mejoró el throughput pero empeoró la latencia?
El throughput puede mejorar por localidad de caché y menos migraciones. La latencia cola puede empeorar porque la VM no puede escapar de interferencia transitoria
en sus CPUs fijadas. La ráfaga revela la desventaja.
3) ¿Es “límite de CPU” lo mismo que pinning?
No. Límite de CPU es throttling (cuota cgroup). Pinning es ubicación (afinidad/cpuset). El throttling puede añadir latencia forzando sleeps periódicos.
La colocación puede añadir latencia al impedir migración frente a contención. Diferentes cuchillos, diferentes cortes.
4) ¿Habilitar NUMA en la VM arregla los problemas NUMA?
Puede ayudar, pero no es mágico. Aún necesitas asegurar que el host coloque vCPUs y memoria sensatamente. Verifica con herramientas NUMA del host y observa
si el conjunto de CPU de la VM mapea limpiamente a un nodo.
5) ¿Cuál es la forma más simple de decir si las interrupciones están robando mis CPUs fijadas?
Mira /proc/interrupts y /proc/softirqs, luego correlaciónalo con mpstat %irq/%soft en las CPUs fijadas.
Si las CPUs fijadas muestran softirq alto durante picos de latencia, ya tienes la respuesta.
6) ¿Debo desactivar SMT/Hyper-Threading para baja latencia?
A veces. SMT puede aumentar throughput pero también añade contención y jitter en algunas cargas. Antes de desactivarlo globalmente, prueba fijar a un hilo por núcleo
(evita hermanos) y mide. Desactivar SMT es un martillazo mayor con consecuencias amplias.
7) ¿Puede el pinning causar deriva de tiempo o problemas de reloj en el guest?
El pinning en sí no causa deriva de reloj directamente, pero puede aumentar retrasos de planificación que afecten aplicaciones sensibles al tiempo.
Asegura que la sincronización temporal del guest sea correcta y evita políticas de throttling que introduzcan stalls periódicos.
8) Mi VM está fijada y sigue lenta. ¿Qué hago ahora?
Valida que el pinning esté completo (hilos), verifica que no haya puntos calientes de IRQ en esas CPUs, comprueba localidad NUMA, verifica escalado de frecuencia y throttling,
y confirma que no estés simplemente falto de CPU por demanda de carga. El pinning no arregla “necesita más CPU”.
9) ¿Es mejor fijar menos vCPUs y escalar verticalmente, o más vCPUs y confiar en la planificación?
Para muchos servicios sensibles a latencia, menos vCPUs bien utilizadas con holgura supera a muchas vCPUs peleando por núcleos limitados.
Dimensiona según backlog ejecutable y concurrencia de la aplicación. Mide, no adivines.
Siguientes pasos prácticos
Si recuerdas una cosa: el pinning no es aislamiento. Es una restricción. Las restricciones aumentan el costo de los errores.
Si vas a restringir al planificador, le debes un entorno limpio: interrupciones donde tú las quieres, localidad NUMA sensata,
y suficiente holgura de CPU para que las ráfagas no se conviertan en tarea de teoría de colas.
- Ejecuta el guion de diagnóstico rápido e identifica si los picos se correlacionan con %soft/%irq, backlog ejecutable o latencia de almacenamiento.
- Audita la completitud del pinning: hilos vCPU, IOThread y cualquier hilo auxiliar que importe para tu ruta de I/O.
- Deja de fijar sobre puntos calientes de IRQ. Si debes fijar, empareja con higiene de afinidad de IRQ.
- Alinea el pinning con núcleos físicos y nodos NUMA. Evita fijar accidentalmente a hermanos SMT para trabajo crítico de latencia.
- Haz cambios uno a la vez y mantén rollbacks listos. Tu yo del futuro estará cansado y agradecido.
Si sigues esos pasos, fijarás menos a menudo—y cuando lo hagas, será por una razón que puedas explicar sin superstición.
Ese es todo el trabajo.