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.

10voto

adamf Points 71

TLDR : utiliser rsync -a --delete emptyfolder/ x .

Cette question a été vue 50 000 fois et a donné lieu à de nombreuses réponses, mais personne ne semble avoir analysé les différentes réponses. Il y a un lien vers un benchmark externe, mais celui-ci date de plus de 7 ans et n'a pas examiné le programme fourni dans cette réponse : https://serverfault.com/a/328305/565293

La difficulté réside en partie dans le fait que le temps nécessaire pour supprimer un fichier dépend fortement des disques utilisés et du système de fichiers. Dans mon cas, j'ai testé les deux avec un SSD grand public exécutant BTRFS sur Arch Linux (mis à jour en 2020-03), mais j'ai obtenu le même classement des résultats sur une distribution (Ubuntu 18.04), un système de fichiers (ZFS) et un type de disque (HDD dans une configuration RAID10) différents.

La configuration du test était identique pour chaque essai :

# setup
mkdir test && cd test && mkdir empty
# create 800000 files in a folder called x
mkdir x && cd x
seq 800000 | xargs touch
cd ..

Résultats des tests :

rm -rf x : 30.43s

find x/ -type f -delete : 29.79

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

rsync -a --delete empty/ x : 25.11s

(Le programme suivant est celui de cette réponse mais modifié pour ne rien imprimer ou attendre avant de supprimer les fichiers).

./dentls --delete x : 29.74

El rsync s'est avéré être le vainqueur à chaque fois que j'ai répété le test, bien que par une marge assez faible. Le site perl était plus lente que toute autre option sur mes systèmes.

De manière quelque peu choquante, le programme de la première réponse à cette question s'est avéré ne pas être plus rapide sur mes systèmes qu'un simple rm -rf . Essayons de comprendre pourquoi.

Tout d'abord, la réponse prétend que le problème est que rm utilise readdir avec une taille de buffer fixe de 32Kb avec getdents . Cela ne s'est pas avéré être le cas sur mon système Ubuntu 18.04, qui utilisait un buffer quatre fois plus grand. Sur le système Arch Linux, il utilisait getdents64 .

De plus, la réponse fournit de manière trompeuse des statistiques donnant sa vitesse à liste les fichiers d'un grand répertoire, mais sans les supprimer (ce qui est le sujet de la question). Il compare dentls a ls -u1 mais un simple strace révèle que getdents es no la raison pour laquelle ls -u1 est lent, du moins pas sur mon système (Ubuntu 18.04 avec 1000000 fichiers dans un répertoire) :

strace -c ls -u1 x >/dev/null
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 94.00    7.177356           7   1000000           lstat
  5.96    0.454913        1857       245           getdents
[snip]

Ce site ls fait un million d'appels à lstat ce qui ralentit considérablement le programme. Le site getdents les appels ne font que 0,455 seconde. Combien de temps durent les getdents les appels sont pris en charge dentls sur le même dossier ?

strace -c ./dentls x >/dev/null
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 99.91    0.489895       40825        12           getdents
[snip]

C'est vrai ! Même si dentls ne fait que 12 appels au lieu de 245, il faut en fait au système plus longtemps pour exécuter ces appels. L'explication donnée dans cette réponse est donc incorrecte, du moins pour les deux systèmes sur lesquels j'ai pu la tester.

Il en va de même pour rm y dentls --delete . Considérant que rm prend 0,42s en appelant getdents , dentls prend 0,53s. Sur soit Dans ce cas, la grande majorité du temps est passée à appeler unlink !

En résumé, ne vous attendez pas à des accélérations massives en cours d'exécution. dentls à moins que votre système ne soit comme celui de l'auteur et qu'il ait beaucoup de frais généraux sur les individus. getdents . Peut-être que les gens de la glibc ont considérablement accéléré le processus depuis que la réponse a été écrite, et qu'il faut maintenant un temps de réponse linéaire pour différentes tailles de tampon. Ou peut-être que le temps de réponse de getdents dépend de l'architecture du système d'une manière qui n'est pas évidente.

1 votes

J'ai remarqué l'utilisation de -u et je pense que cela devrait être -U (ce qui est le contraire, -U signifie ne pas trier sur les propriétés par fichier, de sorte que lstat peut être évitée). J'ai demandé sur l'autre réponse juste maintenant si @Matthew Ife a utilisé -u accidentellement.

0 votes

Pourriez-vous strace l'approche rsync et vérifier quels appels syscalls elle effectue, pour comprendre pourquoi elle est plus rapide que dentls ici ?

0 votes

Je n'ai jamais vu ça, mais honnêtement, très bon travail. Ma réponse originale a presque 10 ans. C'est une EON en termes informatiques. Au minimum ext4 et extents accélèrent vraiment VRAIMENT unlink() pour les gros fichiers et je suis sûr que des trucs comme la pré-allocation de blocs aident unlink à fonctionner plus vite sur les gros répertoires. Pour ce qui est de getdents(), il y a une surcharge mais sur les périphériques de bloc modernes, elle est négligeable. En fait, je ne suis même pas sûr que sur les systèmes de fichiers modernes, le problème des "millions de fichiers dans un répertoire" ait le même impact qu'avant.

4voto

Find n'a tout simplement pas fonctionné pour moi, même après avoir modifié les paramètres du fs ext3 comme suggéré par les utilisateurs ci-dessus. Il consomme beaucoup trop de mémoire. Ce script PHP a fait l'affaire - rapide, utilisation insignifiante du CPU, utilisation insignifiante de la mémoire :

<?php 
$dir = '/directory/in/question';
$dh = opendir($dir)) { 
while (($file = readdir($dh)) !== false) { 
    unlink($dir . '/' . $file); 
} 
closedir($dh); 
?>

J'ai posté un rapport de bogue concernant ce problème avec find : http://savannah.gnu.org/bugs/?31961

0 votes

Cela m'a sauvé !

3voto

karmawhore Points 3865

Assurez-vous de le faire :

mount -o remount,rw,noatime,nodiratime /mountpoint

ce qui devrait aussi accélérer un peu les choses.

4 votes

Bien vu, mais c'est déjà monté noatime, comme je l'ai mentionné dans l'en-tête de la question. Et nodiratime est redondant ; voir lwn.net/Articles/245002 .

1 votes

Les gens répètent ce mantra "noatime,nodiratime,nodevatime,noreadingdocsatime"

3voto

Matthew Read Points 132

J'ai récemment été confronté à un problème similaire et j'ai été incapable d'obtenir des ring0's. data=writeback suggestion de fonctionner (peut-être en raison du fait que les fichiers sont sur ma partition principale). En cherchant des solutions de contournement, je suis tombé sur ceci :

tune2fs -O ^has_journal <device>

Cela désactivera complètement la journalisation, quelle que soit l'option choisie. data option donnée à mount . J'ai combiné cela avec noatime et le volume avait dir_index et ça semblait fonctionner plutôt bien. La suppression s'est terminée sans que j'aie besoin de la tuer, mon système est resté réactif et il est maintenant de nouveau opérationnel (avec la journalisation réactivée) sans aucun problème.

0 votes

J'allais suggérer de le monter en ext2 au lieu de ext3, pour éviter de journaliser les opérations de métadonnées. Cela devrait faire la même chose.

2voto

bindbn Points 5093

La commande ls est très lente. Essayez :

find /dir_to_delete ! -iname "*.png" -type f -delete

0 votes

Rm -rf a tourné pendant un jour et demi, et j'ai fini par le tuer, sans jamais savoir s'il avait réellement accompli quelque chose. J'avais besoin d'une barre de progression.

4 votes

Quant à la lenteur de rm, "time find . -delete" sur 30k fichiers : 0m0.357s / 0m0.019s / 0m0.337s real/user/sys. "time ( ls -1U | xargs rm -f )" sur ces mêmes fichiers : 0m0.366s / 0m0.025s / 0m0.340s. Ce qui est en fait le territoire de la marge d'erreur.

1 votes

Vous auriez pu simplement exécuter strace -r -p <pid of rm> pour s'attacher au processus rm déjà en cours. Vous pouvez alors voir à quelle vitesse unlink les appels du système défilent. ( -r met le temps écoulé depuis le précédent appel système au début de chaque ligne).

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