Ubuntu 24.04: swappiness y ajustes vm.dirty — las pequeñas afinaciones que realmente importan

¿Te fue útil?

A las 02:17 tu gráfico hace la clásica escena de película de terror: picos de latencia, el “steal” de CPU parece normal, pero la aplicación empieza a agotar tiempos. Los discos no están llenos. La red está aburrida. Aun así la máquina “se siente” como si caminara por cemento húmedo. Entras por SSH y ves actividad de swap y un montón de páginas sucias. Nada está técnicamente “caído”, y sin embargo todo es inutilizable.

Aquí es donde los pequeños ajustes del kernel dejan de ser trivia y pasan a ser el volante. Ubuntu 24.04 trae valores por defecto sensatos para sistemas de propósito general. Producción rara vez es de propósito general. Si tu carga consume mucha memoria, tiene ráfagas de I/O o corre sobre volúmenes en la nube con personalidad, vm.swappiness y los controles vm.dirty* están entre las pocas afinaciones “pequeñas” que realmente pueden cambiar el resultado.

1. Modelo mental: qué controlan realmente estos ajustes

Swapping no es solo “se acabó la RAM”; es “elegí una víctima”

La gestión de memoria en Linux es una negociación entre memoria anónima (heap, pilas, asignaciones temporales) y memoria respaldada por archivos (page cache, archivos mapeados). Cuando ves actividad de swap, no significa necesariamente que te hayas “quedado sin RAM”. Significa que el kernel decidió que algunas páginas anónimas son menos valiosas que mantener otras páginas residentes.

vm.swappiness no es un interruptor simple de encendido/apagado. Es una señal de preferencia: cuán agresivamente debe reclamar el kernel memoria anónima intercambiándola a swap frente a reclamar caché de archivos (descartar páginas limpias de caché). Valores más altos fomentan más swapping más temprano; valores más bajos favorecen mantener memoria anónima en RAM y sacrificar primero la caché.

Ese sesgo importa porque el swapping tiene un modo de fallo brutal: la latencia se vuelve no lineal. Un sistema puede sentirse perfectamente bien y luego volverse repentinamente inusable cuando entra en un comportamiento sostenido de swap-in/swap-out. Ese es la tormenta de swap: no un único swap grande, sino un ciclo repetido donde el conjunto de trabajo es mayor que la RAM efectiva y el kernel sigue desalojando lo que el proceso necesitará de nuevo pronto.

Las páginas sucias son “deuda de IO” que tarde o temprano vence

Cuando una aplicación escribe a un archivo, el kernel a menudo marca páginas como sucias en RAM y devuelve el control rápido. Es una característica de rendimiento: agrupar escrituras es más barato que emitir IOs pequeños y sincrónicos. La deuda es que esas páginas sucias deben vaciarse a almacenamiento más tarde. Los ajustes vm.dirty* deciden cuánta deuda puede acumularse, qué tan rápido debe pagarse y cuán agresivamente el kernel limita a los escritores cuando el libro se ve peligroso.

Dos ratios tienden a dominar la conversación:

  • vm.dirty_background_ratio: cuando la memoria sucia excede este porcentaje, comienza el writeback en segundo plano.
  • vm.dirty_ratio: cuando la memoria sucia excede este porcentaje, los procesos que escriben son limitados (efectivamente ayudan a hacer flush).

También existen versiones en bytes (vm.dirty_background_bytes, vm.dirty_bytes) que anulan los porcentajes cuando están establecidas. En producción, los ajustes basados en bytes suelen ser más seguros porque los porcentajes escalan con la RAM, y los tamaños modernos de RAM convierten “porcentaje de RAM” en una cantidad peligrosamente grande de páginas sucias. Con 256 GB de RAM, 20% sucio es una gran deuda de IO.

Estos controles no “aceleran discos”. Moldean el dolor

Ningún sysctl hace que un almacenamiento lento sea rápido. Lo que hacen estos ajustes es decidir cuándo pagas el coste de la presión de memoria y del writeback, y si lo pagas con trabajo de fondo manejable o con un paro catastrófico en primer plano. Tu objetivo es previsibilidad aburrida:

  • Hacer swap solo cuando sea genuinamente menos dañino que descartar caché.
  • Volcar datos sucios de forma suficientemente constante para no tocar un acantilado.
  • Limitar a los escritores antes de que el sistema se convierta en una situación de rehenes.

Una cita que envejece bien en operaciones: “La esperanza no es una estrategia.” (idea parafraseada, atribuida al General Gordon R. Sullivan). Los valores por defecto del kernel son esperanza. Producción es evidencia.

2. Datos interesantes y un poco de historia (porque los valores por defecto tienen una historia)

  • Dato 1: El page cache no es “memoria desperdiciada”. Linux llenará felizmente la RAM con caché y la descartará instantáneamente cuando las aplicaciones necesiten memoria. La confusión viene de herramientas antiguas y modelos mentales viejos.
  • Dato 2: Los kernels de Linux tempranos tenían heurísticas de swap muy diferentes; el comportamiento de swap se ha reescrito repetidamente conforme cambiaron el almacenamiento y las cantidades de RAM.
  • Dato 3: Los ratios como dirty_ratio tenían más sentido cuando “mucha RAM” significaba gigabytes de un solo dígito. Hoy, límites basados en ratios pueden traducirse en decenas de gigabytes de datos sucios: enormes ráfagas de IO más tarde.
  • Dato 4: El subsistema de writeback está diseñado para suavizar el IO, pero solo puede suavizar lo que el almacenamiento puede sostener. Aplicaciones con ráfagas sobre discos en la nube con ráfagas producen consecuencias también en ráfagas.
  • Dato 5: El kernel tiene múltiples mecanismos de reclaim: descartar page cache limpio, escribir page cache sucio, compactar memoria y swapear. Estos interactúan; ajustar un control puede cambiar qué camino se elige.
  • Dato 6: Swap en SSD se volvió práctico y luego común, lo que cambió la ecuación de coste. Swap sigue siendo más lento que la RAM, pero ya no es “muerte instantánea” en todos los entornos.
  • Dato 7: Cgroups y contenedores cambiaron lo que significa “presión de memoria”. El swapping puede ocurrir por límites de cgroup incluso cuando el host tiene mucha RAM disponible.
  • Dato 8: Los ajustes “dirty” también afectan cuánto tiempo los datos pueden permanecer en RAM antes de escribirse—importante para expectativas de durabilidad y para lo desagradable que puede ser la recuperación tras un crash.

3. vm.swappiness: cuándo swappear es inteligente y cuándo es grosero

Qué influye realmente swappiness

vm.swappiness varía de 0 a 200 en kernels modernos (la mayoría de las distribuciones usan la convención 0–100, pero el kernel permite 200). En Ubuntu los valores por defecto suelen rondar 60. Esa es una postura intermedia: algo de swap está bien si preserva caché y el rendimiento global.

Clave: swappiness no dice “nunca swapear”. Dice “¿qué tan duro debe intentar el kernel evitar el swap comparado con reclamar caché?”. Si lo pones demasiado bajo, el kernel puede descartar caché agresivamente y acabarás con más lecturas desde disco (cache misses) y peor rendimiento para servicios intensivos en IO. Si lo pones demasiado alto, puedes swapear memoria que se necesitará de nuevo pronto y la latencia se vuelve errática.

Cuándo reducir swappiness suele ser correcto

  • Servicios sensibles a latencia (servidores API, sistemas interactivos) donde pausas largas puntuales son peores que un IO ligeramente mayor en estado estable.
  • Hosts con mucha RAM en relación con el conjunto de trabajo, donde el swapping indica heurísticas pobres más que necesidad.
  • Sistemas donde el swap es lento (swap en red, hipervisores sobresuscritos o discos baratos en la nube con presión de créditos).

Cuándo swappiness más alto puede ser racional

  • Cargas mixtas donde mantener la caché de archivos caliente importa (casos de compilación, servidores de artefactos, bases de datos de solo lectura que dependen de la caché del SO).
  • Sobrecompromiso de memoria con páginas frías conocidas (algunas JVM, caches, jobs por lotes) donde swapear páginas anónimas inactivas puede ser menos dañino que destrozar la page cache.
  • Cuando el swap es lo suficientemente rápido (NVMe, buenos arrays SSD) y aceptas más IO de swap sin destrozar la latencia de cola.

Dos rangos objetivo realistas

Guía de opinión que suele sobrevivir en producción:

  • Servidores generales: 20–60. Comienza en 30 si dudas y tienes swap habilitado.
  • Máquinas sensibles a latencia: 1–10, pero solo después de confirmar que no dependes del swap como válvula de seguridad.

Un swappiness de 0 se malinterpreta a menudo. No significa “swap deshabilitado”. Significa “evitar swapear tanto como sea posible”, pero bajo presión real el swapping aún puede ocurrir. Si realmente quieres cero swap, desactiva el swap (y acepta lo que eso implica: el OOM killer se convierte en tu instrumento contundente).

Broma #1: Poner vm.swappiness=1 es como poner “por favor, sé bueno” en una nota adhesiva para el kernel. A veces escucha; a veces no le corresponde el día.

4. Ajustes vm.dirty: writeback, throttling y por qué “buffers” no es gratis

Conoce al cuarteto dirty

El conjunto comúnmente ajustado:

  • vm.dirty_background_ratio / vm.dirty_background_bytes: inicia el flushing en segundo plano cuando las páginas sucias exceden este umbral.
  • vm.dirty_ratio / vm.dirty_bytes: limita a los escritores en primer plano cuando las páginas sucias exceden este umbral.
  • vm.dirty_expire_centisecs: cuánto tiempo pueden quedarse datos sucios antes de considerarse lo bastante viejos para escribir (en centésimas de segundo).
  • vm.dirty_writeback_centisecs: con qué frecuencia despiertan los hilos de flusher para escribir (en centésimas de segundo).

Los ratios son porcentajes de la memoria total. Los bytes son umbrales absolutos. Si configuras bytes, el ratio se ignora para ese lado. El ajuste basado en bytes suele ser más predecible a través de tipos de instancia y futuras ampliaciones de RAM.

El modo de fallo que intentas evitar: el acantilado de writeback

Si tus umbrales sucios son altos, el kernel puede acumular una enorme cantidad de datos sucios en RAM y luego decidir de repente que es hora de vaciarlos. Eso puede saturar tu almacenamiento, provocar colas de IO y bloquear escrituras. Si tu aplicación escribe vía IO buffered (la mayoría lo hace), el bloqueo se muestra como procesos en estado D y un io wait elevado. Los usuarios lo llaman “el sistema se congeló”. Los ingenieros lo llaman “el throttling de writeback hizo su trabajo, simplemente no de una manera que alguien disfrutara”.

En volúmenes en la nube, el acantilado es más divertido porque el throughput base y los créditos de ráfaga pueden cambiar con el tiempo. Puedes “salirte con la tuya” con ratios sucios altos durante ráfagas y luego ser castigado cuando el volumen vuelva a su throughput base. Parece aleatorio hasta que recuerdas que el almacenamiento es literalmente una cuenta bancaria.

Elegir ratios vs bytes

Usa ratios cuando:

  • Gestionas una flota bastante uniforme con tamaños de RAM similares.
  • Tu envelope de rendimiento escala con RAM y almacenamiento proporcionalmente.
  • Quieres cálculos mentales más simples y aceptas cierta variabilidad.

Usa bytes cuando:

  • Los tamaños de RAM varían ampliamente (común en grupos de autoescalado).
  • El throughput de almacenamiento es la verdadera restricción y no escala con la RAM.
  • Quieres limitar la deuda de IO a algo que tu almacenamiento pueda vaciar de forma predecible.

Puntos de partida concretos que suelen comportarse

No son mágicos, pero es menos probable que produzcan un comportamiento de acantilado que la escuela de pensamiento “subamos dirty_ratio”.

  • Para VMs SSD de propósito general: establece dirty_background_bytes entre 256–1024 MB y dirty_bytes entre 1–4 GB, dependiendo del ancho de banda de almacenamiento y la ráfaga de escrituras.
  • Para servicios muy escritos en discos modestos: umbrales más pequeños (background 128–512 MB, máximo 512 MB–2 GB) y un writeback más frecuente pueden reducir picos.
  • Para nodos de memoria grande: bytes casi siempre ganan a ratios, a menos que el almacenamiento escale con la RAM (raro fuera de máquinas muy caras).

Intervalos expire y writeback: los controles de “qué tan agrupado es agrupado”

dirty_writeback_centisecs establece con qué frecuencia comienza el writeback en segundo plano. Intervalos más cortos pueden significar writeback más suave a costa de más sobrecarga por flushes frecuentes. dirty_expire_centisecs trata sobre cuánto tiempo pueden permanecer los datos sucios antes de que se consideren suficientemente viejos para empujarlos fuera.

Para la mayoría de servidores, los valores por defecto están bien. Pero si ves un comportamiento periódico de “cada N segundos el mundo se pausa”, estos son sospechosos, especialmente cuando se combinan con ratios sucios altos. No te excedas. Cambios pequeños, mide, repite.

Broma #2: Las páginas sucias son como los platos en el fregadero: puedes apilarlas notablemente alto, pero al final alguien debe enfrentar las consecuencias.

5. Guion de diagnóstico rápido (primero/segundo/tercero)

Primero: decide si mueres por presión de memoria o por presión de writeback

  • Señales de presión de memoria: aumento de pgscan/pgsteal, reclaim frecuente, swap in/out creciente, fallos de página mayores, kswapd ocupado.
  • Señales de presión de writeback: muchas tareas atascadas en estado D, alto io wait, páginas sucias altas, balance_dirty_pages aparece en stacks (si muestreás), utilización de almacenamiento al máximo.

Segundo: localiza la capa del cuello de botella en 5 minutos

  1. ¿Se está usando swap realmente? Si sí, ¿es un goteo constante en segundo plano o una tormenta?
  2. ¿Están subiendo las páginas sucias? Si sí, ¿estás alcanzando el umbral de throttling de dirty?
  3. ¿Está saturado el almacenamiento? Si sí, ¿es throughput, IOPS, profundidad de cola o latencia?
  4. ¿Es por cgroup/contenedor? Si estás en un host con contenedores, revisa límites de memoria e IO de cgroup.

Tercero: elige la palanca correcta

  • Tormenta de swap: reduce el conjunto de trabajo, añade RAM, ajusta swappiness, arregla fugas de memoria, considera zswap/zram solo con cuidado.
  • Acantilado de writeback: limita los dirty bytes, baja los dirty ratios, suaviza los intervalos de writeback y arregla el cuello de botella de almacenamiento (a menudo la respuesta real).
  • Ambos a la vez: probablemente tienes una app que hace escrituras buffered intensas mientras la memoria es escasa. El tuning ayuda, pero la planificación de capacidad arregla.

6. Tareas prácticas (comandos + significado de la salida + decisiones)

Tarea 1: Confirma swappiness y ajustes dirty actuales

cr0x@server:~$ sysctl vm.swappiness vm.dirty_ratio vm.dirty_background_ratio vm.dirty_bytes vm.dirty_background_bytes vm.dirty_expire_centisecs vm.dirty_writeback_centisecs
vm.swappiness = 60
vm.dirty_ratio = 20
vm.dirty_background_ratio = 10
vm.dirty_bytes = 0
vm.dirty_background_bytes = 0
vm.dirty_expire_centisecs = 3000
vm.dirty_writeback_centisecs = 500

Qué significa: los ratios están activos (los bytes son cero). Los datos sucios pueden permanecer ~30 segundos antes de considerarse viejos (3000 centisecs). El writeback despierta cada 5 segundos (500 centisecs).

Decisión: Si la RAM es grande y el almacenamiento es modesto, considera pasar a límites sucios basados en bytes para evitar ráfagas enormes.

Tarea 2: Ver si el swap está habilitado y de qué tipo

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

Qué significa: swapfile de 8 GiB, ~256 MiB usados. Eso no es automáticamente malo; depende de las tendencias.

Decisión: Si el uso de swap sube constantemente durante la carga y nunca baja, probablemente estás excediendo el conjunto de trabajo o hay una fuga de memoria.

Tarea 3: Revisa la presión de memoria rápidamente (incluyendo tendencias de swap)

cr0x@server:~$ free -h
               total        used        free      shared  buff/cache   available
Mem:           31Gi       18Gi       1.2Gi       1.0Gi       12Gi        9.5Gi
Swap:         8.0Gi      256Mi       7.8Gi

Qué significa: “available” es tu amiga; estima la memoria que puede reclamarse sin swappear fuertemente. Un “free” bajo por sí solo no significa nada en Linux.

Decisión: Si available colapsa y el swap sube bajo carga normal, necesitas capacidad o reducir el conjunto de trabajo, no un sysctl mágico.

Tarea 4: Ver la actividad real de swap a lo largo del tiempo

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
 2  0 262144 1265000 120000 9500000    0    0   120   180  600 1200 12  4 82  2  0
 3  0 262144 1180000 120000 9440000    0    0   100   210  620 1300 14  4 80  2  0
 4  1 310000  900000 119000 9300000 2048 4096   90  5000  800 2000 18  6 60 16  0
 2  2 420000  600000 118000 9100000 4096 8192   60  9000 1000 2600 20  7 45 28  0
 1  2 520000  500000 118000 9000000 4096 8192   40 12000 1100 2800 22  7 40 31  0

Qué significa: si/so (swap-in/out) en aumento indica swapping activo. Un b creciente indica procesos bloqueados (a menudo IO). Un wa creciente apunta a IO wait.

Decisión: Si ves swap-in sostenido durante tráfico de usuario, bajar swappiness puede ayudar solo si el sistema está swappeando “innecesariamente”. Si el conjunto de trabajo es demasiado grande, el tuning no te salvará.

Tarea 5: Identificar los principales consumidores de swap

cr0x@server:~$ sudo smem -rs swap -k | head -n 8
  PID User     Command                         Swap      USS      PSS      RSS
14231 app      /usr/bin/java -jar service.jar  512000   420000   600000  1300000
 5123 postgres /usr/lib/postgresql/16/bin/...  128000   300000   380000   700000
 2211 root     /usr/bin/containerd              24000    35000    42000    90000
 1987 root     /usr/lib/systemd/systemd          2000     8000    12000    25000

Qué significa: Qué procesos están realmente swappeados, no solo qué procesos son grandes.

Decisión: Si un proceso crítico para latencia tiene swap significativo, considera ajustar la memoria en la app, añadir RAM o reducir swappiness. Si solo da swapped a demonios inactivos, no te alarmes.

Tarea 6: Ver niveles de páginas sucias y actividad de writeback

cr0x@server:~$ egrep 'Dirty|Writeback|MemTotal' /proc/meminfo
MemTotal:       32734064 kB
Dirty:           184320 kB
Writeback:        16384 kB
WritebackTmp:         0 kB

Qué significa: Dirty es actualmente pequeña (~180 MiB). Writeback está activo pero modesto.

Decisión: Si Dirty crece a varios gigabytes y se mantiene, probablemente estés acumulando deuda de IO más rápido de lo que el almacenamiento puede vaciar.

Tarea 7: Revisar umbrales globales dirty del kernel (calculados)

cr0x@server:~$ cat /proc/sys/vm/dirty_background_ratio /proc/sys/vm/dirty_ratio
10
20

Qué significa: El flushing en segundo plano comienza al 10% de la memoria; el throttling al 20%.

Decisión: En sistemas con mucha RAM, eso suele ser demasiado alto en términos absolutos. Considera límites basados en bytes.

Tarea 8: Medir IO bloqueado y saturación del disco

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

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          14.20    0.00    5.10   18.40    0.00   62.30

Device            r/s     rkB/s   rrqm/s  %rrqm  r_await  w/s     wkB/s   w_await aqu-sz  %util
nvme0n1         120.0   5200.0     2.0    1.6     4.2   980.0  64000.0   22.5    7.8    99.0

Qué significa: El disco está saturado (%util ~99) y la latencia de escritura (w_await) es alta. Eso es consistente con throttling de writeback y/o escrituras intensas.

Decisión: Ajustar los dirty puede suavizar ráfagas, pero si el disco está saturado en reposo, necesitas mejor almacenamiento, menos volumen de escritura o cambios a nivel de aplicación (batching, compresión).

Tarea 9: Buscar tareas atascadas en sleep de IO no interrumpible

cr0x@server:~$ ps -eo state,pid,comm,wchan:32 --sort=state | head -n 15
D  9182 postgres                        io_schedule
D 14231 java                            balance_dirty_pages
D  7701 rsyslogd                        ext4_da_writepages
R  2109 sshd                            -
S  1987 systemd                         ep_poll
S  2211 containerd                      ep_poll

Qué significa: Las tareas en estado D están bloqueadas, típicamente por IO. Ver balance_dirty_pages es una pista fuerte de que el throttling por dirty está activo.

Decisión: Si muchos workers están en balance_dirty_pages, reduce los límites sucios y/o arregla el throughput de almacenamiento. También evalúa si tu app hace grandes escrituras buffered sin conciencia de backpressure.

Tarea 10: Verificar límites de memoria de cgroup (los contenedores pueden atacar aquí)

cr0x@server:~$ cat /sys/fs/cgroup/memory.max
max

Qué significa: Este host (o cgroup actual) no está limitado por memoria.

Decisión: Si ves un número en lugar de max, el comportamiento de swapping puede activarse por presión de cgroup aun cuando la máquina parece con memoria.

Tarea 11: Aplicar un ajuste temporal de forma segura (solo en runtime)

cr0x@server:~$ sudo sysctl -w vm.swappiness=30
vm.swappiness = 30

Qué significa: Cambiaste el parámetro de kernel en ejecución. El reinicio revertirá a menos que lo hagas persistente.

Decisión: Si las tormentas de swap disminuyen y los cache misses no explotan, considera persistir. Si no cambia nada, no lo apliques por costumbre—búscala en otro sitio.

Tarea 12: Cambiar el tuning dirty a topes en bytes (solo runtime)

cr0x@server:~$ sudo sysctl -w vm.dirty_background_bytes=536870912 vm.dirty_bytes=2147483648
vm.dirty_background_bytes = 536870912
vm.dirty_bytes = 2147483648

Qué significa: El writeback en segundo plano inicia a 512 MiB de sucio, el throttling a 2 GiB de sucio—independientemente del tamaño de RAM.

Decisión: Si esto suaviza el io wait y reduce los bloqueos por “writeback cliff”, persístelo. Si tu carga depende de gran caché de escritura para rendimiento, podrías ver menor velocidad máxima; decide si prefieres pico o predictibilidad.

Tarea 13: Persistir sysctls correctamente (y verificar)

cr0x@server:~$ sudo tee /etc/sysctl.d/99-vm-tuning.conf >/dev/null <<'EOF'
vm.swappiness=30
vm.dirty_background_bytes=536870912
vm.dirty_bytes=2147483648
EOF
cr0x@server:~$ sudo sysctl --system | tail -n 6
* Applying /etc/sysctl.d/99-vm-tuning.conf ...
vm.swappiness = 30
vm.dirty_background_bytes = 536870912
vm.dirty_bytes = 2147483648

Qué significa: Los ajustes ahora son persistentes y aplicados. La salida confirma lo cargado.

Decisión: Si sysctl --system muestra que tus valores son sobreescritos posteriormente, tienes otro archivo o herramienta peleando contigo (cloud-init, gestión de configuración, agente del proveedor). Arregla la fuente de la verdad.

Tarea 14: Confirmar que los ratios sucios quedan efectivamente desactivados cuando se fijan bytes

cr0x@server:~$ sysctl vm.dirty_ratio vm.dirty_bytes
vm.dirty_ratio = 20
vm.dirty_bytes = 2147483648

Qué significa: El valor del ratio aún se imprime, pero dirty_bytes tiene precedencia para el throttling.

Decisión: Mantén los ratios por defecto a menos que tengas una razón. Los bytes son ahora tu control real.

Tarea 15: Comprobar ráfagas por delayed allocation de ext4 (contexto, no culpa)

cr0x@server:~$ mount | grep ' on / '
/dev/nvme0n1p2 on / type ext4 (rw,relatime,errors=remount-ro)

Qué significa: ext4 con delayed allocation puede agrupar escrituras, lo que puede amplificar ráfagas de writeback según la carga.

Decisión: No cambies opciones de montaje del sistema de archivos como primera respuesta. Usa topes dirty para moldear la ráfaga; cambia el comportamiento del FS solo tras tener un motivo medido.

7. Perfiles de tuning con opinión que no fallan silenciosamente

Perfil A: “Servidores mayormente normales” (web + sidecars + escrituras moderadas)

Usa esto cuando quieras menos sorpresas, no números máximos de benchmark.

  • vm.swappiness=30
  • vm.dirty_background_bytes=268435456 (256 MiB)
  • vm.dirty_bytes=1073741824 (1 GiB)

Por qué: Mantiene la deuda sucia limitada. Comienza a flushear antes. Aún permite buffering para rendimiento, solo que no “vamos a acumular 30 GB porque podemos”.

Perfil B: “Escritura intensiva, sensible a latencia” (logs, ingestión, colas)

  • vm.swappiness=10 (o 20 si dependes mucho de caché)
  • vm.dirty_background_bytes=134217728 (128 MiB)
  • vm.dirty_bytes=536870912 (512 MiB)

Por qué: Cambias buffering pico por menos acantilados de latencia en cola. Reduce la probabilidad de que un flush repentino arrase tu servicio.

Perfil C: “Máquinas con mucha RAM y almacenamiento desigual” (la trampa clásica)

  • vm.swappiness=20–40 dependiendo de la carga
  • vm.dirty_background_bytes=536870912 (512 MiB)
  • vm.dirty_bytes=2147483648 (2 GiB)

Por qué: Mucha RAM más discos promedio es cómo accidentalmente construyes una máquina de deuda IO. Los ratios escalan, pero los discos no.

Qué evitar (porque te va a tentar)

  • No subas dirty_ratio para “mejorar rendimiento” salvo que estés seguro de que el almacenamiento puede manejar el flush posterior. Usualmente compras una victoria en benchmark con una caída en producción.
  • No desactives swap a ciegas en servidores de propósito general. Estás reemplazando un mecanismo de degradación gradual por un mecanismo de muerte súbita.
  • No ajustes a oscuras. Si no mides io wait, niveles sucios y actividad de swap, solo estás reorganizando vibras del kernel.

8. Tres microhistorias corporativas desde el terreno

Historia 1: El incidente causado por una suposición equivocada

Tenían una flota de servidores Ubuntu ejecutando una API ocupada, además de un worker en segundo plano que periódicamente escribía exportaciones en lote al disco. Nada exótico. El equipo notó que el uso de swap subía durante semanas y decidieron que el swap era el villano. La solución fue decisiva: desactivar swap en todas partes. “El swap es lento; tenemos suficiente RAM.” Una frase que ha terminado muchas rotaciones de on-call pacíficas.

Dos días después llegó una oleada de tráfico con una fuga de memoria modesta en un camino de código raramente ejecutado. La fuga no era enorme, y con swap presente el sistema habría cojeado mientras sonaban alertas. Sin swap, el kernel tenía una sola herramienta: el OOM killer. Bajo carga, el OOM killer hizo lo que hace: mató un proceso que parecía usar mucha memoria. Ese proceso resultó ser el worker de la API.

El outage no fue dramático al principio. Fue peor: un patrón rodante de fallos parciales. Instancias salían del balanceador, reiniciaban, calentaban cachés y volvían a morir. Los usuarios vieron errores intermitentes. Los ingenieros vieron gráficas de CPU “saludables” y una creciente sensación de traición personal.

La suposición equivocada no fue “swap es lento”. El swap es lento. La suposición equivocada fue pensar que el swap es solo para rendimiento. El swap también sirve para estabilidad: te compra tiempo para detectar fugas y recuperarte con gracia. Después del incidente, volvieron a habilitar swap y pusieron una swappiness baja. Luego arreglaron la fuga. El cambio más importante fue cultural: dejaron de tratar al swap como una falla moral.

Historia 2: La optimización que salió mal

Un equipo de plataforma de datos tenía nodos de gran memoria y ingestión intensiva. Alguien leyó que aumentar los dirty ratios puede mejorar el throughput porque el kernel puede agrupar escrituras más grandes. Subieron vm.dirty_ratio y vm.dirty_background_ratio sustancialmente. En una prueba sintética, el throughput se veía genial. Los dashboards sonreían. El PR se fusionó rápido.

En producción, la carga no era constante. Era espigada: ráfagas de escrituras seguidas de períodos tranquilos. Con ratios sucios altos, el sistema acumuló enormes ráfagas en RAM durante las ráfagas. Luego llegó el período tranquilo y el kernel empezó a vaciar una montaña de datos sucios. Los flushes fueron lo bastante grandes como para saturar el almacenamiento, lo que incrementó la latencia de IO para todo lo demás: operaciones de metadata, lecturas e incluso pequeñas escrituras de servicios no relacionados.

Los usuarios percibieron el problema como “congelamientos aleatorios”. Los congelamientos coincidían con los ciclos de flush, pero solo si graficabas memoria sucia y latencia de IO en el mismo eje. La primera respuesta fue culpar a la aplicación. La segunda, al proveedor de la nube. La tercera—eventualmente—fue admitir que el kernel hacía exactamente lo que se le pidió, y la optimización estaba afinada para un benchmark, no para una flota espigada.

La solución fue aburrida: cambiar a límites sucios basados en bytes que reflejaran lo que el almacenamiento podía vaciar sin drama. El throughput cayó ligeramente en las ráfagas más agresivas. La latencia en cola mejoró drásticamente. Todos fingieron que eso fue el plan desde el principio, lo cual también es aburrido y por ende aceptable.

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

Un equipo financiero manejaba hosts Ubuntu 24.04 que procesaban cierres de mes. La carga era predecible: escrituras intensas durante unas horas y luego silencio. No perseguían rendimiento máximo; perseguían “no me despierten”. Su práctica era dolorosamente poco glamorosa: cada cambio de tuning del kernel requería un despliegue canario, un conjunto de mediciones antes/después y un plan de rollback.

Ya se habían estandarizado en topes sucios basados en bytes y una swappiness moderada. También tenían dashboards para memoria sucia, latencia de IO y tasas de swap-in/out. No porque fuera divertido, sino porque evita debates basados en sensaciones.

Un mes, un cambio en el backend de almacenamiento introdujo una latencia de escritura ligeramente mayor. Nada catastrófico, simplemente más lento. En la primera corrida de procesamiento pesado, sus métricas mostraron páginas sucias subiendo más rápido de lo habitual y el throttling en primer plano activándose antes. Pero porque sus topes sucios eran conservadores, el sistema degradó con gracia: el procesamiento tardó más, pero los hosts permanecieron responsivos y nada derivó en una tormenta.

El equipo usó la evidencia para rechazar el cambio de almacenamiento y ajustar temporalmente la programación de trabajos. Sin heroísmos, sin arqueología del kernel a las 3 AM. La práctica correcta no fue un sysctl mágico; fue tratar los sysctls como configuración de producción con observabilidad y control de cambios.

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

1) Síntoma: “Se usa swap aunque haya RAM libre”

Causa raíz: “Free” no es la métrica. Linux usa RAM para caché; las decisiones de swap dependen del coste de reclaim, no de “MB libres”. Además, páginas anónimas frías pueden swapearse para mantener la caché caliente.

Solución: Mira available en free, traza swap-in/out con vmstat. Si swap-in está cerca de cero y el rendimiento es bueno, no hagas nada. Si swap-in sube bajo carga, reduce el conjunto de trabajo o baja swappiness con cautela.

2) Síntoma: stalls periódicos de 10–60 segundos; muchos procesos en estado D

Causa raíz: Acantilado de writeback: demasiadas páginas sucias se acumulan, luego un flush grande satura el almacenamiento y limita a los escritores.

Solución: Establece vm.dirty_background_bytes y vm.dirty_bytes a topes sensatos; verifica con /proc/meminfo y herramientas de latencia IO (iostat). Considera bajar modestamente los intervalos expire/writeback si los flushes son demasiado agrupados.

3) Síntoma: alto IO wait pero bajo throughput de disco

Causa raíz: Almacenamiento ligado a latencia (limitado por IOPS, colas, throttling o vecino ruidoso). El throttling por dirty puede amplificarlo porque los escritores esperan a que termine el writeback.

Solución: Usa iostat -xz para revisar await y tamaño de cola; reduce topes sucios para limitar explosiones de cola; resuelve límites de almacenamiento (volumen con más IOPS, NVMe local o reducir escrituras síncronas).

4) Síntoma: swapping provoca picos en latencia cola, pero el total de swap usado es “pequeño”

Causa raíz: Incluso pequeñas tasas de swap-in pueden perjudicar si golpean páginas calientes en pico. Las cargas sensibles a latencia odian el swap-in más que bajar la caché.

Solución: Baja swappiness (p. ej., 10–30), confirma que la app no tiene fugas de memoria y asegúrate de que el dispositivo de swap no sea lento. Considera fijar servicios críticos con cgroups apropiados en lugar de hacks globales.

5) Síntoma: tras ajustar dirty bytes, el throughput cayó y la CPU subió

Causa raíz: Redujiste demasiado el buffering, causando flushes más frecuentes y pequeños y más overhead, o forzaste throttling en primer plano con demasiada frecuencia.

Solución: Aumenta dirty_bytes modestamente (por ejemplo, de 512 MiB a 1 GiB) y vuelve a medir. No regreses a ratios enormes; busca los topes más pequeños que eviten stalls.

6) Síntoma: “Los ajustes no persisten” tras reiniciar

Causa raíz: Cambiaste solo el runtime sysctl, o algún componente sobrescribe sysctls al arranque.

Solución: Pon los ajustes en /etc/sysctl.d/, ejecuta sysctl --system e inspecciona logs de arranque o gestión de configuración para encontrar la fuente del override.

7) Síntoma: un contenedor swappea mucho mientras el host parece bien

Causa raíz: Límites de memoria de cgroup (y posiblemente límites de swap) disparan reclaim dentro del contenedor.

Solución: Revisa memory.max del cgroup, requests/limits del contenedor y el dimensionamiento de la carga. El swappiness global no arreglará un límite de contenedor demasiado estricto.

10. Listas de verificación / plan paso a paso

Paso a paso: flujo de trabajo seguro para tuning en producción

  1. Escribe el síntoma en términos operativos: “p99 latencia sube durante escrituras por lotes”, “hosts se congelan 30 segundos”, “swap-in sube tras deploy”. Si no puedes expresarlo, no puedes validarlo.
  2. Captura líneas base: swappiness/dirty settings, uso de swap, memoria sucia, latencia de IO y tareas bloqueadas.
  3. Prueba qué presión domina: memoria vs writeback vs saturación de almacenamiento.
  4. Cambia una dimensión a la vez: swappiness o límites dirty, no ambos, salvo que ya estés seguro de que ambos contribuyen.
  5. Usa sysctl en runtime primero: valida el efecto bajo carga real sin comprometer.
  6. Despliegue canario: aplica a un subconjunto pequeño; compáralo con patrones de carga idénticos.
  7. Persiste vía sysctl.d: mantén un solo archivo autoritativo por rol de host; evita “ajustes misteriosos”.
  8. Validación post-reboot: confirma tras reiniciar que los ajustes permanecen y el comportamiento coincide.
  9. Documenta el porqué: incluye la métrica que buscabas cambiar y el resultado observado.

Lista de verificación: qué significa “bien” tras tunear

  • Swap-in/out cercano a cero durante tráfico normal (a menos que haya una razón conocida).
  • La memoria sucia sube y baja de forma suave, no en dientes de sierra con largos stalls.
  • La latencia de IO no tiene picos periódicos enormes durante flush en segundo plano.
  • Pocos o ningún hilo de aplicación atascado en estado D por mucho tiempo.
  • El throughput es aceptable y la latencia cola es estable.

Lista de verificación: plan de rollback

  • Mantén el contenido del último archivo sysctl conocido bueno en el historial de tu gestión de configuración.
  • Tener un one-liner para revertir ajustes en runtime (sysctl -w con los valores antiguos).
  • Saber qué gráficas deberían mejorar en minutos (dirty/IO wait) frente a horas (tendencias de swap, comportamiento de caché).

11. Preguntas frecuentes

1) ¿Debería poner vm.swappiness=1 en todos los servidores?

No. En servicios sensibles a latencia puede ser razonable. En servidores intensivos en IO que se benefician de la caché puede empeorar las cosas al descartar caché agresivamente. Empieza alrededor de 20–30 y mide.

2) ¿El uso de swap siempre es malo?

La actividad de swap durante picos suele ser mala. Pequeñas cantidades de swap usadas pueden estar bien si esas páginas son verdaderamente frías y el swap-in permanece cerca de cero.

3) ¿Qué es mejor: ratios dirty o bytes dirty?

Los bytes dirty son más predecibles con tamaños de RAM variables y suelen ser más seguros en máquinas con mucha memoria. Los ratios son más simples pero pueden escalar a deudas de writeback absurdas en servidores modernos.

4) Si limito dirty bytes, ¿perderé rendimiento?

Puede que pierdas throughput máximo de escrituras agrupadas durante ráfagas. A menudo ganas estabilidad y menor latencia cola. En sistemas de producción, ese intercambio suele ser correcto.

5) ¿Estos ajustes importan para bases de datos?

A veces. Bases de datos con su propio buffering y patrones WAL pueden importar más el scheduler de IO, el sistema de archivos y el almacenamiento. Pero el writeback sucio aún puede afectar tareas en segundo plano, logs y cualquier ruta de IO buffered alrededor de la base de datos.

6) ¿Por qué veo alto IO wait cuando la CPU está mayormente inactiva?

Porque los hilos están bloqueados en IO. CPU “idle” no significa que el sistema esté sano; puede significar que la CPU no tiene trabajo mientras todos esperan el almacenamiento.

7) ¿Debo desactivar swap para forzar que la app falle rápido?

Sólo si realmente quieres que los OOM kills sean tu mecanismo principal de control y tienes fuerte orquestación/reintentos. Para muchas flotas, el swap es una red de seguridad que evita fallos en cascada durante picos cortos de memoria.

8) ¿Necesito tunear dirty_expire_centisecs y dirty_writeback_centisecs?

Normalmente no. Empieza con dirty bytes/ratios. Considera los intervalos solo si tienes stalls periódicos relacionados con flush y confirmaste que los límites sucios no son el problema central.

9) Cambié sysctls y no mejoró nada. ¿Por qué?

Porque el cuello de botella suele ser throughput/latencia de almacenamiento, patrones de escritura de la aplicación o fugas de memoria. Los sysctls moldean el comportamiento en los márgenes; no crean capacidad.

10) ¿Cuál es una forma segura de probar sin arriesgar la flota?

Cambios en runtime en una instancia canaria bajo carga representativa, con métricas claras de éxito: tasa de swap-in, niveles sucios, latencia de IO y latencia de solicitudes.

12. Conclusión: próximos pasos prácticos

Si tus máquinas Ubuntu 24.04 se quedan atascadas y ves o bien churn de swap o una pared de writeback sucio, no necesitas folklore. Necesitas un ciclo cerrado: observar, limitar, verificar.

  1. Ejecuta el diagnóstico rápido: ¿es presión de swap, presión de writeback o almacenamiento saturado?
  2. Si los acantilados de writeback son la historia, pasa de thresholds basados en ratios a topes en bytes dimensionados a la realidad de tu almacenamiento.
  3. Si las tormentas de swap son la historia, baja swappiness con cautela y arregla la causa real: conjunto de trabajo, fugas o RAM insuficiente.
  4. Persiste los ajustes en /etc/sysctl.d/, haz canary, y verifica tras reiniciar.

Estos ajustes son pequeños. Ese es el punto. Cambios pequeños y dirigidos que convierten las 02:17 en una noche normal.

← Anterior
Repositorio no-subscription de Proxmox: configurar repos sin romper actualizaciones
Siguiente →
Ubuntu 24.04: Sorpresas de ballooning de memoria — establece límites sensatos y detén las tormentas de swap (caso #76)

Deja un comentario