Millones de archivos de texto (pequeños) en una carpeta

15

Nos gustaría almacenar millones de archivos de texto en un sistema de archivos Linux, con el propósito de poder comprimir y servir una colección arbitraria como servicio. Hemos probado otras soluciones, como una base de datos clave / valor, pero nuestros requisitos de concurrencia y paralelismo hacen que el uso del sistema de archivos nativo sea la mejor opción.

La forma más directa es almacenar todos los archivos en una carpeta:

$ ls text_files/
1.txt
2.txt
3.txt

que debería ser posible en un sistema de archivos EXT4 , que no tiene límite para la cantidad de archivos en una carpeta.

Los dos procesos de FS serán:

  1. Escribir un archivo de texto desde el web scrape (no debería verse afectado por la cantidad de archivos en la carpeta).
  2. Comprima los archivos seleccionados, dados por la lista de nombres de archivos.

Mi pregunta es: ¿el almacenamiento de hasta diez millones de archivos en una carpeta afectará el rendimiento de las operaciones anteriores, o el rendimiento general del sistema, de manera diferente a hacer un árbol de subcarpetas para que vivan los archivos?

usuario1717828
fuente
44
Relacionado: Cómo corregir los errores intermitentes "No queda espacio en el dispositivo" durante el mv cuando el dispositivo tiene mucho espacio . El uso dir_index, que a menudo está habilitado de forma predeterminada, acelerará las búsquedas, pero puede limitar la cantidad de archivos por directorio.
Mark Plotnick
¿Por qué no probarlo rápidamente en una máquina virtual y ver cómo es? Con bash es trivial poblar una carpeta con un millón de archivos de texto con caracteres aleatorios dentro. Siento que obtendrá información realmente útil de esa manera, además de lo que aprenderá aquí.
JoshuaD
2
@JoshuaD: Si lo completa todo de una vez en un FS nuevo, es probable que tenga todos los inodos contiguos en el disco, por lo que ls -lo cualquier otra cosa que sea statcada inodo en el directorio (por ejemplo, bashfinalización de pestañas / tabulación) será artificialmente más rápido que después de un poco de desgaste (borre algunos archivos, escriba algunos nuevos). ext4 podría funcionar mejor con esto que XFS, porque XFS asigna dinámicamente espacio para inodes frente a datos, por lo que puede terminar con inodos más dispersos, creo. (Pero esa es una suposición pura basada en muy poco conocimiento detallado; apenas he usado ext4). Ir con abc/def/subdirs.
Peter Cordes
Sí, no creo que la prueba que sugerí pueda decirle al OP "esto funcionará", pero definitivamente podría decirle rápidamente "esto no funcionará", lo cual es útil.
JoshuaD
1
pero nuestros requisitos de concurrencia y paralelismo hacen que el uso del sistema de archivos nativo sea la mejor opción ¿Qué probó? De improviso, creo que incluso un RDBMS de gama baja como MySQL y un servlet Java que crea los archivos zip sobre la marchaZipOutputStream superaría a casi cualquier sistema de archivos nativo de Linux gratuito. Dudo que quiera pagar por el GPFS de IBM. El ciclo para procesar un conjunto de resultados JDBC y hacer que la secuencia zip sea probablemente solo de 6 a 8 líneas de código Java.
Andrew Henle

Respuestas:

10

El lscomando, o incluso la finalización de TAB o la expansión de comodín por parte del shell, normalmente presentará sus resultados en orden alfanumérico. Esto requiere leer el listado completo del directorio y ordenarlo. Con diez millones de archivos en un solo directorio, esta operación de clasificación tomará una cantidad de tiempo no despreciable.

Si puede resistir el impulso de completar TAB y, por ejemplo, escribir los nombres de los archivos que se van a comprimir por completo, no debería haber problemas.

Otro problema con los comodines podría ser la expansión de comodines que posiblemente produzca más nombres de archivo de los que caben en una línea de comando de longitud máxima. La longitud máxima típica de la línea de comandos será más que adecuada para la mayoría de las situaciones, pero cuando hablamos de millones de archivos en un solo directorio, esto ya no es una suposición segura. Cuando se excede la longitud máxima de la línea de comando en la expansión de comodines, la mayoría de los shells simplemente fallarán en toda la línea de comando sin ejecutarla.

Esto se puede resolver haciendo sus operaciones comodín con el findcomando:

find <directory> -name '<wildcard expression>' -exec <command> {} \+

o una sintaxis similar siempre que sea posible. El find ... -exec ... \+automáticamente tomará en cuenta la longitud máxima de la línea de comando, y ejecutará el comando tantas veces como sea necesario mientras ajusta la cantidad máxima de nombres de archivo a cada línea de comando.

telcoM
fuente
Los sistemas de archivos modernos usan B, B + o árboles similares para mantener las entradas del directorio. en.wikipedia.org/wiki/HTree
dimm
44
Sí ... pero si el shell o el lscomando no sabrán que la lista de directorios ya está ordenada, de todos modos se tomarán el tiempo para ejecutar el algoritmo de ordenación. Y además, el espacio de usuario puede estar usando un orden de clasificación localizado (LC_COLLATE) que puede ser diferente de lo que el sistema de archivos podría hacer internamente.
telcoM
17

Esto está peligrosamente cerca de una pregunta / respuesta basada en la opinión, pero intentaré proporcionar algunos hechos con mis opiniones.

  1. Si tiene una gran cantidad de archivos en una carpeta, cualquier operación basada en shell que intente enumerarlos (por ejemplo mv * /somewhere/else) puede fallar al expandir el comodín con éxito, o el resultado puede ser demasiado grande para usar.
  2. ls llevará más tiempo enumerar una gran cantidad de archivos que una pequeña cantidad de archivos.
  3. El sistema de archivos podrá manejar millones de archivos en un solo directorio, pero la gente probablemente tendrá dificultades.

Una recomendación es dividir el nombre del archivo en fragmentos de dos, tres o cuatro caracteres y usarlos como subdirectorios. Por ejemplo, somefilename.txtpodría almacenarse como som/efi/somefilename.txt. Si está utilizando nombres numéricos, divídalos de derecha a izquierda en lugar de izquierda a derecha para que haya una distribución más uniforme. Por ejemplo, 12345.txtpodría almacenarse como 345/12/12345.txt.

Puede usar el equivalente de zip -j zipfile.zip path1/file1 path2/file2 ...para evitar incluir las rutas intermedias del subdirectorio en el archivo ZIP.

Si está sirviendo estos archivos desde un servidor web (no estoy completamente seguro de si eso es relevante) es trivial ocultar esta estructura a favor de un directorio virtual con reglas de reescritura en Apache2. Supongo que lo mismo es cierto para Nginx.

roaima
fuente
La *expansión tendrá éxito a menos que se quede sin memoria, pero a menos que aumente el límite de tamaño de pila (en Linux) o use un shell donde mvestá integrado o puede estar integrado (ksh93, zsh), la execve()llamada al sistema puede fallar con un error E2BIG.
Stéphane Chazelas
@ StéphaneChazelas sí está bien, mi elección de palabras podría haber sido mejor, pero el efecto neto para el usuario es muy similar. Veré si puedo alterar las palabras ligeramente sin atascarme en la complejidad.
roaima
¿Es curioso cómo descomprimiría ese archivo zip si evita incluir las rutas intermedias del subdirectorio en él, sin encontrarse con los problemas que discute?
Octopus
1
@Octopus el OP indica que el archivo zip contendrá " archivos seleccionados, dados por la lista de nombres de archivos ".
roaima
Recomiendo usar zip -j - ...y canalizar el flujo de salida directamente a la conexión de red del cliente zip -j zipfile.zip .... Escribir un archivo zip real en el disco significa que la ruta de datos se lee desde el disco-> comprimir-> escribir en el disco-> leer desde el disco-> enviar al cliente. Eso puede triplicar los requisitos de E / S del disco sobre la lectura desde el disco-> comprimir-> enviar al cliente.
Andrew Henle
5

Dirijo un sitio web que maneja una base de datos para películas, TV y videojuegos. Para cada uno de estos, hay varias imágenes con TV que contienen docenas de imágenes por programa (es decir, instantáneas de episodios, etc.).

Termina siendo una gran cantidad de archivos de imagen. En algún lugar en el rango de más de 250,000. Todos estos se almacenan en un dispositivo de almacenamiento en bloque montado donde el tiempo de acceso es razonable.

Mi primer intento de almacenar las imágenes fue en una sola carpeta como /mnt/images/UUID.jpg

Me encontré con los siguientes desafíos.

  • lsa través de una terminal remota simplemente se colgaría. El proceso se volvería zombie y CTRL+Cno lo rompería.
  • antes de llegar a ese punto, cualquier lscomando llenaría rápidamente el búfer de salida y CTRL+Cno detendría el desplazamiento sin fin.
  • Comprimir 250,000 archivos de una sola carpeta tomó aproximadamente 2 horas. Debe ejecutar el comando zip desconectado del terminal; de lo contrario, cualquier interrupción en la conexión significa que debe comenzar de nuevo.
  • No me arriesgaría a tratar de usar el archivo zip en Windows.
  • La carpeta se convirtió rápidamente en una zona prohibida para humanos .

Terminé teniendo que almacenar los archivos en subcarpetas usando el tiempo de creación para crear la ruta. Tales como /mnt/images/YYYY/MM/DD/UUID.jpg. Esto resolvió todos los problemas anteriores y me permitió crear archivos zip que apuntaban a una fecha.

Si el único identificador para un archivo que tiene es un número numérico, y estos números tienden a ejecutarse en secuencia. ¿Por qué no agruparlos por 100000, 10000y 1000.

Por ejemplo, si tiene un archivo llamado 384295.txtla ruta sería:

/mnt/file/300000/80000/4000/295.txt

Si sabes llegarás a unos pocos millones. Usa 0prefijos para 1,000,000

/mnt/file/000000/300000/80000/4000/295.txt
Reactgular
fuente
1

Escribir un archivo de texto desde el web scrape (no debería verse afectado por la cantidad de archivos en la carpeta).

Para crear un nuevo archivo es necesario escanear el archivo del directorio en busca de suficiente espacio vacío para la nueva entrada del directorio. Si no se encuentra un espacio que sea lo suficientemente grande como para almacenar la nueva entrada del directorio, se colocará al final del archivo del directorio. A medida que aumenta el número de archivos en un directorio, también aumenta el tiempo para escanear el directorio.

Mientras los archivos de directorio permanezcan en la memoria caché del sistema, el rendimiento afectado no será malo, pero si se liberan los datos, leer el archivo de directorio (generalmente muy fragmentado) del disco podría consumir bastante tiempo. Un SSD mejora esto, pero para un directorio con millones de archivos, aún podría haber un impacto notable en el rendimiento.

Comprima los archivos seleccionados, dados por la lista de nombres de archivos.

También es probable que esto requiera tiempo adicional en un directorio con millones de archivos. En un sistema de archivos con entradas de directorio hash (como EXT4), esta diferencia es mínima.

¿El almacenamiento de hasta diez millones de archivos en una carpeta afectará el rendimiento de las operaciones anteriores, o el rendimiento general del sistema, de manera diferente a hacer un árbol de subcarpetas para que vivan los archivos?

Un árbol de subcarpetas no tiene ninguno de los inconvenientes de rendimiento anteriores. Además, si el sistema de archivos subyacente se cambia para que no tenga nombres de archivo hash, la metodología del árbol seguirá funcionando bien.

Peter
fuente
1

Primero: evite que 'ls' se ordene con 'ls -U', quizás actualice su ~ / bashrc para que tenga 'alias ls = "ls -U"' o similar.

Para su gran conjunto de archivos, puede probar esto así:

  • crear un conjunto de archivos de prueba

  • ver si muchos nombres de archivo causan problemas

  • use el xargs parmeter-batching y el comportamiento zip (predeterminado) de agregar archivos a un zip para evitar problemas.

Esto funcionó bien:

# create ~ 100k files
seq 1 99999 | sed "s/\(.*\)/a_somewhat_long_filename_as_a_prefix_to_exercise_zip_parameter_processing_\1.txt/" | xargs touch
# see if zip can handle such a list of names
zip -q /tmp/bar.zip ./*
    bash: /usr/bin/zip: Argument list too long
# use xargs to batch sets of filenames to zip
find . -type f | xargs zip -q /tmp/foo.zip
l /tmp/foo.zip
    28692 -rw-r--r-- 1 jmullee jmullee 29377592 2017-12-16 20:12 /tmp/foo.zip
jmullee
fuente