Un servidor virtual dedicado es una garantía: tus vCPUs y tu memoria no se comparten con otros inquilinos. Pero esa garantía es un techo, no un suelo. Si el guest se configura sin tener en cuenta la topología de CPU, el alineamiento NUMA y el pinning de memoria, una fracción significativa de esa capacidad reservada simplemente queda sin usar o, peor aún, perjudica activamente el rendimiento. El host cumple el contrato; la mala configuración lo rompe desde dentro.

No es una preocupación teórica. Sin una configuración correcta de CPU y NUMA, las máquinas virtuales pueden sufrir thrashing de caché, penalizaciones en el acceso a memoria y variabilidad en el rendimiento que reduce el throughput efectivo en un 50% o más. Los recursos físicos están ahí. El problema es que nada le indica al guest dónde se encuentran realmente.

Entender por qué requiere un breve desvío por el funcionamiento real de los servidores modernos con múltiples sockets y por qué el comportamiento por defecto del hipervisor está optimizado para densidad, no para rendimiento.

Por qué la sobrecontratación es el estándar y qué elimina el VDS

En un host cloud estándar, no todas las VMs necesitan ciclos de CPU al mismo tiempo. Los hipervisores aprovechan esto planificando vCPUs en cualquier núcleo físico disponible, admitiendo más vCPUs que hilos físicos en total. Esto es la sobrecontratación (overcommitment), y bajo cargas ligeras o intermitentes es completamente razonable. Un white paper de Dell vSphere lo expresa con claridad: una relación vCPU/pCPU de hasta 3:1 es generalmente segura; de 3:1 a 5:1 puede comenzar a causar degradación; y a partir de 6:1 suele generar problemas.

Un VDS elimina por completo la sobrecontratación. Tus vCPUs se asignan a hilos físicos reservados; nadie más compite por ellos. Pero aquí está la trampa: el hipervisor sigue planificando esas vCPUs en los núcleos físicos disponibles dentro de la reserva, a menos que le indiques explícitamente lo contrario. La reserva evita la contención de otros inquilinos, pero por sí sola no impide que el planificador mueva tus vCPUs entre núcleos, entre nodos NUMA o entre sockets.

La garantía elimina la interferencia externa. Una mala configuración reintroduce la interferencia interna. Ambas te cuestan los mismos ciclos.

Topología NUMA: el precipicio de rendimiento oculto

NUMA (Non-Uniform Memory Access) es la arquitectura de memoria dominante en cualquier servidor con múltiples sockets y en muchos sistemas de un solo socket con alto número de núcleos. Cada socket de CPU tiene su propio banco de memoria local. Acceder a la memoria local es rápido; acceder a la memoria conectada a un socket diferente atraviesa un bus de interconexión con ancho de banda limitado y latencia apreciable.

Las cifras no son abstractas. Un acceso aleatorio a memoria tarda aproximadamente 100 ns en local. Cruzar a un nodo NUMA remoto añade alrededor del 50% a esa cifra, según la descripción de la arquitectura publicada por ACM Queue. Los benchmarks con Intel Memory Latency Checker muestran que el acceso NUMA remoto produce casi el doble de latencia en reposo y alrededor de un tercio del ancho de banda respecto al acceso local. Para cargas de trabajo intensivas en memoria, el desalineamiento NUMA por sí solo puede reducir el throughput en un promedio del 30%, con degradaciones en el peor caso que alcanzan el 51%.

Cuando una VM se ejecuta sin conciencia NUMA, entran en juego dos topologías distintas: la distribución NUMA física del host y la topología de hardware virtual expuesta al sistema operativo guest. Si no están alineadas, el asignador de memoria del guest toma decisiones que parecen óptimas localmente pero se traducen en accesos a nodos remotos en el hardware físico. El guest cree que está leyendo memoria local; el hardware está cruzando un bus.

La documentación de Red Hat RHEL 9 describe el objetivo con precisión: configurar NUMA para VMs mapea los procesos de CPU y memoria del host sobre los procesos de CPU y memoria de la VM de la forma más ajustada posible, proporcionando a la vCPU un acceso más directo a la memoria del sistema asignada a la VM. Esto no es una optimización exclusiva para clústeres HPC. Es relevante para bases de datos, analítica, capas de caché y cualquier servicio donde la memoria esté en la ruta crítica.

CPU pinning: qué hace y cómo configurarlo correctamente

El CPU pinning vincula cada vCPU a un hilo de CPU físico específico. El planificador deja de rotar la vCPU entre núcleos. El estado de caché acumulado en ese núcleo persiste entre intervalos de planificación. Los cambios de contexto ya no obligan a repoblar la caché desde cero.

El argumento de rendimiento a favor del pinning es más sólido cuando el host está completamente cargado. En un sistema que usa todos los núcleos de CPU disponibles, el CPU pinning mejora el tiempo de respuesta de la aplicación al eliminar las rotaciones de procesos entre núcleos que provocan repoblaciones innecesarias de caché. También elimina la degradación de rendimiento que resulta de las migraciones entre distintos nodos NUMA. Es importante destacar que el pinning no reduce el número de núcleos de CPU permitidos, por lo que no introduce congestión en los recursos compartidos.

Configurar el pinning correctamente en un VDS requiere tres pasos realizados en orden:

  1. Identificar la topología física del host. Usa lscpu, numactl --hardware o lstopo para mapear qué núcleos físicos pertenecen a qué nodo NUMA y a qué socket. Esta es la fuente de verdad que debe reflejar tu configuración de pinning.
  2. Fijar las vCPUs a núcleos dentro de un único nodo NUMA. Si tu VM tiene 8 vCPUs, fija las 8 a núcleos que compartan un nodo NUMA y una caché de último nivel. Distribuir las vCPUs entre nodos para una misma VM es el error más común: obtienes lo peor de ambos mundos, ni la localidad de caché de un solo nodo ni el ancho de banda de una asignación correcta entre nodos.
  3. Alinear la asignación de memoria del guest al mismo nodo. Fijar las vCPUs sin restringir la asignación de memoria deja al guest libre de asignar en un nodo remoto. Usa numactl --membind o la política de memoria equivalente a nivel de hipervisor para vincular la memoria del guest al mismo nodo NUMA que las vCPUs fijadas. Sin este paso, el pinning entrega solo la mitad de su beneficio potencial.

Cargas de trabajo que pagan el precio más alto por una mala configuración

No todas las cargas de trabajo son igual de sensibles. Los trabajos por lotes tolerantes a la latencia absorben razonablemente bien las penalizaciones NUMA. Las que no pueden hacerlo son exactamente las que se despliegan con mayor frecuencia en VDS: bases de datos, cachés en memoria, almacenes clave-valor y microservicios con presupuestos ajustados de latencia de cola.

Una investigación sobre trazas de producción de Azure encontró que las cargas de trabajo de microservicios, caché y clave-valor se encuentran entre las más sensibles a la sobrecontratación, con una degradación del rendimiento de hasta 2,35x en el peor caso con VMs completamente sobrecontratadas. Estas cargas acceden a memoria en la ruta crítica, lo que significa que cualquier latencia adicional se acumula en cada solicitud. Una instancia de Redis con desalineamiento NUMA, por ejemplo, mostrará una latencia promedio aceptable mientras sus percentiles p99 y p999 suben silenciosamente. Las métricas promedio ocultan el problema hasta que se convierte en un incidente en producción.

El mismo principio se aplica al efecto de NUMA sobre la latencia de cola. El throughput promedio puede parecer correcto mientras la cola sufre. Monitorizar la utilización promedio de CPU en un VDS mal configurado no revelará esto. Necesitas utilización por núcleo, ratios de acierto/fallo NUMA de perf stat o numastat, y métricas de ancho de banda de memoria para ver lo que realmente está ocurriendo.

Configurar un VDS correctamente: una lista de verificación práctica

Antes de ajustar cualquier cosa, verifica con qué estás trabajando:

  • Ejecuta numastat -m para comprobar si la memoria se está asignando en el nodo correcto. Un contador numa_miss alto significa que tu asignador ya está cruzando nodos.
  • Revisa /proc/cpuinfo y contrasta con numactl --hardware para confirmar qué CPUs lógicas comparten un núcleo físico (hermanos de hyperthreading) y cuáles comparten un nodo NUMA.
  • Si ejecutas un guest basado en KVM, verifica que la definición XML de la VM incluya un bloque numatune con mode='strict' y un nodeset que coincida con tus vCPUs fijadas.
  • Para cargas de trabajo en contenedores sobre un VDS, establece la afinidad de CPU a nivel de cgroup con cpuset.cpus y cpuset.mems, manteniendo ambos dentro del mismo nodo NUMA.
  • Después de cualquier cambio de configuración, vuelve a ejecutar el benchmark de tu aplicación bajo carga realista. Los efectos NUMA dependen de la carga de trabajo; un microbenchmark sintético puede no revelar los mismos cuellos de botella que un patrón de consultas en producción.

El producto VDS de DIVERSITY se ejecuta en hosts sin sobrecontratación donde los hilos de CPU físicos y la memoria están reservados por inquilino, lo que significa que la información de topología expuesta al guest refleja hardware dedicado real en lugar de una aproximación virtualizada de un pool de tiempo compartido. Eso hace que la configuración correcta de pinning sea tanto más alcanzable como más determinante: los recursos físicos están genuinamente disponibles, por lo que una mala configuración es lo único que se interpone entre la garantía en papel y el rendimiento en producción.

Un VDS no es una garantía de rendimiento por sí solo. Es una oportunidad de rendimiento que una configuración correcta debe materializar. Ajusta la topología, fija las vCPUs a un único nodo NUMA, vincula la memoria para que coincida, y la capacidad reservada por la que estás pagando aparecerá realmente en tus benchmarks.