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”.

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.