Este artículo explica qué ocurre cuando un sistema de archivos se ha quedado sin espacio disponible pero la herramienta du indica que no hay archivos llenando el espacio disponible.

El día de ayer me ocurrió algo que no había visto nunca. La administradora de Moodle me comunica que los usuarios no podían subir archivos al servidor. Este cuatrimestre se le está dando un uso intensivo a la plataforma Moodle a causa de la cuarentena provocada por el covid-19 y se ha convertido en un sistema crítico para la Universidad.

Lo primero que se me ocurrió fue que el directorio temporal se hubiese quedado sin espacio en disco, porque los sistemas de archivos críticos de Moodle se encuentran monitoreados desde Grafana y no hubieron alarmas. Al ingresar por SSH al servidor, ¡bingo!

Al consultar el espacio disponible con df se confirmaron mis sospechas, el sistema de archivos donde está montado el directorio temporal del sistema operativo se ha quedado sin espacio libre:

root@debian:/tmp# df -h /tmp
Filesystem                Size  Used Avail Use% Mounted on
/dev/mapper/debian-tmp    992M  992M     0 100% /tmp

Se observa que la columna de uso indica "100%". Se está utilizando todo el espacio.

Como PHP utiliza este directorio temporal para subir archivos al servidor y mover entre directorios, al estar lleno ya no es posible hacerlo.

Ahora bien, en general es raro que esto ocurra ya que el tamaño de archivo de subida desde los clientes está limitado en la configuración dentro de php.ini (en las variables upload_max_filesize y post_max_size).

Me imaginé entonces que algún otro servicio hubiera llenado el temporal. Pero ¡oh, sorpresa! al consultar el uso de disco en el mismo:

root@debian:/tmp# du -hs /tmp
24K	/tmp

du reporta que sólo se están utilizando 24 KB de los 992 MB disponibles. Obviamente volví a correr df inmediatamente porque pensé que habría visto mal la salida de df o se me hubieran mezclado las columnas. Pues no, efectivamente el uso de disco era del 100% pero los archivos ocupaban apenas 24K.

No tenía sentido comprobar el uso de inodos en el sistema de archivos ya que el problema era de espacio (figuraba 100% ocupado). Cuando un sistema de archivos se queda sin inodos, generalmente hay espacio (bloques) disponibles y la columna de uso es <100% aunque no se puedan crear nuevos archivos.

En estos casos se debe recurrir inmediatamente a herramientas como lsof o fuser.

Al listar todos los archivos abiertos en el sistema de archivos /tmp con lsof descubrí ésto:

root@debian:/tmp# lsof /tmp | grep 1032548352
gs        15812  php    8u   REG  254,4 1032548352   23 /tmp/gs_wSMmG3 (deleted)

Un archivo borrado de casi 1 GB de tamaño (notar la palabra "deleted" entre paréntesis).

Afortunadamente lsof es capaz de listar todos los archivos borrados que permanecen en disco debido a descriptores de archivo aún en uso por procesos. En estos casos los bloques no se liberan inmediatamente porque un proceso en ejecución todavía tiene un descriptor de archivo abierto al archivo recientemente borrado. Esto ocurre porque, si un proceso aún está intentando usar el archivo, no se desea que el kernel Linux lo elimine. El espacio en disco (bloques del archivo) se liberan físicamente sólo cuando no hay enlaces al archivo borrado. Por lo tanto, mientras un proceso tenga los archivos abiertos, no se espera que se recupere el espacio. Esto es una de las razones importantes por las cuales se deben cerrar los archivos cuando se terminan de utilizar desde PHP o cualquier programa en cualquier otro lenguaje compilado o interpretado.

Ahora bien, ¿cómo resolver ésto? La alternativa más simple es matar o terminar el proceso que aún mantiene abierto el archivo borrado. Esto se puede traducir a reiniciar un servicio. En este caso se trataba de PHP, por ende reiniciando php-fpm se resolvía la cuestión en el momento. Pero ocurren 2 cosas: primero que no es adecuado reiniciar un servicio en producción para resolver un problema (esto está debidamente aclarado en el Capítulo 5 de la Biblia del SysAdmin); segundo que no resuelvo la cuestión de fondo (¿por qué PHP generó un archivo temporal de 1 GB siendo que las subidas están limitadas?).

En esta última pregunta está la clave. Resulta que un proceso automático de anotaciones sobre archivos PDF de Moodle necesitaba mucho espacio en disco para resolver su tarea. Por ende la solución adecuada fue redimensionar el sistema de archivos /tmp con LVM para que este inconveniente no vuelva a ocurrir.

Compartí este artículo