Sorpresas del inflado de memoria en Ubuntu 24.04: establezca límites sensatos y detenga las tormentas de swap (Caso n.º 16)

¿Te fue útil?

Comienza como una “pequeña ralentización”. Luego tus gráficos se vuelven arte moderno: el load average sube, aparecen picos de latencia, los discos parecen muy ocupados sin hacer nada útil y la VM se siente como si corriera en melaza.

En Ubuntu 24.04, en entornos virtualizados, el inflado de memoria (ballooning) puede pasar de ser una “función útil de sobrecommit” a un “generador de tormentas de swap”. La solución rara vez es mística. Normalmente son límites, contabilidad y una decisión firme sobre qué quieres que ocurra cuando la memoria escasea de verdad.

Qué está pasando realmente cuando el ballooning duele

El inflado de memoria existe porque a los hipervisores les gusta jugar al Tetris con la RAM. Una VM puede estar configurada para 16 GB, pero la mayor parte del tiempo usa 4–8 GB. El ballooning permite al host reclamar la parte “no usada” y prestarla a otras VMs. En teoría, todos ganan.

En la práctica, el ballooning puede producir un tipo específico de miseria:

  • El host reclama memoria del invitado en el peor momento posible (porque el host está bajo presión).
  • El kernel del invitado ve menos RAM disponible y empieza a recuperar páginas.
  • Si la recuperación no encuentra suficiente page cache “limpio”, empieza a hacer swap de memoria anónima.
  • El I/O de swap ocurre en discos virtuales, que a menudo están en almacenamiento compartido, lo que amplifica la latencia.
  • Ahora el invitado va más lento, avanza más despacio, permanece bajo presión más tiempo y por tanto hace más swap.

El ballooning en sí no es maligno. El problema es el ballooning sin límites combinado con un sobrecommit optimista y pocas salvaguardas. La forma más fácil de describirlo: estás haciendo gestión de memoria en dos sitios (host e invitado) con dos kernels que no comparten cerebro.

Aquí está la parte que sorprende a la gente en Ubuntu 24.04: el ecosistema por defecto alrededor de la presión de memoria se ha vuelto más “proactivo”. cgroup v2 es estándar, systemd actúa con más iniciativa y el comportamiento ante OOM puede parecer distinto que en una LTS anterior. Nada de eso es malo. Solo significa que tus viejas suposiciones pueden fallar de forma ruidosa.

Una cita para mantenerte honesto (idea parafraseada): la mentalidad de fiabilidad de Gene Kranz: “Debemos ser duros y competentes—sin excusas.” Se aplica también a los límites de memoria.

Broma #1: El inflado de memoria es como una mesa “temporal” que montas en la cocina. Es temporal hasta que arruina la cena y no encuentras los tenedores.

Hechos interesantes y contexto (lo que explica las rarezas)

  1. El ballooning no es nuevo. VMware popularizó drivers de balloon hace décadas; el virtio-balloon de KVM lo hizo común en open stacks.
  2. Linux no trata el swap como un fallo por defecto. El kernel hará swap para mantener el cache de archivos y suavizar picos; eso es racional hasta que el backend de almacenamiento hace que el swap sea caro.
  3. cgroup v2 cambió el juego. La contabilidad de memoria es más estricta y unificada. Si fijas un memory.max, es un muro real, no una sugerencia educada.
  4. “Available” no es “free”. la métrica “available” en Linux estima lo que se puede recuperar sin hacer swap. La gente aún se asusta con “free”. No debería.
  5. kswapd es un síntoma, no un villano. Alto uso de CPU de kswapd significa que el kernel está intentando recuperar páginas bajo presión. La causa raíz casi siempre es: poca RAM para el working set.
  6. Las tormentas de swap son contagiosas. Una VM que hace mucho swap puede degradar la latencia del almacenamiento compartido, lo que ralentiza otras VMs, incrementa su presión de memoria y provoca más swap. Felicidades: has inventado un incidente de rendimiento a nivel de clúster.
  7. El ballooning puede parecer una fuga de memoria. Desde dentro del invitado, se siente como si la RAM hubiera desaparecido. Porque de hecho desapareció.
  8. zram es un compromiso moderno. El swap comprimido en RAM reduce I/O pero aumenta uso de CPU. Genial para algunas cargas; pésimo para otras.
  9. THP puede complicar la recuperación. Transparent Huge Pages puede hacer que la reclaim y la compactación sean más costosas durante la presión, dependiendo de la carga y la configuración.

Guía rápida de diagnóstico (primero/segundo/tercero)

Primero: confirma que la máquina está realmente bajo presión de memoria

  • Busca actividad de swap-in/out y fallos mayores.
  • Confirma si la memoria “available” es baja y se mantiene baja.
  • Revisa PSI (pressure stall information) para ver si el sistema se queda bloqueado esperando reclaim de memoria.

Segundo: prueba si el ballooning está involucrado

  • Comprueba la presencia del driver virtio_balloon y las estadísticas del balloon.
  • Correlaciona el comportamiento “host reclaimed” (si es visible) con caídas de memoria en el invitado.
  • En plataformas como Proxmox/OpenStack, compara memoria configurada vs efectiva y cambios en el objetivo de balloon.

Tercero: identifica qué está consumiendo memoria y si es recuperable

  • Procesos principales por RSS y por memoria anónima.
  • Cache de archivos vs uso anónimo (el page cache es tu amigo hasta que deja de serlo).
  • Límites de cgroup y topes por servicio.

Decide el resultado que deseas

  • Si quieres latencia predecible: reduce el overcommit, establece límites rígidos y prefiere OOM kill en lugar de tormentas de swap.
  • Si quieres máxima consolidación: permite ballooning pero fija un mínimo, usa zram con cuidado y monitoriza PSI como si fuera ingresos.

Tareas prácticas: comandos, salidas y la decisión que tomas

Estas son las tareas que realmente ejecuto cuando una VM “misteriosamente se volvió lenta”. Cada una incluye lo que significa la salida y la decisión que sigue. Ejecútalas en orden hasta que tengas una historia coherente. Si no puedes explicar la historia, no toques los mandos todavía.

Tarea 1: Comprueba el estado principal de memoria (y no lo leas mal)

cr0x@server:~$ free -h
               total        used        free      shared  buff/cache   available
Mem:           7.7Gi       6.9Gi       112Mi       204Mi       731Mi       402Mi
Swap:          4.0Gi       3.6Gi       128Mi

Qué significa: Available es solo 402 MiB y el swap está muy usado. Ya estás en “modo reclaim”.

Decisión: Pasa inmediatamente a vmstat y PSI para ver si está ocurriendo thrashing activo o si el swap es de un evento pasado.

Tarea 2: Detecta tormenta de swap activa vs “el swap pasó antes”

cr0x@server:~$ vmstat 1 10
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 2  1 3718920 112640  55872 634320  64  128   220   310  410  790 12  8 55 25  0
 3  2 3721048  98304  53120 621104 256  512   880  1200  610 1200 14 10 35 41  0
 4  2 3723096  94208  51456 609440 512 1024  1300  1900  720 1400 12  9 28 51  0
 3  1 3725144  90112  49664 601112 128  256   600   840  540 1100 10  7 45 38  0

Qué significa: Valores si/so no nulos en múltiples muestras junto con alto wa indican swap activo y espera de I/O. Esto no es “swap viejo”.

Decisión: Necesitas detener la presión. O añades memoria / reduces ballooning / reduces la carga / o aceptas OOM para un radio de impacto acotado.

Tarea 3: Comprueba PSI para confirmar bloqueos (Ubuntu 24.04 lo tiene)

cr0x@server:~$ cat /proc/pressure/memory
some avg10=18.24 avg60=12.10 avg300=5.43 total=218443992
full avg10=6.12 avg60=3.98 avg300=1.22 total=68423992

Qué significa: “some” indica que las tareas se retrasan a veces; “full” indica que el sistema se queda totalmente bloqueado esperando memoria. Valores no triviales en full correlacionan fuertemente con dolor visible para el usuario.

Decisión: Trátalo como un incidente. Deja de pensar “quizá es CPU” hasta que la presión de memoria esté controlada.

Tarea 4: Comprueba si existe el driver de balloon (lado invitado)

cr0x@server:~$ lsmod | grep -E 'virtio_balloon|vmw_balloon'
virtio_balloon         24576  0

Qué significa: el balloon virtio está disponible. Eso no prueba que se esté inflando activamente, pero hace plausible que el ballooning sea la causa.

Decisión: Inspecciona las estadísticas del balloon para ver si el invitado cree que le han quitado memoria.

Tarea 5: Inspecciona estadísticas de virtio balloon (si están expuestas)

cr0x@server:~$ grep -H . /sys/devices/virtio*/balloon*/{num_pages,actual,free_page_hint} 2>/dev/null
/sys/devices/virtio0/virtio0/balloon/num_pages:2097152
/sys/devices/virtio0/virtio0/balloon/actual:1572864
/sys/devices/virtio0/virtio0/balloon/free_page_hint:1

Qué significa: El valor “actual” del balloon indica las páginas actualmente ballooned (reclamadas). Si esto crece durante la presión del host, tu VM verá menos RAM utilizable.

Decisión: Si las páginas ballooned son significativas respecto al total, deja de tratar la RAM como “garantizada”. Fija un mínimo o desactiva el ballooning para VMs críticas con requisitos de latencia.

Tarea 6: Confirma memoria configurada vs la que ve el kernel

cr0x@server:~$ grep -E 'MemTotal|MemAvailable' /proc/meminfo
MemTotal:        8049136 kB
MemAvailable:     411224 kB

Qué significa: MemTotal es lo que el invitado cree que tiene actualmente. Si “configuraste 16G” pero MemTotal es ~8G, hay ballooning (o hotplug) en juego.

Decisión: Alinea las expectativas con la realidad. Si la VM necesita 16G, no permitas que el host la trate como una alcancía.

Tarea 7: Identifica los principales consumidores de memoria por RSS (triage rápido)

cr0x@server:~$ ps -eo pid,comm,rss --sort=-rss | head -n 10
  PID COMMAND           RSS
 2481 java           2381440
 1822 postgres       1024320
 1999 node            612480
 1320 snapd           188224
  911 systemd-journal  123456
 2766 python3          112320

Qué significa: Tienes grandes consumidores de memoria anónima (p. ej., JVM) y servicios con estado. Matar el proceso equivocado puede “arreglar el swap” y también “borrar tu negocio”.

Decisión: Para los grandes consumidores conocidos (JVM, bases de datos), verifica la configuración de heap/buffers. La presión de memoria causada por ballooning puede exponer valores por defecto demasiado optimistas en las apps.

Tarea 8: Comprueba problemas de reclaim del kernel (compactación, THP)

cr0x@server:~$ grep -E 'compact|thp' /proc/vmstat | head
compact_migrate_scanned 184221
compact_free_scanned 91234
compact_isolated 1422
thp_fault_alloc 1221
thp_collapse_alloc 18

Qué significa: Alta actividad de compactación bajo presión puede aumentar el tiempo de CPU en reclaim y bloquear cargas de trabajo. Las asignaciones/collapse de THP pueden añadir sobrecarga.

Decisión: Si ves compactación intensa junto a PSI “full”, considera ajustar THP o garantizar más margen en lugar de “optimizar el swap”.

Tarea 9: Busca eventos OOM o casi OOM (journal)

cr0x@server:~$ journalctl -k --since "2 hours ago" | egrep -i "oom|out of memory|kswapd|memory pressure" | tail -n 20
Dec 30 09:41:12 server kernel: Memory cgroup out of memory: Killed process 1999 (node) total-vm:3128448kB, anon-rss:598112kB, file-rss:2048kB
Dec 30 09:41:12 server kernel: oom_reaper: reaped process 1999 (node), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB

Qué significa: El kernel mató algo debido a cgroup memory OOM. Eso no es aleatorio; es un límite que se está haciendo cumplir (o una fuga que alcanzó la pared).

Decisión: Si prefieres esto a las tormentas de swap, bien—hazlo intencional: fija límites de memoria por servicio y documéntalo. Si no, aumenta límites o reduce ballooning.

Tarea 10: Inspecciona dispositivos de swap y prioridades

cr0x@server:~$ swapon --show --bytes
NAME       TYPE      SIZE        USED       PRIO
/swap.img  file  4294967296  3865051136       -2

Qué significa: Un swapfile está en uso, baja prioridad. Si vive en almacenamiento lento o contendido, el swapping castigará a todos.

Decisión: Decide si mantener swap (a menudo sí, pero más pequeño) y si usar zram, o desactivar swap en VMs críticas por latencia (con salvaguardas).

Tarea 11: Comprueba knobs de comportamiento de swap (swappiness, vfs cache pressure)

cr0x@server:~$ sysctl vm.swappiness vm.vfs_cache_pressure
vm.swappiness = 60
vm.vfs_cache_pressure = 100

Qué significa: Valores casi por defecto. Bajo presión por ballooning, el kernel podría hacer swap antes de lo que deseas para ciertas cargas.

Decisión: Para VMs de bases de datos/latencia, considera bajar swappiness (p. ej., 10–20) después de establecer límites sensatos. Afinar sin límites es desear sin fundamento.

Tarea 12: Confirma restricciones de cgroup v2 (a nivel sistema)

cr0x@server:~$ stat -fc %T /sys/fs/cgroup
cgroup2fs

Qué significa: Estás en cgroup v2. Ubuntu 24.04 lo habilita por defecto.

Decisión: Usa los controles v2 (memory.max, memory.high, memory.swap.max). Deja de aplicar consejos de la era v1 textualmente.

Tarea 13: Comprueba si systemd-oomd está activo (puede cambiar resultados)

cr0x@server:~$ systemctl is-enabled systemd-oomd
enabled

Qué significa: El demonio OOM de usuariospace puede intervenir basado en señales de presión. Eso puede ser bueno (recuperación más rápida) o confuso (kills inesperados).

Decisión: Si ejecutas cargas críticas mono-inquilino, configura scopes/slices de oomd o desactívalo deliberadamente—no lo “descubras” en medio de un incidente.

Tarea 14: Detecta saturación de I/O causada por swap (el ángulo “es almacenamiento”)

cr0x@server:~$ iostat -xz 1 3
Linux 6.8.0-41-generic (server) 	12/30/2025 	_x86_64_	(4 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          11.01    0.00    7.32   39.88    0.00   41.79

Device            r/s     rkB/s   rrqm/s  %rrqm r_await rareq-sz     w/s     wkB/s   wrqm/s  %wrqm w_await wareq-sz  aqu-sz  %util
vda              42.0   1120.0     0.0   0.00   18.5    26.7      51.0   2496.0     2.0   3.77   44.2    48.9     3.3   95.0

Qué significa: Alto %util, alto await y muchas escrituras pequeñas: clásico churn de swap en disco virtual.

Decisión: Arregla la presión de memoria primero. Afinar almacenamiento no podrá superar a una VM que se está matando a swap en un backing compartido.

Tarea 15: Confirma si estás en una VM y qué hipervisor (ayuda a escoger los knobs)

cr0x@server:~$ systemd-detect-virt
kvm

Qué significa: Invitado KVM. El ballooning probablemente es basado en virtio; qemu-guest-agent también puede ser relevante según tu plataforma.

Decisión: En pilas KVM, decide: desactivar ballooning para cargas críticas, o fijar un piso y monitorizar cambios en el objetivo del balloon.

Tarea 16: Comprueba memory.high/memory.max para un servicio específico (culpable real en sistemas modernos)

cr0x@server:~$ systemctl show -p ControlGroup -p MemoryMax -p MemoryHigh nginx.service
ControlGroup=/system.slice/nginx.service
MemoryMax=infinity
MemoryHigh=infinity

Qué significa: Sin control de memoria explícito. Si nginx no es el gran consumidor, está bien. Para servicios pesados, “infinity” significa que pueden competir hasta que toda la máquina sufra.

Decisión: Pon límites de memoria en el puñado de servicios que pueden balloonar de forma impredecible (workers, JVMs, herramientas de build, trabajos por lotes).

Establecer límites sensatos: hipervisor, invitado y cgroup v2

El ballooning se vuelve tolerable cuando estableces límites. En producción, “compartir sin límites” es simplemente una interrupción con optimismo.

1) Decide tu contrato de memoria: garantizada, escalable o best-effort

Cada VM debería tener uno de estos contratos:

  • Garantizada: la memoria está reservada. Ballooning desactivado o mínimo cercano al máximo. Usado para bases de datos, servicios de control y SLOs de latencia.
  • Escalable (Burstable): se permite cierta reclaim, pero hay un suelo firme. Usado para capas web, caches y servidores de aplicaciones que pueden descargar carga.
  • Best-effort: ballooning activado, piso bajo, puede swapear/oomear. Usado para dev, CI, batch y workers efímeros.

2) En el hipervisor: deja de prometer la misma RAM a todos

Sea la stack que sea—libvirt, Proxmox, OpenStack—el concepto es el mismo:

  • Fija una memoria máxima (lo que la VM podría tener).
  • Fija una memoria mínima/reservada (lo que debería conservar siempre).
  • Limita el overcommit a nivel host a algo que puedas soportar cuando las cargas coincidan mal.

¿Por qué? Porque reclamar no es gratis. El invitado puede tener que swapear páginas anónimas para satisfacer la inflación del balloon, y tu almacenamiento pagará la factura.

3) En el invitado: usa límites de cgroup v2 para forzar contención local

Si no puedes controlar el host perfectamente (bienvenido a la Tierra), al menos puedes contener el daño en el invitado. cgroup v2 te da tres herramientas importantes:

  • memory.max: límite duro. Si lo excedes, obtienes un OOM kill en ese cgroup.
  • memory.high: límite blando. El kernel hará throttle/reclaim para mantener el uso cerca de este valor.
  • memory.swap.max: limita el uso de swap por cgroup (poderoso para prevenir tormentas de swap originadas por un solo servicio).

Para servicios gestionados por systemd, normalmente se establecen mediante overrides de unidad. Así se ve para un servicio “escalable pero acotado”.

cr0x@server:~$ sudo systemctl edit worker.service
[Service]
MemoryHigh=2G
MemoryMax=3G
MemorySwapMax=512M

Qué significa: El servicio puede usar memoria, pero no puede consumir la máquina. Si crece más de 3G, se le mata en lugar de empujar toda la VM al swap.

Decisión: Usa esto para componentes no confiables o con picos: trabajos en background, consumidores de mensajes, “un exporter más” y cualquier cosa escrita en un lenguaje que pueda descubrir el infinito.

4) Haz las decisiones OOM deliberadas: kernel vs systemd-oomd

Ubuntu 24.04 puede involucrar systemd-oomd, que actúa en base a PSI y presión de memoria de cgroups. Esto no es el clásico OOM killer del kernel. Disparadores distintos, comportamiento distinto, a veces más temprano y a menudo más limpio.

Lo que haces:

  • Si quieres contención estricta por servicio, mantén oomd habilitado y gestiona slices.
  • Si ejecutas un monolito crítico y oomd podría matar lo equivocado, ajústalo o desactívalo—pero solo después de tener contención alternativa (límites, reservas).
cr0x@server:~$ systemctl status systemd-oomd --no-pager
● systemd-oomd.service - Userspace Out-Of-Memory (OOM) Killer
     Loaded: loaded (/usr/lib/systemd/system/systemd-oomd.service; enabled; preset: enabled)
     Active: active (running) since Mon 2025-12-30 08:01:12 UTC; 2h 10min ago

Qué significa: Está en ejecución y actuará si las slices configuradas cruzan umbrales de presión.

Decisión: Si no lo planeaste, pláncalo ahora. “Kills inesperados de procesos” no es una estrategia de monitorización.

Detener las tormentas de swap: opciones de swap, swappiness y control de presión

El swap no es ni puro bueno ni puro malo. Es una herramienta. En VMs, también es una trampa de rendimiento porque el I/O de swap suele ser la ruta más lenta y contendida de toda la pila.

Elige tu modelo de swap

Normalmente quieres uno de estos:

  • Swap pequeño + low swappiness: Suficiente para evitar OOM catastrófico durante picos pequeños, pero no suficiente para sostener un thrash prolongado.
  • zram swap: Swap en RAM con compresión. Reduce I/O en disco pero consume CPU. Bueno para dev y cargas con picos; evaluarlo en producción con uso intensivo de CPU.
  • Sin swap (rara vez correcto): Solo cuando tienes reservas estrictas y prefieres fallo inmediato antes que comportamiento degradado. Funciona mejor con buenos límites y alertas.

Tarea: Comprueba si ya estás usando zram

cr0x@server:~$ lsblk -o NAME,TYPE,SIZE,MOUNTPOINT | grep -E 'zram|swap'
zram0 disk  2G

Qué significa: Existe un dispositivo zram. Puede o no estar configurado como swap.

Decisión: Confirma con swapon --show. Si tienes zram y swap en disco, fija prioridades intencionalmente.

Tarea: Establece swappiness de forma persistente (solo después de arreglar límites)

cr0x@server:~$ sudo tee /etc/sysctl.d/99-memory-sane.conf >/dev/null <<'EOF'
vm.swappiness=15
vm.vfs_cache_pressure=100
EOF
cr0x@server:~$ sudo sysctl --system | tail -n 5
* Applying /etc/sysctl.d/99-memory-sane.conf ...
vm.swappiness = 15
vm.vfs_cache_pressure = 100

Qué significa: El kernel preferirá recuperar cache antes que swapear memoria anónima comparado con el comportamiento por defecto, aunque aún puede swapear bajo presión real.

Decisión: Si la VM hace swap porque ballooning le quitó RAM, ajustar swappiness ayuda en los márgenes. La solución principal es dejar de robar la RAM en primer lugar.

Limita swap por servicio (la corrección infravalorada)

Las tormentas de swap suelen empezar con un proceso codicioso. Si limitas su swap, no podrá esparcir su sufrimiento por toda la máquina.

cr0x@server:~$ sudo systemctl set-property batch-jobs.service MemorySwapMax=0
cr0x@server:~$ systemctl show -p MemorySwapMax batch-jobs.service
MemorySwapMax=0

Qué significa: Ese servicio no puede usar swap. Si se queda sin memoria, será OOM-killed dentro de su cgroup.

Decisión: Aplica esto a cargas donde “ir lento en swap por 30 minutos” es peor que “fallar rápido y reintentar”. El procesamiento por lotes es candidato ideal.

Entiende por qué las tormentas de swap parecen fallos de almacenamiento

Como ingeniero de almacenamiento, diré la parte silenciosa en voz alta: una VM que hace mucho swap es indistinguible de un ataque DoS al almacenamiento—excepto que es autoinfligido y autenticado.

El swap produce:

  • Pequeñas escrituras aleatorias (page-outs), más lecturas en fallos.
  • Altas profundidades de cola y latencia aumentada.
  • Dispositivos de backing contendidos (especialmente en pools SSD compartidos o almacenamiento en red).

Cuando ves un “incidente de latencia de almacenamiento” y un invitado está haciendo swap, arregla primero el contrato de memoria del invitado. Luego habla de IOPS.

Broma #2: Las tormentas de swap son el único sistema meteorológico que se forma en interiores, y aun así consigue tumbar tu servicio.

Tres mini-historias corporativas (cómo falla esto en empresas reales)

Mini-historia #1: El outage causado por una suposición equivocada

La compañía tenía una creencia ordenada: “La memoria configurada es memoria garantizada.” Habían estado ejecutando invitados Ubuntu más antiguos en un clúster KVM con ballooning habilitado durante años, y la mayoría de VMs nunca se quejaron. Los dashboards mostraban que cada VM “tenía 16 GB”, así que la gente dimensionó aplicaciones en consecuencia.

Entonces un incidente a nivel de host ocurrió: un nodo físico perdió un canal DIMM y el clúster reequilibró. El overcommit seguía habilitado. El scheduler empaquetó cargas para mantener capacidad. El ballooning se infló en varios invitados a la vez, porque el host necesitaba memoria de inmediato, no de forma educada.

Dentro de una VM crítica con Ubuntu 24.04, MemTotal no cambió en la cabeza de nadie, pero efectivamente sí cambió: available cayó, el reclaim se disparó y el kernel empezó a swapear partes del heap de una JVM. La latencia aumentó, lo que provocó que las colas de peticiones crecieran, reteniendo más heap. Fue un bucle de retroalimentación con una bonita interfaz gráfica.

El equipo inicialmente persiguió CPU steal y “vecino ruidoso en almacenamiento”. Ambos eran reales, pero secundarios. El evento primario fue la remoción de memoria vía ballooning durante la presión del host. Su suposición era errónea: la memoria configurada no era una promesa; era un máximo.

La solución fue aburrida y efectiva: desactivar ballooning para la capa SLO, fijar reservas de host para VMs críticas y dejar de overcommittear memoria en los nodos que soportaban servicios stateful. El coste subió un poco. Los incidentes bajaron mucho.

Mini-historia #2: La optimización que salió mal

Una organización distinta intentó ser lista: habilitaron ballooning en todas partes y redujeron tamaños base de VM, apostando a que “Linux usa cache y puede devolverlo”. Tenían razón técnicamente y eran operativamente imprudentes.

Durante unas semanas, funcionó genial. Cabía más VMs por host. Finanzas sonrió. Luego un despliegue rutinario lanzó una build que incrementó el uso de memoria de un servicio de indexado en background. No era una fuga—solo más estructuras de datos.

Bajo ballooning, esas VMs vivían al borde. Cuando el indexador subió, el invitado recuperó page cache y empezó a swapear. El rendimiento del indexador cayó, por lo que tardó más y se mantuvo memory-hot más tiempo. Mientras tanto, el I/O de swap golpeó el mismo pool de almacenamiento usado por volúmenes de base de datos.

La “optimización” se convirtió en un incidente de degradación multi-servicio: peticiones web expiraban, commits de la base de datos se enlentecían y on-call miraba gráficos de almacenamiento preguntándose por qué la latencia de escritura subió durante “bajo tráfico”. No era bajo tráfico; era swap alto.

Revirtieron el despliegue y vieron recuperación parcial, pero la solución real fue arquitectónica: separar capas por contrato de memoria, fijar pisos de balloon y aplicar topes por servicio para que una tarea en background no pueda convertir todo el clúster en un desastre en cámara lenta.

Mini-historia #3: La práctica aburrida pero correcta que salvó el día

Esta es menos dramática, y ese es el punto. Un equipo ejecutaba invitados Ubuntu 24.04 para runners de CI internos y una pequeña API de producción. Tenían una regla estricta: VMs de producción con ballooning desactivado, swap capado y límites de systemd definidos. Los runners de CI eran best-effort y descartables.

Un día el clúster de hipervisores experimentó presión de memoria inesperada después de que una actualización de firmware del proveedor cambiara características de potencia/rendimiento. El host empezó a reclamar agresivamente. Varias VMs best-effort se ralentizaron de inmediato.

La producción se mantuvo estable. No porque tuviera “más memoria”, sino porque tenía una reserva real y nada de reclaim por balloon. Las VMs de la API tenían un swap pequeño, swappiness bajo y un memory.max en una unidad de procesamiento de logs en background. Cuando la presión subió, la unidad en background murió y se reinició. La latencia de la API casi no se movió.

El informe del incidente fue corto: “La presión de memoria del host afectó a la capa best-effort. Producción no afectada gracias a reservas y límites de servicio.” Sin heroísmos, sin buceo en el kernel, sin hilos de Slack para soporte emocional.

No ganaron un premio por ello. Lanzaron funciones según lo previsto mientras el resto aprendía, otra vez, que la previsibilidad se compra con restricciones.

Errores comunes: síntoma → causa raíz → corrección

1) Síntoma: el load average se dispara, el uso de CPU parece moderado

Causa raíz: presión de memoria que bloquea y espera de I/O por swapping. El load incluye tareas esperando I/O y reclaim, no solo ejecución de CPU.

Corrección: Confirma con vmstat (si/so), iostat (await/util) y PSI. Luego reduce ballooning y/o añade margen de memoria. Baja swappiness solo después de fijar el contrato.

2) Síntoma: “Tenemos 16 GB configurados, pero MemTotal es ~8 GB”

Causa raíz: Ballooning/hotplug significa que el invitado no está reteniendo la memoria máxima actualmente.

Corrección: Fija un mínimo/reserva de balloon en el hipervisor para esa VM, o desactiva ballooning para esa capa. Verifica volviendo a comprobar MemTotal y estadísticas de balloon.

3) Síntoma: el uso de swap sigue creciendo aunque el tráfico sea plano

Causa raíz: El working set excede la RAM tras la reclaim por ballooning; el kernel swapea páginas anonimas para sobrevivir.

Corrección: Incrementa memoria garantizada o reduce la reclaim. Si el crecimiento está ligado a un único servicio, capéalo con cgroup v2 memory.max y memory.swap.max.

4) Síntoma: kills de procesos aleatorios que parecen “impredecibles”

Causa raíz: systemd-oomd o cgroup memory OOM está aplicando límites (o reglas de presión por slice por defecto).

Corrección: Haz el control de memoria explícito: fija MemoryMax/High en servicios, define slices y decide qué componentes deben morir primero. Revisa la configuración de oomd en lugar de asumir comportamiento solo del kernel.

5) Síntoma: incidente de latencia de almacenamiento sin carga obvia

Causa raíz: churn de swap de una VM genera I/O constante pequeño. En almacenamiento compartido parece un vecino ruidoso.

Corrección: Encuentra el invitado que está swappeando vía iostat/vmstat en el invitado y métricas del host. Detén la presión de memoria. Afinar almacenamiento no arreglará un churn sostenido de swap.

6) Síntoma: “Desactivamos swap y ahora nos caemos más”

Causa raíz: No hay colchón para picos transitorios; además no hay contención por servicio, así que un pico dispara OOM a nivel sistema.

Corrección: Reintroduce swap pequeño o zram, pero añade límites de cgroup para contener picos. Prefiere fail-fast para servicios no críticos con MemorySwapMax=0.

7) Síntoma: kswapd con CPU alto y stutters frecuentes durante GC o compactación

Causa raíz: Overhead de reclaim y compactación durante presión de memoria, potencialmente empeorado por THP.

Corrección: Añade margen y reduce ballooning primero. Luego considera cambios en el modo THP para cargas específicas si la compactación es un centro de coste recurrente.

Listas de verificación / plan paso a paso

Paso a paso: estabilizar una VM Ubuntu 24.04 que está swappeando ahora mismo

  1. Prueba que es presión de memoria: ejecuta free -h, vmstat 1 10 y cat /proc/pressure/memory. Si PSI full no es trivial y si/so están activos, estás en una tormenta de swap.
  2. Encuentra al culpable: usa ps ordenado por RSS. Confirma si es un único proceso o presión general.
  3. Revisa el ballooning: confirma que virtio_balloon está cargado e inspecciona estadísticas de balloon. Si MemTotal es menor de lo esperado, trátalo como reclaim por balloon hasta probar lo contrario.
  4. Reduce el daño inmediato: si un servicio en background es el culpable, capéalo con MemoryMax y opcionalmente MemorySwapMax. Reinicia ese servicio para volver a un estado estable.
  5. Detén el daño colateral al almacenamiento: si el swap está golpeando el disco, aligera carga o escala temporalmente. No “tunes iostat”.
  6. Haz la corrección permanente: decide el contrato de memoria de la VM y ajusta la reserva/ballooning del host y los límites del invitado en consecuencia.

Checklist: decisiones de configuración que previenen incidentes repetidos

  • Para cada VM: clasifica como garantizada/escalable/best-effort. Escríbelo.
  • Para VMs garantizadas: desactiva ballooning o fija un mínimo alto. Evita swap pesado; mantiene un swap pequeño si lo toleras.
  • Para VMs escalables: fija mínimo de balloon, usa cgroup memory.high/max para servicios conocidos hog y considera topes de swap.
  • Para best-effort: permite ballooning, pero implementa reintentos y automatización. Trata OOM como un modo de fallo normal.
  • Monitorización: alerta en PSI memory full, tasa sostenida de swap-out y picos de disk await/util correlacionados con swap.
  • Gestión de cambios: cualquier cambio en la política de overcommit del host es un cambio de producción. Pásalo por la misma revisión que una migración de base de datos.

Checklist: “deja de empeorarlo” durante un incidente

  • No reinicies primero. Reiniciar puede ocultar la causa y garantiza tiempo de inactividad.
  • No aumentes el tamaño de swap como respuesta primaria. Eso suele alargar la ventana de sufrimiento.
  • No “liberes caches” como reflejo. Si la presión es de memoria anónima, soltar cache no lo soluciona y puede aumentar lecturas en disco.
  • No cambies cinco sysctls a la vez. No sabrás qué ayudó y el tú del futuro odiará al tú del presente.

Preguntas frecuentes

1) ¿El inflado de memoria siempre es malo?

No. Es útil para consolidación y cargas con picos. Es malo cuando lo tratas como invisible y permites que reclame memoria de servicios críticos por latencia o stateful.

2) ¿Cómo sé si el ballooning es la causa y no una fuga de memoria de la aplicación?

Busca una discrepancia entre “lo que crees que la VM tiene” y MemTotal en /proc/meminfo, además de cambios en las estadísticas del virtio balloon. Una fuga suele mostrar un aumento sostenido en RSS de un proceso independiente de cambios en el objetivo del balloon.

3) ¿Debo desactivar el swap en VMs Ubuntu 24.04 para prevenir tormentas de swap?

A veces, pero rara vez como primer movimiento. Desactivar swap sin reservas de memoria y sin límites por servicio convierte “lento y degradado” en “rápido y muerto”. Prefiere swap pequeño + contención, o zram cuando proceda.

4) ¿Cuál es la mejor métrica única para alertar?

El PSI “memory full” sostenido por encima de un pequeño umbral es difícil de ignorar porque mide directamente el tiempo pasado bloqueado por presión de memoria. Combínalo con la tasa de swap-out.

5) ¿Por qué la VM parece tener mucha memoria en la UI del hipervisor, pero el invitado está swap

Porque las UIs a menudo muestran la memoria máxima configurada, no la memoria efectiva actual tras ballooning. Confía en la visión del invitado y en las estadísticas del balloon.

6) ¿Bajar vm.swappiness arregla problemas de ballooning?

Puede reducir la predisposición del kernel a swapear, pero no puede fabricar RAM. Si el ballooning quita demasiada memoria, seguirás sufriendo reclaim—solo en distinto orden.

7) ¿Es zram un buen valor por defecto en VMs?

Depende. zram intercambia I/O de disco por CPU. En sistemas con CPU libre pero almacenamiento contendido, es una victoria. En cargas CPU-bound (o cuando el steal de CPU es alto), puede empeorar la latencia cola.

8) ¿Cómo dimensiono el swap en una VM que no debe thrash?

Pequeño: lo suficiente para absorber picos breves (o capturar un dump si haces eso), no suficiente para permitir que la VM “sobreviva” en un estado permanentemente degradado. Luego usa topes de swap por cgroup para ofensores conocidos.

9) ¿Cuál es la mejor práctica para bases de datos bajo ballooning?

No las balloonees. Reserva memoria. Si debes usar ballooning, fija un piso alto y asegura que los ajustes de memoria de la base de datos (buffers, caches) no asuman que el máximo siempre estará presente.

10) ¿Por qué todo se vuelve lento aunque solo un servicio consuma mucha memoria?

Porque el reclaim global y el I/O de swap afectan al sistema entero: tiempo de CPU dedicado a reclaim, colas de I/O llenas y latencia en cascada. Contén el uso de memoria a nivel de servicio.

Conclusión: pasos prácticos siguientes

Si Ubuntu 24.04 en una VM te sorprende con tormentas de swap impulsadas por ballooning, el remedio no es un sysctl mágico. Es un contrato de memoria explícito y su aplicación en las capas correctas.

  1. En 30 minutos: ejecuta la guía rápida de diagnóstico, captura free, vmstat, PSI y iostat. Prueba si el swap está activo y si el ballooning está involucrado.
  2. En un día: clasifica las VMs en garantizadas/escalables/best-effort y ajusta mínimos de ballooning o desactiva ballooning para las capas garantizadas.
  3. En una semana: añade límites de cgroup v2 para el puñado de servicios que pueden dominar la memoria, y limita swap para los servicios donde “fallar rápido” es mejor que “ir lento para siempre”.
  4. Siempre: alerta en PSI memory full y en swap-out sostenido. Trata las tormentas de swap como una clase de incidente, no como un misterio.

El ballooning está bien cuando puedes permitir sorpresas. La producción generalmente no puede. Establece límites como si los tomaras en serio, y tu almacenamiento dejará de gritar a las 3 a.m.

← Anterior
Riesgos de la red host de Docker: cuándo merece la pena y cómo limitar el daño
Siguiente →
Un clic de phishing: cómo las empresas acaban en los titulares

Deja un comentario