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.

0voto

Roy Points 4136

Voici comment je supprime les millions de fichiers de trace qui peuvent parfois s'accumuler sur un gros serveur de base de données Oracle :

for i in /u*/app/*/diag/*/*/*/trace/*.tr? ; do rm $i; echo -n . ;  done

Je trouve que cela entraîne une suppression assez lente qui a un faible impact sur les performances du serveur, généralement de l'ordre d'une heure par million de fichiers sur une configuration "typique" de 10 000 IOPS.

Il faut souvent plusieurs minutes avant que les répertoires soient analysés, que la liste initiale des fichiers soit générée et que le premier fichier soit supprimé. À partir de là, un . est envoyé en écho pour chaque fichier supprimé.

Le retard causé par l'écho vers le terminal s'est avéré suffisant pour empêcher toute charge significative pendant que la suppression progresse.

0 votes

Tu es dévoré vivant par le globbing. Que diriez-vous de quelque chose de plus comme : find /u* -maxdepth 3 -mindepth 3 -type d -path '*/app/*' -name diag -print0 | xargs -0I = find = -mindepth 4 -maxdepth 4 -type d -name 'trace' -print0 | xargs -0I = find = -mindepth 1 -maxdepth 1 -name '*.tr' ? Ajouter -delete jusqu'au dernier pour supprimer réellement des choses ; tel qu'il est écrit, il ne fait que lister ce qu'il veut supprimer. Notez que ceci est optimisé pour des circonstances où vous avez beaucoup de choses sans intérêt dans des répertoires proches ; si ce n'est pas le cas, vous pouvez simplifier la logique de façon considérable.

0 votes

Find -delete a tendance à provoquer trop d'E/S et affecte facilement les performances de production. Peut-être avec ionice.

0 votes

Il cause toutes ces entrées/sorties simplement en étant plus efficace, cependant ! Dans votre exemple, le globbing est entièrement chargé en amont (c'est-à-dire que la liste complète des fichiers est générée avant la première opération d'écriture de la commande rm se produit), ce qui permet d'avoir des entrées/sorties relativement efficaces au démarrage, suivies par des entrées/sorties douloureuses et désordonnées. rm qui ne causent probablement pas beaucoup d'E/S, mais qui impliquent scandir parcourir le répertoire de façon répétée (sans causer d'E/S car il a déjà été chargé dans le cache de bloc ; voir aussi vfs_cache_pressure ). Si vous voulez ralentir les choses, ionice est une option, mais j'utiliserais probablement des fractionnements de secondes. sleep s.

-1voto

Jeremy Points 141

Vous pouvez utiliser les fonctions de parallélisation de 'xargs' :

ls -1|xargs -P nb_concurrent_jobs -n nb_files_by_job rm -rf

1 votes

Cela n'aidera pas. Le goulot d'étranglement est la faiblesse des E/S aléatoires sur le disque. Faire des suppressions parallèles pourrait aggraver la situation et augmenter la charge du CPU.

-2voto

karmawhore Points 3865
ls|cut -c -4|sort|uniq|awk '{ print "rm -rf " $1 }' | sh -x

1 votes

Wow. Je suppose que ça tombe assez fermement dans le camp de "plus d'une façon d'écorcher un chat". Sérieusement, avec le tri et l'uniq ? "ls" trie par défaut, de toute façon, et j'espère bien que les noms de fichiers sont uniques. :/

-2voto

En fait, celle-ci est un peu meilleure si le Shell que vous utilisez fait l'expansion de la ligne de commande :

ls|cut -c -4|sort|uniq|awk '{ print "echo " $1 ";rm -rf " $1 "*"}' |sh

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