Linux OOM (Out Of Memory) Killer: Qué es y qué importancia tiene

Publicado el 11 mayo 2021 por Drassill

Pongámonos en la situación teórica de que tenemos un equipo con OS basado en Linux (independientemente de la distribución) con una alta carga de memoria RAM y múltiples servicios en marcha. De repente, el equipo tiene que realizar tareas que le suponen un mayor esfuerzo en términos de memoria y esto hace que al poco tiempo tengamos errores, tales como aplicaciones que misteriosamente se han cerrado solas, o errores más graves; como, por ejemplo, que una base de datos, haya entrado en modo “recovery mode”. ¿Por qué ha ocurrido esto?  Este fenómeno proviene del concepto OOM Killer.

OOM Killer, se trata de un mecanismo que posee el kernel para liberar memoria RAM de forma abrupta para evitar el colapso del sistema; es decir que se trata de un mecanismo usado para evitar situaciones críticas, el cual, generalmente, previene de forma adecuada cualquier tipo de saturación del sistema y evita el bloqueo total del equipo/servidor. Desgraciadamente, en equipos con altas cargas de trabajo que poseen algunos servicios críticos (como puede ser PostgreSQL, MySQL o Apache), este mecanismo puede resultar fatal… Para entenderlo, primero hay que saber cómo funciona este mecanismo; o mejor dicho, qué prioridades tiene éste.

Las prioridades de OOM por defecto serían:

  • Mantener un mínimo de memoria RAM libre para que el Kernel pueda funcionar correctamente
  • Elimina el mínimo número de procesos posibles
  • Priorizar procesos que consuman mucha memoria
  • No eliminar procesos que consuman poca memoria
  • Mediante múltiples algoritmos internos dictaminar qué servicio con mucha memoria eliminar en caso de tener varios para elegir.

Además de esto, OOM revisa el oom_score (puntuación OOM), que revisa si el proceso tiene una mayor o menor prioridad para ser eliminado. Por defecto la mayoría de los procesos tienen una puntuación de 0, es decir que son servicios que ni tienen la prioridad de ser eliminados, ni tampoco tienen ninguna “protección” que les impida ser eliminados.  Dicha puntuación se puede ajustar asignarle un valor que puede oscilar entre -1000 y 1000, siendo -1000 el valor que haría que un proceso, jamás se eliminase, y 1000 el valor que haría que dicho proceso fuese eliminado el primero. 

Para revisar la puntuación por defecto de un proceso concreto habría que realizar el comando: 

cat /proc/numero_proceso/oom_score

Ejemplo:

cat /proc/1032/oom_score0

Si deseásemos ajustar la puntuación de dicho proceso, habría que modificar el fichero /proc/numero_proceso/oom_score_adj, asignándole un valor entre -1000 y 1000, según conveniencia nuestra… 

echo -100 > /proc/1032/oom_score_adj

El problema que tiene este método es que es un método de ajuste “en vivo”, es decir que si bien ajustaría la puntuación del proceso, solamente sería valido mientras el proceso estuviese activo; si dicho proceso se parase/reiniciase por cualquier motivo, la puntuación volvería a ser la misma que se tenía originalmente… 

Afortunadamente, puede modificar de forma permanente, accediendo servicio de arranque del proceso deseado…  Presuponiendo que estemos usando un sistema basado en Systemd, simplemente habría que ir a la ruta /etc/systemd/system/ o a /run/systemd/generator.late/ (dependiendo del servicio) y editar el servicio que deseemos modificar. 

Dentro del fichero que estemos editando, habrá diferentes secciones, entre las cuales se encontrará la sección “service”; dentro de dicha sección habría que añadir la línea OOMScoreAdjust= Valor_deseado. Pongamos como ejemplo el servicio apache2.service

Antes:
[Service]Type=forkingRestart=noTimeoutSec=5minIgnoreSIGPIPE=noKillMode=processGuessMainPID=noRemainAfterExit=yesExecStart=/etc/init.d/apache2 startExecStop=/etc/init.d/apache2 stopExecReload=/etc/init.d/apache2 reload

Despues:
[Service]OOMScoreAdjust=-100Type=forkingRestart=noTimeoutSec=5minIgnoreSIGPIPE=noKillMode=processGuessMainPID=noRemainAfterExit=yesExecStart=/etc/init.d/apache2 startExecStop=/etc/init.d/apache2 stopExecReload=/etc/init.d/apache2 reload

Gracias a esto habríamos ajustado la puntuación del servicio deseado; la gran cuestión sería… ¿Por qué casi siempre son escogidos los servicios más “críticos” tales como las webs o bases de datos?  Simplemente debido a que son procesos que de por sí consumen mucha memoria, pues manejan un gran volumen de datos y transacciones… En el caso de las bases de datos, podría también deberse a alguna consulta/operación SQL que esté tomando demasiado tiempo y recursos.
Si se desea, también se puede deshabilitar directamente esta utilidad, si bien no es recomendable, ya que se trata de una funcionalidad preventiva... Aún así, si fuese 100% hacerlo, solamente habría que ejecutar:
echo 1 > /proc/sys/vm/panic_on_oom
En caso de querer hacerlo permanente, habría que añadir dicho parámetro al fichero sysctl.conf, ya que el anterior comando solamente sería válido hasta el siguiente reinicio:
echo vmoom-kill = 1 >> /etc/sysctlconf
Es importante resaltar que esto no hace que OOM Killer sea una servicio “dañino”; simplemente ejecuta labores que protegen a todo el sistema, la cuestión está en que a veces, en un intento de protege el sistema entero,  se pueden llegar a detener de forma accidental servicios indeseados... Por eso, es recomendable no deshabilitarlo y buscar otras vías... Una de ellas es la modificación de un valor llamado overcommit_memory...
Linux por defecto puede asignar toda la memoria del mundo sin tener en cuenta la RAM disponible, cosa que no lo hace siempre sino que lo realiza cuando lo ve necesario; nosotros podemos editar el fichero overcommit_memory dentro /proc/sys/vm/ para que siempre lo haga o que solamente lo haga en determinadas circunstancias... El fichero en cuestión tiene un dígito que oscila entre 0 y 2 y que tiene lo siguientes comportamientos:
0: Valor por defecto. El sistema decide si asignarle más memoria de la que estaba previsa al proceso.
1: Si asignamos este valor, el sistema SIEMPRE  asignará toda la memoria al proceso en sin ningún tipo de garantía de que ésta esté disponible, en    caso de verlo necesario. Esta opción es peligrosa, pues corremos riesgo que el efecto OMM Killer ocurra aún más veces.
2: Al asignar este valor, solamente se asignará un % X de la memoria total sin garantía de que ésta esté disponible, donde X sería el valor numérico especificado en /proc/sys/vm/overcommit_ratio más la memoria swap de la que dispongamos. En caso de que no se pueda asignar más memoria de la que tengamos disponible, dicho asignamiento adicional de memoria no se realizaría, manteniendo "a salvo" el sistema y ayudando en parte a que no se ejecute el OMM Killer; especialmente cuando trabajamos con bases de datos como PostgreSQL. Es por eso que de que dicho valor se recomienda muy a menudo al trabajar con bases de datos PostgresSQL para garantizar que no se consuma más memoria la necesaria. Es importante resaltar que el valor por defecto es de 50, valor que es fácilmente modificable editando el fichero /proc/sys/vm/overcommit_ratio.
La formula exacta para conocer el limite de overcommit al tener el overcommit_memory con valor 2 sería:
(RAM_total * overcommit_ratio /100) + Memoria_swap
Visto todo esto, tendríamos una mayor comprensión de cómo funciona "Out Of Memory Killer", y las medidas a las que podemos recurrir para paliarlo. 
Como siempre, la solución más sencilla para estos casos siempre sería instalar más memoria RAM en el equipo; pero eso en muchas ocasiones es utópico, con lo que siempre es interesante conocer este tipo de medidas con el fin paliar el problema y evitar demasiados quebraderos de cabeza por cierres inesperados de programas.
Espero que os haya resultado útil.
Saludos.