Dominando la Política OOM: Ajustando la Respuesta de Systemd a Eventos de Falta de Memoria
Aprende a controlar el comportamiento del asesino OOM (Out-of-Memory) de Linux usando systemd. Esta guía explora las directivas `OOMScoreAdjust` y `OOMPolicy` para proteger servicios críticos influyendo en qué procesos se terminan durante condiciones de baja memoria. Domina el ajuste OOM de systemd para una mayor estabilidad y resiliencia del sistema.
Dominando la Política OOM: Ajustando la Respuesta de Systemd a Eventos de Falta de Memoria
Las fallas por falta de memoria rara vez ocurren en un momento conveniente. Una importación por lotes recibe un archivo más grande de lo habitual, un servicio pierde memoria durante la noche, una copia de seguridad se superpone con un pico de tráfico, o un despliegue duplica el número de procesos de trabajo. Cuando Linux no puede liberar suficiente memoria para una asignación, el kernel puede invocar al asesino OOM y terminar un proceso para que la máquina pueda seguir funcionando.
La parte incómoda es que la víctima predeterminada puede no ser el servicio que habrías elegido. En un host compartido, podrías preferir que muera un trabajador de cola reintentable antes que la API principal. En un servidor de base de datos, es posible que desees que SSH y la monitorización sigan vivos para poder recuperar la máquina. Systemd te da dos perillas para ese tipo de decisión: OOMScoreAdjust= y OOMPolicy=.
OOMScoreAdjust= influye en qué proceso es seleccionado. OOMPolicy= controla lo que systemd hace después de que un proceso en el servicio ha sido eliminado. Resuelven problemas diferentes, y mezclarlos conduce a malos runbooks.
Lo que el Kernel está Puntuando
Cada proceso de Linux tiene una puntuación OOM, visible en /proc/<pid>/oom_score. Una puntuación más alta significa que el proceso es una víctima OOM más probable. El kernel deriva esa puntuación del uso de memoria y otro contexto, luego aplica el valor de ajuste de /proc/<pid>/oom_score_adj.
El OOMScoreAdjust= de systemd escribe ese ajuste para los procesos que inicia. El rango es de -1000 a 1000.
-1000proporciona la protección más fuerte y efectivamente desactiva la eliminación OOM para ese proceso.- Los valores negativos hacen que el proceso sea menos propenso a ser eliminado.
- Los valores positivos hacen que el proceso sea más propenso a ser eliminado.
0deja el ajuste neutral.
El enfoque más seguro generalmente no es "proteger todo lo importante". Si cada servicio está protegido, el kernel tiene menos opciones útiles cuando el host ya tiene poca memoria. Protege un pequeño número de servicios y haz que el trabajo desechable sea más fácil de eliminar.
Para un servicio de API principal, un ajuste moderado suele ser suficiente:
[Service]
OOMScoreAdjust=-300
Para un trabajador de cola que puede reintentar trabajos:
[Service]
OOMScoreAdjust=500
Ese trabajador puede morir primero durante la presión de memoria, pero ese es el punto. Un trabajo fallido puede volver a la cola. Una base de datos muerta o un host inalcanzable es un incidente mayor.
Lo que OOMPolicy Realmente Hace
OOMPolicy= no marca una unidad como "crítica", y no elige el primer proceso a eliminar. Los valores admitidos son continue, stop y kill.
continue: systemd registra el evento OOM y deja la unidad en ejecución si quedan procesos.stop: systemd registra el evento y detiene la unidad limpiamente.kill: si un proceso en la unidad es eliminado por OOM, los procesos restantes en esa unidad son eliminados como grupo.
Usa esta configuración para evitar servicios medio vivos. Si un servicio web multiproceso pierde un trabajador y sigue aceptando tráfico en un estado roto, continue puede ocultar la falla. OOMPolicy=kill hace que la falla sea obvia y permite que Restart=on-failure devuelva el servicio a un estado limpio.
[Service]
OOMPolicy=kill
Restart=on-failure
RestartSec=5s
Para un trabajo por lotes con procesos auxiliares, stop puede ser menos abrupto para los procesos restantes:
[Service]
OOMPolicy=stop
El proceso elegido por el kernel ya se ha ido. stop solo afecta lo que systemd hace con el resto del servicio, así que no confíes en él como un punto de guardado elegante. Los trabajos de larga duración deberían guardar su propio progreso.
Un Patrón Práctico de Ajuste
Comienza clasificando los servicios en tres grupos.
Primero, identifica los servicios que mantienen el host recuperable: SSH, redes, monitorización y la carga de trabajo principal. Da solo a los más importantes ajustes negativos modestos.
Segundo, identifica los servicios que pueden reintentarse: trabajadores, importadores, generadores de informes, procesadores de imágenes, calentadores de caché y ayudantes de desarrollo. Dales ajustes positivos.
Tercero, decide si cada servicio puede seguir ejecutándose de manera segura después de que un proceso sea eliminado. Si no, usa OOMPolicy=kill y una política de reinicio.
Una anulación realista de trabajador podría verse así:
# /etc/systemd/system/image-worker.service.d/oom.conf
[Service]
OOMScoreAdjust=500
OOMPolicy=kill
Restart=on-failure
RestartSec=10s
Un servicio de aplicación principal podría verse así:
# /etc/systemd/system/api.service.d/oom.conf
[Service]
OOMScoreAdjust=-300
OOMPolicy=kill
Restart=on-failure
RestartSec=5s
Evitaría OOMScoreAdjust=-1000 a menos que hayas probado el modo de fallo. Si ese servicio protegido es el que está perdiendo memoria, la máquina aún necesita una forma de recuperarse.
Aplicando y Verificando el Cambio
Usa drop-ins en lugar de editar archivos de unidad empaquetados:
sudo systemctl edit api.service
Después de guardar la anulación, recarga systemd y reinicia el servicio:
sudo systemctl daemon-reload
sudo systemctl restart api.service
Verifica la unidad combinada y los valores que systemd ve:
systemctl cat api.service
systemctl show api.service -p OOMPolicy -p OOMScoreAdjust
Luego inspecciona el proceso en ejecución:
PID=$(systemctl show api.service -p MainPID --value)
cat /proc/$PID/oom_score_adj
cat /proc/$PID/oom_score
oom_score_adj debería coincidir con tu ajuste configurado. oom_score puede cambiar a medida que el proceso usa más o menos memoria.
Después de un incidente, verifica tanto los registros de la unidad como el registro del kernel:
journalctl -u api.service --since "1 hour ago"
journalctl -k --since "1 hour ago" | grep -i oom
En sistemas que usan systemd-oomd, también verifica:
systemctl status systemd-oomd
oomctl
La Política OOM No es Planificación de Capacidad
El ajuste OOM es una última línea de defensa. Aún necesitas límites de memoria, alertas y suficiente margen para picos normales. Para servicios con límites predecibles, considera los controles de memoria de cgroup:
[Service]
MemoryHigh=1500M
MemoryMax=2G
MemoryHigh= aplica presión antes del límite duro. MemoryMax= es un techo. El comportamiento exacto depende de la versión de systemd y la configuración de cgroup, pero la idea operativa es simple: contener un servicio antes de que consuma el host.
El swap merece el mismo tipo de consideración. Sin swap, los picos cortos pueden convertirse en eliminaciones OOM abruptas. Demasiado swap lento puede mantener el host vivo mientras la latencia se vuelve inútil. Revisa la política OOM junto con el swap, los límites de memoria, el comportamiento de reinicio y las alertas.
Ejemplo: Un Host, Tres Servicios
Supongamos que un pequeño host de producción ejecuta una API, un caché Redis y un trabajador de informes en segundo plano. El trabajador de informes es útil, pero puede reintentar el trabajo. Redis mejora la latencia, pero la aplicación aún puede servir algunas solicitudes yendo a la base de datos. La API es el servicio orientado al cliente.
Un primer paso razonable podría ser:
# api.service
[Service]
OOMScoreAdjust=-300
OOMPolicy=kill
Restart=on-failure
# redis.service drop-in, si esta instancia de Redis es solo caché
[Service]
OOMScoreAdjust=0
OOMPolicy=kill
# report-worker.service
[Service]
OOMScoreAdjust=600
OOMPolicy=kill
Restart=on-failure
Eso no garantiza que el trabajador muera primero en todos los casos posibles, pero hace clara tu intención. Si el trabajador de informes crece demasiado, es un objetivo más fácil. Si la API pierde uno de sus procesos, systemd mata al resto y lo reinicia limpiamente. Si Redis es solo un caché, puedes optar por no protegerlo mucho; si Redis es tu almacén de datos principal, tomarías una decisión diferente.
Por eso la política OOM debe estar vinculada al rol del servicio, no al nombre del producto. "Redis" no es automáticamente crítico o desechable. "El caché que podemos reconstruir" y "la única copia del estado de sesión" son objetos operativos diferentes.
Probando Sin Crear un Desastre
No necesitas estrellar un servidor de producción para aprender si las configuraciones están aplicadas. Comienza con la inspección:
systemctl show report-worker.service -p OOMScoreAdjust -p OOMPolicy
systemctl status report-worker.service
Luego verifica el proceso en ejecución:
PID=$(systemctl show report-worker.service -p MainPID --value)
cat /proc/$PID/oom_score_adj
Para pruebas más profundas, usa un host de staging o una máquina virtual desechable con la misma versión de systemd y modo cgroup. Ejecuta allí una herramienta controlada de presión de memoria, no en un servidor de producción compartido. El objetivo es confirmar el comportamiento general: el trabajador es más fácil de eliminar, el servicio principal no permanece medio vivo, y el comportamiento de reinicio es visible en el journal.
Si usas contenedores, prueba en la misma forma en que despliegas. Un servicio ejecutándose directamente bajo systemd no se comporta exactamente como un proceso dentro de un contenedor con su propio límite de memoria. El kernel puede hacer cumplir el límite del contenedor antes de que el host esté globalmente sin memoria. En ese caso, tu runtime de contenedor, Kubernetes o configuraciones de cgroup pueden ser la primera capa que decide qué muere.
Leyendo el Incidente Después
Después de un evento OOM, evita saltar directamente a "necesitamos más RAM". A veces sí. A veces un caché olvidó los TTL. A veces un despliegue cambió la concurrencia del trabajador. A veces la actividad de persistencia o copia de seguridad causó un pico de memoria copy-on-write.
Busca tres cosas:
journalctl -k --since "2026-05-24 01:00" | grep -i oom
journalctl -u api.service --since "2026-05-24 01:00"
systemctl show api.service -p Result -p NRestarts
El registro del kernel generalmente te dice qué proceso fue eliminado. El registro de la unidad te dice cómo reaccionó systemd. Los contadores de reinicio te dicen si el servicio se recuperó limpiamente o fluctuó.
Luego compara el proceso eliminado con tu prioridad prevista. Si un servicio protegido murió antes que un trabajador desechable, verifica si el trabajador realmente se estaba ejecutando bajo la unidad que ajustaste, si la anulación se cargó, y si otro límite de memoria se disparó primero. Si la víctima elegida coincide con la política pero el incidente aún perjudicó a los usuarios, tu clasificación de servicio puede necesitar cambiar.
Documenta la Razón, No Solo el Valor
Las configuraciones OOM son fáciles de olvidar porque permanecen en silencio en las anulaciones de unidad hasta un mal día. Deja un breve comentario en la anulación o en tu repositorio de infraestructura explicando la razón del ajuste.
[Service]
# Trabajador de cola reintentable. Prefiere matar esto antes que api.service durante presión del host.
OOMScoreAdjust=600
OOMPolicy=kill
Ese comentario ahorra tiempo durante una revisión de incidente. Sin él, alguien puede ver una puntuación OOM positiva y "arreglarla" de vuelta a cero sin darse cuenta de que era una decisión de prioridad intencional.
También registra cuándo revisaste la configuración por última vez. Un servicio puede cambiar de rol con el tiempo. Un trabajador que una vez manejó miniaturas desechables podría luego procesar pagos, exportaciones o trabajos visibles para el cliente. La política OOM debe seguir el riesgo actual, no el propósito original del servicio.
Configuraciones Incorrectas Comunes
Una configuración incorrecta es proteger la base de datos, la API, el trabajador, el caché, el transportador de registros y el agente de monitoreo todos a la vez. Eso se siente cuidadoso, pero le da al kernel menos opciones. Elige prioridades.
Otra configuración incorrecta es establecer OOMPolicy=continue en un servicio que no puede tolerar procesos hijos faltantes. Un administrador de procesos, servidor web o demonio personalizado puede mantener la unidad activa incluso después de que parte de la carga de trabajo se haya ido. Si tu balanceador de carga solo verifica si el puerto está abierto, el tráfico puede continuar fluyendo hacia un servicio degradado.
Una tercera configuración incorrecta es un ajuste positivo sin comportamiento de reintento. Si haces que un servicio sea fácil de eliminar, asegúrate de que eliminarlo sea aceptable. Para un trabajador de cola, eso significa que los trabajos se reconocen solo después de un procesamiento exitoso. Para un trabajo por lotes, eso significa puntos de control. Para un calentador de caché, eso significa que el caché puede reconstruirse más tarde.
Finalmente, evita ocultar eventos OOM solo con reinicios automáticos. Reiniciar un servicio con pérdida de memoria puede ganar tiempo, pero también puede crear un bucle donde la memoria sube, el servicio muere y los usuarios ven fallas periódicas. Agrega alertas sobre el conteo de reinicios y el crecimiento de memoria, no solo el estado del proceso.
Un Runbook Corto
Cuando ajustes un servidor real, usa una lista de verificación repetible:
- Enumera los servicios necesarios para la recuperación y el tráfico de usuarios.
- Enumera los servicios reintentables que pueden ser eliminados primero.
- Agrega valores positivos de
OOMScoreAdjustal trabajo desechable. - Agrega valores negativos moderados solo a los pocos servicios que merecen protección.
- Usa
OOMPolicy=killpara servicios que no deberían ejecutarse parcialmente. - Verifica los valores aplicados a través de
systemctl showy/proc. - Alerta sobre la presión de memoria antes de que ocurran eventos OOM.
El objetivo no es hacer que los eventos OOM sean inofensivos. El objetivo es hacerlos comprensibles. OOMScoreAdjust= ayuda a elegir la víctima. OOMPolicy= ayuda a definir qué sucede con el resto de la unidad. Juntos, te dan un orden de falla más predecible cuando la memoria ya está agotada.