No hay nada que arruine tu mañana como una VM Linux que “funciona bien” hasta que deja de hacerlo: los prompts de shell se quedan colgados, los commits de MySQL se paran, journald se atasca y la gráfica de latencia de tu aplicación se vuelve aterradora. Miras CPU y RAM—bien. Red—bien. Luego abres iostat y lo ves: latencias de escritura aleatorias que se disparan a segundos. El guest parece estar en un disco giratorio impulsado por buenas intenciones.
Esto suele ser solucionable. A menudo no es que “el almacenamiento sea lento”, sino que elegiste el controlador de disco virtual equivocado, el modo de caché incorrecto, o pusiste a QEMU en un modo que convierte un pico de pequeñas escrituras en un atasco de tráfico. La buena noticia: Proxmox te da las palancas. La mala noticia: los valores por defecto no siempre encajan con tu carga de trabajo.
Guía rápida de diagnóstico
Si tu VM “tartamudea”, necesitas decidir de dónde viene la latencia: guest, QEMU, kernel del host, backend de almacenamiento o el dispositivo/cluster físico. No adivines. Triagéalos.
Primero: demuestra que es latencia de almacenamiento (no steal de CPU ni presión de memoria)
-
En el guest: comprueba si los bloqueos se correlacionan con tiempo de espera de disco.
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 1 0 0 812340 22012 903112 0 0 120 980 310 510 4 2 92 2 0 0 2 0 810112 22020 904000 0 0 0 4096 280 420 2 1 58 39 0 0 1 0 809980 22020 904120 0 0 0 2048 270 410 2 1 70 27 0Qué significa: un
waalto indica tiempo gastado esperando IO. Sist(steal) es alto, estás con sobreasignación de CPU; arregla eso antes de obsesionarte con las opciones de disco.Decisión: si
wase dispara durante el stutter ystes bajo, sigue investigando el almacenamiento.
Segundo: encuentra la capa lenta (cola del guest vs dispositivo del host)
-
En el host Proxmox: observa latencia por dispositivo y saturación.
cr0x@server:~$ iostat -x 1 5 Linux 6.8.12-pve (pve01) 12/26/2025 _x86_64_ (32 CPU) avg-cpu: %user %nice %system %iowait %steal %idle 10.12 0.00 3.01 8.33 0.00 78.54 Device r/s w/s rMB/s wMB/s avgrq-sz avgqu-sz await r_await w_await svctm %util nvme0n1 22.0 480.0 1.2 46.8 192.0 3.8 7.9 1.2 8.2 0.4 20.5 sda 0.0 95.0 0.0 6.0 128.0 18.2 191.3 0.0 191.3 1.1 99.8Qué significa:
awaites latencia por petición;%utilcercano a 100% con altoawaitgrita “dispositivo saturado”. Aquísdaes el villano.Decisión: si la latencia es mala en el dispositivo del host, los ajustes de controlador/caché en la VM no te salvarán. Arregla el backend (migración, disco más rápido, mejor layout del pool) antes de ajustar.
-
En el host: comprueba si los threads de QEMU están bloqueados en IO.
cr0x@server:~$ pgrep -a qemu-system-x86_64 | head -n 1 24891 /usr/bin/kvm -id 103 -name vm103 -m 8192 ... -drive file=/dev/zvol/rpool/vm-103-disk-0,if=none,id=drive-scsi0,format=raw,cache=none,aio=native ...Qué significa: la línea de comando de QEMU te dice el modo de caché, el modo aio y si usas virtio-scsi/virtio-blk.
Decisión: si ves
cache=writebacken un host sin UPS y sin cache persistente estable, estás poniendo en riesgo la integridad; si vesaio=threadscon CPU alta y latencia, consideraaio=nativesi es compatible.
Tercero: confirma que el guest ve un dispositivo virtual y modelo de colas razonable
-
En el guest: identifica el tipo de disco y si es virtio.
cr0x@server:~$ lsblk -o NAME,MODEL,TYPE,SIZE,ROTA,DISC-MAX,DISC-GRAN,MOUNTPOINTS NAME MODEL TYPE SIZE ROTA DISC-MAX DISC-GRAN MOUNTPOINTS vda QEMU HARDDISK disk 200G 0 2G 4K ├─vda1 part 1G 0 └─vda2 part 199G 0 /Qué significa:
vdasuele indicar virtio-blk.sdadentro de una VM a menudo indica SATA/SCSI emulado y normalmente tiene más overhead.Decisión: si estás en controladores emulados, planifica una ventana de mantenimiento para migrar a virtio.
Esta guía es intencionalmente breve. El resto del artículo explica el “por qué” y te da las perillas que reducen confiablemente los picos de latencia sin crear otros problemas.
Qué significa realmente el “stutter” en un VM
El stutter no va sobre el rendimiento medio. Tu monitor mostrará 50 MB/s y todos felicitarán al almacenamiento. Mientras tanto, tu VM se congela 800 ms cada pocos segundos porque pequeñas escrituras síncronas se están acumulando detrás de algo caro.
En el almacenamiento virtualizado hay varias colas y puntos de flush:
- Caché de páginas del guest: las escrituras en buffer se acumulan hasta que el kernel decide vaciarlas.
- Capa de bloque del guest: hace merges y planifica IO (menos dramático en kernels nuevos, pero real).
- Cola del controlador virtual: virtio-blk y virtio-scsi tienen modelos de colas e interrupciones distintos.
- Capa de bloque de QEMU: el modo de caché controla si QEMU usa la caché de páginas del host y dónde aterrizan los flushes.
- Sistema de archivos / gestor de volúmenes del host: ZFS, LVM-thin, ext4 sobre SSD, Ceph RBD, cada uno tiene características de latencia diferentes.
- Dispositivo/cluster: el disco real, controlador RAID, NVMe, SAN o los OSDs de Ceph.
El stutter suele ser uno de estos patrones:
- Tormentas de flush: un lote de escrituras choca contra una barrera de flush (
fsync(), commit del journal, commit de base de datos) y la latencia se dispara. - Estrangulamiento de cola única: un modelo de disco/controlador usa una sola cola, por lo que cargas paralelas se serializan.
- Presión de memoria en el host: la caché de páginas del host hace thrashing y el IO se vuelve “síncrono sorpresa”.
- Amplificación de escritura en el backend: aprovisionamiento delgado, copy-on-write o replicación hacen que pequeñas escrituras sean caras.
Una frase para tener presente: el guest cree que habla con un disco; en realidad estás ejecutando un sistema distribuido hecho de colas.
Idea parafraseada de Werner Vogels (fiabilidad/operaciones): “Todo falla eventualmente; diseña para que las fallas sean esperadas y manejables.”
Broma #1: El rendimiento del almacenamiento es como el chisme—todo es rápido hasta que pides un sync.
Hechos e historia que explican los extraños valores por defecto
Algunas opciones “misteriosas” de Proxmox/QEMU tienen mucho más sentido cuando sabes de dónde vienen.
- Virtio nació para evitar emular hardware. La virtualización temprana a menudo emulaba IDE/SATA; virtio llegó después como una interfaz paravirtualizada para reducir overhead.
- virtio-blk precede a virtio-scsi. virtio-blk fue más simple y ampliamente soportado; virtio-scsi apareció para ofrecer características más cercanas a SCSI real (múltiples LUNs, hotplug, mejores patrones de escalado).
- Las barreras de escritura y flushes se hicieron más estrictas con el tiempo. Sistemas de archivos y bases de datos dejaron de “confiar” en las cachés tras historias de corrupción en los 2000s; la semántica de flush importa hoy más.
- La caché de páginas del host solía ser una victoria barata. En discos giratorios y sistemas con poca RAM, usar la caché del host (writeback) podía alisar el IO; en SSD/NVMe modernos con múltiples VMs, puede causar contención de caché entre vecinos ruidosos.
- AIO en Linux tiene dos personalidades. AIO “nativo” (kernel AIO) se comporta distinto que la emulación basada en threads; cuál es mejor depende del backend y el alineamiento.
- NCQ y multi-queue no siempre fueron comunes. Mucho folklore de tuning viene de la era de SATA de cola única y paralelismo limitado.
- ZFS se popularizó en virtualización por snapshots/clones. La compensación: copy-on-write y checksumming añaden overhead; vale la pena, pero hay que respetar el comportamiento de escrituras síncronas.
- Ceph popularizó “almacenamiento como un cluster”. Excelente para resiliencia y escala; la latencia es producto de quórum, red y carga de OSDs, no solo de un disco.
Esto no es trivia. Explica por qué una perilla arregla el stutter en tu entorno y lo empeora en el del compañero.
Elección de controlador: virtio-scsi vs virtio-blk (y cuándo SATA aún es útil)
Si ejecutas Proxmox y el dispositivo de disco de tu VM Linux aparece como sda con un controlador “Intel AHCI”, estás pagando por cosplay de hardware. Los controladores emulados existen por compatibilidad. El rendimiento no es su propósito.
Qué usar por defecto
- Usa virtio-scsi-single para la mayoría de VMs Linux modernas. Es una opción sólida: buen soporte de funciones, buen escalado y comportamiento predecible con iothreads.
- Usa virtio-blk para configuraciones simples o cuando quieres menos partes móviles. virtio-blk puede ser muy rápido. También es más simple, lo que a veces importa para depuración y madurez del driver en guests peculiares.
- Usa SATA solo para bootstrapping o medios de instalación raros. Una vez instalado, cambia a virtio.
virtio-scsi: qué te aporta
virtio-scsi modela una HBA SCSI con transporte virtio. En Proxmox verás opciones como:
- virtio-scsi-pci: puede adjuntar múltiples discos; puede compartir una cola, depende de la configuración.
- virtio-scsi-single: crea un controlador separado por disco (o aísla efectivamente el queueing), reduciendo la contención de locks y mejorando la equidad entre discos.
En la práctica: virtio-scsi-single suele ser la forma más sencilla de obtener latencia predecible entre múltiples discos ocupados. Si tienes un disco de base de datos y un disco de logs, quieres que dejen de molestarse en una cola compartida.
virtio-blk: qué te aporta
virtio-blk es un dispositivo de bloque paravirtualizado. Es ligero. Puede ofrecer alto rendimiento y bajo overhead. Pero puede ser menos flexible cuando necesitas características al estilo SCSI, y históricamente algunos comportamientos avanzados (como ciertos patrones de discard/unmap) han sido más sencillos con virtio-scsi.
Cuándo la elección de controlador realmente arregla el stutter
Los cambios de controlador reducen el stutter cuando el cuello de botella está en el queueing del dispositivo virtual o en el procesamiento de interrupciones. Señales típicas:
- El dispositivo del host muestra
awaitbajo (el backend está bien), pero el guest tiene alto IO wait y pausas periódicas. - Una VM obtiene picos de buen throughput seguidos de silencio total, aunque el almacenamiento host no esté saturado.
- Múltiples discos ocupados en la misma VM se interfieren entre sí (logs detienen commits de DB, temporales bloquean escrituras de la app).
Recomendación pragmática
Si tienes stutter y no estás seguro: cambia el controlador de disco de la VM a virtio-scsi-single, habilita iothread para ese disco y usa cache=none a menos que tengas una razón muy específica para no hacerlo.
Esto no es “la única configuración correcta”. Es la que más a menudo arregla los picos de latencia p99 sin convertir la integridad de tus datos en una elección de vida.
Modos de caché: ajustes que hacen o rompen tu p99 latencia
El modo de caché es donde el rendimiento y la seguridad se dan un apretón de manos incómodo. En términos Proxmox/QEMU, decides si la caché de páginas del host se interpone y cómo se manejan los flushes.
Los modos comunes (qué significan realmente)
- cache=none: QEMU usa IO directo (cuando es posible). La caché de páginas del host se evita en gran medida. La caché del guest sigue existiendo. Los flushes se mapean más directamente al backend. A menudo es lo mejor para predictibilidad de latencia y evitar doble caché.
- cache=writeback: las escrituras de QEMU aterrizan en la caché de páginas del host y se reconocen rápido; luego se vacían a almacenamiento. Rápido, hasta que no lo es. Riesgoso sin protección de energía o cachés estables, porque el guest cree que los datos están seguros antes de que realmente lo estén.
- cache=writethrough: las escrituras pasan por la caché del host pero se vacían antes de completar. Más seguro que writeback, generalmente más lento, y a veces con stutter en cargas intensas de sync.
- cache=directsync: intenta hacer cada escritura síncrona. Generalmente una gran manera de aprender paciencia.
- cache=unsafe: no lo uses. Existe para benchmarks y arrepentimientos.
Por qué writeback puede causar stutter (incluso cuando “benchmarks rápido”)
Writeback a menudo convierte tu problema en un problema de tiempo. La caché de páginas del host absorbe escrituras rápidamente, así que el guest produce más escrituras. Luego el host decide que es hora de flush. El flush ocurre en ráfagas, y esas ráfagas pueden bloquear nuevas escrituras o dejar sin CPU a las lecturas. Tu VM experimenta esto como congelamientos periódicos.
En un host con poca carga y una sola VM, writeback puede sentirse genial. En producción con múltiples VMs y cargas mixtas, es el equivalente de IO a permitir que todos se mezclen en un solo carril al mismo tiempo.
Por qué cache=none es aburrido en el buen sentido
Con cache=none reduces la doble caché y mantienes la visión del guest sobre la persistencia más cercana a la realidad. Esto suele estabilizar la latencia. El guest sigue cacheando agresivamente, así que no es “sin caché”. Es “una caché, en un lugar, con menos sorpresas”.
Flushes, fsync y por qué las bases de datos exponen configuraciones malas
Las bases de datos llaman a fsync() porque no quieren perder datos. Los sistemas de archivos con journal también emiten barreras/flushes para ordenar operaciones. Si tu modo de caché y backend convierten los flushes en operaciones globalmente caras, obtendrás el patrón clásico de stutter: la VM va bien hasta un punto de commit, luego todos se detienen.
Nota sobre seguridad (porque te gustan los fines de semana)
cache=writeback puede ser aceptable si tienes:
- una UPS que realmente funcione y esté integrada (el host se apagará limpiamente),
- almacenamiento con protección contra pérdida de energía (PLP) o un controlador con cache con batería,
- y entiendes los modos de fallo.
Si no, la opción “rápida” es rápida hasta que se convierte en un postmortem.
AIO e iothreads: convertir IO paralelo en paralelismo real
Incluso con el controlador y modo de caché adecuados, puedes seguir teniendo stutter porque el procesamiento de IO de QEMU está serializado o contendido. Dos ajustes importan mucho: modo AIO y iothreads.
aio=native vs aio=threads
QEMU puede enviar IO usando AIO nativo de Linux o un pool de threads que hace llamadas de IO bloqueantes. Cuál gana depende del backend y del comportamiento del kernel, pero una regla decente:
- aio=native: a menudo menor overhead y mejor para caminos de IO directo; puede reducir jitter cuando el backend lo soporta limpiamente.
- aio=threads: más compatible; a veces mayor uso de CPU y puede introducir jitter de planificador bajo carga.
Si estás sobre zvols ZFS o dispositivos raw, AIO nativo suele ser buena idea. Si estás sobre imágenes file-backed o configuraciones inusuales, threads puede comportarse mejor. Mide, no vibes.
iothreads: el estabilizador de latencia que deberías usar
Sin iothreads, QEMU puede procesar IO en el hilo principal del event loop (más helpers), lo que significa que el IO compite con la emulación y manejo de interrupciones. Con iothreads, cada disco puede tener su propio hilo de IO, reduciendo la contención y suavizando la latencia.
En Proxmox puedes habilitar un iothread por disco. Esto es especialmente útil cuando:
- tienes múltiples discos ocupados en una misma VM,
- tienes un solo disco muy ocupado haciendo muchas escrituras síncronas,
- quieres mantener p99 bajo control más que ganar una contienda de throughput secuencial.
¿Cuántos iothreads?
No crees 20 iothreads solo porque puedes. Cada thread añade overhead de planificación. Crea iothreads para los discos que importan: volumen de base de datos, sistema de archivos con journal, disco de logs. Para una VM con un disco, un iothread suele ser suficiente.
Broma #2: Añadir iothreads es como contratar más baristas—genial hasta que la cafetería se convierte en una reunión sobre cómo hacer café.
Implicaciones del backend de almacenamiento (ZFS, Ceph, LVM-thin, ficheros)
Puedes elegir el controlador y caché perfectos y aún así tener stutter porque el backend está haciendo algo caro. Proxmox abstrae almacenamiento, pero la física no se impresiona.
ZFS: escrituras síncronas, ZIL/SLOG y la pregunta “¿por qué mi NVMe sigue lento?”
ZFS es excelente para integridad y snapshots. También es honesto respecto a las escrituras síncronas. Si tu carga solicita escrituras síncronas (bases de datos, journaling, apps fsync-intensas), ZFS debe confirmarlas de forma segura. Sin un dispositivo SLOG rápido dedicado con protección contra pérdida de energía, cargas intensas en sync pueden provocar stutter incluso en pools rápidos.
Puntos clave:
- zvol vs dataset: Las VMs sobre zvols suelen comportarse de forma más predecible que qcow2 sobre datasets en IO intensivo, aunque ambos pueden funcionar.
- comportamiento sync: si un guest pide flushes, ZFS se los toma en serio. Si lo “arreglas” con
sync=disabled, cambias durabilidad por velocidad. - recordsize/volblocksize: un desajuste puede aumentar la amplificación de escritura. Para zvols de VM,
volblocksizeimporta al crear.
Ceph RBD: la latencia es una propiedad del cluster
Ceph es resiliente y escalable. También tiene más sitios donde se puede introducir latencia: red, carga de OSDs, backfill/recovery, peering de PGs y colas del lado cliente.
Los patrones de stutter en Ceph suelen venir de:
- recovery/backfill saturando discos o red,
- OSDs con rendimiento desigual (un disco lento arrastra una escritura replicada),
- opciones del cliente que hacen costosos los flushes,
- vecinos ruidosos en nodos OSD compartidos.
Las elecciones de controlador/caché siguen importando, pero no superarán un cluster que está recuperando en el peor momento. (Siempre lo está.)
LVM-thin: presión de metadatos y comportamiento de discard
LVM-thin es rápido y simple, pero el aprovisionamiento delgado introduce escrituras de metadatos y puede volverse espasmódico bajo escrituras aleatorias intensas o cuando el thin pool está casi lleno. Discard/TRIM puede ayudar a recuperar espacio pero también puede crear picos de trabajo según la configuración.
qcow2 con backing file: los snapshots son cómodos, pero vigila el overhead
qcow2 es conveniente: snapshots, compresión, cifrado. También añade metadata copy-on-write y riesgo de fragmentación. Bajo cargas de escrituras aleatorias, qcow2 puede provocar stutter notable comparado con raw sobre un dispositivo de bloque.
Si necesitas rendimiento y latencia estable, prefiere raw sobre un backend de bloque. Si necesitas funciones, usa qcow2 pero acepta que quizá debas sobreaprovisionar y desfragmentar/convertir periódicamente.
Tareas prácticas: comandos, salidas y decisiones (12+)
Estas son tareas reales que puedes ejecutar hoy. Cada una incluye: un comando, salida realista, qué significa y la decisión que tomas.
Tarea 1: Identificar el controlador de disco de la VM y el modo de caché en la configuración de Proxmox
cr0x@server:~$ qm config 103
boot: order=scsi0
cores: 4
memory: 8192
name: api-prod-03
net0: virtio=DE:AD:BE:EF:10:03,bridge=vmbr0
ostype: l26
scsihw: virtio-scsi-single
scsi0: rpool:vm-103-disk-0,cache=none,discard=on,iothread=1
agent: 1
Qué significa: scsihw es el modelo de controlador; la línea del disco muestra cache=none, discard activado e iothread habilitado.
Decisión: si ves sata0 o scsihw: lsi con IO intenso, planifica migrar a virtio-scsi-single o virtio-blk. Si la caché es writeback, valida protección de energía y dominio de fallo.
Tarea 2: Confirmar qué lanzó realmente QEMU (confía pero verifica)
cr0x@server:~$ ps -p $(pgrep -f "kvm -id 103") -o pid,cmd --cols 200
PID CMD
24891 /usr/bin/kvm -id 103 -name vm103 -m 8192 -smp 4 ... -device virtio-scsi-pci,id=scsihw0 ... -device scsi-hd,drive=drive-scsi0 ... -drive file=/dev/zvol/rpool/vm-103-disk-0,if=none,id=drive-scsi0,format=raw,cache=none,aio=native ...
Qué significa: confirma el cache efectivo y el modo AIO; a veces los ajustes de la GUI difieren de la realidad por configs antiguas o overrides.
Decisión: si esperabas aio=native y ves threads, investiga compatibilidad del backend y la versión de Proxmox.
Tarea 3: Comprobar latencia y saturación del almacenamiento host bajo carga
cr0x@server:~$ iostat -x -d 1 3
Device r/s w/s rMB/s wMB/s avgrq-sz avgqu-sz await svctm %util
nvme0n1 40.0 620.0 2.8 62.5 205.0 5.2 8.4 0.3 19.0
Qué significa: el dispositivo no está saturado (%util bajo), la latencia es aceptable.
Decisión: si la latencia del dispositivo host es baja pero el guest tartamudea, sospecha del controlador/caché/queueing dentro de las capas de virtualización.
Tarea 4: Identificar qué proceso genera IO en el host
cr0x@server:~$ pidstat -d 1 3
Linux 6.8.12-pve (pve01) 12/26/2025 _x86_64_ (32 CPU)
# Time UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
12:01:01 0 24891 120.00 54000.00 0.00 12 kvm
12:01:01 0 1820 0.00 220.00 0.00 1 zfs
Qué significa: el proceso QEMU de la VM es responsable de la mayoría de las escrituras; estás mirando lo correcto.
Decisión: si el IO viene de pvestatd, backups o trabajos de replicación, programa/limita esos antes de afinar discos de VM.
Tarea 5: Comprobar latencia y profundidad de cola a nivel guest
cr0x@server:~$ iostat -x 1 3
Device r/s w/s rkB/s wkB/s aqu-sz await r_await w_await %util
vda 5.0 220.0 320.0 22000.0 8.4 38.0 2.0 38.8 98.0
Qué significa: el guest ve el disco casi saturado con alto tiempo de espera de escritura. Podría ser latencia real del backend, o cola virtual/flush behavior.
Decisión: correlaciona con iostat del host. Si la latencia del host es baja pero las esperas del guest son altas, sospecha de queueing en controladores/emulación o tormentas de flush.
Tarea 6: Comprobar si el guest emite muchos flushes
cr0x@server:~$ grep -R . /sys/block/vda/queue/* 2>/dev/null | egrep "write_cache|nomerges|max_sectors_kb"
/sys/block/vda/queue/max_sectors_kb:1280
/sys/block/vda/queue/nomerges:0
/sys/block/vda/queue/write_cache:write through
Qué significa: write through sugiere que el dispositivo presenta caché write-through; el comportamiento de flush importa.
Decisión: si las cargas son fsync-intensas y ves tormentas de flush, prefiere cache=none y asegúrate de que el backend maneje bien writes síncronas (ZFS SLOG, Ceph tuning, etc.).
Tarea 7: Medir comportamiento de commit del sistema de archivos en el guest (presión de journaling)
cr0x@server:~$ dmesg | tail -n 8
[ 9123.112233] EXT4-fs (vda2): re-mounted. Opts: (null)
[ 9450.774411] INFO: task jbd2/vda2-8:341 blocked for more than 120 seconds.
[ 9450.774419] Tainted: G W 6.5.0-28-generic #29-Ubuntu
[ 9450.774425] "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
Qué significa: el hilo del journal ext4 está bloqueado—síntoma clásico de stalls de almacenamiento, a menudo en rutas de flush/commit.
Decisión: trata esto como señal de severidad. Arregla la latencia de IO primero; no “tunes ext4” para ocultarlo.
Tarea 8: Comprobar presión de memoria del host (thrashing de caché host causa stutter)
cr0x@server:~$ free -h
total used free shared buff/cache available
Mem: 125Gi 98Gi 1.2Gi 2.0Gi 26Gi 18Gi
Swap: 16Gi 12Gi 4.0Gi
Qué significa: el uso de swap es alto; el host puede estar bajo presión de memoria, haciendo el IO más lento y con más jitter.
Decisión: reduce overcommit, añade RAM o deja de usar la caché de páginas del host para discos VM. cache=none ayuda reduciendo dependencia de caché host, pero aún necesitas memoria suficiente.
Tarea 9: Inspeccionar salud del pool ZFS e indicadores de latencia (si usas ZFS)
cr0x@server:~$ zpool status -v
pool: rpool
state: ONLINE
config:
NAME STATE READ WRITE CKSUM
rpool ONLINE 0 0 0
mirror-0 ONLINE 0 0 0
nvme0n1 ONLINE 0 0 0
nvme1n1 ONLINE 0 0 0
errors: No known data errors
Qué significa: el pool está sano; no hay dispositivos fallando que causen reintentos.
Decisión: si ves vdevs degradados o conteos de errores creciendo, deja de afinar VMs y reemplaza hardware / resilvera primero.
Tarea 10: Comprobar ajustes de sync de ZFS y si estás “haciendo trampa” con durabilidad
cr0x@server:~$ zfs get -o name,property,value -s local,default sync rpool
NAME PROPERTY VALUE
rpool sync standard
Qué significa: las escrituras síncronas se honran normalmente.
Decisión: si alguien puso sync=disabled para “arreglar rendimiento”, trátalo como un riesgo. Si el rendimiento es inaceptable con sync=standard, añade un SLOG adecuado o rediseña el almacenamiento.
Tarea 11: Confirmar que discard/TRIM está habilitado end-to-end (thin pools y SSDs)
cr0x@server:~$ qm config 103 | grep -E "discard|ssd|iothread"
scsi0: rpool:vm-103-disk-0,cache=none,discard=on,iothread=1
Qué significa: discard está habilitado en la capa de disco virtual.
Decisión: si usas aprovisionamiento delgado (LVM-thin, comportamiento thin de ZFS, Ceph), considera discard para evitar explosiones de espacio—pero prueba, porque discard puede introducir ráfagas según el comportamiento del guest.
Tarea 12: Comprobar que el guest usa drivers virtio y multiqueue (lado guest)
cr0x@server:~$ lsmod | egrep "virtio_blk|virtio_scsi|scsi_mod" | head
virtio_scsi 28672 2
scsi_mod 274432 3 virtio_scsi,sd_mod,sg
virtio_pci 32768 0
virtio_ring 40960 2 virtio_net,virtio_scsi
Qué significa: virtio-scsi está cargado; no estás en drivers SATA emulados.
Decisión: si los módulos virtio no están presentes, puede que estés usando el controlador equivocado o un initramfs viejo. Arregla la disponibilidad de drivers antes de cambiar otras perillas.
Tarea 13: Medir comportamiento flush-intenso con un rápido y honesto test fio (host o guest)
cr0x@server:~$ fio --name=syncwrite --filename=/var/lib/testfile --size=1G --rw=randwrite --bs=4k --iodepth=1 --numjobs=1 --direct=1 --sync=1 --time_based --runtime=20
syncwrite: (groupid=0, jobs=1): err= 0: pid=2123: Thu Dec 26 12:10:01 2025
write: IOPS=3200, BW=12.5MiB/s (13.1MB/s)(250MiB/20001msec)
clat (usec): min=120, max=42000, avg=310.42, stdev=900.12
Qué significa: la latencia máxima de finalización alcanza 42ms en esta corrida; en casos de stutter verás cientos de ms o segundos.
Decisión: si la latencia máxima es enorme, céntrate en la ruta síncrona: modo de caché, manejo sync del backend (ZFS SLOG), salud de Ceph o saturación del host.
Tarea 14: Comprobar ajustes de la cola de bloque del host (a veces un limitador silencioso)
cr0x@server:~$ cat /sys/block/nvme0n1/queue/scheduler
[mq-deadline] none kyber bfq
Qué significa: el host usa mq-deadline, un valor sensato para muchas cargas SSD/NVMe.
Decisión: si estás en discos rotacionales, bfq o deadline pueden cambiar la latencia. Para NVMe, cambiar el scheduler no suele ser la primera solución, pero puede ayudar en equidad bajo cargas mixtas.
Tarea 15: Encontrar si backups/replicación chocan con el IO de la VM
cr0x@server:~$ systemctl list-timers --all | egrep "pve|vzdump" || true
Thu 2025-12-26 12:30:00 UTC 20min left Thu 2025-12-26 12:00:01 UTC 9min ago vzdump.timer vzdump backup job
Qué significa: hay un job de backup programado y puede estar corriendo frecuentemente; los backups pueden crear read storms y overhead de snapshots.
Decisión: si el stutter se alinea con ventanas de backup, limita/agenda backups, usa modos de snapshot apropiados para el backend e intenta aislar IO de backups.
Tres micro-historias corporativas desde las trincheras IO
Micro-historia 1: El incidente causado por una suposición equivocada
El equipo heredó un cluster Proxmox que “funcionaba mayormente” para servicios internos. Se desplegó una API orientada al cliente en una VM con un pequeño PostgreSQL. La carga no era masiva; solo constante. En un día, on-call empezó a ver picos periódicos de latencia y timeouts extraños. Las gráficas de CPU parecían correctas. Red aburrida. La VM estaba “sana”.
Alguien supuso que el backend de almacenamiento era lento y aplicó el arreglo habitual: “habilitar caché writeback, lo suaviza”. Lo hizo—temporalmente. El throughput mejoró. El stutter se redujo, lo que hizo que el cambio pareciera brillante. Luego el host se rebotó inesperadamente tras un evento de energía lo suficientemente largo como para evitar un apagado limpio pero corto para arruinarte la noche.
La base de datos volvió con síntomas de corrupción: segmentos WAL faltantes y estado inconsistente. Postgres hizo lo suyo y se negó a fingir que todo estaba bien. La recuperación funcionó, pero no fue rápida ni divertida. Lo incómodo no fue la caída; fue darse cuenta de que el “arreglo de rendimiento” había cambiado el contrato de durabilidad sin que nadie lo autorizara explícitamente.
Lo que realmente solucionó el stutter después no fue writeback. Moveron el disco de VM de un controlador emulado a virtio-scsi-single, habilitaron iothread, usaron cache=none y arreglaron la latencia sync del backend con un dispositivo optimizado para escrituras. La latencia se aplanó. La durabilidad se mantuvo. La lección quedó: nunca asumas que un modo de caché es solo un dial de velocidad.
Micro-historia 2: La optimización que salió mal
Otra organización corría cargas mixtas: runners de CI, un pipeline de ingestión de logs y algunos servicios stateful. Vieron que los jobs de CI eran lentos en fases intensas de disco, así que tunearon agresivamente. Cambiaron discos a virtio-blk, pusieron un iodepth alto en el guest y habilitaron discard por doquier para mantener tidy los thin pools.
Los benchmarks se veían mejor. El equipo de CI celebró. Dos semanas después, el sistema de ingestión de logs empezó a tartamudear en horas pico. No era CPU. No era red. Era picos de latencia de IO—afilados, periódicos y molesta y difícil de correlacionar. Lo peor: no era una sola VM. Era un patrón en muchas.
La causa raíz fue una combinación de “buenas ideas” que se sincronizaron mal: operaciones de discard desde muchas VMs generaban ráfagas de trabajo en el backend, y el tuning de iodepth creó colas más grandes, así que las colas de latencia empeoraron bajo contención. El cluster no estaba “más lento”, estaba más jittery. El p50 mejoró mientras el p99 empeoró—la forma clásica de engañarte con promedios.
La solución fue aburrida: limitar el comportamiento de discard (programarlo en vez de constante), reducir la profundidad de cola para VMs sensibles a latencia, reintroducir virtio-scsi-single para ciertos huéspedes multi-disco y usar iothreads selectivamente. Mantuvieron las mejoras de CI sin sacrificar el pipeline de ingestión. La victoria real fue aprender que el tuning es una negociación entre cargas, no un número único.
Micro-historia 3: La práctica correcta y aburrida que salvó el día
Una compañía financiera corría Proxmox con mirrors ZFS sobre NVMe. No era glamoroso ni barato, pero sensato. Tenían la costumbre que parecía burocracia: cada VM tenía un runbook con tipo de controlador, modo de caché, si iothread estaba activado y por qué. Nada sofisticado—suficiente para evitar improvisaciones.
Un día, una actualización de kernel más un cambio de carga en el guest dispararon stalls intermitentes en una VM crítica. La VM era un broker de mensajes y odiaba los picos de latencia. Los usuarios vieron procesamiento retrasado. Los ingenieros empezaron a buscar “qué cambió”. El runbook hizo rápido el trabajo: no había cambios recientes en la config de VM. Controlador y caché eran los esperados. Eso descartó la mitad de sospechas habituales.
Fueron directo a métricas del host y vieron latencia elevada en un NVMe durante ráfagas. No estaba fallando, pero se comportaba raro. Sospecharon un bug de firmware. Gracias a configuraciones consistentes pudieron reproducir el problema con tests dirigidos y aislarlo al dispositivo en vez de discutir sobre flags de QEMU.
La mitigación fue limpia: migrar los discos de la VM fuera del dispositivo sospechoso, aplicar firmware y validar con los mismos perfiles fio usados en tests de aceptación. La caída quedó contenida. El postmortem fue corto. Nadie tuvo que explicar por qué se hizo un cambio “rápido” de writeback a las 3 a.m. Las prácticas aburridas no son virales; mantienen los ingresos pegados a la realidad.
Errores comunes: síntoma → causa raíz → arreglo
1) Síntoma: congelamientos periódicos de 1–5 segundos; el throughput medio parece correcto
Causa raíz: tormentas de flush desencadenadas por fsync/commits de journal + modo de caché/backend que convierte el flush en un bloqueo.
Arreglo: usa cache=none, habilita iothread, asegúrate de que el backend maneje escrituras síncronas (ZFS con SLOG adecuado, Ceph sano y sin recovery), evita qcow2 para cargas muy síncronas.
2) Síntoma: el guest muestra alto await, el host muestra bajo await
Causa raíz: contention en colas virtuales/controlador (cola compartida), contención del main-loop de QEMU o modo AIO subóptimo.
Arreglo: cambia a virtio-scsi-single o verifica multiqueue de virtio-blk donde aplique; habilita iothread; considera aio=native para rutas de IO directas.
3) Síntoma: el stutter empieza después de habilitar writeback “por velocidad”
Causa raíz: flush y throttling de la caché de páginas del host; la presión de memoria lo amplifica.
Arreglo: revierte a cache=none a menos que tengas protección contra pérdida de energía; añade RAM o reduce overcommit de memoria; aísla VMs con IO intensivo en almacenamiento dedicado.
4) Síntoma: el thin pool se llena inesperadamente; la VM se vuelve lenta al acercarse al lleno
Causa raíz: presión de metadatos en LVM-thin y comportamiento near-full; discard no habilitado o ineficaz.
Arreglo: monitoriza uso de thin pool, mantén margen libre, habilita discard con cuidado, ejecuta fstrim periódicamente en guests y evita sobreaprovisionar thin pools para cargas con muchas escrituras.
5) Síntoma: VMs con Ceph tartamudean durante el día, bien de noche
Causa raíz: recovery/backfill o rendimiento desigual de OSDs durante horas laborales; contención de red.
Arreglo: ajusta límites de recuperación, arregla OSDs lentos, asegura capacidad de red dedicada de almacenamiento y verifica que opciones cliente no estén empeorando la cola de latencia.
6) Síntoma: VM multi-disco; la carga de un disco interrumpe al otro
Causa raíz: controlador/cola compartida y sin aislamiento por iothread; efectos del scheduler/merge de IO.
Arreglo: usa virtio-scsi-single, habilita iothreads por disco y separa discos por propósito (DB vs logs) con prioridades claras.
7) Síntoma: benchmarks muestran gran throughput secuencial, la app aún tartamudea
Causa raíz: perfil de benchmark incorrecto; la carga real son pequeñas escrituras aleatorias síncronas con requisitos estrictos de latencia.
Arreglo: prueba con random writes de 4k/8k, iodepth bajo y patrones sync/fsync. Optimiza para las colas de latencia, no para MB/s pico.
Listas de verificación / plan paso a paso
Paso a paso: el plan “arreglar stutter sin jugarse la integridad”
- Mide primero. Captura
iostat -xen guest y host durante el stutter. Guarda las salidas en el ticket. - Verifica tipo de controlador. Si estás en SATA/IDE/LSI emulado sin motivo, programa el cambio a virtio.
- Elige un controlador:
- Por defecto virtio-scsi-single para VMs Linux de propósito general.
- Usa virtio-blk si quieres simplicidad y tienes un solo disco ocupado, y lo has probado y se comporta bien.
- Configura el modo de caché a
none. Es la opción de rendimiento más segura para la mayoría de sistemas en producción. - Habilita iothread para los discos ocupados. Empieza con uno por disco importante.
- Confirma el modo AIO. Prefiere
aio=nativecuando esté soportado y estable; si no, acepta threads y confía en iothreads. - Revisa la memoria del host. Si el host hace swap, tendrás comportamientos extraños de IO. Arregla la presión de memoria.
- Arreglos específicos del backend:
- ZFS: asegúrate que la ruta de escrituras síncronas sea lo suficientemente rápida; considera un SLOG apropiado para VMs sync-intensas.
- Ceph: asegura que el cluster esté sano y no en recovery; detecta OSDs lentos.
- LVM-thin: mantén margen libre; vigila metadatos; administra discard.
- Vuelve a probar con IO realista. Usa fio con patrones sync, no solo escrituras secuenciales.
- Despliega gradualmente. Cambia una VM a la vez, valida latencia p95/p99 y luego estandariza.
Lista: qué evitar cuando estás cansado y de guardia
- No habilites
cache=unsafe. Nunca. - No ajustes ZFS
sync=disabledcomo “arreglo temporal” sin una autorización de riesgo por escrito. - No afines solo para throughput; las colas de latencia son lo que sienten los usuarios.
- No habilites discard por todas partes sin entender el comportamiento del backend thin.
- No asumas que “NVMe” significa “baja latencia”. Significa “más rápido en fallar ruidosamente cuando se satura”.
Cambios rápidos (con comandos)
Estos ejemplos son operaciones CLI típicas de Proxmox. Hazlos siempre en ventana de mantenimiento y con plan de backup/snapshot adecuado al backend.
cr0x@server:~$ qm set 103 --scsihw virtio-scsi-single
update VM 103: -scsihw virtio-scsi-single
Qué significa: establece el modelo del controlador SCSI.
Decisión: procede si el guest soporta virtio y puedes reiniciar si es necesario.
cr0x@server:~$ qm set 103 --scsi0 rpool:vm-103-disk-0,cache=none,iothread=1,discard=on
update VM 103: -scsi0 rpool:vm-103-disk-0,cache=none,iothread=1,discard=on
Qué significa: fuerza el modo de caché y habilita iothread en ese disco.
Decisión: tras el reinicio, valida latencia del guest y comportamiento de la aplicación.
Preguntas frecuentes
1) ¿Debo usar virtio-scsi-single o virtio-blk para VMs Linux?
Por defecto usa virtio-scsi-single si te importa la latencia predecible y tienes múltiples discos o IO mixto. Usa virtio-blk para VMs simples de un solo disco donde hayas medido y funciona bien.
2) ¿Es cache=none siempre la mejor opción?
Para la mayoría de entornos productivos en Proxmox: sí. Evita la doble caché y reduce efectos secundarios en memoria del host. Las excepciones son nichos—usualmente cuando usas la caché del host deliberadamente para lecturas intensas y tienes suficiente RAM y disciplina operativa.
3) ¿Por qué cache=writeback hizo mi VM “más rápida” pero menos estable?
Porque reconoce las escrituras antes al almacenarlas en RAM del host y las vacía después en ráfagas. Eso puede crear congelamientos periódicos y aumenta el riesgo en fallos de energía o caídas del host.
4) ¿Los iothreads siempre ayudan?
A menudo ayudan la latencia y la concurrencia, especialmente bajo carga. Pueden ser neutrales o levemente negativos en cargas muy pequeñas donde el overhead domina. Habilítalos para los discos que importan, no automáticamente para todos.
5) ¿Cómo sé si el stutter es del backend (ZFS/Ceph) o de la configuración de la VM?
Compara la latencia del dispositivo en el host (iostat -x en el host) con la latencia del guest. Si el host está bien pero el guest no, sospecha controlador/queueing/modo de caché virtual. Si el host está mal, arregla el backend primero.
6) ¿Puedo arreglar el stutter de ZFS poniendo sync=disabled?
Puedes “arreglarlo” como se arregla una alarma de humo sacando la pila. Cambia durabilidad por velocidad. Si necesitas rendimiento con cargas sync-intensas, usa un SLOG correcto con protección contra pérdida de energía o ajusta la arquitectura.
7) ¿qcow2 causa stutter?
Puede hacerlo, especialmente bajo escrituras aleatorias y cuando está fragmentado. Si necesitas latencia estable para bases de datos, prefiere raw sobre un backend de bloque (zvol, LV de LVM, RBD) a menos que realmente necesites funciones de qcow2.
8) ¿Debo habilitar discard/TRIM para discos de VM?
A menudo sí para SSD con aprovisionamiento delgado, pero con deliberación. El discard continuo puede crear ráfagas de trabajo en el backend. Muchos equipos prefieren fstrim periódico en el guest más discard habilitado en la capa virtual y luego medir.
9) ¿Por qué mi disco de VM se llama sda aunque seleccioné virtio?
Dentro del guest, los nombres dependen del driver y controlador. virtio-blk suele verse como vda. virtio-scsi suele aparecer como sda pero sigue usando transporte virtio. Verifica con lspci/lsmod, no solo por el nombre del dispositivo.
10) ¿Cuál es la razón más común de stutter “aleatorio” en Proxmox?
Contención a nivel de host: backups, replicación, scrubs, recovery de Ceph o un disco saturado. La segunda más común: caché writeback más presión de memoria que crea tormentas de flush.
Conclusión: próximos pasos que puedes hacer hoy
Si tu VM Linux en Proxmox tiene stutter en disco, no empieces por el folklore. Empieza con un bucle corto de medición: vmstat/iostat en el guest, iostat en el host y las flags reales de QEMU de la VM. Luego aplica los cambios que mejoran de forma fiable las colas de latencia:
- Pasa de controladores emulados a virtio-scsi-single o virtio-blk.
- Prefiere cache=none a menos que puedas justificar writeback con protección de energía y garantías operativas.
- Habilita iothread para los discos que concentran el dolor de tu carga.
- Haz arreglos específicos del backend en lugar de culpar a la VM: ruta sync de ZFS, salud de Ceph, margen en thin pools.
Hazlo en una VM, mide p95/p99 antes y después, y luego estandariza el patrón. Si aún tartamudea después de eso, enhorabuena: eliminaste las causas fáciles y ahora toca ingeniería real. Ese es el trabajo.