Des millions de (petits) fichiers texte dans un dossier


15

Nous aimerions stocker des millions de fichiers texte dans un système de fichiers Linux, dans le but de pouvoir compresser et servir une collection arbitraire en tant que service. Nous avons essayé d'autres solutions, comme une base de données clé / valeur, mais nos exigences de concurrence et de parallélisme font de l'utilisation du système de fichiers natif le meilleur choix.

Le moyen le plus simple consiste à stocker tous les fichiers dans un dossier:

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

ce qui devrait être possible sur un système de fichiers EXT4 , qui n'a pas de limite au nombre de fichiers dans un dossier.

Les deux processus FS seront:

  1. Écrire un fichier texte à partir du Web scrape (ne devrait pas être affecté par le nombre de fichiers dans le dossier).
  2. Compressez les fichiers sélectionnés en fonction de la liste des noms de fichiers.

Ma question est la suivante: le stockage d'un maximum de dix millions de fichiers dans un dossier affectera-t-il les performances des opérations ci-dessus, ou les performances générales du système, différemment de la création d'une arborescence de sous-dossiers dans laquelle les fichiers doivent vivre?


4
Connexes: Comment corriger les erreurs intermittentes «Il n'y a plus d'espace sur l'appareil» pendant la mv lorsque l'appareil a beaucoup d'espace . L'utilisation dir_index, qui est souvent activée par défaut, accélérera les recherches mais peut limiter le nombre de fichiers par répertoire.
Mark Plotnick

Pourquoi ne pas l'essayer rapidement sur une machine virtuelle et voir à quoi ça ressemble? Avec bash, il est trivial de remplir un dossier avec un million de fichiers texte avec des caractères aléatoires à l'intérieur. J'ai l'impression que vous obtiendrez des informations vraiment utiles de cette façon, en plus de ce que vous apprendrez ici.
JoshuaD

2
@JoshuaD: Si vous remplissez tout à la fois sur un nouveau FS, vous aurez probablement tous les inodes contigus sur le disque, donc ls -lou toute autre chose qui se trouve à statchaque inode dans le répertoire (par exemple bashglobbing / tabulation) sera artificiellement plus rapide qu'après une certaine usure (supprimez certains fichiers, écrivez-en de nouveaux). ext4 pourrait faire mieux avec cela que XFS, car XFS alloue dynamiquement de l'espace pour les inodes par rapport aux données, donc vous pouvez vous retrouver avec des inodes plus dispersés, je pense. (Mais c'est une pure supposition basée sur très peu de connaissances détaillées; j'ai à peine utilisé ext4). Allez avec des sous- abc/def/dires.
Peter Cordes

Oui, je ne pense pas que le test que j'ai suggéré sera en mesure de dire à l'OP "cela fonctionnera", mais il pourrait certainement lui dire rapidement "cela ne fonctionnera pas", ce qui est utile.
JoshuaD

1
mais nos exigences en matière de simultanéité et de parallélisme font de l'utilisation du système de fichiers natif le meilleur choix. Qu'avez-vous essayé? Offhand, je pense que même un RDBMS bas de gamme tel que MySQL et un servlet Java créant les fichiers zip à la voléeZipOutputStream battrait à peu près n'importe quel système de fichiers natif Linux gratuit - je doute que vous souhaitiez payer pour le GPFS d'IBM. La boucle pour traiter un jeu de résultats JDBC et créer ce flux zip n'est probablement que de 6 à 8 lignes de code Java.
Andrew Henle

Réponses:


10

La lscommande, ou même l'achèvement TAB ou l'expansion générique par le shell, présentera normalement ses résultats dans l'ordre alphanumérique. Cela nécessite la lecture de la liste complète du répertoire et son tri. Avec dix millions de fichiers dans un seul répertoire, cette opération de tri prendra un temps non négligeable.

Si vous pouvez résister à l'envie de terminer TAB et par exemple écrire les noms des fichiers à compresser en entier, il ne devrait pas y avoir de problèmes.

Un autre problème avec les caractères génériques pourrait être l'expansion des caractères génériques, produisant éventuellement plus de noms de fichiers que ne le permet une ligne de commande de longueur maximale. La longueur maximale typique de la ligne de commande sera plus que suffisante dans la plupart des situations, mais lorsque nous parlons de millions de fichiers dans un seul répertoire, ce n'est plus une hypothèse sûre. Lorsqu'une longueur de ligne de commande maximale est dépassée dans l'extension générique, la plupart des shells échouent simplement à la ligne de commande entière sans l'exécuter.

Cela peut être résolu en effectuant vos opérations génériques à l'aide de la findcommande:

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

ou une syntaxe similaire chaque fois que possible. Le find ... -exec ... \+prendra automatiquement en compte la longueur maximale de la ligne de commande et exécutera la commande autant de fois que nécessaire tout en ajustant la quantité maximale de noms de fichiers à chaque ligne de commande.


Les systèmes de fichiers modernes utilisent B, B + ou des arbres similaires pour conserver les entrées du répertoire. en.wikipedia.org/wiki/HTree
dimm

4
Oui ... mais si le shell ou la lscommande ne parviennent pas à savoir que la liste des répertoires est déjà triée, ils prendront quand même le temps d'exécuter l'algorithme de tri. De plus, l'espace utilisateur peut utiliser un ordre de tri localisé (LC_COLLATE) qui peut être différent de ce que le système de fichiers pourrait faire en interne.
telcoM

17

C'est dangereusement proche d'une question / réponse basée sur une opinion mais je vais essayer de fournir quelques faits avec mes opinions.

  1. Si vous avez un très grand nombre de fichiers dans un dossier, toute opération basée sur un shell qui essaie de les énumérer (par exemple mv * /somewhere/else) peut échouer à développer correctement le caractère générique, ou le résultat peut être trop volumineux pour être utilisé.
  2. ls prendra plus de temps pour énumérer un très grand nombre de fichiers qu'un petit nombre de fichiers.
  3. Le système de fichiers sera capable de gérer des millions de fichiers dans un seul répertoire, mais les gens auront probablement du mal.

Une recommandation est de diviser le nom de fichier en deux, trois ou quatre blocs de caractères et de les utiliser comme sous-répertoires. Par exemple, somefilename.txtpeut être stocké sous som/efi/somefilename.txt. Si vous utilisez des noms numériques, divisez-les de droite à gauche au lieu de gauche à droite pour une distribution plus uniforme. Par exemple, 12345.txtpeut être stocké sous 345/12/12345.txt.

Vous pouvez utiliser l'équivalent de zip -j zipfile.zip path1/file1 path2/file2 ...pour éviter d'inclure les chemins de sous-répertoire intermédiaires dans le fichier ZIP.

Si vous servez ces fichiers à partir d'un serveur Web (je ne suis pas tout à fait sûr que ce soit pertinent), il est trivial de cacher cette structure en faveur d'un répertoire virtuel avec des règles de réécriture dans Apache2. Je suppose que la même chose est vraie pour Nginx.


L' *expansion réussira sauf si vous manquez de mémoire, mais à moins que vous n'augmentiez la limite de taille de pile (sous Linux) ou que vous n'utilisiez un shell où mvest intégré ou peut être intégré (ksh93, zsh), l' execve()appel système peut échouer avec une erreur E2BIG.
Stéphane Chazelas

@ StéphaneChazelas oui ok, mon choix de mots aurait pu être mieux, mais l'effet net pour l'utilisateur est sensiblement le même. Je vais voir si je peux modifier légèrement les mots sans m'enliser dans la complexité.
roaima

Juste curieux de savoir comment vous décompressez ce fichier zip si vous évitez d'y inclure les chemins de sous-répertoire intermédiaires, sans rencontrer les problèmes que vous discutez?
Octopus

1
@Octopus l'OP indique que le fichier zip contiendra " les fichiers sélectionnés, donnés par la liste des noms de fichiers ".
roaima

Je recommande d'utiliser zip -j - ...et de diriger le flux de sortie directement vers la connexion réseau du client zip -j zipfile.zip .... L'écriture d'un fichier zip réel sur le disque signifie que le chemin de données est lu sur le disque-> compresser-> écrire sur le disque-> lire sur le disque-> envoyer au client. Cela peut jusqu'à tripler les besoins d'E / S de votre disque par rapport à la lecture depuis le disque-> compresser-> envoyer au client.
Andrew Henle

5

Je gère un site Web qui gère une base de données pour les films, la télévision et les jeux vidéo. Pour chacun d'eux, il y a plusieurs images avec TV contenant des dizaines d'images par émission (c.-à-d. Instantanés d'épisode, etc.).

Il finit par y avoir beaucoup de fichiers image. Quelque part dans la gamme 250,000+. Ceux-ci sont tous stockés dans un périphérique de stockage en bloc monté où le temps d'accès est raisonnable.

Ma première tentative de stockage des images a été dans un seul dossier /mnt/images/UUID.jpg

J'ai rencontré les défis suivants.

  • lsvia un terminal distant serait juste se bloquer. Le processus deviendrait zombie et CTRL+Cne le briserait pas.
  • avant d'atteindre ce point, toute lscommande remplirait rapidement le tampon de sortie et CTRL+Cn'arrêterait pas le défilement sans fin.
  • Zipper 250 000 fichiers à partir d'un seul dossier a pris environ 2 heures. Vous devez exécuter la commande zip détachée du terminal, sinon toute interruption de connexion signifie que vous devez recommencer.
  • Je ne risquerais pas d'essayer d'utiliser le fichier zip sous Windows.
  • Le dossier est rapidement devenu une zone interdite aux humains .

J'ai fini par devoir stocker les fichiers dans des sous-dossiers en utilisant le temps de création pour créer le chemin. Tels que /mnt/images/YYYY/MM/DD/UUID.jpg. Cela a résolu tous les problèmes ci-dessus et m'a permis de créer des fichiers zip ciblant une date.

Si le seul identifiant pour un fichier que vous avez est un numéro numérique, et ces numéros ont tendance à s'exécuter en séquence. Pourquoi ne pas les regrouper par 100000, 10000et 1000.

Par exemple, si vous avez un fichier nommé, 384295.txtle chemin d'accès serait:

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

Si vous savez que vous atteindrez quelques millions. Utiliser des 0préfixes pour 1000000

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

1

Écrire un fichier texte à partir du Web scrape (ne devrait pas être affecté par le nombre de fichiers dans le dossier).

Pour créer un nouveau fichier, il faut analyser le fichier de répertoire en recherchant suffisamment d'espace vide pour la nouvelle entrée de répertoire. Si aucun espace n'est suffisamment grand pour stocker la nouvelle entrée de répertoire, il sera placé à la fin du fichier de répertoire. À mesure que le nombre de fichiers dans un répertoire augmente, le temps d'analyse du répertoire augmente également.

Tant que les fichiers de répertoire restent dans le cache du système, les performances obtenues ne seront pas mauvaises, mais si les données sont publiées, la lecture du fichier de répertoire (généralement très fragmenté) à partir du disque pourrait prendre beaucoup de temps. Un SSD améliore cela, mais pour un répertoire contenant des millions de fichiers, il pourrait toujours y avoir un impact notable sur les performances.

Compressez les fichiers sélectionnés en fonction de la liste des noms de fichiers.

Cela peut également nécessiter du temps supplémentaire dans un répertoire contenant des millions de fichiers. Dans un système de fichiers avec des entrées de répertoire hachées (comme EXT4), cette différence est minime.

le stockage d'un maximum de dix millions de fichiers dans un dossier affectera-t-il les performances des opérations ci-dessus, ou les performances générales du système, différemment de la création d'une arborescence de sous-dossiers dans laquelle les fichiers doivent vivre?

Un arbre de sous-dossiers ne présente aucun des inconvénients de performances ci-dessus. De plus, si le système de fichiers sous-jacent est modifié pour ne pas avoir de noms de fichier hachés, la méthodologie d'arbre fonctionnera toujours bien.


1

Premièrement: empêcher 'ls' de trier avec 'ls -U', peut-être mettre à jour votre ~ / bashrc pour avoir 'alias ls = "ls -U"' ou similaire.

Pour votre grand ensemble de fichiers, vous pouvez essayer ceci comme ceci:

  • créer un ensemble de fichiers de test

  • voir si de nombreux noms de fichiers causent des problèmes

  • utilisez xargs parmeter-batching et le comportement (par défaut) de zip d'ajouter des fichiers à un zip pour éviter les problèmes.

Cela a bien fonctionné:

# 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
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.