Anti-freeze en NixOS: zram + earlyoom, o por que aurin se nos colgo dos veces


5 de mayo de 2026

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:

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=true

Eso 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

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:

  1. Defensiva permanente: bajar common.nix.maxJobs a doce. Trade-off aceptable: rebuilds normales un pelin mas lentos, pero NUNCA cuelgue.

  2. 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 8

    Cuatro 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:

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.

Comparte este post:

Es tu post

Estas seguro? Esto no se puede deshacer.

Comentarios (0)

Sin comentarios todavia. Se el primero!

Deja un comentario