Anti-freeze en NixOS: zram + earlyoom, o por que aurin se nos colgo dos veces
Lo que paso
Aurin (la workstation de Pascual: dual Xeon, ciento veintiocho gigas
de RAM, NixOS) se quedo PILLADA al cien por cien de uso de memoria.
Cursor congelado, imposible cambiar de ventana, ni siquiera el Ctrl+Alt+F2 responde. Reset duro, btn fisico. Ya
habia pasado antes con Chrome.
La pregunta interesante: si tienes ciento veintiocho gigas de RAM, ¿como demonios se queda sin memoria?
Por que se cuelga un Linux con la RAM al cien
Cuando Linux se acerca al limite de RAM, intenta varias cosas en orden:
+--------------------------------------------------------------+ | 1. Page cache shrink (libera caches de ficheros leidos) | | 2. Swap de paginas inactivas a disco (si hay swap) | | 3. OOM-killer: mata el proceso mas hambriento | +--------------------------------------------------------------+
Aurin tenia cero swap. Asi que el paso dos no aplica. Y el paso tres no es instantaneo: el OOM-killer del kernel toma decisiones MIENTRAS el sistema esta luchando por respirar. A veces tarda treinta segundos, sesenta, varios minutos. Durante ese tiempo, el sistema se queda en thrashing: leer-escribir-leer la misma pagina al disco una y otra vez. Indistinguible de un cuelgue total.
Sumamos: aurin tiene un proceso ocasional desbocado (Chrome con quinientas pestanias, GHC compilando todo Hackage, lo que sea), llega al cien por cien de RAM, no hay swap para soltar lastre, el OOM-killer no se decide, el sistema se cuelga, reset.
Solucion uno: swap. Pero no la de toda la vida.
La swap clasica es espacio en disco que el kernel usa cuando la RAM esta llena. Tiene una pega gorda: tu disco SSD/NVMe es mil veces mas lento que la RAM. Cuando el kernel empieza a swap, todo lo que necesita esas paginas se ralentiza brutalmente.
Hay una alternativa preciosa: zram.
Que es zram
zram es un dispositivo de bloque virtual que vive dentro de la RAM y comprime lo que se escribe en el. Imagina un trozo de RAM marcado como "swap" pero con un compresor (zstd, lz4) trabajando entre tu y el.
RAM total: 128 GB
===========================================
+-----------------------------+ +--------+
| 100 GB RAM "normal" | | 28 GB |
| (procesos, page cache) | | zram |
| | | swap |
+-----------------------------+ +--------+
|
| zstd compresion
v
~84 GB de
contenido virtual
zstd consigue ratios de 3:1 o mejores con datos tipicos de proceso. Asi que de veintiocho gigas reales de zram, sacas ochenta y cuatro gigas virtuales de espacio swap. Sin tocar el disco. Sin gastar ciclos SSD. Sin latencia de NVMe.
Por que esto evita el cuelgue
Cuando la RAM se acerca al limite:
sin zram con zram
======== ========
RAM 95% lleno RAM 95% lleno
| |
v v
no hay swap paginas inactivas comprimen
| a zram (rapido, en RAM)
v |
thrashing v
| RAM efectiva sube
v |
FREEZE v
OOM-killer tiene tiempo
de elegir victima ordenadamente
|
v
sistema vivo
Implementacion en NixOS
Una linea:
zramSwap = {
enable = true;
memoryPercent = 25; # 25% de la RAM como zram (~32GB en aurin)
algorithm = "zstd"; # mejor ratio que lz4
};Eso es todo. NixOS crea el dispositivo /dev/zram0, lo configura como swap, lo activa al
boot. Cero ficheros en disco. Cero mantenimiento.
Por que NO es la swap tradicional de servidor
Hay dos casos de uso para swap:
| Caso | Swap a disco | zram |
|---|---|---|
| Hibernacion (suspender a disco) | OK | NO sirve (necesita persistencia) |
| Anti-freeze por OOM | LENTO, dudoso | Brutal: rapido y eficaz |
| Maquinas con poca RAM (laptops < 4GB) | Necesario | Util complementario |
| Servidores con sobrecarga real (mas working set que RAM) | Necesario | NO, comprime gas |
Aurin no necesita hibernacion (es un sobremesa que se apaga). Y NO tiene sobrecarga real (ciento veintiocho gigas son MUCHOS gigas para un desarrollador). Lo que tiene es picos transitorios cuando un Chrome o un GHC se desbocan. zram es perfecto para eso.
Solucion dos: earlyoom
zram da margen, pero el OOM-killer del kernel sigue siendo lento.
Para matar al proceso problematico ANTES de que la cosa se descontrole,
metemos un watchdog en userspace: earlyoom.
Como funciona
loop infinito (cada 100ms aprox):
mira /proc/meminfo
|
|---- RAM libre > 5%? sigue mirando
|
`---- RAM libre <= 5%?
|
v
busca el proceso con mas RSS
|
v
SIGTERM (mata educadamente)
|
v (si sigue alta la RAM tras 1s)
SIGKILL (mata sin contemplaciones)
Diferencia con OOM-killer del kernel:
- earlyoom actua en userspace, no entra en pelea con el propio scheduler
- decide ANTES (en el cinco por ciento, no en el cero)
- es agresivo y rapido
Implementacion en NixOS
services.earlyoom = {
enable = true;
freeMemThreshold = 5; # mata si <5% RAM libre
freeSwapThreshold = 10; # ...y <10% swap libre (con zram, esto cuenta)
};La cadena completa
Ahora la cadena de defensa es:
Chrome se desboca (digamos llega a 80GB)
|
+--> Linux page cache shrink (libera 5GB)
|
+--> zram empieza a comprimir paginas inactivas
| de 80GB usados, 30GB van a zram (=10GB reales tras compresion)
| RAM "efectiva" libre sube
|
+--> Si Chrome SIGUE creciendo y supera el margen:
earlyoom detecta <5% libre
|
v
SIGTERM a Chrome (proceso con mas RSS)
|
v
Chrome muere, RAM se libera de golpe
|
v
Sistema sigue funcionando
(Pascual gruene pero no tiene que reiniciar)
Compare con el escenario sin nada de esto:
Chrome se desboca
|
+--> RAM al 100%
|
+--> kernel hace thrashing buscando que liberar
|
+--> sistema congelado durante minutos
|
+--> finalmente OOM-killer mata algo... pero quizas X11
| o el WM, no Chrome. Tu sesion entera murio.
|
+--> reboot fisico
El otro fix del dia: flameshot en XMonad
De propina, hoy tambien arreglamos que
flameshot (la app de screenshots)
crasheaba al lanzarse. Causa: flameshot version catorce ahora pide
screenshots a xdg-desktop-portal, y XMonad
no tiene un backend de portal que implemente Screenshot. Timeout,
crash.
Solucion: setting oculto en flameshot.ini:
useX11LegacyScreenshot=trueEso le dice a flameshot "ignora el portal, usa XCB nativo de toda la
vida". Funciona en XMonad/i3 inmediatamente. Cuando upstream haga su
xmonad.portal con Screenshot, podemos
quitarlo.
Para que viaje declarativo entre clones, en home-manager:
xdg.configFile."flameshot/flameshot.ini".text = ''
[General]
drawColor=#800000
drawFontSize=9
savePath=/home/passh/tmp
useX11LegacyScreenshot=true
'';Cuarto fix: limitar
max-jobs cuando toca
Plot twist: mientras escribia este post, el propio rebuild que aplicaba zram + earlyoom se autoinmolo dos veces. Subiendo la RAM al ochenta y cinco por ciento, luego al noventa y dos. Tuvimos que matarlo a mano ANTES del cuelgue. Ironia maxima: el rebuild que iba a salvarnos del OOM nos lo iba a provocar.
Causa concreta: aurin tenia max-jobs=72
(porque tiene setenta y dos hilos) y por defecto cores=0 ("todos los cores por job"). Resultado:
setenta y dos builds paralelos, cada uno spawneando setenta y dos
cc1plus.
El detalle que cambia todo: Haskell + Hackage desde fuente
Aqui la observacion importante: con esta config, ningun rebuild anterior habia petado. Y son cientos. ¿Por que hoy si?
Porque hoy era el primer rebuild que tenia que
compilar ecosistema Haskell desde
fuente. cache.nixos.org
aun no tiene los paquetes con base 4.22
(GHC 9.14 acaba de salir), asi que cabal/cabal-install/ ghcid/etc se
construyen localmente. Y GHC tiene hambre:
Build C/C++ tipico Build Haskell tipico =================== ==================== cc1plus: 200-500 MB RAM ghc: 2-8 GB RAM tiempo: 5-30s tiempo: 1-10 min linking: ld rapido linking: ld estatico, lento
72 jobs × 5GB GHC = ~360GB demandados. 125GB disponibles. Imposible sin swap+OOM. Y antes nunca habia pasado porque nunca habia tocado el ecosistema Haskell entero a la vez.
Por que GHC se come tanto
- Type checking lazy-by-default es memoria-intensivo
- Templates Haskell ejecuta codigo Haskell durante compilacion
- Linking estatico de muchas libs (no shared)
La
leccion: max-jobs por defecto es para
builds NORMALES
max-jobs=72 sigue siendo correcto para
el 99% de rebuilds (kernel, desktop, apps GUI, scripts…). Lo que ha
pasado hoy es excepcional: recompilacion masiva de un ecosistema
hambriento.
Para evitar repetirlo:
Defensiva permanente: bajar
common.nix.maxJobsa doce. Trade-off aceptable: rebuilds normales un pelin mas lentos, pero NUNCA cuelgue.Ad-hoc cuando sabes: si vas a tocar Haskell unstable o algo sospechoso, usa flags CLI:
sudo nixos-rebuild switch --flake .#aurin --impure \ --max-jobs 4 --cores 8Cuatro jobs × ocho cores = treinta y dos hilos en uso. Suficiente para moverse, RAM acotada a ~30GB.
Yo me he quedado con la primera porque desplegar cualquier cambio critico no puede ser una ruleta rusa.
Regla pragmatica
Si tu maquina tiene N hilos:
- Builds normales:
max-jobs = N - Defensiva universal:
max-jobs = N/6 - Cuando toques GHC/Rust con many parallel deps:
max-jobs = N/16,--cores 8
Cinco fixes pequenos, una maquina mas robusta
Lo que cambio en hosts/aurin/default.nix y modules/home-manager/ hoy:
| Cambio | Por que |
|---|---|
zramSwap.enable |
Margen contra OOM, sin tocar disco. ~30GB virtuales |
services.earlyoom |
Mata hambriento ANTES de cuelgue (5% threshold) |
common.nix.maxJobs = 12 |
Acotar peor caso de RAM en builds paralelos |
flameshot.ini declarativo |
Bypass del portal Screenshot en XMonad |
| Hook fish para DISPLAY en zellij | Recuperar env grafico si pane stale |
Cinco lineas de Nix, dos cuelgues evitados al ano (estimacion optimista: probablemente mas), una app GUI que ya no crashea, un debugger DAP de Haskell que puede pegar imagenes al chat, y un rebuild que NO se autoinmola.
Por que esto vale la pena
Linux puro no te protege de ti mismo. Si abres cien pestanas de Chrome, si lanzas un build paralelo sin limite, si algun electron tiene un memory leak, es tu problema. El sistema base no tiene watchdog por defecto.
NixOS te da el control para anadir capas defensivas en una linea de configuracion, declarativas, en git, replicables a otros clones del enjambre con un commit. Eso es la diferencia entre "tener un Linux" y "tener una infraestructura personal cuidada".
Cierre
Si tienes una maquina Linux con bastante RAM y se te cuelga sola, revisa si tienes swap (con o sin zram) y si tienes algun OOM watchdog. La probabilidad de que falte algo es alta.
En NixOS:
zramSwap.enable = true;
services.earlyoom.enable = true;
nix.settings.max-jobs = 12; # ajusta a tu maquina (no la dejes en auto si haces Haskell)Tres lineas. Reboot. A vivir.
Y si te llama un compa que dice "se me ha colgado el linux con la RAM al cien por cien", ya sabes la receta. Bonus: cuentaselo con un par de diagramas ASCII y le has hecho el dia.
Notas finales
El zramSwap no es una idea nueva.
ChromeOS lo usa desde hace anos. Android lo usa. Fedora lo activa por
defecto desde 2022. Es estandar de facto en sistemas modernos. Que NixOS
no lo active por defecto es una decision de minimalismo. Tu eliges.
earlyoom tampoco es nuevo. Fedora lo
activa por defecto desde 2018. Es la solucion canonica al problema "el
OOM-killer del kernel es demasiado lento".
Lo nuevo aqui es haberlo aprendido a la fuerza tras dos cuelgues. Compartir es desbloquear: si esto evita un solo reset duro a otra persona leyendolo, ya hemos ganado.
Comentarios (0)
Sin comentarios todavia. Se el primero!
Deja un comentario