124 votes

rm sur un répertoire contenant des millions de fichiers

Contexte : serveur physique, âgé d'environ deux ans, disques SATA 7200-RPM connectés à une carte RAID 3Ware, ext3 FS monté noatime et data=ordered, pas sous une charge folle, noyau 2.6.18-92.1.22.el5, temps de fonctionnement 545 jours. Le répertoire ne contient aucun sous-répertoire, juste des millions de petits fichiers (~100 octets), avec quelques fichiers plus gros (quelques Ko).

Nous avons un serveur qui est devenu un peu fou au cours des derniers mois, mais nous ne l'avons remarqué que l'autre jour lorsqu'il a commencé à être incapable d'écrire dans un répertoire parce qu'il contenait trop de fichiers. Plus précisément, il a commencé à envoyer cette erreur dans /var/log/messages :

ext3_dx_add_entry: Directory index full!

Le disque en question a beaucoup d'inodes restants :

Filesystem            Inodes   IUsed   IFree IUse% Mounted on
/dev/sda3            60719104 3465660 57253444    6% /

Je suppose donc que cela signifie que nous avons atteint la limite du nombre d'entrées pouvant se trouver dans le fichier de répertoire lui-même. Je n'ai aucune idée du nombre de fichiers que cela représente, mais cela ne peut pas dépasser, comme vous pouvez le voir, trois millions environ. Non pas que ce soit une bonne chose, remarquez ! Mais c'est la première partie de ma question : quelle est exactement cette limite supérieure ? Est-elle ajustable ? Avant de me faire engueuler, je veux la régler en bas ; cet énorme répertoire a causé toutes sortes de problèmes.

Quoi qu'il en soit, nous avons trouvé le problème dans le code qui générait tous ces fichiers, et nous l'avons corrigé. Maintenant, je suis coincé avec la suppression du répertoire.

Quelques options ici :

  1. rm -rf (dir)

    J'ai d'abord essayé ça. J'ai abandonné et je l'ai tué après qu'il ait fonctionné pendant un jour et demi sans aucun impact discernable.

  2. unlink(2) sur le répertoire : Cela vaut vraiment la peine d'être considéré, mais la question est de savoir s'il serait plus rapide de supprimer les fichiers à l'intérieur du répertoire via fsck que de les supprimer via unlink(2). C'est à dire, d'une manière ou d'une autre, je dois marquer ces inodes comme inutilisés. Cela suppose, bien sûr, que je puisse dire à fsck de ne pas supprimer les entrées des fichiers dans /lost+found ; sinon, j'ai juste déplacé mon problème. En plus de toutes les autres préoccupations, après avoir lu un peu plus sur le sujet, il s'avère que je devrais probablement appeler des fonctions internes de FS, car aucune des variantes de unlink(2) que je peux trouver ne me permettrait de supprimer allègrement un répertoire avec des entrées dedans. Pooh.

  3. while [ true ]; do ls -Uf | head -n 10000 | xargs rm -f 2>/dev/null; done )

    Il s'agit en fait de la version abrégée ; la vraie version que j'utilise, qui ajoute simplement quelques rapports de progression et un arrêt net lorsque nous n'avons plus de fichiers à supprimer, est la suivante :

    export i=0;
    time ( while \[ true \]; do
      ls -Uf | head -n 3 | grep -qF '.png' || break;
      ls -Uf | head -n 10000 | xargs rm -f 2>/dev/null;
      export i=$(($i+10000));
      echo "$i...";
    done )

    Cela semble fonctionner plutôt bien. Au moment où j'écris ces lignes, il a supprimé 260 000 fichiers au cours des trente dernières minutes environ.

Maintenant, pour les questions :

  1. Comme mentionné ci-dessus, la limite d'entrée par répertoire est-elle réglable ?
  2. Pourquoi il a fallu à "real 7m9.561s / user 0m0.001s / sys 0m0.001s" pour supprimer un seul fichier qui était le premier de la liste retournée par ls -U Il a mis peut-être dix minutes à supprimer les 10 000 premières entrées avec la commande n° 3, mais il se porte maintenant comme un charme ? De même, il a supprimé 260 000 entrées en trente minutes environ, mais il lui a fallu quinze minutes de plus pour en supprimer 60 000. Pourquoi ces énormes variations de vitesse ?
  3. Y a-t-il une meilleure façon de faire ce genre de choses ? Pas en stockant des millions de fichiers dans un répertoire ; je sais que c'est idiot, et que cela ne serait pas arrivé sous ma surveillance. En cherchant le problème sur Google et en consultant SF et SO, vous trouverez de nombreuses variations sur les points suivants find qui ne seront pas significativement plus rapides que mon approche pour plusieurs raisons évidentes. Mais est-ce que l'idée de supprimer via fsck a des chances de succès ? Ou quelque chose d'entièrement différent ? Je suis impatient d'entendre les idées qui sortent de l'ordinaire (ou qui sont à l'intérieur d'une boîte pas très connue).

Merci d'avoir lu ce petit roman ; n'hésitez pas à poser des questions et je ne manquerai pas d'y répondre. Je mettrai également à jour la question avec le nombre final de fichiers et la durée d'exécution du script de suppression script dès que je l'aurai.

Sortie finale du script! :

2970000...
2980000...
2990000...
3000000...
3010000...

real    253m59.331s
user    0m6.061s
sys     5m4.019s

Donc, trois millions de fichiers supprimés en un peu plus de quatre heures.

1 votes

Rm (GNU coreutils) 8.4 possède cette option : "-v, --verbose explique ce qui est fait" . Il affichera tous les fichiers en cours de suppression.

2 votes

En fait, ce serait une bonne façon de faire une barre de progression : puisque chaque fichier comporterait trente-sept caractères (36 + un ' '), il serait possible d'afficher une barre de progression. \n '), je pourrais facilement écrire un analyseur syntaxique pour cela, et puisque printf() est bon marché et que la commande rm a déjà le nom du fichier chargé, il n'y a pas de pénalité de performance particulière. Il semble qu'il n'y ait pas de raison de faire tout le bazar, puisque je ne pourrais jamais faire en sorte que "rm" fasse quelque chose comme ça, de toute façon. Mais ça pourrait très bien fonctionner comme une barre de progression intra-10.000 ; peut-être un "." pour chaque centaine de fichiers ?

8 votes

rm -rfv | pv -l >/dev/null . le pv devrait être disponible dans le EPEL dépôt.

1voto

Hubert Kario Points 6321

De ce que je me souviens, la suppression des inodes dans les systèmes de fichiers ext est O(n^2), donc plus vous supprimez de fichiers, plus le reste ira vite.

Il y a une fois où j'ai été confronté à un problème similaire (bien que mes estimations prévoyaient un temps de suppression de ~7h), j'ai finalement suivi la voie suggérée par jftuga. dans le premier commentaire .

0voto

rrichter Points 2273

J'aurais probablement sorti un compilateur C et fait l'équivalent moral de ton script. C'est-à-dire, utiliser opendir(3) pour obtenir un identifiant de répertoire, puis utiliser readdir(3) pour obtenir le nom des fichiers, puis comptabiliser les fichiers au fur et à mesure que je les dissocie et, de temps en temps, afficher "%d fichiers supprimés" (et éventuellement le temps écoulé ou l'horodatage actuel).

Je ne m'attends pas à ce que ce soit sensiblement plus rapide que la version Shell Shell, c'est juste que j'ai l'habitude de devoir arracher le compilateur de temps en temps, soit parce qu'il n'y a pas de moyen propre de faire ce que je veux à partir du Shell, soit parce que bien que faisable dans Shell, c'est d'une lenteur improductive de cette façon.

0 votes

Il pourrait au moins commencer par modifier le code source de rm à partir de coreutils .

0voto

BillThor Points 27096

Vous rencontrez probablement des problèmes de réécriture avec le répertoire. Essayez d'abord de supprimer les fichiers les plus récents. Examinez les options de montage qui permettent de différer la réécriture sur le disque.

Pour une barre de progression, essayez d'exécuter quelque chose comme rm -rv /mystuff 2>&1 | [pv](http://www.ivarch.com/programs/quickref/pv.shtml) -brtl > /dev/null

0 votes

En ce qui concerne la suppression des fichiers les plus récents en premier : ls -Ur ? Je suis presque sûr que cela chargerait les entrées du répertoire, puis les inverserait ; je ne crois pas que ls soit assez intelligent pour commencer à la fin de la liste des entrées du répertoire et revenir en arrière jusqu'au début. "ls -1" n'est probablement pas non plus une bonne idée, puisqu'il faudrait probablement 50+ Mo de mémoire et plusieurs minutes pour l'exécuter ; il faudrait "ls -U" ou "ls -f".

0 votes

Ce n'est probablement pratique que si les noms de fichiers augmentent selon un schéma prévisible. Cependant, vous pouvez essayer ls -1 avec un pipeline vers reverse, et un pipeline vers xargs. Utilisez des fichiers au lieu de pipes si vous voulez voir vos résultats intermédiaires. Vous n'avez fourni aucune information sur le nom des fichiers. Vous généreriez le modèle à l'envers et supprimeriez les fichiers utilisant le modèle. Vous devrez peut-être gérer les entrées de fichiers manquantes. Compte tenu de votre commentaire sur la mémoire requise, vous avez une idée des E/S nécessaires pour réécrire le répertoire.

0voto

Chris Points 11

Eh bien, ce n'est pas une vraie réponse, mais...

Serait-il possible de convertir le système de fichiers en ext4 et voir si les choses changent ?

0 votes

Il semble que faire cela "en direct" nécessite un fsck sur un système de fichiers monté, ce qui est... alarmant. Vous avez un meilleur moyen ?

0 votes

Le système de fichiers doit être démonté avant la conversion, c'est-à-dire avant les commandes tunefs nécessaires.

0voto

MattyB Points 993

D'accord, ce sujet a été abordé de diverses manières dans le reste du fil de discussion, mais j'ai pensé que je pourrais ajouter mon grain de sel. Le coupable des performances dans votre cas est probablement readdir. Vous récupérez une liste de fichiers qui ne sont pas nécessairement séquentiels sur le disque, ce qui provoque des accès au disque dans tous les sens lorsque vous reliez. Les fichiers sont suffisamment petits pour que l'opération de déliaison ne saute pas trop dans tous les sens pour remettre l'espace à zéro. Si vous lisez le répertoire et ensuite triez par inode croissant, vous obtiendrez probablement de meilleures performances. Donc, lire le répertoire dans la mémoire (trier par inode) -> délier -> profit.

Inode est une approximation grossière ici je pense ... mais en se basant sur votre cas d'utilisation il pourrait être assez précis ...

1 votes

Corrigez-moi si je me trompe, mais unlink(2) ne met pas à zéro l'inode, il supprime juste la référence à celui-ci dans le répertoire. J'aime l'audace de cette approche, cependant. Vous voulez faire des essais de temps et voir si ça tient la route ?

SistemesEz.com

SystemesEZ est une communauté de sysadmins où vous pouvez résoudre vos problèmes et vos doutes. Vous pouvez consulter les questions des autres sysadmins, poser vos propres questions ou résoudre celles des autres.

Powered by:

X