Linux: Método de 10 minutos para encontrar qué consume realmente tu RAM

¿Te fue útil?

Te alertan: “El host se quedó sin memoria.” Entras por SSH, ejecutas free -h y te dice que la memoria está “used”. Muy útil. Como una alarma de humo que solo dice “HUMO”.

El truco es dejar de tratar la RAM como un único cubo. Linux la divide en cubos que importan operativamente: memoria anónima (tus procesos), caché respaldada por archivos (I/O rápido), slabs del kernel (metadatos) y límites impuestos por cgroups. En diez minutos normalmente puedes identificar al verdadero consumidor y elegir la solución correcta en vez del clásico movimiento: reiniciar y rezar.

Guía rápida de diagnóstico

Este es el orden que evita que persigas fantasmas. Buscas primero el cubo más grande, luego estrechas al culpable, y finalmente validas con la contabilidad del propio kernel.

Primero: ¿Es presión real de memoria o solo caché de páginas?

  • Revisa free -h y céntrate en available, no en used.
  • Revisa la actividad de swap (vmstat / sar) y los fallos de página mayores si los tienes.

Segundo: ¿Está el kernel bajo presión de memoria (riesgo OOM) y por qué?

  • Busca en dmesg logs del OOM killer y qué cgroup lo disparó.
  • Consulta /proc/meminfo para el desglose anon vs file vs slab.

Tercero: Identifica a los mayores consumidores con la métrica correcta

  • Empieza con RSS por proceso para hallar culpables obvios (ps).
  • Luego usa PSS cuando la memoria compartida haga que RSS sea engañoso (smem o /proc/*/smaps_rollup).
  • Si hay contenedores, revisa primero los cgroups; “top en el host” no es una herramienta de contabilidad de contenedores.

Cuarto: Si los procesos no lo explican, sospecha memoria del kernel

  • Crecimiento de slab: slabtop, /proc/slabinfo, Slab en meminfo.
  • Pilas del kernel, tablas de páginas y memoria no reclamable pueden tumbar un host silenciosamente.

Quinto: Valida la vía de corrección antes de hacer algo drástico

  • Mata o reinicia solo al culpable, no a toda la máquina.
  • Establece límites sensatos (systemd/Kubernetes) después de conocer el uso en estado estacionario.
  • Corrige fugas con evidencia: curvas de crecimiento, no sensaciones.

Modelo mental: qué significa realmente “RAM usada”

Linux usa la RAM agresivamente porque la memoria inactiva es una oportunidad desperdiciada. Llenará memoria con caché de archivos, dentries, cachés de inodos y otros aceleradores de rendimiento. Eso no es una fuga; es el kernel haciendo su trabajo.

La pregunta operativa no es “¿Por qué está alto el used?” Es “¿El sistema tiene poca memoria reclamable?” Por eso free muestra una estimación available. “Available” significa, aproximadamente: si empiezas una carga nueva ahora mismo, ¿cuánto puede liberar el kernel sin provocar un colapso?

La presión de memoria se vuelve real cuando:

  • Swap se usa activamente y especialmente si las tasas de swap-in/out no son triviales.
  • El reclaim directo retrasa tus cargas: picos de latencia, CPU en kernel, kswapd ocupado.
  • Aparece el OOM killer, ya sea a nivel de host o dentro de un cgroup (los contenedores suelen morir silenciosamente).
  • Slab o memoria no reclamable crece, dejando menos espacio reclamable.

Aquí está el desglose que debes tener en mente:

  • Memoria anónima (AnonPages): heaps, stacks, JITs, mallocs. Esto es lo que consumen las “aplicaciones”.
  • Memoria respaldada por archivos (Cached): caché de sistema de archivos. Reclamable cuando hace falta, por lo general.
  • Slab (SReclaimable + SUnreclaim): cachés/metadatos del kernel. Parcialmente reclamable.
  • Memoria comprometida: promesas. No todas las promesas se convierten en deuda, pero vigila el overcommit.
  • cgroups: límites. Puedes tener mucha RAM en el host y aun así OOM dentro de un contenedor.

Una idea parafraseada de una leyenda de la confiabilidad: Idea parafraseada — John Allspaw ha argumentado que los incidentes de producción a menudo son “trabajo normal” colisionando con suposiciones; las páginas de memoria encajan perfectamente en ese patrón.

Chiste #1: El troubleshooting de memoria es como hacer dieta: los números son reales, pero las etiquetas te mienten.

Método de 10 minutos (tareas con comandos)

Abajo hay tareas prácticas que puedes ejecutar en cualquier máquina Linux (metal, VM, host de contenedores). Cada tarea incluye: el comando, qué significa la salida y la decisión que tomas.

Tarea 1: Haz la comprobación de realidad con free

cr0x@server:~$ free -h
               total        used        free      shared  buff/cache   available
Mem:            31Gi        27Gi       1.2Gi       512Mi       2.8Gi       2.9Gi
Swap:          8.0Gi       2.1Gi       5.9Gi

Qué significa: El número clave es available. Aquí es ~2.9Gi, que no es cómodo si la carga pica. Swap ya está en uso (2.1Gi), lo que apunta a presión real.

Decisión: Si available es bajo y swap está usado o creciendo, sigue investigando. Si available está sano y swap tranquilo, tu “RAM usada” probablemente es caché y puede que tengas otro problema (como disco o CPU).

Tarea 2: Revisa paging activo y comportamiento de reclaim con vmstat

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 2211840 923456  65536 911872   12   40    15    20  420  880 12  6 78  4  0
 1  0 2211840 905216  65536 918528    0   16     0     8  410  860 11  6 79  4  0
 3  0 2211840 892928  65536 924160    0    0     0     0  450  910 14  7 76  3  0
 4  0 2211840 876544  65536 931072    0   24     0    12  470  980 17  8 72  3  0
 2  0 2211840 868352  65536 936960    0    8     0     4  440  900 13  6 77  4  0

Qué significa: si/so son swap-in/out por segundo. Valores no cero sostenidos significan que el kernel está activamente moviendo páginas. También observa wa (I/O wait) y b (procesos bloqueados).

Decisión: Si el swap-out persiste, estás bajo presión de memoria. Tu siguiente paso es identificar qué cubo está creciendo: procesos, caché, slab o cgroups.

Tarea 3: Lee el suero de la verdad: /proc/meminfo

cr0x@server:~$ egrep 'MemTotal|MemFree|MemAvailable|Buffers|Cached|SwapTotal|SwapFree|AnonPages|Mapped|Shmem|Slab|SReclaimable|SUnreclaim|KernelStack|PageTables' /proc/meminfo
MemTotal:       32949044 kB
MemFree:         842112 kB
MemAvailable:   3026112 kB
Buffers:          65536 kB
Cached:          956812 kB
SwapTotal:      8388604 kB
SwapFree:       6193152 kB
AnonPages:     25801120 kB
Mapped:          612340 kB
Shmem:           524288 kB
Slab:           1523400 kB
SReclaimable:    812000 kB
SUnreclaim:      711400 kB
KernelStack:      112000 kB
PageTables:       184000 kB

Qué significa: AnonPages es enorme: los procesos son el consumidor principal. Cached es relativamente pequeño, así que no es “solo caché de páginas.” Slab no es trivial; observa cuánto es no reclamable.

Decisión: Si AnonPages domina, busca en procesos/cgroups. Si Cached domina y MemAvailable es bajo, podrías estar thrashing por patrones de I/O. Si Slab/SUnreclaim domina, sospecha crecimiento de memoria del kernel (a menudo relacionado con sistema de archivos o red).

Tarea 4: Comprueba si el OOM killer ya actuó

cr0x@server:~$ dmesg -T | egrep -i 'oom|out of memory|killed process' | tail -n 20
[Tue Feb  4 10:18:22 2026] Memory cgroup out of memory: Killed process 24198 (java) total-vm:8123456kB, anon-rss:6123456kB, file-rss:12000kB, shmem-rss:0kB, UID:1001 pgtables:14200kB oom_score_adj:0
[Tue Feb  4 10:18:22 2026] oom_reaper: reaped process 24198 (java), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB

Qué significa: Esto no fue un “OOM del host.” Fue un OOM de cgroup, matando a java dentro de un grupo limitado (unidad systemd o contenedor). Esta es la razón más común por la que los equipos dicen “pero el host tenía memoria libre”.

Decisión: Si aparece un OOM de cgroup, deja de mirar listas globales de top. Céntrate en la contabilidad de cgroups y en los límites de contenedores/unidades.

Tarea 5: Identifica rápidamente procesos con mayor RSS

cr0x@server:~$ ps -eo pid,user,comm,rss,pmem --sort=-rss | head -n 15
  PID USER     COMMAND        RSS %MEM
24198 app      java        6321452 19.2
19872 app      node        1823340  5.5
 1321 root     dockerd      612448  1.8
 2012 mysql     mysqld       588120  1.7
 1711 root     prometheus   312880  0.9
  922 root     systemd-jou  188244  0.5
 2666 root     nginx         92240  0.2

Qué significa: RSS es resident set size: RAM física mapeada en el proceso. Es una herramienta tosca, pero detecta rápidamente a los ofensores evidentes.

Decisión: Si un proceso está muy por encima de los demás y coincide con el momento del incidente, tienes a un sospechoso principal. Luego: verifica con PSS (la memoria compartida puede inflar RSS) y revisa cgroups/límites.

Tarea 6: No te engañe la memoria compartida—usa PSS con smaps_rollup

cr0x@server:~$ sudo sh -c 'cat /proc/24198/smaps_rollup | egrep "Pss:|Rss:|Private_Dirty:|Private_Clean:|Shared_Dirty:|Shared_Clean:"'
Rss:                6321452 kB
Pss:                6189021 kB
Shared_Clean:         12400 kB
Shared_Dirty:          1024 kB
Private_Clean:        88000 kB
Private_Dirty:      6219028 kB

Qué significa: PSS (proportional set size) reparte las páginas compartidas entre procesos. Aquí PSS está cerca de RSS, así que el proceso realmente posee esa memoria (private dirty es masiva).

Decisión: Un alto Private_Dirty sugiere crecimiento de heap o patrones de fuga. Si PSS es mucho menor que RSS, podrías estar culpando al proceso equivocado debido a bibliotecas compartidas o mapeos compartidos.

Tarea 7: Si tienes smem, úsalo; ahorra tiempo

cr0x@server:~$ smem -r -k -t | head -n 12
  PID User     Command                         Swap      USS      PSS      RSS
24198 app      java                           1024K   6000M   6044M   6173M
19872 app      node                             0K   1600M   1652M   1802M
 1321 root     dockerd                          0K    420M    435M    598M
 2012 mysql    mysqld                           0K    510M    522M    575M
-------------------------------------------------------------------------------
                                        1024K   8530M   8653M   9148M

Qué significa: USS es unique set size (memoria privada). PSS es la mejor métrica de atribución a nivel sistema para saber “quién consume realmente”.

Decisión: Prioriza procesos con PSS/USS altos al diseñar una mitigación. RSS sirve para una mirada rápida; PSS es lo que citás en un postmortem.

Tarea 8: Revisa límites de contenedores o unidades systemd (cgroups v2)

cr0x@server:~$ mount | grep cgroup2
cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime)

cr0x@server:~$ systemctl status myapp.service --no-pager | egrep 'Memory|Tasks'
     Memory: 6.3G (limit: 6.5G)
     Tasks: 92 (limit: 2048)

Qué significa: Esta unidad está limitada. Puedes tener 128GB libres en el host y aun así ser matado a 6.5GB. El límite es parte del contrato del sistema.

Decisión: Si el tope está por debajo del working set pico, o elevas el límite, o reduces uso de memoria, o aceptas la muerte como tu “autoescalador” (no recomendado para servicios con estado).

Tarea 9: Inspecciona memory.current/peak y events del cgroup

cr0x@server:~$ CG=/sys/fs/cgroup/system.slice/myapp.service
cr0x@server:~$ sudo sh -c "cat $CG/memory.current; cat $CG/memory.max; cat $CG/memory.peak; cat $CG/memory.events"
6848124928
6983510016
6950022144
low 0
high 0
max 12
oom 12
oom_kill 12

Qué significa: memory.max es el tope duro. memory.peak muestra el peor uso observado. oom_kill confirma que hubo kills por cgroup.

Decisión: Si memory.current está cerca de memory.max, deja de tratarlo como un problema del host. Es un problema de límite o una fuga dentro del servicio.

Tarea 10: Encuentra el desglose por cgroup (anon/file/slab) en cgroups v2

cr0x@server:~$ sudo sh -c "cat $CG/memory.stat | egrep 'anon |file |slab |sock |shmem |file_mapped|file_dirty|inactive_anon|inactive_file|active_anon|active_file'"
anon 6423011328
file 211345408
shmem 0
slab 142110720
sock 9123840
file_mapped 54476800
file_dirty 122880
inactive_anon 6112147456
active_anon 310863872
inactive_file 188743680
active_file 22601728

Qué significa: El cgroup está dominado por anon. Esa es memoria de la aplicación, no caché. Slab está presente pero no es la historia principal.

Decisión: Si file domina, podrías estar haciendo cache dentro del cgroup; ajusta patrones de lectura o permite más margen. Si anon domina, necesitas disciplina de heap, menos objetos en memoria o un límite mayor.

Tarea 11: Investiga crecimiento de slab con slabtop

cr0x@server:~$ sudo slabtop -o | head -n 15
 Active / Total Objects (% used)    : 4821102 / 5012240 (96.2%)
 Active / Total Slabs (% used)      : 118220 / 118220 (100.0%)
 Active / Total Caches (% used)     : 94 / 132 (71.2%)
 Active / Total Size (% used)       : 1289012.40K / 1390024.00K (92.7%)
 Minimum / Average / Maximum Object : 0.01K / 0.28K / 8.00K

  OBJS ACTIVE  USE OBJ SIZE  SLABS OBJ/SLAB CACHE SIZE NAME
812320 801200  98%    0.19K  38777       21    155108K dentry
610112 605900  99%    0.62K  23832       16    238320K inode_cache
420000 418000  99%    0.10K  10769       39     43076K kmalloc-96

Qué significa: Grandes caches de dentry/inode apuntan a presión por metadatos del sistema de archivos: muchos ficheros, muchas búsquedas de rutas, o una carga que remueve entradas de directorio (hosts de build, desempaquetadores, extracción de imágenes de contenedores).

Decisión: Si slab es el culpable, no intentes “optimizar la app.” Observa el comportamiento del sistema de archivos, scripts que hacen escaneos de directorio descontrolados, backups recursivos, o millones de ficheros pequeños. También revisa bugs del kernel o fugas de drivers si los slabs son extraños/inesperados.

Tarea 12: Revisa archivos abiertos y crecimiento de FD cuando la memoria “desaparece”

cr0x@server:~$ sudo lsof -p 24198 | wc -l
18452

cr0x@server:~$ cat /proc/24198/limits | egrep 'Max open files'
Max open files            1048576              1048576              files

Qué significa: Altos contadores de FD suelen correlacionarse con uso de memoria (buffers, memoria de sockets, estructuras por FD, caching en espacio de usuario). No siempre, pero es una señal fuerte de que algo está creciendo.

Decisión: Si los FDs suben de forma sostenida con la memoria, probablemente tienes una fuga de recursos (conexiones, ficheros, watchers). Arregla la fuga; subir límites solo retrasa el incidente.

Tarea 13: Comprueba si tmpfs o memoria compartida comen RAM

cr0x@server:~$ df -hT | egrep 'tmpfs|shm'
tmpfs      tmpfs  3.2G  2.7G  0.5G  85% /run
tmpfs      tmpfs   16G  9.0G  7.0G  57% /dev/shm

Qué significa: tmpfs usa RAM (y swap). Si /dev/shm crece, esa memoria es efectivamente anónima y puede presionar el sistema.

Decisión: Si el crecimiento de tmpfs es inesperado, encuentra al escritor (a menudo navegadores, cargas de ML, sistemas con IPC intensivo) y limita o reubica ese almacenamiento.

Tarea 14: Revisa KSM y THP si persigues rarezas

cr0x@server:~$ cat /sys/kernel/mm/transparent_hugepage/enabled
[always] madvise never

cr0x@server:~$ cat /sys/kernel/mm/ksm/run
0

Qué significa: THP puede mejorar rendimiento o crear picos de latencia y fragmentación en algunas cargas. KSM normalmente está apagado a menos que lo actives (común en hosts de virtualización históricamente).

Decisión: No los cambies en medio del incidente a menos que conozcas el comportamiento de tu carga. Si sospechas problemas de THP, captura evidencia (tasas de fallos, latencia, fragmentación) y cambia en ventanas de mantenimiento.

Interpretación de resultados: decisiones defendibles

Cuando “used” está alto pero “available” está bien

Si MemAvailable está sano y la actividad de swap es casi nula, probablemente tienes un sistema Linux normal con mucha caché. La acción correcta suele ser: no hacer nada, pero verificar que no estés alcanzando límites de cgroup para servicios críticos.

Los equipos a veces “arreglan” esto vaciando caches. Eso no es una solución; es deliberadamente hacer la próxima lectura más lenta.

Cuando domina AnonPages

Aquí viven las fugas. Pero también aquí viven los working sets legítimos en memoria (datastores, heaps de JVM, caches intencionales).

  • Si Private_Dirty de un proceso sube de forma sostenida durante horas/días, sospecha una fuga o un cache sin límites.
  • Si sube con el tráfico y baja cuando el tráfico cae (y corre GC), puede ser elasticidad normal.
  • Si sube tras un deploy y no vuelve, trátalo como una regresión hasta probar lo contrario.

Cuando domina slab

La memoria del kernel suele ser “el impuesto invisible.” Inodes, dentries, buffers de red, tablas de conntrack y metadatos de sistema de archivos pueden sumar. Slab no es malo; slab sin límites lo es.

Causas típicas:

  • Millones de ficheros pequeños y recorrido constante de directorios.
  • Capas de contenedores explotando y comportamiento de extracción de imágenes.
  • Sistemas con mucha red y tablas de tracking de conexiones grandes.
  • Bugs del kernel/drivers (más raros, pero lo sabrás porque nada en espacio de usuario explica la pérdida).

Cuando la máquina tiene RAM pero el servicio recibe OOM-kill

Esto es un problema de cgroups. El límite del servicio es finito. O tu límite está mal, o tu working set creció, o el servicio hace algo nuevo (como cachear más datos).

Operativamente, trátalo como un tema de contrato de producción: alinea límites con la realidad y añade alertas en memory.current aproximándose a memory.max.

Chiste #2: El OOM killer es el único integrante del equipo que siempre toma acción decisiva—desafortunadamente, nunca asiste al postmortem.

Tres mini-historias de la vida corporativa

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

Una empresa mediana operaba una flota de servidores API detrás de un balanceador. Sus paneles mostraban hosts al 40–50% de “used” memoria, y todos se sentían seguros. Entonces, tras un pico de tráfico, las APIs empezaron a devolver 502 aleatoriamente. No un fallo total—peor. Parcial, que hace que todos discutan.

El on-call miró métricas del host: mucha RAM, CPU bien, disco bien. Reiniciaron algunos pods (Kubernetes), mejoró, luego volvió a degradarse. El incidente se etiquetó como “inestabilidad de red” porque así llaman a los problemas que no ven.

Alguien finalmente hizo tail a dmesg en un nodo y vio mensajes repetidos de Memory cgroup out of memory. Cada kill ocurría dentro del cgroup de un pod. El host tenía memoria, pero los pods estaban limitados y eran matados bajo carga burst. Su suposición—memoria libre del host = memoria del servicio—era la mentira.

La solución no fue heroica: fijaron el límite del pod basado en PSS observada durante picos, añadieron un margen de seguridad y alertas en memory.events. Tras eso, el mismo patrón de tráfico produjo mayor latencia (aceptable) en vez de muertes aleatorias (inaceptable). La lección más grande: la “RAM libre” en el nodo es irrelevante cuando vives dentro de cgroups.

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

Un equipo de plataforma de datos quería reducir lecturas de disco en un host de procesamiento por lotes. Alguien activó un ajuste para mantener más datos en memoria en la capa de aplicación: caches más grandes, buffers mayores, más paralelismo. Los benchmarks mejoraron. Todos celebraron. Lo desplegaron.

Dos semanas después, la flota empezó a hacer swap bajo carga normal. Latencias y duración de jobs se dispararon. El equipo culpó al array de almacenamiento. Luego al hypervisor. Luego a “Linux siendo raro”. Clásico.

Cuando midieron AnonPages y PSS por proceso, hallaron que el nuevo cache era esencialmente ilimitado bajo ciertas mezclas de jobs. No era una “fuga” en sentido estricto; era un cache sin disciplina de expulsión. La app acumulaba memoria, expulsando caché de sistema de archivos y forzando al kernel a hacer swap de otras páginas.

El retroceso fue sutil: su “optimización” redujo lecturas en pequeña escala, pero en concurrencia de producción aumentó la presión de memoria total y creó amplificación de I/O vía swapping. La solución fue poner topes duros en caches de la app y probar con concurrencia y dataset similares a producción. Las ganancias de memoria existen; la arrogancia con memoria sale cara.

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

Un servicio de pagos corría en unidades systemd (sin contenedores). Tenían un hábito poco glamuroso: cada servicio tenía un perfil de memoria base registrado después de cada release. No un APM sofisticado. Solo un script que capturaba /proc/meminfo, listas top RSS de ps y resúmenes smaps_rollup por proceso, almacenados con metadatos de build.

Una tarde, un nodo mostró uso de memoria en ascenso. Aún no había alarmas, solo una subida lenta. El on-call comparó el perfil PSS actual con el de la semana anterior. La diferencia fue obvia: la memoria private dirty del proceso principal era mayor y en tendencia ascendente.

Hicieron rollback antes de que el OOM killer actuara. Sin incidente visible al cliente. Luego, en ventana sin presión, reprodujeron la fuga en staging: una nueva ruta de feature asignaba objetos que se mantenían en un mapa global más tiempo del debido. El bug se arregló rápido porque el equipo tenía una línea base y podía decir “esto es nuevo” sin debate.

La práctica no era glamorosa. Simplemente convirtió discusiones de memoria de religión a aritmética. En producción, la corrección aburrida vence la conjetura ingeniosa todos los días.

Errores comunes (síntomas → causa raíz → solución)

1) Síntoma: alerta “RAM al 95% usada” sigue sonando, pero el rendimiento está bien

Causa raíz: La alerta se basa en used en lugar de available. Linux usa RAM para caché por diseño.

Solución: Alerta sobre MemAvailable (o un “porcentaje disponible” derivado), más actividad de swap. Cambia el runbook para que ignore solo “used”.

2) Síntoma: el servicio recibe OOM-kill pero el host tiene mucha memoria libre

Causa raíz: Límite de memoria de cgroup alcanzado (systemd/Kubernetes). Métricas a nivel host confunden.

Solución: Inspecciona memory.max, memory.current y memory.events. Establece límites basados en PSS/pico observado. Añade margen para picos y fragmentación.

3) Síntoma: swap está usado, pero ningún proceso parece enorme en top

Causa raíz: Memoria compartida infla/defla la percepción de RSS; o slab del kernel está consumiendo; o varios procesos medianos suman presión.

Solución: Usa PSS (smem o smaps_rollup), luego revisa slab (slabtop) y /proc/meminfo.

4) Síntoma: memoria sube tras un deploy y finalmente OOMs

Causa raíz: Fuga o cache sin límite en espacio de usuario; a veces un cambio en la mezcla de tráfico dispara una ruta de crecimiento.

Solución: Confirma crecimiento con tendencia de Private_Dirty. Aplica un cap duro (config) como mitigación y luego depura con herramientas de heap apropiadas al runtime (JVM, Go, Python). No “simplemente añadas swap”.

5) Síntoma: slab crece, dentry/inode_cache dominan

Causa raíz: Churn de metadatos del sistema de archivos: directorios enormes, escaneos recursivos, artefactos de build, tormentas de logs, desempaquetado de imágenes de contenedores.

Solución: Reduce número y churn de ficheros; arregla scripts que ejecutan find recursivos repetidos; rota logs de forma sensata; evita explotar ficheros pequeños en hosts compartidos. Si es un host de build, aísla cargas.

6) Síntoma: picos de latencia aleatorios, CPU de kswapd, pero gráficas de RAM parecen “bien”

Causa raíz: Reclaim y compaction; posibles efectos secundarios de THP; fragmentación de memoria.

Solución: Correlaciona con estadísticas de paging y reclaim. Considera modo THP madvise para ciertas cargas, pero solo tras pruebas. La solución suele ser “menos presión de memoria”, no “gráficas diferentes”.

7) Síntoma: “La caché está enorme, drop_caches lo arregla”

Causa raíz: Vaciar caché enmascara un problema subyacente como un proceso fugado, metadatos fuera de control o un cgroup insuficiente; el kernel volverá a llenar la caché.

Solución: No operacionalices drop_caches como acción rutinaria. Mide qué impulsa el crecimiento de la caché; arregla la carga o establece presupuestos realistas de memoria.

Listas de verificación / plan paso a paso

Lista de 10 minutos para on-call (haz esto en orden)

  1. Ejecuta free -h. Si available es bajo, continúa; si no, valida swap y cgroups de todos modos.
  2. Ejecuta vmstat 1 5. Si si/so son persistentes y no cero, trátalo como presión real.
  3. Ejecuta el grep de meminfo. Decide: ¿anon-heavy vs cache-heavy vs slab-heavy?
  4. Revisa dmesg por OOM. Si es OOM de cgroup, detente y pivota a cgroups.
  5. Lista procesos top RSS con ps; identifica candidatos.
  6. Valida candidatos con smaps_rollup (PSS/private dirty).
  7. Si hay contenedores/unidades systemd: lee memory.current, memory.max, memory.events y memory.stat.
  8. Si los procesos no suman: investiga slab con slabtop.
  9. Revisa uso de tmpfs (df -hT para tmpfs/shm).
  10. Elige acción: reiniciar al culpable, aumentar límite, arreglar fuga, reducir churn de ficheros o escalar. Evita reiniciar salvo que sea necesario para detener la hemorragia.

Lista de decisiones: qué cambias según lo que encuentres

  • Un solo proceso posee memoria (alto PSS/private dirty): Mitiga reiniciando/rollback; implementa límites; depura la fuga.
  • Muchos procesos suman presión: Reduce concurrencia, escala horizontalmente o mueve jobs pesados fuera de hosts compartidos.
  • El cgroup está muy bajo: Sube el tope con margen; dimensiona requests/limits; alerta al acercarse al tope.
  • Slab es el consumidor principal: Arregla churn de sistema de archivos/red; reduce conteo de ficheros; investiga subsistemas del kernel.
  • La actividad de swap es el problema: Reduce uso de memoria; considera cambiar swappiness solo tras confirmar que ayuda a la carga.

Lista posterior al incidente (para no repetirlo)

  1. Captura una instantánea de memoria: meminfo, lista top PSS, stats de cgroup, resumen de slabtop.
  2. Añade alertas que reflejen la realidad: MemAvailable, I/O de swap, kills por cgroup OOM y crecimiento de slab.
  3. Establece presupuestos: presupuestos de memoria por servicio y pruébalos con concurrencia realista.
  4. Documenta la “línea base conocida” por release para que las regresiones sean obvias.

Hechos y contexto histórico (lo que explica las rarezas de hoy)

  1. El campo MemAvailable es relativamente moderno en términos del kernel; se añadió porque “memoria libre” era un pésimo predictor de memoria reclamable.
  2. Linux usa intencionalmente la RAM sobrante para page cache para evitar lecturas lentas de disco; ver “free” bajo suele ser señal de que el kernel hace su trabajo.
  3. Las decisiones del OOM killer son heurísticas: asigna puntuaciones de “maldad” a procesos. No es juicio moral; es triaje bajo presión.
  4. Los cgroups hicieron normal “un host, muchas realidades de memoria”. Un contenedor puede OOM mientras el nodo está bien, porque el nodo no es el universo del contenedor.
  5. RSS puede sobrecontar páginas compartidas. Por eso existe PSS: para atribuir páginas compartidas de forma más justa entre procesos.
  6. tmpfs está respaldado por RAM (y por swap). Almacenar “ficheros temporales” en tmpfs puede convertirse en presión de memoria permanente.
  7. Los caches slab son una característica de rendimiento: los kernels cachean objetos como dentries/inodes porque asignarlos/liberarlos continuamente es caro.
  8. Transparent Huge Pages se popularizó por ganancias de throughput, pero también introdujo compensaciones operativas: coste de compactación, fragmentación y sensibilidad a la latencia.

Preguntas frecuentes

1) ¿Por qué free muestra casi nada de memoria “free” en un sistema sano?

Porque Linux usa la RAM como caché. Mira available, no free. “Free” son páginas mayormente sin usar; “available” estima páginas reclamables más páginas no usadas.

2) ¿Debería vaciar caches para “liberar memoria”?

Casi nunca en producción. Vaciar caches es como volcar tu caja de herramientas en el suelo para que el banco parezca limpio. Puede aliviar la presión brevemente, pero suele perjudicar el rendimiento y ocultar la causa raíz.

3) ¿Cuál es la diferencia entre RSS, VIRT, USS y PSS?

VIRT es el espacio de direcciones virtual; puede ser enorme y sin significado. RSS son las páginas residentes en RAM, pero sobrecuenta páginas compartidas. USS es memoria privada (única). PSS es la mejor métrica de atribución a nivel sistema porque reparte proporcionalmente las páginas compartidas.

4) El OOM killer mató mi proceso más grande. ¿Significa que fue la causa?

No necesariamente. Fue el proceso más “matable” según la heurística en ese momento. La causa puede ser presión agregada, un límite de cgroup, o crecimiento no reclamable del kernel.

5) ¿Cómo sé si fue un OOM de cgroup?

Mira en dmesg por “Memory cgroup out of memory” y revisa /sys/fs/cgroup/.../memory.events. Si oom_kill incrementa ahí, es un problema de cgroup.

6) ¿Por qué el uso de swap es distinto de cero aunque tenga RAM libre?

El kernel puede mover páginas anónimas frías a swap para mantener más caché de archivos caliente. Tener swap distinto de cero no es automáticamente malo. Lo que importa son las tasas activas de swap-in/out y el impacto en latencia.

7) ¿Qué suele causar que la memoria slab explote?

Churn de metadatos de sistema de archivos (dentires/inodes), estructuras de tracking de red y a veces bugs del kernel. slabtop te dice qué cache está creciendo; eso apunta al subsistema culpable.

8) Mi contenedor muestra bajo uso de memoria por dentro, pero el host dice que es enorme. ¿Quién tiene razón?

Ambos pueden tener “razón” porque informan alcances y métricas diferentes. Para debug de contenedores, confía en archivos de cgroup: memory.current y memory.stat. Para debug del host, usa meminfo y PSS entre procesos.

9) ¿Cómo detecto una fuga de memoria rápidamente sin herramientas de profiling?

Busca crecimiento monótono en Private_Dirty (vía /proc/PID/smaps_rollup) e incremento de PSS a lo largo del tiempo, correlacionado con uptime o requests. Luego mitiga con reinicio/rollback e implementa un cap si es posible.

10) ¿Alguna vez es correcto añadir más RAM?

Sí—cuando el working set legítimamente excede la máquina y el coste de optimizar supera el coste de la RAM. Pero confirma con PSS y señales de presión primero; no pagues hardware para cubrir una fuga.

Conclusión: siguientes pasos que realmente reducen páginas

Cuando intentas responder “qué está comiendo la RAM”, no te quedes mirando un solo número. Usa una secuencia que obligue a la verdad: señales de presión, desglose por cubos, luego atribución con la métrica correcta (PSS), luego ámbito (cgroups vs host), y por último memoria del kernel si el espacio de usuario no cuadra.

Pasos prácticos:

  • Actualiza alertas para priorizar MemAvailable, actividad de swap y kills por cgroup OOM—no el “used” bruto.
  • Añade un script ligero de snapshot de memoria a tu kit de herramientas de incidentes (meminfo + top PSS + stats de cgroup + resumen de slab).
  • Dimensiona bien límites de memoria usando memory.peak observado y líneas base de PSS, luego añade margen.
  • Para reincidentes: implementa topes para caches en la app y considera cualquier crecimiento monótono de private dirty como una regresión hasta demostrar lo contrario.

Si no haces otra cosa: la próxima vez que te alerten, lee /proc/meminfo antes de formarte una opinión. Es más difícil discutir con el kernel cuando te está dando partidas.

← Anterior
Desastre al redimensionar particiones: deshazlo y arranca de nuevo
Siguiente →
DNS: Tu dominio funciona… hasta que deja de hacerlo — La trampa de la delegación explicada

Deja un comentario