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.

2voto

jnthnjns Points 121

Il est évident qu'il ne s'agit pas de comparer des pommes avec des pommes, mais j'ai fait un petit test et j'ai fait ce qui suit :

Création de 100 000 fichiers de 512 octets dans un répertoire ( dd y /dev/urandom dans une boucle) ; j'ai oublié de chronométrer, mais il a fallu environ 15 minutes pour créer ces fichiers.

J'ai exécuté ce qui suit pour supprimer lesdits fichiers :

ls -1 | wc -l && time find . -type f -delete

100000

real    0m4.208s
user    0m0.270s
sys     0m3.930s 

Il s'agit d'une boîte Pentium 4 2.8GHz (quelques centaines de Go IDE 7200 RPM je pense ; EXT3). Noyau 2.6.27.

0 votes

Intéressant, donc peut-être que le fait que les fichiers aient été créés sur une longue période de temps est pertinent ? Mais cela ne devrait pas être important ; le cache de bloc devrait avoir tous les blocs de métadonnées pertinents en RAM. Peut-être est-ce parce que unlink(2) est transactionnel ? A votre avis, désactiver la journalisation pour la durée du rm serait-il une solution potentielle (bien qu'admise comme quelque peu dangereuse) ? Il ne semble pas que vous puissiez désactiver entièrement la journalisation sur un système de fichiers monté sans un tune2fs/fsck/reboot, ce qui va à l'encontre du but recherché.

0 votes

Je ne peux pas me prononcer sur ce point, mais de manière anecdotique (lors de diverses discussions sur le NIX au fil des ans), j'ai toujours entendu dire que rm est horriblement lent sur un grand nombre de fichiers, d'où la find -delete option. Avec un joker sur le Shell, il va développer chaque nom de fichier correspondant, et je suppose qu'il y a une mémoire tampon limitée pour cela, donc vous pourriez voir comment cela deviendrait inefficace.

1 votes

Rm serait lent parce qu'il recherche un fichier par son nom, ce qui signifie qu'il faut itérer dans les entrées du répertoire une par une jusqu'à ce qu'il le trouve. Dans ce cas, cependant, puisque chaque entrée qu'on lui remet est (à ce moment-là) la première de la liste (ls -U/ls -f), il devrait être presque aussi vite. Cela dit, rm -rf <dir>, qui aurait dû fonctionner comme un champion, était aussi lent que possible. Peut-être est-il temps d'écrire un patch à coreutils pour accélérer les suppressions massives ? Peut-être que c'est secrètement globbing/tri d'une certaine manière récursive afin d'implémenter rm -rf ? Ce genre d'incertitudes est la raison pour laquelle j'ai posé la question ;)

2voto

sam Points 21

Est dir_index défini pour le système de fichiers ? ( tune2fs -l | grep dir_index ) Si ce n'est pas le cas, activez-le. Il est généralement activé pour les nouveaux RHEL.

1 votes

Oui, il est activé, mais suggestion géniale !

2voto

onur Points 1

Il y a quelques années, j'ai trouvé un répertoire avec 16 millions de XML dans le / système de fichiers. En raison de la criticité du serveur, nous avons utilisé la commande suivante qui a pris à peu près 30 heures pour finir :

perl -e 'for(<*>){((stat)[9]<(unlink))}'

C'était un vieux 7200 rpm et, malgré le goulot d'étranglement des entrées-sorties et les pics d'activité du processeur, l'ancien serveur Web a continué à fonctionner.

1voto

Phil P Points 3020

Mon option préférée est l'approche newfs, déjà suggérée. Le problème de base est, comme on l'a déjà noté, que le balayage linéaire pour gérer la suppression est problématique.

rm -rf devrait être presque optimale pour un système de fichiers local (NFS serait différent). Mais avec des millions de fichiers, 36 octets par nom de fichier et 4 par inode (une supposition, pas de vérification de la valeur pour ext3), cela fait 40 * millions, à conserver en RAM juste pour le répertoire.

Je pense que vous utilisez la mémoire cache des métadonnées du système de fichiers sous Linux, de sorte que les blocs d'une page du fichier de répertoire sont supprimés alors qu'une autre partie est encore utilisée, pour revenir à cette page du cache lorsque le fichier suivant est supprimé. L'optimisation des performances de Linux n'est pas mon domaine, mais /proc/sys/{vm,fs}/ contient probablement quelque chose de pertinent.

Si vous pouvez vous permettre un temps d'arrêt, vous pouvez envisager d'activer la fonction dir_index. Elle fait passer l'index du répertoire de linéaire à quelque chose de beaucoup plus optimal pour la suppression dans les grands répertoires (b-trees hachés). tune2fs -O dir_index ... suivi par e2fsck -D fonctionnerait. Cependant, bien que je sois convaincu que cela aiderait avant il y a des problèmes, je ne sais pas comment la conversion (e2fsck avec les -D ) se comporte lorsqu'il s'agit d'un répertoire existant de très grande taille. Les sauvegardes + ça craint.

2 votes

pubbs.net/201008/squid/ suggère que /proc/sys/fs/vfs_cache_pressure pourrait être la valeur à utiliser, mais je ne sais pas si le répertoire lui-même compte pour le cache des pages (parce que c'est ce qu'il est) ou pour le cache des inodes (parce que, bien qu'il ne soit pas un inode, il s'agit de métadonnées du FS et elles y sont regroupées pour cette raison). Comme je l'ai dit, le tuning des VM Linux n'est pas mon domaine. Jouez et voyez ce qui vous aide.

1voto

Janne Pikkarainen Points 31244

Parfois, Perl peut faire des merveilles dans des cas comme celui-ci. Avez-vous déjà essayé si un petit script comme celui-ci pourrait surpasser bash et les commandes de base script ?

#!/usr/bin/perl 
open(ANNOYINGDIR,"/path/to/your/directory");
@files = grep("/*\.png/", readdir(ANNOYINGDIR));
close(ANNOYINGDIR);

for (@files) {
    printf "Deleting %s\n",$_;
    unlink $_;
}

Ou une autre approche, peut-être encore plus rapide, en Perl :

#!/usr/bin/perl
unlink(glob("/path/to/your/directory/*.png")) or die("Could not delete files, this happened: $!");

EDITAR: Je viens de donner un essai à mes scripts en Perl. Le plus verbeux fait quelque chose de bien. Dans mon cas, j'ai essayé avec un serveur virtuel avec 256 Mo de RAM et un demi-million de fichiers.

time find /test/directory | xargs rm les résultats :

real    2m27.631s
user    0m1.088s
sys     0m13.229s

par rapport à

time perl -e 'opendir(FOO,"./"); @files = readdir(FOO); closedir(FOO); for (@files) { unlink $_; }'

real    0m59.042s
user    0m0.888s
sys     0m18.737s

0 votes

J'hésite à imaginer ce que ferait cet appel glob() ; je suppose qu'il fait un scandir(). Si c'est le cas, cela va prendre un temps fou pour revenir. Une modification de la première suggestion qui ne pré-lit pas toutes les entrées de répertoire pourrait avoir des chances d'aboutir ; cependant, dans sa forme actuelle, elle aussi utiliserait une quantité impensable de CPU pour lire toutes les entrées de répertoire en une seule fois. Une partie du but ici est de diviser pour mieux conquérir ; ce code n'est pas fondamentalement différent de 'rm -f *.png', malgré les problèmes avec l'expansion de Shell. Si cela peut vous aider, il n'y a rien dans le répertoire que je n'a pas que vous voulez supprimer.

0 votes

Je dois en essayer d'autres dès que j'arrive au travail. Je viens d'essayer de créer 100 000 fichiers dans un seul répertoire et la combinaison find + xargs + rm a pris 7,3 secondes, la combinaison Perl + unlink(glob)... a fini en 2,7 secondes. J'ai essayé plusieurs fois, le résultat était toujours le même. Au travail, je vais essayer avec plus de fichiers.

0 votes

J'ai appris quelque chose de nouveau en testant ceci. Au moins avec ext3 et ext4 l'entrée du répertoire lui-même reste énorme même après avoir supprimé tous les fichiers de là. Après quelques tests, mon répertoire /tmp/test prenait 15 Mo d'espace disque. Y a-t-il un autre moyen de nettoyer cela que de supprimer le répertoire et de le recréer ?

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