Ubuntu 24.04: Sorpresas de ballooning de memoria — establece límites sensatos y detén las tormentas de swap (caso #76)

¿Te fue útil?

Empieza como “la VM se siente lenta”. Diez minutos después es “¿por qué kswapd está ocupando un core?”, y una hora después alguien sugiere “simplemente añadir swap” como si fuera agua bendita para el fuego.

Ubuntu 24.04 es perfectamente capaz de funcionar estable bajo presión de memoria. Pero si mezclas ballooning, overcommit, límites de cgroup y valores por defecto optimistas, puedes fabricar una tormenta de swap que parece un problema de CPU, de disco y de red al mismo tiempo. No es magia. Es contabilidad, y tiene solución.

Qué significa realmente “sorpresas de ballooning de memoria”

El ballooning es simple en teoría: el hipervisor reclama RAM de una VM que “no la está usando” y se la da a otra cosa. En la práctica, es una negociación con información imperfecta.

Dentro del invitado (Ubuntu 24.04 aquí), el driver de balloon fija páginas para que el invitado crea que tiene menos memoria utilizable. Si el invitado luego necesita esa memoria, el hipervisor puede “desinflar” el globo—si puede. Ese “si” es donde nacen los cortes.

Una tormenta de swap es lo que ocurre cuando un sistema pasa más tiempo moviendo páginas entre RAM y swap que haciendo trabajo útil. La forma clásica:

  • sube la presión de RAM → las páginas anónimas se intercambian (swap)
  • la carga de trabajo toca páginas swap → explotan las faltas mayores
  • sube la espera de E/S → aumentan las latencias de las peticiones
  • la CPU se consume en reclaim y faltas de página → la máquina “tiene CPU” pero nada termina

Ahora añade las sorpresas del ballooning:

  • la memoria “disponible” del invitado cae rápidamente (el globo se infla), a veces más rápido de lo que el invitado puede recuperar limpiamente.
  • el invitado empieza a hacer swap aunque el host todavía tenga memoria en conjunto—o el host también esté presionado, y ambas capas reclamen al mismo tiempo.
  • las métricas parecen contradictorias: el invitado muestra swapping; el host muestra memoria libre; las latencias de la aplicación parecen de disco; y la CPU está ocupada pero improductiva.

La parte opinable: si necesitas predictibilidad, deja de tratar la memoria como una sugerencia flotante. Pon límites duros donde corresponda, dimensiona el swap intencionalmente y elige una capa para que haga reclaim primero. Si no, estás ejecutando un sistema de paginación distribuida, y nadie lo pidió.

Broma #1 (corta, relevante): El swap es como llevar cajas a un trastero durante una mudanza—útil hasta que necesitas la tostadora ahora mismo.

Datos interesantes y contexto histórico (corto, útil, un poco nerd)

  1. El ballooning es anterior a la “nube”. VMware popularizó el ballooning a principios de los 2000 para mejorar las ratios de consolidación—hasta que las cargas se volvieron espasmódicas y sensibles a latencia.
  2. Virtio-balloon es cooperativo. Depende del invitado para devolver páginas. Si el invitado ya está luchando, el ballooning puede amplificar el dolor porque el propio invitado hace el trabajo.
  3. El swapping en Linux no es “un bug”. Históricamente Linux usó swap de forma proactiva para aumentar la caché de páginas; los kernels modernos son más matizados, pero la actividad de swap por sí sola no es una condena.
  4. cgroup v2 hizo el control de memoria más estricto y observable. Es más difícil evitar límites por accidente y más fácil ver presión y eventos—si realmente miras.
  5. PSI (Pressure Stall Information) es relativamente nuevo. Se fusionó alrededor del kernel Linux 4.20 para cuantificar “tiempo empleado esperando recursos”, haciendo medible eso de “la máquina se siente lenta”.
  6. systemd-oomd es un matador en espacio de usuario con opinión. Puede actuar antes que el OOM killer del kernel para preservar el sistema, lo que sorprende a quienes suponen que OOM significa “solo cuando ya no queda memoria”.
  7. Los defaults de overcommit difieren según el entorno. Los hipervisores overcommit; Linux puede overcommit; los contenedores pueden overcommit. Súmalos y obtienes una matrioska de optimismo.
  8. Las tormentas de swap empeoraron con discos rápidos. NVMe hace que el swap sea “menos terrible”, lo que tienta a apoyarse en él; eso a menudo solo mueve el cuello de botella a la CPU y a la varianza de latencia.

Guía de diagnóstico rápido: encuentra el cuello de botella en minutos

Este es el orden que gana en producción porque separa “estamos presionados por memoria” de “estamos confundidos”. Hazlo primero en el invitado, luego en el host si puedes.

1) Confirma que es presión de memoria, no solo “alto uso de RAM”

  • Revisa la tasa de swap-in y las faltas mayores (dolor real).
  • Revisa la PSI de memoria (tiempo detenido).
  • Revisa la CPU de reclaim (kswapd y reclaim directo).

2) Identifica dónde ocurre el reclaim (¿invitado? ¿host? ¿ambos?)

  • En el invitado, confirma el estado del driver de balloon y el tamaño actual del globo si se expone.
  • En el host, comprueba el objetivo del balloon de la VM y la actividad de swap del host.

3) Determina el plano de control que realmente aplica límites

  • cgroup v2 memory.max / memory.high para servicios y contenedores
  • actividad y registros de systemd-oomd
  • límites de memoria / política de ballooning del hipervisor

4) Decide la mitigación inmediata

  • Si la latencia está en llamas: reduce el objetivo del balloon o asigna RAM real (no solo añadas swap).
  • Si un servicio es codicioso: plafónalo vía cgroups, o reinícialo con un heap / conteo de workers menor.
  • Si el host está presionado: deja de overcommit, migra cargas o aligera la carga—el swap del host pone tristes a todos los invitados.

Tareas prácticas: comandos, salidas y decisiones (12+)

Estos no son “ejecuta y siéntete bien” comandos. Cada uno tiene una interpretación y una decisión adjunta. Úsalos durante un incidente y otra vez en el postmortem.

Task 1: Check memory and swap totals quickly

cr0x@server:~$ free -h
               total        used        free      shared  buff/cache   available
Mem:            15Gi        13Gi       210Mi       220Mi       1.8Gi       820Mi
Swap:          8.0Gi       6.4Gi       1.6Gi

Qué significa: “available” por debajo de 1 GiB más uso intenso de swap sugiere presión activa, no solo memoria caché.

Decisión: Si la latencia es alta y el uso de swap está creciendo, pasa a las Tareas 2–4 inmediatamente. Si el swap usado es alto pero estable y PSI es bajo, podrías estar bien.

Task 2: Measure swap-in/out and reclaim churn

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
 3  1 671088  180000  12000 860000 420  980  1800  2100 3200 8800 18 12 10 58  2
 2  1 672000  175000  12000 850000 300  760  1400  1800 3100 8200 16 11 12 59  2
 4  2 675000  165000  11000 845000 520 1200  2200  2400 3500 9300 20 14  8 56  2
 2  1 677000  160000  11000 840000 410  900  1700  2000 3200 8600 18 12 10 58  2
 3  2 679000  155000  10000 835000 600 1300  2400  2600 3600 9700 21 15  7 55  2

Qué significa: si/so no nulos de forma continua indican swapping activo. Alto wa significa que la espera de E/S domina. Este es el perfil de una tormenta de swap.

Decisión: Mitigar reduciendo la presión de memoria (más RAM / menos ballooning / limitar ofensores). Ajustar swappiness no te salvará en medio de la tormenta.

Task 3: Look for major page faults (the “I touched swapped pages” counter)

cr0x@server:~$ sar -B 1 3
Linux 6.8.0-xx-generic (server)  12/31/25  _x86_64_ (8 CPU)

12:01:11 AM  pgpgin/s pgpgout/s   fault/s  majflt/s  pgfree/s pgscank/s pgscand/s pgsteal/s   %vmeff
12:01:12 AM   1800.00   2400.00  52000.00   980.00  14000.00   9200.00     0.00   6100.00    66.30
12:01:13 AM   1600.00   2100.00  50500.00  1020.00  13200.00   8900.00     0.00   5800.00    65.10
12:01:14 AM   2000.00   2600.00  54000.00  1100.00  15000.00   9800.00     0.00   6400.00    65.30

Qué significa: majflt/s cerca de 1000 es brutal para la mayoría de servicios. Cada falta mayor es “espera por disco”.

Decisión: Para detener la hemorragia: reduce el ballooning, mata/reinicia el procesos consumidor de memoria, o escala horizontalmente. Si no haces nada, tendrás timeouts y reintentos en cascada.

Task 4: Read PSI memory pressure (guest-side truth serum)

cr0x@server:~$ cat /proc/pressure/memory
some avg10=35.20 avg60=22.10 avg300=10.88 total=987654321
full avg10=12.90 avg60=7.40 avg300=3.20 total=123456789

Qué significa: “some” = tareas detenidas por presión de memoria; “full” = nadie puede ejecutar porque la memoria es el cuello de botella. Un “full” sostenido no trivial es un incendio.

Decisión: Si full avg10 está por encima de ~1–2% en sistemas sensibles a latencia, trátalo como incidente de producción. Reduce la presión; no discutas con la matemática.

Task 5: See if kswapd is burning CPU

cr0x@server:~$ top -b -n1 | head -n 15
top - 00:01:40 up 12 days,  3:22,  1 user,  load average: 8.22, 7.90, 6.40
Tasks: 243 total,   4 running, 239 sleeping,   0 stopped,   0 zombie
%Cpu(s): 21.3 us, 14.9 sy,  0.0 ni,  6.8 id, 56.0 wa,  0.0 hi,  1.0 si,  0.0 st
MiB Mem :  16384.0 total,   15870.0 used,    220.0 free,    180.0 buff/cache
MiB Swap:   8192.0 total,    6600.0 used,   1592.0 free.    820.0 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
  431 root      20   0       0      0      0 R  85.0   0.0 120:22.11 kswapd0
 2331 app       20   0 5020.0m 3900.0m  4200 S  60.0  23.8  88:10.22 java
 1102 postgres  20   0 2800.0m 1500.0m  9800 S  25.0   9.2  40:11.03 postgres

Qué significa: Alta espera de E/S más kswapd0 caliente significa que el reclaim está thrashing. La CPU está “ocupada” haciendo tareas administrativas.

Decisión: No optimices la aplicación aún. Arregla la presión de memoria primero; si no, estarás afinando la lista de reproducción de un barco que se hunde.

Task 6: Inspect what the kernel thinks about memory and reclaim

cr0x@server:~$ egrep 'MemTotal|MemAvailable|SwapTotal|SwapFree|Active|Inactive|Dirty|Writeback|AnonPages|Mapped|SReclaimable' /proc/meminfo
MemTotal:       16384000 kB
MemAvailable:     780000 kB
SwapTotal:       8388604 kB
SwapFree:        1654320 kB
Active:          8920000 kB
Inactive:        5120000 kB
Dirty:            184000 kB
Writeback:          2400 kB
AnonPages:      11200000 kB
Mapped:           420000 kB
SReclaimable:     520000 kB

Qué significa: Gran AnonPages implica memoria anónima (heaps, no caché). De ahí viene la presión de swap.

Decisión: Si es mayormente anónima, necesitas menos procesos, heaps más pequeños o más RAM. Si es mayormente caché, podrías recuperar limpiamente.

Task 7: Find top memory consumers (RSS, not VIRT)

cr0x@server:~$ ps -eo pid,comm,rss,pmem --sort=-rss | head
 2331 java        3998200 24.3
 1102 postgres    1543000  9.4
 4120 node        1022000  6.2
 1887 redis-server  620000  3.8
 2750 python       410000  2.5
  987 systemd-journald 180000  1.1
 1450 nginx        90000  0.5
  812 snapd        82000  0.5
  701 multipathd   62000  0.3
  655 unattended-upgr 52000  0.3

Qué significa: RSS es memoria residente real. Un solo proceso grande puede desestabilizar todo el invitado una vez que el ballooning reduce el margen.

Decisión: Si un proceso domina, plafónalo (cgroup), reconfigúralo o reinícialo de forma intencionada—antes de que el sistema lo haga por ti.

Task 8: Check cgroup v2 memory limits for a systemd service

cr0x@server:~$ systemctl show myapp.service -p MemoryMax -p MemoryHigh -p OOMPolicy -p ManagedOOMMemoryPressure
MemoryMax=infinity
MemoryHigh=infinity
OOMPolicy=stop
ManagedOOMMemoryPressure=auto

Qué significa: Sin techos de memoria. La app puede consumir todo el invitado. Con ballooning, esto se vuelve ruleta rusa.

Decisión: Para hosts críticos, establece MemoryHigh (estrangulamiento suave) y MemoryMax (tope duro) para forzar predictibilidad.

Task 9: Inspect the cgroup’s actual memory current and events

cr0x@server:~$ CG=$(systemctl show -p ControlGroup --value myapp.service); echo $CG
/system.slice/myapp.service
cr0x@server:~$ cat /sys/fs/cgroup$CG/memory.current
4219031552
cr0x@server:~$ cat /sys/fs/cgroup$CG/memory.events
low 0
high 182
max 3
oom 1
oom_kill 1

Qué significa: El servicio alcanzó memory.high muchas veces e incluso tocó memory.max. Hubo un OOM kill dentro del cgroup.

Decisión: Si high aumenta rápido, ajusta la app (heap/workers) o sube límites. Si max/oom_kill incrementa, estás infra-provisionado o mal configurado.

Task 10: See swap usage per process (smaps rollup)

cr0x@server:~$ sudo awk '/^Swap:/ {sum+=$2} END {print sum " kB"}' /proc/2331/smaps_rollup
1540000 kB

Qué significa: Ese proceso tiene ~1.5 GiB en swap. Aunque siga “corriendo”, probablemente se detiene cada vez que toca regiones swap.

Decisión: Si es un servicio sensible a latencia, reiniciarlo (tras reducir su huella) puede ser la menos mala de las opciones. También arregla la causa upstream (ballooning/límites).

Task 11: Check kernel swappiness and reclaim knobs

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

Qué significa: Swappiness 60 es el valor por defecto genérico. No es “malo”, pero no está adaptado para VMs ballooned que ejecutan aplicaciones sensibles a latencia.

Decisión: Tras el incidente, considera vm.swappiness=1030 para muchos servidores, pero solo después de arreglar dimensionamiento y límites.

Task 12: Confirm which swap devices are active and their priority

cr0x@server:~$ swapon --show --bytes
NAME        TYPE  SIZE        USED       PRIO
/swapfile   file  8589934592  7088373760   -2

Qué significa: Un único swapfile, prioridad baja (normal). Si tienes múltiples backends de swap, las prioridades deciden adónde van las páginas primero.

Decisión: Si introduces zram, dale mayor prioridad que el swap en disco para comprimir antes de alcanzar latencias de E/S.

Task 13: Observe disk latency during swapping

cr0x@server:~$ iostat -x 1 3
Linux 6.8.0-xx-generic (server)  12/31/25  _x86_64_ (8 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          18.20    0.00   12.10   56.30    2.10    11.30

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
nvme0n1         420.0   16800.0     2.0   0.47    9.80    40.0   520.0   22000.0     3.0   0.57   18.50    42.3   12.10  98.00

Qué significa: %util ~98% y altas esperas mean que el disco está saturado. Bajo swapping, incluso NVMe puede convertirse en el cuello de botella.

Decisión: Si el swap es la carga, añadir “disco más rápido” no es la solución. Reduce el swapping primero.

Task 14: Check for host-side memory ballooning from within the guest (driver presence)

cr0x@server:~$ lsmod | grep -E 'virtio_balloon|vmw_balloon|xen_balloon'
virtio_balloon         24576  0
virtio                   16384  2 virtio_balloon,virtio_net

Qué significa: El driver virtio balloon está cargado, así que el ballooning puede estar activo dependiendo de la política del hipervisor.

Decisión: Si no toleras memoria dinámica, coordina con el equipo de hipervisor para deshabilitar o restringir el ballooning para esta clase de VM.

Task 15: See recent OOM and oomd actions

cr0x@server:~$ journalctl -k -g -i 'oom|out of memory' --since '1 hour ago' | tail -n 20
Dec 31 00:12:02 server kernel: Out of memory: Killed process 4120 (node) total-vm:2103456kB, anon-rss:980000kB, file-rss:12000kB, shmem-rss:0kB, UID:1001 pgtables:3400kB oom_score_adj:0
Dec 31 00:12:02 server kernel: oom_reaper: reaped process 4120 (node), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB
cr0x@server:~$ journalctl -u systemd-oomd --since '1 hour ago' | tail -n 20
Dec 31 00:11:58 server systemd-oomd[812]: Killed /system.slice/myapp.service due to memory pressure for /system.slice.

Qué significa: Tanto el OOM killer del kernel como systemd-oomd pueden matar. Si oomd mató primero, tu “evento OOM” puede ocurrir antes de que la RAM llegue a cero.

Decisión: Decide qué política quieres: oomd proactivo (generalmente bueno para mantener la respuesta del host) o “dejar que el kernel lo maneje” (a menudo más ruidoso). Configura deliberadamente.

Causas raíz: ballooning, overcommit, reclaim y por qué ocurren las tormentas de swap

El ballooning es una apuesta sobre la estabilidad de la carga

El ballooning funciona mejor cuando:

  • los invitados tienen memoria inactiva predecible
  • las cargas no son sensibles a latencia
  • el host tiene suficiente margen para desinflar rápido

El ballooning funciona peor cuando:

  • el uso de memoria crece de repente (cachés que se calientan, despliegues, picos de tráfico)
  • el host también está presionado, así que la desinflación no puede ocurrir a tiempo
  • el invitado está configurado con swap generoso, enmascarando señales tempranas

Reclaim doble: el invitado hace swap, el host hace swap, todos pierden

Los incidentes más feos implican reclaim en dos capas. El invitado hace swap porque su “memoria física” ballooned se redujo. El host hace swap porque overcommitó demasiado. Ahora cada page-in del invitado puede desencadenar page-ins en el host. La latencia se convierte en una matrioska.

El overcommit de memoria es una decisión de política, no un valor por defecto heredado

Hay tres ideas de “overcommit” que la gente confunde:

  • Overcommit del hipervisor: asignar más vRAM total que la RAM del host, asumiendo que no todas las VMs alcanzarán picos simultáneamente.
  • Overcommit de memoria virtual de Linux: permitir que procesos reserven más memoria virtual que RAM+swap, según heurísticas.
  • Overcommit de densidad de contenedores: programar pods/contenedores con requests de memoria menores que los límites, apostando por medias.

Cualquiera de estos puede ser sensato. Súmalos y estás construyendo una máquina de catástrofes en picos de carga.

Las tormentas de swap suelen ser desencadenadas por uno de cuatro patrones

  1. El objetivo del balloon cambia demasiado agresivamente (evento de presión del host, automatización de “redimensionado”, o un humano intentando ser listo).
  2. Un servicio basado en heap crece hasta entrar en thrash de GC, luego empieza a fallar como loco una vez swappeado.
  3. La caché de archivos no es el problema pero se afinan knobs de caché y se estrangula la memoria anónima.
  4. La desaceleración de E/S hace que el reclaim no siga el ritmo (vecino ruidoso en almacenamiento, RAID degradado, tormentas de snapshot), empujando al sistema a stalls de reclaim directo.

Hay una idea para tener en mente al diseñar límites: idea parafraseada — Gene Kim ha enfatizado que la fiabilidad viene de reducir la varianza y hacer el trabajo predecible, no de heroicidades.

Establece límites sensatos: VMs, contenedores y el host

“Límites sensatos” significa que decides qué capa puede decir “no” primero, y cómo. Mi preferencia para sistemas de producción que la gente critica:

  • Asignación dura para VMs críticas (o al menos pisos de balloon estrictos) para que los invitados tengan RAM predecible.
  • Límites de cgroup por servicio para que un proceso desbocado no convierta al SO en un benchmark de swap.
  • oomd configurado intencionalmente para matar lo correcto temprano, en lugar de que el kernel mate algo al azar tarde.

Capa VM: no dejes que “dinámico” signifique “sorprendente”

En muchos hipervisores puedes configurar:

  • memoria mínima (piso de balloon)
  • memoria máxima (tope)
  • shares/prioridad (quién obtiene memoria primero)

Reglas prácticas que funcionan con el tiempo:

  • Establece el piso al menos en el RSS en estado estable observado + margen de seguridad, no “la mitad del máximo porque parece bien”.
  • Para bases de datos y JVMs, los pisos deben ser conservadores; reclamar su working set las perjudica de inmediato.
  • Deshabilita el ballooning para cargas extremadamente sensibles a latencia o con picos si no has demostrado que es inocuo bajo pruebas de carga.

Capa contenedor: memory.max es tu cinturón de seguridad

Si ejecutas contenedores directamente bajo systemd (o incluso solo servicios), cgroup v2 ya está presente en Ubuntu 24.04. Úsalo.

Ejemplo: establece un límite suave y duro para un servicio systemd

cr0x@server:~$ sudo systemctl set-property myapp.service MemoryHigh=6G MemoryMax=7G
cr0x@server:~$ systemctl show myapp.service -p MemoryHigh -p MemoryMax
MemoryHigh=6442450944
MemoryMax=7516192768

Qué significa: El servicio será estrangulado alrededor de 6 GiB y será matado a 7 GiB (dependiendo del comportamiento y la política de oomd/kernel).

Decisión: Elige límites basados en tests de carga y RSS observado. Si no puedes probar, empieza conservador y vigila memory.events.

Capa host: detén el swap del host antes de que comience

El swap del host es un impuesto pagado por cada VM, en forma de latencia. Si tu host está swappeando, tu “vecino ruidoso” es el propio host.

Política de host que funciona:

  • evita ratios de overcommit que dependan de “nunca todos pican al mismo tiempo”
  • mantén el swap del host mínimo, o configúralo como freno de emergencia, no como vía diaria
  • monitorea PSI en el host y alerta por “full” sostenido de memoria

Estrategia de swap en Ubuntu 24.04: menos drama, más control

El swap no es malo. Es una herramienta. El problema es cuando se convierte en tu capa principal de memoria por accidente.

Elige un enfoque de swap que coincida con tu modo de fallo

  • Swapfile en disco: simple, persistente, puede absorber picos, pero puede causar amplificación de E/S bajo presión.
  • zram: swap comprimido en RAM, rápido, reduce E/S en disco, pero usa CPU y reduce la RAM efectiva con ratios altos de compresión.
  • Sin swap: fuerza OOM más pronto, puede ser aceptable para servicios sin estado que reinician limpiamente, arriesgado para sistemas que necesitan degradación controlada.

Mi postura habitual para VMs con cargas mixtas: swap en disco pequeño + zram (opcional) + límites fuertes de cgroup. La meta es sobrevivir picos cortos sin dejar que el sistema entre en deuda permanente de swap.

Haz de swappiness una política, no folclore

Reducir swappiness disminuye la propensión del kernel a swapear memoria anónima. Para muchos servidores de producción, 1030 es un rango inicial razonable. Para escritorios, los valores por defecto pueden estar bien. Para invitados ballooned, prefiero bajarlo porque el ballooning ya reduce el margen de forma impredecible.

cr0x@server:~$ sudo sysctl -w vm.swappiness=20
vm.swappiness = 20
cr0x@server:~$ printf "vm.swappiness=20\n" | sudo tee /etc/sysctl.d/99-swappiness.conf
vm.swappiness=20

Qué significa: Cambio inmediato y persistente.

Decisión: Si reduces swappiness, asegúrate también de tener suficiente margen de RAM; de lo contrario puedes alcanzar OOM más rápido. Eso puede ser deseable—si lo planeaste.

Cuándo ayuda zram (y cuándo no)

zram puede salvar en tormentas de swap porque reemplaza E/S lenta por compresión más rápida. Pero no es gratis: bajo presión sostenida, la CPU puede convertirse en el siguiente cuello de botella. Úsalo cuando tu modo de fallo sea espera por E/S y faltas mayores, no cuando tu CPU ya esté saturada.

Comprobación rápida: ¿ya tienes zram?

cr0x@server:~$ swapon --show
NAME       TYPE      SIZE   USED PRIO
/swapfile  file        8G   6.7G   -2
/dev/zram0 partition    2G     0B  100

Qué significa: zram existe y tiene mayor prioridad (100) que el swapfile (-2), lo cual es correcto si eliges este enfoque.

Decisión: Si ves que el swap en disco se golpea mientras zram está sin usar, las prioridades están mal. Corrige prioridades o deshabilita uno de los backends de swap.

Broma #2 (corta, relevante): Lo único que crece más rápido que el uso de swap es la confianza de alguien que “simplemente aumentó el swapfile”.

Comportamiento OOM: kernel OOM killer vs systemd-oomd

Ubuntu 24.04 suele ejecutarse con systemd-oomd disponible. El OOM killer del kernel es el último recurso cuando el kernel no puede asignar memoria. systemd-oomd es un motor de políticas que puede matar antes basado en señales de presión (PSI) y límites de cgroup.

Por qué deberías importarte

Si esperas “OOM solo cuando la RAM esté al 100%”, te sorprenderás. oomd puede matar un servicio cuando el sistema todavía está técnicamente vivo pero profundamente detenido. No es crueldad. Es triaje.

Qué configurar

  • Asegura que los servicios críticos estén protegidos (o separados) para que oomd no dispare sobre el objetivo equivocado.
  • Pon tus cargas principales en slices dedicados con presupuestos de memoria claros.
  • Decide si “matar al mayor ofensivo” es correcto, o si prefieres “matar al menos importante” vía estructura de unidades y prioridades.

Comprueba si oomd está habilitado

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

Qué significa: oomd actuará si está configurado/triggered por presión y ajustes de unidades.

Decisión: Si ejecutas cargas multi-tenant en una VM, oomd suele ser tu aliado. Si ejecutas un monolito crítico con SLOs estrictos, puede que prefieras plafonar memoria y dejar que el kernel OOM dentro de un cgroup controlado.

PSI: medir la presión en lugar de adivinar

PSI te dice cuánto tiempo las tareas pasan detenidas debido a contención de recursos. PSI de memoria es la mejor métrica de “¿el sistema realmente está sufriendo?” que he usado en años. Es mejor que “RAM usada” y más honesta que “load average”.

Cómo se ve bien

  • some avg10 en dígitos bajos durante picos suele ser aceptable.
  • full avg10 debe estar cerca de cero para servicios sensibles a latencia.

Cómo se ve mal

  • full avg10 sostenido por encima de ~1–2% en nodos de servicio de producción suele indicar que la latencia en las colas ya está rota.
  • some avg10 por encima de ~20–30% sugiere presión crónica. Tu sistema pasa un tercio de su tiempo esperando memoria. Eso no es estilo de vida.

Revisa PSI de CPU también (las tormentas de swap pueden fingir ser presión de CPU debido al reclaim):

cr0x@server:~$ cat /proc/pressure/cpu
some avg10=9.12 avg60=6.10 avg300=2.00 total=55555555
full avg10=0.00 avg60=0.00 avg300=0.00 total=0

Qué significa: La CPU está ocupada pero no saturada de forma que bloquee todas las tareas. Si la PSI de memoria es alta y la PSI de CPU es moderada, la memoria es la culpable.

Decisión: No “escales CPU” para arreglar un stall de memoria. Solo lograrás un stall más rápido.

Tres mini-historias corporativas desde el campo

Mini-historia 1: El incidente causado por una suposición incorrecta

La compañía: un proveedor SaaS mediano con mezcla de APIs orientadas al cliente y workers en background. El equipo de plataforma desplegó invitados Ubuntu 24.04 en un clúster KVM. También habilitaron ballooning porque el clúster estaba “mayormente inactivo por la noche”, y los dashboards lo mostraban seguro.

La suposición equivocada fue sutil: “Si el host tiene memoria libre, el invitado siempre puede recuperar memoria rápido.” En realidad, el allocator del host tenía memoria libre en agregado, pero no estaba disponible donde la VM la necesitaba en ese momento debido a VMs competidoras que subieron y objetivos de balloon cambiando rápido.

La capa API recibió el primer golpe. Las peticiones empezaron a hacer timeouts, pero la CPU no estaba saturada. Los ingenieros persiguieron una regresión de red imaginada. La pista estaba a la vista: las faltas mayores se dispararon y la PSI de memoria full trepó por encima de 5% durante minutos.

Eventualmente, el OOM killer del kernel fue culpado por “matar al azar” un proceso Node. No fue aleatorio. Fue el que tocó más páginas swap mientras manejaba tráfico. El postmortem no fue complicado: subir el piso de balloon por encima del RSS en estado estable, establecer límites de cgroup por servicio y alertar en PSI y tasa de swap-in en lugar de “RAM usada”.

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

Otra organización, mismo género de problemas. Estaban orgullosos de sus controles de coste. Alguien sugirió reducir asignaciones de memoria de VM porque “Linux usa memoria libre para caché de todos modos”. Afirmación verdadera, aplicación equivocada.

Reducieron vRAM, luego intentaron compensar aumentando el tamaño de swap “para no OOM”. La primera semana fue bien: menos eventos OOM, menos alertas de pager. Luego vino la carga de fin de mes, con consultas de reporting más pesadas y un proceso batch que calentó grandes estructuras en memoria.

El sistema no se cayó; simplemente se volvió inutilizable. Ese es el peor tipo de fallo porque provoca reintentos y timeouts, no un reinicio limpio. Las gráficas de almacenamiento mostraban alta utilización; todos miraron al SAN como si hubiera cometido una traición personal.

Causa raíz: la VM entró en deuda de swap sostenida. El swapfile más grande retrasó el OOM, pero también permitió que el conjunto de procesos creciera más allá de lo que la VM podía ejecutar eficientemente. La “optimización” convirtió un fallo agudo en una caída en cámara lenta. La solución eventual: swap más pequeño, límites estrictos por servicio y una política que dijera: para servicios de latencia, swapping se trata como incidente, no como mecanismo de supervivencia.

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

Una empresa del área financiera ejecutaba un puñado de nodos PostgreSQL críticos como VMs. Tenían reputación de ser conservadores—a veces excesivamente. Sin ballooning en las VMs de base de datos. Asignaciones de memoria fijas. Reservas estrictas en el host. Aburrido.

Un día, un clúster vecino tuvo una fuga de memoria en un conjunto de VMs worker. Los hosts empezaron a experimentar presión. En las cargas de otro equipo, el radio de impacto fue feo: objetivos de balloon cambiaron, invitados hicieron swap y la latencia subió. Pero las VMs de base de datos se mantuvieron estables. Estaban protegidas por política, no por suerte.

El equipo de BD aún tuvo que manejar efectos downstream (apps con timeouts, tormentas de conexiones), pero sus nodos no se unieron al caos. Eso importó: bases de datos estables te dan opciones. Puedes reducir carga, drenar tráfico y recuperar con menos piezas en movimiento.

La lección post-incident no fue glamorosa: siguieron haciendo lo aburrido—reservar memoria para sistemas stateful, plafonar todo lo demás y tratar el overcommit como un riesgo controlado con monitoreo. A veces la mejor optimización es negarse a optimizar lo equivocado.

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

1) “La CPU está alta, así que debe ser cómputo”

Síntoma: load average alto, tiempo de sistema CPU alto, latencia alta, pero CPU usuario no tan alta.

Causa raíz: overhead de reclaim y faltas de página (reclaim directo, kswapd), a menudo desencadenado por ballooning o fuga de memoria.

Solución: Revisa PSI y faltas mayores. Reduce la presión de memoria primero: sube el piso de VM / reduce ballooning / plafona servicios / arregla la fuga. Solo después revisa el dimensionado de CPU.

2) “Tenemos memoria libre en el host, así que el invitado no debería swapear”

Síntoma: el invitado swappea intensamente; el host muestra memoria disponible; la gente discute sobre quién tiene la gráfica equivocada.

Causa raíz: el invitado ve memoria ballooned; la política y el timing del hipervisor hacen que “libre” no sea igual a “disponible para este invitado ahora”.

Solución: Establece pisos de balloon y detén cambios agresivos de objetivos. Para VMs críticas, deshabilita ballooning o usa reservas estáticas.

3) “Simplemente añadamos más swap”

Síntoma: menos kills OOM, pero timeouts más largos y caídas de rendimiento más pronunciadas bajo carga.

Causa raíz: el swap se vuelve una muleta que permite que la huella de memoria supere la capacidad del working set.

Solución: Mantén el swap moderado. Usa límites de cgroup y oomd para fallar rápido y recuperarte, o provee más RAM.

4) “Ajustar swappiness arreglará la tormenta”

Síntoma: alguien pone swappiness a 1 durante un incidente; nada mejora.

Causa raíz: una vez que estás thrashing, el sistema ya está en deuda; los knobs de política no borran páginas swap instantáneamente.

Solución: La mitigación inmediata es reducir demanda de memoria o aumentar memoria real. Aplica cambios de swappiness tras estabilizar, y luego valida con pruebas de carga.

5) “El OOM killer del kernel es aleatorio”

Síntoma: mueren procesos distintos cada vez; la gente concluye que Linux es impredecible.

Causa raíz: la puntuación OOM depende del uso de memoria y ajustes; bajo presión, la “mejor” víctima cambia con el timing de la carga.

Solución: Pon cargas en cgroups con presupuestos explícitos, ajusta OOMScoreAdjust para servicios críticos, y confía en oomd para kills más tempranos y dirigidos por política.

6) “La caché me está robando RAM” (la queja eterna)

Síntoma: free muestra poca memoria libre; la gente entra en pánico; vacían caches.

Causa raíz: malentender la page cache de Linux vs memoria disponible; vaciar caches causa picos de E/S y puede empeorar la latencia.

Solución: Usa MemAvailable, PSI y faltas mayores. Solo vacía caches en pruebas controladas, no en fuegos de producción.

Listas de verificación / plan paso a paso

Paso a paso: estabilizar una tormenta de swap activa (invitado)

  1. Confirma que es presión real: ejecuta vmstat, revisa si/so, haz cat /proc/pressure/memory.
  2. Identifica al glotón: ps ... --sort=-rss, revisa swap por proceso vía /proc/<pid>/smaps_rollup.
  3. Detén el crecimiento: plafona el servicio con systemctl set-property ... MemoryMax= o reduce workers/heap.
  4. Coordina con el hipervisor: aumenta memoria asignada o reduce el objetivo/piso del balloon. A menudo es la solución real más rápida.
  5. Reinicia estratégicamente: reinicia los procesos críticos más swappeados después de reducir su apetito de memoria. Reiniciar sin arreglar el apetito solo repite el incidente.
  6. Observa la recuperación: las faltas mayores y PSI deberían caer primero. El swap usado puede quedarse alto; está bien si el swap-in se detiene y la latencia se recupera.

Paso a paso: prevenir recurrencia (política)

  1. Elige tu capa de reclaim: prefiere límites en el invitado (cgroup y oomd) sobre ballooning sorpresa a nivel host.
  2. Define pisos de VM: asegúrate de que el mínimo de balloon cubra steady-state + margen de ráfaga para cada clase de VM.
  3. Establece presupuestos por servicio: usa MemoryHigh/MemoryMax y valida con memory.events.
  4. Dimensiona el swap correctamente: swap pequeño-moderado; considera zram con prioridad correcta; trata la tasa de swap-in como métrica alertable.
  5. Alerta por PSI: especialmente memory full avg10. Atrapará la “muerte lenta” antes que la mayoría de dashboards.
  6. Prueba carga con ballooning activado: si insistes en ballooning, pruébalo bajo concurrencia pico con churn de memoria realista.
  7. Documenta el comportamiento de kill: qué servicios pueden morir primero y cómo se recuperan (políticas de restart de systemd, timeouts de apagado graceful).

Paso a paso: verificación de cordura tras cambios

  1. Corre una carga controlada. Captura free -h, PSI, sar -B, iostat -x.
  2. Verifica la aplicación de cgroups: memory.events debe reflejar tus umbrales durante stress, no solo después del desastre.
  3. Confirma que no hay swap en el host bajo picos normales (si controlas el host). Si hay swap en el host, tu política de plataforma te está mintiendo.

Preguntas frecuentes

1) ¿El swap siempre es malo en servidores Ubuntu 24.04?

No. El swap es un buffer de seguridad. Se vuelve malo cuando se usa de forma continua o suficientemente intensa para causar faltas mayores y espera de E/S. Trata la tasa de swap-in y la PSI de memoria full como las verdaderas señales rojas.

2) ¿Por qué mi invitado hace swap cuando el host muestra memoria libre?

Porque la “memoria física” disponible del invitado puede reducirse por ballooning, independientemente de las gráficas de memoria libre del host. Además, “libre” en el host no significa “inmediatamente asignable a tu VM” durante la contención.

3) ¿Debería deshabilitar el ballooning?

Para sistemas stateful críticos (bases de datos, colas) y SLOs de latencia estrictos: por lo general sí, o al menos establece un piso conservador. Para flotas stateless y con ráfagas: ballooning puede ser aceptable si monitorizas PSI y tienes límites fuertes por servicio.

4) ¿Cuál es la diferencia entre MemoryHigh y MemoryMax?

MemoryHigh es un punto de estrangulamiento: el kernel empezará a reclamar dentro del cgroup y aplicar presión. MemoryMax es un techo duro: las asignaciones fallan y puedes desencadenar OOM dentro de ese cgroup.

5) ¿Por qué systemd-oomd mató un servicio aunque todavía había RAM?

oomd puede actuar sobre presión sostenida (PSI) para mantener la respuesta del sistema. Eso puede ocurrir antes de que la RAM llegue a cero, especialmente bajo stalls de reclaim y thrash de swap.

6) ¿Bajar swappiness es suficiente para detener las tormentas de swap?

No por sí solo. Puede reducir la predisposición del kernel a swapear bajo presión moderada, pero no compensará RAM insuficiente, ballooning agresivo o una fuga de memoria. Arregla dimensionamiento y límites primero.

7) ¿Cómo sé si sufro faltas mayores específicamente?

Usa sar -B (mira majflt/s) y correlaciónalo con la latencia. Las faltas mayores significan page-ins respaldadas por disco. Si son altas, tu carga literalmente espera por swap.

8) ¿En qué debo alertar para detectar esto temprano?

Como mínimo: PSI de memoria (full y some), tasa de swap-in (tendencia de vmstat si), faltas mayores y memory.events de cgroups para servicios clave. “Porcentaje de RAM usada” es una señal débil por sí sola.

9) ¿Debería usar zram en VMs de producción?

A veces. Si tus tormentas de swap están limitadas por E/S y tienes margen de CPU, zram puede reducir drásticamente la espera de E/S. Si ya estás CPU-bound, zram puede intercambiar un cuello de botella por otro.

10) ¿Una tormenta de swap puede parecer una caída de almacenamiento?

Sí. Las tormentas de swap generan mucho I/O aleatorio, saturan dispositivos e inflan latencias. El almacenamiento parece “lento”, pero está respondiendo a una demanda patológica. Arregla la presión de memoria y la “caída de almacenamiento” a menudo desaparece.

Conclusión: pasos siguientes que puedes aplicar hoy

Si recuerdas una cosa: ballooning más límites débiles es cómo accidentalmente construyes un generador de tormentas de swap. Ubuntu 24.04 te da las herramientas para evitarlo—controles cgroup v2, visibilidad PSI y política OOM práctica. Úsalas con intención.

Pasos prácticos:

  • En una VM problemática, toma línea base: free -h, vmstat 1, sar -B, cat /proc/pressure/memory.
  • Establece presupuestos de memoria por servicio con MemoryHigh y MemoryMax; valida vía memory.events.
  • Pon de acuerdo una política de memoria para VMs: pisos de balloon conservadores para sistemas críticos y reglas explícitas para cuándo se permite ballooning.
  • Dimensiona el swap para que sea un buffer, no un modo de vida. Considera zram solo con los ojos abiertos.
  • Alerta por presión (PSI) y swap-in, no solo por “porcentaje usado”.

Haz eso, y la próxima vez que alguien diga “la VM se siente lenta”, el diagnóstico será de cinco minutos en vez de una búsqueda de tres horas que termina en echar la culpa.

← Anterior
Ubuntu 24.04: swappiness y ajustes vm.dirty — las pequeñas afinaciones que realmente importan
Siguiente →
ZFS para archivos multimedia: registros grandes, gran compresión, grandes beneficios

Deja un comentario