Además de la opción de usar un pipe:

10 * * * * /usr/local/bin/micomando 2>&1 | mail foo@bar.com

Podemos usar la variable MAILTO, que por defecto manda un correo a root, y poner cualquier dirección de correo. Además, si necesitamos usar un correo determinado solo para una tarea podemos hacer lo siguiente:

MAILTO=”foo@bar.com”
10 * * * * /usr/local/bin/micomando
MAILTO=”foo_perez@bar.com”
10 * * * * /usr/local/bin/miotrocomando

También podríamos usar multiples destinatarios simplemente separandolos por comas:

MAILTO=”foo@bar.com,foo_perez@bar.com”

Con la primera opción tenemos la ventaja de poder modificar el asunto, enviar copias ocultas, etcétera, con la segunda opción tenemos la ventaja de que incluso si el comando falla porque nos hemos equivocado al escribirlo, nos manda un mail con el error.

Solucion propuesta :

#!/bin/bash
source_dir=/mydir
target_dir=/newdir

###  GETTING THE LIST OF FILES  ###

find $source_dir -type f -name “*” -print0 | xargs -0 ls > $target_dir/files_list.txt

while read line
do
  size=`du $line | awk ‘{print $1}’`
  #echo $line
  #echo $size
  if [ $size -eq 0 ]
  then
      echo “DELETING $line” >> $target_dir/empty_files.txt
      rm $line
  fi

done < $target_dir/files_list.txt

Solucion propuesta:

find $source_dir -size 0 -delete

Vamos directamente a un caso practico, moviendo 500.000 ficheros con find -exec ; tardamos 88 minutos:

# find /target/tmp/ -type f | wc -l
524275
# time find /target/tmp/ -type f -name ‘*’ -exec mv {} /rescaling/test/ ;

real    88m23.913s
user    5m12.872s
sys    81m43.738s

Si usamos -exec comando + bajamos a 4m30s:

# ls -1 /target/tmp/ | wc -l
524276
# time find /target/tmp/ -type f -name ‘*’ -exec mv -t /rescaling/test/ {} +

real    4m30.750s
user    0m9.777s
sys     1m35.633s

Y como bonus final, con find | xargs, se tardan menos de 3 minutos:

# ls -1 /target/tmp/ | wc -l
524276
# time find /target/tmp -type f -print0 | xargs -P 0 -0 -I {} mv {} /rescaling/test/

real    2m42.905s
user    3m38.539s
sys    14m5.651s

La explicacion, es que en el primer comando, al usarse “-exec comando ;” , lo que ocurre es que se ejecuta un comando mv por cada fichero. Cuando se usa “-exec comando +”, cada comando es agregado al comando y se ejecuta a continuacion, por lo que, tal y como explica la pagina man de find, esto hace que el total de invocaciones es mucho menor y de ahi esa mejora enorme en el rendimiento. Este compartiendo es el mismo que cuando usamos xargs, la diferencia de rendimiento en este caso radica en usar la opcion “-P” que crea tantos procesos como les indiquemos, o infinitos si usamos “-P 0”.

En este ejemplo tenemos una maquina virtual con 8 cores:

processor       : 7
vendor_id       : GenuineIntel
cpu family      : 6
model           : 45
model name      : Intel(R) Xeon(R) CPU E5-2650 0 @ 2.00GHz
stepping        : 7
cpu MHz         : 2000.000
cache size      : 20480 KB

Y tenemos 1 filesystem con un millon de ficheros, y otro totalmente lleno (espacio o inodos) como por ejemplo:

Filesystem            Inodes   IUsed   IFree IUse% Mounted on
/dev/mapper/vgTemp-rescaling
                     16777216 1000012 15777204    6% /rescaling
                     
/dev/mapper/vgTarget-target
                      524288  524288       0  100% /target

Pues bien si intentamos moverlos con find+xargs:
time find /rescaling/test/ -type f -print0 | xargs -P 0 -0 -I {} mv {}  /target/tmp

Evidentemente el comando dara errores del tipo:

mv: cannot create regular file `/target/tmp/219449-mod.tiff’: No space left on device
mv: cannot create regular file `/target/tmp/171387-mod.tiff’: No space left on device

El numero de procesos se disparara:
[root@vmlbcipacl63 ~]# ps aux | grep mv | wc -l
12262

Y el load tambien:

top – 12:09:34 up 12 days, 21:04,  5 users,  load average: 11861.25, 9365.14, 5167.78

Pero si vemos las CPUs, carga real no hay:

Tasks: 12224 total,   2 running, 12222 sleeping,   0 stopped,   0 zombie
Cpu0  :  7.9%us, 25.5%sy,  0.0%ni, 63.6%id,  2.6%wa,  0.0%hi,  0.3%si,  0.0%st
Cpu1  :  5.9%us, 19.0%sy,  0.0%ni, 69.4%id,  4.7%wa,  0.0%hi,  0.9%si,  0.0%st
Cpu2  :  2.9%us, 18.4%sy,  0.0%ni, 74.9%id,  3.2%wa,  0.0%hi,  0.6%si,  0.0%st
Cpu3  :  1.5%us, 18.8%sy,  0.0%ni, 61.5%id, 17.6%wa,  0.0%hi,  0.6%si,  0.0%st
Cpu4  :  2.3%us, 29.9%sy,  0.0%ni, 62.8%id,  4.4%wa,  0.0%hi,  0.6%si,  0.0%st
Cpu5  :  1.8%us, 11.9%sy,  0.0%ni, 76.8%id,  8.9%wa,  0.0%hi,  0.6%si,  0.0%st
Cpu6  :  3.8%us, 50.4%sy,  0.0%ni, 41.6%id,  3.5%wa,  0.0%hi,  0.6%si,  0.0%st
Cpu7  :  2.4%us, 30.4%sy,  0.0%ni, 60.5%id,  6.2%wa,  0.0%hi,  0.6%si,  0.0%st

La maquina sigue siendo totalmente usable y no hay problema ejecutando comandos, esto es debido a que el load average no es mas que la media de procesos que estan demandando atencion de la cpu, y debido a que usamos el parametro -P de xargs (numero de procesos, siendo 0 que cree todos los procesos que pueda) se crea un proceso por cada intento de mover un fichero, y aunque este solo haga uso de la cpu para notificar que no puede ejecutarse (por falta de espacio), tiene que esperar su turno correspondiente. Si hubiera espacio/inodos suficientes, la carga subiria pero no de forma tan espectacular.

Viernes, casi mediodía, aparece el CEO:
“Necesitamos recomendaciones para performance de sistemas de ficheros con millones de ficheros pequeños”, así sin vaselina ni nada.

Como no tengo una bola de cristal, lo primero que comenté fue la necesidad de tener los detalles del tipo de carga de trabajo, características de los sistemas involucrados, aplicaciones, etcétera Ya que no hay recomendaciones mágicas que sirvan para todas las condiciones.

Después de ponerme en contacto con varias personas, y de algunas reuniones, obtenemos en claro cual era el problema real.

Hay un procedimiento diario para tratamiento de ficheros de froma que procesa los ficheros desde un filesystem origen, los transforma y los “coloca” en un filesystem destino.

Evidentemente antes de mirar nada, la culpa de todo la tenia Linux y los sistemas de ficheros ext4.

Desde el principio dijimos que aunque podría haber posibles mejoras que hacer, en general ext4 es la recomendación por defecto para este tipo de carga de trabajo, pero que lo mas importante era tener los detalles del proceso para ir analizando parte por parte los posibles cuellos de botella.

Conseguimos un conjunto de ficheros de prueba, 57K, que en las pruebas iniciales tardaban 34 minutos en procesar. Como aun no teníamos los comandos para probar, lo primero que probamos fue crear 2 filesystems en ram para ver la diferencia, y en este caso se llego a procesar en 25 minutos, una mejora sustancial, pero insuficiente, y que volvía a indicar que el problema muy posiblemente no fuera de los filesystems.

Finalmente al probar con los comandos en consola, vimos que con el comando original:

“/opt/bin/app” -input “/filesys1/”* -dir “/filesys2/tmp/” 

El proceso tenia un retraso enorme en empezar a escribir en disco, del orden de mas de 20 minutos, y eso nos dio una pista del problema:

Como el parámetro para seleccionar los ficheros originales usaba el comodín ‘*’ fuera de las comillas, bash expandía el resultado de forma, que construía un comando con un argumento por cada fichero origen, es decir 57.000 argumentos. Cambiamos el comando a:

“/opt/bin/app” -input “/filesys1/*” -dir “/filesys2/tmp/” 

Y el proceso empezó a escribir en disco inmediatamente , y tardó 2m30s en lugar de los 25 minutos, una mejora del 1000%, no está nada mal para unas simples comillas :)

Esperemos que la próxima vez entiendan que antes de echar la culpa a Linux, hagan un análisis de los elementos involucrados, dividan las tareas y vayan delimitando lo máximo posible el problema.