Virtualización

Memoria por niveles en QEMU: Red Hat quiere llevar la RAM fría de las VM a NVMe sin romper la migración en vivo

Por Equipo Cloud Privado · · 14 min de lectura
Esquema sobre fondo grafito: memoria con páginas calientes en rojo y una flecha que lleva las frías a una unidad NVMe

Las claves en 30 segundos

  • Peter Xu, ingeniero de virtualización de Red Hat, presentó el 12 de noviembre en el KVM Forum 2026 de Vancouver su trabajo para que las máquinas virtuales de QEMU tengan parte de la memoria en DRAM y el resto en NVMe sin perder rendimiento.
  • El motivo es el precio de la RAM. En sus diapositivas, la DDR5 RDIMM sale a unos 27-41 € por GB (30-45 $) y un NVMe a menos de 0,90 € por GB (menos de 1 $).
  • Linux ya sabe hacerlo: un cgroup por VM con memory.high, swap en NVMe y MGLRU para decidir qué baja. En estado estable funciona bien.
  • Lo que se rompe es la migración en vivo. QEMU lee todas las páginas para copiarlas, trae de vuelta las frías desde el NVMe, expulsa las calientes y la VM se arrastra: en su prueba, latencia p99 de unos 30 ms durante la migración frente a 30 µs sin swap.
  • Propone tres caminos y elige el más barato: usar mincore() y MADV_PAGEOUT, que ya existen, para que la migración sepa qué páginas están frías y las deje frías también en el destino. Con eso la migración pasa de unos 32 s a unos 13 s y la carga casi no lo nota.
  • No está en ninguna versión de QEMU. Los parches aún no se han publicado y las pruebas se hicieron con un disco emulado, no con NVMe reales. Para Proxmox es algo a seguir, no a activar.

La memoria se ha convertido en la partida que más ha subido en un servidor de virtualización. Ya contamos cómo la IA ha encarecido la RAM de servidor y por qué conviene empezar a dimensionar un nodo por la memoria y no por la CPU. Broadcom ha respondido con Memory Tiering en vSphere, que mueve la memoria poco usada de las VM a NVMe. Ahora le toca al mundo KVM.

Lo contó Phoronix el 14 de noviembre. Hemos ido a la ficha de la charla en el programa del KVM Forum, a sus diapositivas y a la propuesta que el propio Xu mandó en febrero a la lista linux-mm para debatirla en el encuentro LSF/MM/BPF. Lo que sigue es qué problema hay, qué soluciones se barajan y qué significa para quien tiene un clúster de Proxmox.

Qué es la memoria por niveles y por qué ahora

La idea es sencilla. Una VM con 64 GB asignados no usa sus 64 GB por igual. Una parte está caliente, se lee y se escribe a cada momento. Otra está fría: cachés que nadie consulta, código cargado una vez, servicios que se despiertan una vez al día. La memoria por niveles deja lo caliente en DRAM y lleva lo frío a un medio más barato. La VM sigue viendo sus 64 GB y no sabe dónde vive cada página.

Las cifras que pone Xu sobre la mesa explican el interés:

NivelPrecio por GB (2026, aproximado)Latencia de acceso a una página fría
DRAM (DDR5 RDIMM)27-41 € (30-45 $)~100 ns
Memoria CXLParecido a la DRAM2-3 veces la DRAM
SSD NVMeMenos de 0,90 € (menos de 1 $)Decenas o cientos de µs

Precios de las diapositivas de Peter Xu, en dólares. Pasados a euros al tipo de referencia del BCE del 13 de noviembre de 2026: 1 € = 1,1052 $.

Hay una diferencia de unas cuarenta veces en precio y de mil veces en latencia. Por eso esto solo funciona si lo que baja al NVMe está frío de verdad. ¿Cuánto hay? Xu cita el trabajo de Meta sobre TMO (Transparent Memory Offloading, ASPLOS 2022), que encontró entre un 20 y un 32 % de memoria descargable sin perjudicar a las aplicaciones. Él mismo lo pone con interrogación: depende de cada carga.

Conviene no confundirlo con otras técnicas. El ballooning, virtio-mem o el free page reporting recuperan memoria libre dentro del invitado. La memoria por niveles se ocupa de la memoria usada pero fría. Son complementarias.

Quién decide qué página baja

Xu repasa tres sitios donde se puede hacer el trabajo:

Sistema invitadoKernel del hostQEMU
Cómo sabe qué está calienteSus tablas de páginasTablas EPT/NPTSeguimiento de escrituras, page_idle, userfaultfd
Cómo mueve las páginasSwap o NUMA del invitadocgroup con memory.high, swap, zswapSu propio almacén en NVMe
A favorConoce qué es cada páginaReutiliza todo lo que hace el kernel; un cgroup por VMConoce la VM
En contraNecesita cooperación del invitadoQEMU no sabe nada de nivelesDuplica el swap y es más lento

Su apuesta es la columna del medio. Linux lleva décadas con una jerarquía de dos niveles, la memoria y el swap, y con MGLRU (el algoritmo que decide qué páginas reclamar y que ya comentamos al hablar de Linux 7.2) lo hace razonablemente bien. Si se mete cada VM en su propio cgroup y se limita su DRAM con memory.high, el kernel se encarga de todo y QEMU se beneficia de cada mejora futura del kernel sin cambiar una línea.

Lo que ya funciona y lo que se rompe

Xu montó la prueba con Linux sin modificar y sin tocar QEMU: DRAM como nivel rápido, un NVMe de empresa como swap, un cgroup por VM con memory.high a la mitad de la memoria (relación 1:1) y MGLRU eligiendo qué baja.

En estado estable funciona. Lo caliente se queda en DRAM y lo frío en el NVMe.

La migración en vivo es el problema. Para mover una VM a otro nodo, QEMU recorre toda su memoria y la copia por la red. Para él, una página en swap es una página más, así que la lee. Y al leerla, el kernel la trae de vuelta del NVMe a la DRAM. Pasa lo siguiente:

  1. En la primera pasada cada página fría vuelve a la DRAM.
  2. Como el cgroup tiene un tope, se expulsan páginas calientes para hacer sitio, y la VM, que sigue funcionando, empieza a esperar al disco.
  3. Se pierde la información de qué estaba caliente.
  4. En el destino todo aterriza en DRAM y el kernel tiene que volver a aprender qué estaba frío y bajarlo otra vez.

Las cifras de su prueba, con una VM de 6 GB, un cgroup de 3 GB, swap con 100 µs de latencia y la migración limitada a 1 GB/s:

Sin swapCon swap, sin cambios en QEMU
Duración de la migración~6 s~40 s hasta estabilizarse
Ancho de banda real1,1 GB/s300-550 MB/s, con caídas por debajo de 50 MB/s al final
Ritmo de escritura de la cargaConstante, 300 MB/sCae a ~5 MB/s
Latencia p99 de la carga≤30 µs~30 ms
En el destino—Unos 3,8 GB devueltos al swap después

Mil veces más latencia durante la migración. Para una base de datos o un servicio con usuarios, eso no es una migración en vivo, es una parada larga con otro nombre. Y en Proxmox la migración en vivo no es una función rara: es lo que usa la alta disponibilidad, lo que permite vaciar un nodo para actualizarlo y lo que hace el reparto de carga.

El problema no es nuevo. En el KVM Forum de 2021 ya se presentó una solución de overcommit con cgroups y swap que buscaba las páginas en swap, las enviaba y volvía a expulsarlas, pero tocar los huecos de swap del kernel desde fuera es propenso a carreras. En 2024 otra propuesta llevaba el swap a un demonio externo con userfaultfd, pero exigía que el destino pudiera leer el almacén del origen, algo que en la práctica casi nunca se cumple.

Las tres soluciones

1. Una llamada al sistema nueva: maccess()

La más limpia. Una llamada al sistema que lee la memoria de un proceso sin alterar la información de qué está caliente y que, si la página está en swap, la lee directamente del NVMe sin traerla a la DRAM. El origen enviaría cada página con un bit que diga si estaba caliente o fría, y en el destino una operación de escritura metería las frías directamente en el swap.

En su propuesta de febrero a linux-mm la llamaba swap_access(). Es precisa, síncrona y serviría para más cosas. El inconveniente: una llamada al sistema nueva es una interfaz que hay que mantener para siempre, y los responsables de la gestión de memoria del kernel no la aceptan sin una justificación muy sólida. Es también la que más trabajo exige.

2. Que QEMU haga su propio swap

QEMU sería dueño del nivel lento: vigilaría qué páginas se usan, escribiría las frías en su almacén, las quitaría de la RAM y, cuando el invitado las tocara, las traería de vuelta con userfaultfd. La migración consciente de niveles vendría de serie, porque QEMU sabría exactamente qué está frío.

El precio es reimplementar el swap fuera del kernel, con un camino de fallo de página más lento. Y medir qué está caliente desde fuera sigue sin estar resuelto: el seguimiento de páginas inactivas no se lleva bien con MGLRU y el modo nuevo de userfaultfd para esto aún no es compatible con la protección contra escritura que haría falta para la memoria anónima.

3. Usar lo que ya existe: mincore() y MADV_PAGEOUT

La que ha elegido. Sin tocar el kernel:

  • En el origen, QEMU pregunta con mincore() qué páginas están en DRAM. Las que no, están frías. Las lee, las envía marcadas como frías y vuelve a expulsarlas con MADV_PAGEOUT.
  • En el destino, nada más escribir una página fría, QEMU pide también MADV_PAGEOUT para que vuelva al NVMe.
  • En el protocolo de migración basta un bit nuevo, RAM_SAVE_FLAG_COLD, y un campo cold_pages para la migración con varios hilos (multifd).
  • Se activa en los dos extremos con un parámetro: migrate_set_parameter ram-tiering on.

Funciona con memoria anónima y compartida, con migración precopy, postcopy y multifd, y los cambios en QEMU son pequeños. Los resultados con la misma VM de prueba:

Memoria compartida (shmem)ram-tiering desactivadoram-tiering activado
Duración~32 s~13 s
Ritmo de escritura de la cargaMenos de 40 MB/s durante ~25 sConstante, 300 MB/s
Pico de latencia p99~100 ms~30 µs
Memoria anónimaram-tiering desactivadoram-tiering activado
Duración~25 s~12 s
Ritmo de escritura de la carga~5 MB/s durante ~10 sUna caída de ~1 s
Pico de latencia p99~40 ms~400 µs

En memoria compartida la carga casi no se entera de la migración. En la anónima Xu escogió a propósito una ejecución con un pico, y aun así la mejora es de dos órdenes de magnitud.

Y el remate: MADV_PAGEOUT_FULL

MADV_PAGEOUT tiene dos limitaciones. Es una petición, no una orden: puede ser asíncrona según el dispositivo de swap, y el kernel la atiende cuando puede. Además no expulsa páginas compartidas por más de un proceso, como las que usa vhost-user. Y aquí hay un detalle que nos interesa mucho: también es el caso de las páginas que fusiona KSM, que Proxmox activa por defecto para deduplicar memoria entre VM.

Para eso propone un parche pequeño al kernel, MADV_PAGEOUT_FULL, que espera a que la página salga de verdad antes de volver y permite expulsar páginas compartidas. La primera pasada de la migración va más lenta porque espera a las escrituras, pero a cambio:

MADV_PAGEOUTMADV_PAGEOUT_FULL
Duración~12 s~20 s
Ritmo de escritura de la cargaUna caída de ~1 sConstante, 300 MB/s
Pico de latencia p99~400 µs~20 µs
Exceso sobre el tope de memoria20 %Casi nulo

Ese 20 % de exceso es importante en un host con muchas VM: si cada cgroup se pasa de su tope un 20 % durante la migración, la memoria del nodo que creías tener libre no lo está.

La letra pequeña

Antes de entusiasmarse, cuatro avisos que da el propio Xu o que salen de sus diapositivas:

  • No hay nada publicado. Ni los parches de QEMU ni el del kernel están en las listas de correo todavía. Su siguiente paso es pulirlos y publicarlos, y pregunta abiertamente si hay más interesados aparte de Red Hat.
  • No se ha probado con NVMe reales. Las pruebas se hicieron en un solo host con scsi_debug, un disco emulado por el kernel al que se le fijan 100 µs de latencia. Él mismo dice que se quedaron sin el hardware antiguo. Un NVMe real tiene colas, recolección de basura y latencias de cola que un disco emulado no reproduce.
  • Las versiones son de desarrollo: Linux 7.1 y QEMU 11.1.50, es decir, la rama posterior a QEMU 11.1.
  • La proporción entre DRAM y NVMe es fija en las pruebas. Ajustarla por VM y adaptarla a cómo cambia la carga queda como trabajo futuro.

Qué significa para Proxmox

Proxmox VE usa QEMU y KVM, así que si esto llega a QEMU y al kernel, acabará llegando a Proxmox. Pero hay camino por delante, y algunas piezas ya están:

  • Cada VM ya vive en su propio cgroup. Proxmox arranca cada VM en un scope de systemd dentro de qemu.slice (qemu.slice/<vmid>.scope), justo el cgroup por VM que pide este diseño. Hoy no le pone MemoryHigh, así que no hay reparto entre DRAM y swap por VM.
  • El kernel de Proxmox (7.0 en PVE 9.2) parte del de Ubuntu, que ya incluye MGLRU. La parte de decidir qué baja al swap existe.
  • Falta la parte de QEMU, el parámetro ram-tiering, que todavía no está en ninguna versión, y que Proxmox lo active en ambos nodos al migrar.

¿Se puede imitar hoy a mano? Técnicamente, sí: swap en un NVMe dedicado y un systemctl set-property --runtime con MemoryHigh en el scope de la VM. Pero nosotros no lo haríamos en producción, por tres razones. Es una configuración que Proxmox no soporta ni conoce. Te expones exactamente al problema que describe Xu en cuanto la alta disponibilidad o un mantenimiento migren la VM. Y además el swap en ZFS sigue siendo mala idea, de modo que necesitarías un dispositivo aparte.

Lo que sí se puede hacer hoy es lo de siempre, y sigue siendo lo más rentable: medir. Saber cuánta memoria usa de verdad cada VM y no asignar 32 GB a un servicio que vive con 6. Lo contamos con detalle en el post sobre overcommit de CPU y RAM en Proxmox, y la presión de memoria por VM se puede seguir con PSI, como explicamos al hablar del vecino ruidoso.

Y frente a VMware

Broadcom va por delante. Memory Tiering con NVMe es una función soportada desde VCF 9.0 y VVF 9.0, con una proporción por defecto de 1:1 entre DRAM y NVMe que se puede llevar hasta 1:4 en cargas con muy poca memoria activa, y particiones NVMe de hasta 4 TB. La propia VMware recomienda quedarse en 1:1 salvo estudio previo de la carga. Lo contamos en el análisis del nuevo vSphere Standard, donde Memory Tiering aparecía como función en estudio para la gama baja; y en vSphere Express queda fuera.

La diferencia de enfoque es interesante. ESXi gestiona los niveles dentro del propio hipervisor, y ahí la migración con vMotion ya sabe qué está frío. En KVM el hipervisor delega la memoria en el kernel de Linux, y eso tiene una ventaja enorme (todo lo que mejore el kernel, lo aprovechan las VM) y un coste, que es justo el que intenta resolver Red Hat: QEMU no ve lo que hace el kernel. Si la opción 3 entra, el resultado será una memoria por niveles abierta, sin licencia aparte y sobre el mismo kernel que todo lo demás.

Nuestra lectura

Es la noticia correcta en el momento correcto. Con la RAM a precios que nadie había visto, poder poner la mitad de la memoria de un nodo en NVMe cambia el coste de un clúster. El enfoque elegido es el sensato: no reinventar el swap, no pelear una llamada al sistema nueva en el kernel y cambiar lo mínimo en QEMU.

Pero hoy es una charla con buenas cifras sobre un disco emulado. Hasta que los parches estén en QEMU, Proxmox los integre y alguien los pruebe con NVMe de verdad bajo carga real, la forma de ahorrar memoria sigue siendo dimensionar bien. Le daremos seguimiento cuando los parches lleguen a la lista de QEMU.

Si estás dimensionando un clúster y la memoria se ha comido el presupuesto, en Proxmox VE gestionado y en servidores bare-metal empezamos precisamente por ahí: cuánta memoria usa de verdad cada carga, cuánta hay que comprar y cómo repartirla entre nodos.

Preguntas frecuentes

¿Qué es la memoria por niveles (memory tiering) en una máquina virtual?

Es repartir la memoria de una VM entre un medio rápido y caro, la DRAM, y otro más lento y barato, como un SSD NVMe. Las páginas que se usan a menudo se quedan en DRAM y las que casi no se tocan bajan al NVMe. La VM sigue viendo toda su memoria asignada.

¿QEMU ya permite usar NVMe como memoria de las VM?

No de forma consciente. Linux puede hacerlo hoy con swap en NVMe y un cgroup por VM, pero QEMU no sabe qué páginas están en el swap y la migración en vivo se degrada mucho. El trabajo de Red Hat presentado en el KVM Forum 2026 corrige eso, pero sus parches aún no se han publicado.

¿Por qué la migración en vivo es el problema?

Porque QEMU tiene que leer toda la memoria de la VM para copiarla al otro nodo. Al leer una página que está en swap, el kernel la trae de vuelta a la DRAM, expulsa otras que estaban calientes y la VM se ralentiza. En la prueba de Peter Xu, la latencia p99 pasó de 30 µs a unos 30 ms durante la migración.

¿Qué solución ha elegido Red Hat?

Usar dos interfaces que ya existen en Linux: mincore(), para saber qué páginas están en DRAM, y MADV_PAGEOUT, para devolver al swap las páginas frías tanto en el origen como en el destino. Se activaría con el parámetro de migración ram-tiering. Opcionalmente, un parche pequeño al kernel, MADV_PAGEOUT_FULL, elimina los problemas que quedan.

¿Puedo usarlo ya en Proxmox VE?

No. Ninguna versión de QEMU lo incluye todavía. Proxmox ya mete cada VM en su propio cgroup y su kernel incluye MGLRU, pero falta la parte de QEMU. Configurar a mano swap y límites de memoria por VM no está soportado y reproduce el problema de rendimiento al migrar.

¿En qué se diferencia del Memory Tiering de VMware?

VMware lo ofrece como función soportada desde VCF 9.0, gestionada dentro de ESXi, con una relación de 1:1 entre DRAM y NVMe por defecto. En KVM, la memoria por niveles la haría el kernel de Linux, y el trabajo de Red Hat consiste en que QEMU lo tenga en cuenta al migrar. Sería abierta y sin licencia adicional.

Fuentes

¿Le das una vuelta a tu infraestructura?

Si estás valorando cambiar algo, cuéntanos en qué punto estás. Te orientamos sin compromiso.

Respuesta en 24–48 h laborables · Sin compromiso

¿Prefieres el correo? Escríbenos a comercialcloudprivado.com