Too many open files: aumentando el límite de descriptores de fichero en Linux

Si en nuestro sistema ejecutamos servidores web, de aplicaciones o BBDD, con frecuencia nos podremos encontrar con errores del tipo “Too many open files”. Este problema surje cuando el nº de descriptores de fichero de un proceso excede al límite definido para el usuario que lo ejecuta.

Veamos un ejemplo práctico con una instancia de Jboss. Podemos comprobar los descriptores de fichero abiertos por este proceso con lsof -p 3974, siendo esto último el PID:

COMMAND    PID USER   FD   TYPE DEVICE  SIZE/OFF    NODE NAME
standalon 3974 jota  cwd    DIR    8,1      4096  401750 /opt/jboss/jboss-eap-6.4/bin
standalon 3974 jota  rtd    DIR    8,1      4096       2 /
standalon 3974 jota  txt    REG    8,1    960392 1048720 /usr/bin/bash
standalon 3974 jota  mem    REG    8,1 106065056 1332081 /usr/lib/locale/locale-archive
standalon 3974 jota  mem    REG    8,1   2112384 1048645 /usr/lib64/libc-2.17.so
standalon 3974 jota  mem    REG    8,1     19520 1062239 /usr/lib64/libdl-2.17.so
standalon 3974 jota  mem    REG    8,1    174520 1048719 /usr/lib64/libtinfo.so.5.9
standalon 3974 jota  mem    REG    8,1    164440 1048639 /usr/lib64/ld-2.17.so
standalon 3974 jota  mem    REG    8,1     26254 1178413 /usr/lib64/gconv/gconv-modules.cache
standalon 3974 jota    0w   CHR    1,3       0t0    4674 /dev/null
standalon 3974 jota    1w   CHR    1,3       0t0    4674 /dev/null
...

Para comprobar los límites de un usuario utilizaremos ulimit -Sa. En este caso el usuario que ejecuta la instancia de Jboss se llama jota, por lo que haciendo login con ese usuario ejecutamos ulimit -Sa en terminal:

core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 15743
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 4096
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

Como vemos, el límite actual es de 1024 descriptores de fichero (open files) por proceso para este usuario. Conforme vayamos recibiendo mayores niveles de concurrencia en nuestra instancia nos encontraremos limitados por estos valores tan bajos.

Si quisiéramos aumentar este límite de open files a 4096 tendríamos que modificar el fichero /etc/security/limits.conf con root o usuario con permisos de root con sudo, introduciendo las líneas:

jota             soft    nofile          4096
jota             hard    nofile          4096

Con estas líneas indicamos que un proceso dado del usuario jota no podrá exceder nunca el límite (hard) de 4096 descriptores de ficheros abiertos de forma concurrente. El único usuario que puede exceder este límite sería root, pero nosotros somos un usuario humilde y no podremos pasarnos de 4096.

Una vez guardado el cambio en el fichero, salimos de nuestra consola SSH y volvemos a entrar. Con el nuevo login se cargará la librería /lib/security/pam_limits.so que se encargará de leer los nuevos límites definidos para el usuario que hace login. Deberían haberse ampliado a 4096 los límites del usuario jota. Comprobamos con ulimit -Sa:

core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 15743
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 4096
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 4096
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

En efecto ya tenemos el límite de open files donde deseábamos, en 4096. ¿Qué ocurre con el proceso de la instancia de Jboss, cogerá este valor o tendrá el límite anterior al cambio (1024)? Podemos comprobarlo de dos formas:

  • En el sistema de ficheros virtual /proc, haciendo cat /proc/3974/limits, siendo 3974 el PID. Observamos los valores para Max open files:
    Limit                     Soft Limit           Hard Limit           Units     
    Max cpu time              unlimited            unlimited            seconds   
    Max file size             unlimited            unlimited            bytes     
    Max data size             unlimited            unlimited            bytes     
    Max stack size            8388608              unlimited            bytes     
    Max core file size        0                    unlimited            bytes     
    Max resident set          unlimited            unlimited            bytes     
    Max processes             4096                 15743                processes 
    Max open files            1024                 1024                 files     
    Max locked memory         65536                65536                bytes     
    Max address space         unlimited            unlimited            bytes     
    Max file locks            unlimited            unlimited            locks     
    Max pending signals       15743                15743                signals   
    Max msgqueue size         819200               819200               bytes     
    Max nice priority         0                    0                    
    Max realtime priority     0                    0                    
    Max realtime timeout      unlimited            unlimited            us  
    
  • Con la utilidad prlimit observando los valores definidos en NOFILE. Lanzamos el comando prlimit -p 3974:
    RESOURCE   DESCRIPTION                             SOFT      HARD UNITS
    AS         address space limit                unlimited unlimited bytes
    CORE       max core file size                         0 unlimited blocks
    CPU        CPU time                           unlimited unlimited seconds
    DATA       max data size                      unlimited unlimited bytes
    FSIZE      max file size                      unlimited unlimited blocks
    LOCKS      max number of file locks held      unlimited unlimited 
    MEMLOCK    max locked-in-memory address space     65536     65536 bytes
    MSGQUEUE   max bytes in POSIX mqueues            819200    819200 bytes
    NICE       max nice prio allowed to raise             0         0 
    NOFILE     max number of open files                1024      1024 
    NPROC      max number of processes                 4096     15743 
    RSS        max resident set size              unlimited unlimited pages
    RTPRIO     max real-time priority                     0         0 
    RTTIME     timeout for real-time tasks        unlimited unlimited microsecs
    SIGPENDING max number of pending signals          15743     15743 
    STACK      max stack size                       8388608 unlimited bytes
    

Como vemos, aunque tras el login el usuario ya ha adquirido los nuevos límites definidos en limits.conf, los procesos que se encontraban en ejecución previamente al cambio siguen con los límites anteriores. Tendremos que reiniciar el servidor de aplicaciones Jboss en este caso para que herede los nuevos valores que hemos definido.

Tras el reinicio, hacemos un prlimit sobre el nuevo proceso, en este caso prlimit -p 4227:

RESOURCE   DESCRIPTION                             SOFT      HARD UNITS
AS         address space limit                unlimited unlimited bytes
CORE       max core file size                         0 unlimited blocks
CPU        CPU time                           unlimited unlimited seconds
DATA       max data size                      unlimited unlimited bytes
FSIZE      max file size                      unlimited unlimited blocks
LOCKS      max number of file locks held      unlimited unlimited 
MEMLOCK    max locked-in-memory address space     65536     65536 bytes
MSGQUEUE   max bytes in POSIX mqueues            819200    819200 bytes
NICE       max nice prio allowed to raise             0         0 
NOFILE     max number of open files                4096      4096 
NPROC      max number of processes                 4096     15743 
RSS        max resident set size              unlimited unlimited pages
RTPRIO     max real-time priority                     0         0 
RTTIME     timeout for real-time tasks        unlimited unlimited microsecs
SIGPENDING max number of pending signals          15743     15743 
STACK      max stack size                       8388608 unlimited bytes

Vemos que en NOFILE tiene el valor 4096, justo lo que buscábamos. Por tanto, cuando modifiquemos los límites de usuario tendremos que reiniciar los procesos asociados que se estuvieran ejecutando anteriormente al cambio. Esto es así ya que un proceso al iniciarse hereda los límites definidos del usuario que lo ejecuta en ese momento dado. Lo que no es necesario para hacer efectivo el cambio es reiniciar el servidor.