web-dev-qa-db-fra.com

rm sur un répertoire avec des millions de fichiers

Contexte: serveur physique, âgé d'environ deux ans, disques SATA 7200 tr/min connectés à une carte RAID 3Ware, ext3 FS noatime monté et données = commandées, 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 des fichiers plus volumineux (quelques Ko).

Nous avons un serveur qui a fait un peu coucou au cours des derniers mois, mais nous ne l'avons remarqué que l'autre jour lorsqu'il a commencé à ne pas pouvoir écrire dans un répertoire car il contient trop de fichiers. Plus précisément, il a commencé à lancer 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 dans le fichier de répertoire lui-même. Je ne sais pas combien de fichiers cela représenterait, mais cela ne peut pas dépasser, comme vous pouvez le voir, plus de trois millions. Pas que ce soit bon, faites attention! Mais c'est la première partie de ma question: quelle est exactement cette limite supérieure? Est-il réglable? Avant de me faire crier dessus - je veux le régler bas; cet énorme répertoire a causé toutes sortes de problèmes.

Quoi qu'il en soit, nous avons détecté 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 essayé ça en premier. J'ai abandonné et tué après qu'il ait fonctionné pendant un jour et demi sans aucun impact perceptible.

  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 supprimer via unlink (2). Autrement dit, 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 dans les fichiers dans/lost + found; sinon, je viens de déplacer mon problème. En plus de toutes les autres préoccupations, après avoir lu un peu plus à ce sujet, il s'avère que je devrais probablement appeler des fonctions internes FS, car aucune des variantes de unlink (2) I peut me permettre de supprimer allègrement un répertoire contenant des entrées.
  3. while [ true ]; do ls -Uf | head -n 10000 | xargs rm -f 2>/dev/null; done )

    Il s'agit en fait de la version raccourcie; le vrai que j'exécute, qui ajoute simplement des rapports de progression et un arrêt net lorsque nous manquons de fichiers à supprimer, est:

    exporter i = 0; 
     heure (tandis que [true]; faire 
     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 ..."; 
    terminé )

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

  1. Comme mentionné ci-dessus, la limite d'entrée par répertoire est-elle réglable?
  2. Pourquoi at-il fallu "vrai 7m9.561s/utilisateur 0m0.001s/sys 0m0.001s" pour supprimer un seul fichier qui était le premier de la liste renvoyé par ls -U, Et il a fallu peut-être dix minutes pour le supprimer les 10 000 premières entrées avec la commande en # 3, mais maintenant ça se passe très bien? D'ailleurs, il a supprimé 260 000 en une trentaine de minutes, mais il a maintenant fallu quinze minutes pour en supprimer 60 000 de plus. Pourquoi les énormes oscillations de vitesse?
  3. Y a-t-il une meilleure façon de faire ce genre de chose? Ne stocke pas des millions de fichiers dans un répertoire; Je sais que c'est idiot, et cela ne serait pas arrivé sur ma montre. Googler le problème et parcourir SF et SO offre beaucoup de variations sur find qui ne vont pas être beaucoup plus rapides que mon approche pour plusieurs raisons évidentes. Mais ne l'idée de supprimer via fsck a des jambes? Ou quelque chose de complètement différent? J'ai hâte d'entendre une réflexion originale (ou à l'intérieur de la boîte peu connue).

Sortie du script final!:

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.

108
BMDan

Le data=writeback L'option de montage mérite d'être essayée, afin d'empêcher la journalisation du système de fichiers. Cela ne doit être fait que pendant la période de suppression, mais il y a un risque si le serveur est en cours d'arrêt ou redémarré pendant l'opération de suppression.

Selon cette page ,

Certaines applications montrent une amélioration très rapide de la vitesse lors de son utilisation. Par exemple, des améliorations de vitesse peuvent être observées (...) lorsque les applications créent et suppriment de gros volumes de petits fichiers.

L'option est définie dans fstab ou pendant l'opération de montage, en remplaçant data=ordered avec data=writeback. Le système de fichiers contenant les fichiers à supprimer doit être remonté.

31
Ring Ø

Bien qu'une cause majeure de ce problème soit les performances ext3 avec des millions de fichiers, la cause racine réelle de ce problème est différente.

Lorsqu'un répertoire doit être répertorié, readdir () est appelé sur le répertoire qui produit une liste de fichiers. readdir est un appel posix, mais le véritable appel système Linux utilisé ici est appelé "getdents". Getdents répertorie les entrées du répertoire en remplissant un tampon d'entrées.

Le problème est principalement dû au fait que readdir () utilise une taille de tampon fixe de 32 Ko pour récupérer les fichiers. À mesure qu'un répertoire devient de plus en plus grand (la taille augmente à mesure que des fichiers sont ajoutés) ext3 devient de plus en plus lent à récupérer les entrées et la taille de tampon supplémentaire de 32 Ko de readdir ne suffit que pour inclure une fraction des entrées dans le répertoire. Cela provoque readdir en boucle encore et encore et appelle l'appel système coûteux encore et encore.

Par exemple, sur un répertoire de test que j'ai créé avec plus de 2,6 millions de fichiers à l'intérieur, l'exécution de "ls -1 | wc-l" affiche une sortie de grande étendue de nombreux appels système getdent.

$ strace ls -1 | wc -l
brk(0x4949000)                          = 0x4949000
getdents(3, /* 1025 entries */, 32768)  = 32752
getdents(3, /* 1024 entries */, 32768)  = 32752
getdents(3, /* 1025 entries */, 32768)  = 32760
getdents(3, /* 1025 entries */, 32768)  = 32768
brk(0)                                  = 0x4949000
brk(0x496a000)                          = 0x496a000
getdents(3, /* 1024 entries */, 32768)  = 32752
getdents(3, /* 1026 entries */, 32768)  = 32760
...

De plus, le temps passé dans ce répertoire était important.

$ time ls -1 | wc -l
2616044

real    0m20.609s
user    0m16.241s
sys 0m3.639s

La méthode pour rendre ce processus plus efficace consiste à appeler manuellement getdents avec un tampon beaucoup plus grand. Cela améliore considérablement les performances.

Maintenant, vous n'êtes pas censé appeler manuellement getdents, donc aucune interface n'existe pour l'utiliser normalement (consultez la page de manuel pour voir getdents!), Mais vous pouvez appelez-le manuellement et améliorez l'efficacité de votre appel système.

Cela réduit considérablement le temps nécessaire pour récupérer ces fichiers. J'ai écrit un programme qui fait ça.

/* I can be compiled with the command "gcc -o dentls dentls.c" */

#define _GNU_SOURCE

#include <dirent.h>     /* Defines DT_* constants */
#include <err.h>
#include <fcntl.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>

struct linux_dirent {
        long           d_ino;
        off_t          d_off;
        unsigned short d_reclen;
        char           d_name[256];
        char           d_type;
};

static int delete = 0;
char *path = NULL;

static void parse_config(
        int argc,
        char **argv)
{
    int option_idx = 0;
    static struct option loptions[] = {
      { "delete", no_argument, &delete, 1 },
      { "help", no_argument, NULL, 'h' },
      { 0, 0, 0, 0 }
    };

    while (1) {
        int c = getopt_long(argc, argv, "h", loptions, &option_idx);
        if (c < 0)
            break;

        switch(c) {
          case 0: {
              break;
          }

          case 'h': {
              printf("Usage: %s [--delete] DIRECTORY\n"
                     "List/Delete files in DIRECTORY.\n"
                     "Example %s --delete /var/spool/postfix/deferred\n",
                     argv[0], argv[0]);
              exit(0);                      
              break;
          }

          default:
          break;
        }
    }

    if (optind >= argc)
      errx(EXIT_FAILURE, "Must supply a valid directory\n");

    path = argv[optind];
}

int main(
    int argc,
    char** argv)
{

    parse_config(argc, argv);

    int totalfiles = 0;
    int dirfd = -1;
    int offset = 0;
    int bufcount = 0;
    void *buffer = NULL;
    char *d_type;
    struct linux_dirent *dent = NULL;
    struct stat dstat;

    /* Standard sanity checking stuff */
    if (access(path, R_OK) < 0) 
        err(EXIT_FAILURE, "Could not access directory");

    if (lstat(path, &dstat) < 0) 
        err(EXIT_FAILURE, "Unable to lstat path");

    if (!S_ISDIR(dstat.st_mode))
        errx(EXIT_FAILURE, "The path %s is not a directory.\n", path);

    /* Allocate a buffer of equal size to the directory to store dents */
    if ((buffer = calloc(dstat.st_size*3, 1)) == NULL)
        err(EXIT_FAILURE, "Buffer allocation failure");

    /* Open the directory */
    if ((dirfd = open(path, O_RDONLY)) < 0) 
        err(EXIT_FAILURE, "Open error");

    /* Switch directories */
    fchdir(dirfd);

    if (delete) {
        printf("Deleting files in ");
        for (int i=5; i > 0; i--) {
            printf("%u. . . ", i);
            fflush(stdout);
            sleep(1);
        }
        printf("\n");
    }

    while (bufcount = syscall(SYS_getdents, dirfd, buffer, dstat.st_size*3)) {
        offset = 0;
        dent = buffer;
        while (offset < bufcount) {
            /* Don't print thisdir and parent dir */
            if (!((strcmp(".",dent->d_name) == 0) || (strcmp("..",dent->d_name) == 0))) {
                d_type = (char *)dent + dent->d_reclen-1;
                /* Only print files */
                if (*d_type == DT_REG) {
                    printf ("%s\n", dent->d_name);
                    if (delete) {
                        if (unlink(dent->d_name) < 0)
                            warn("Cannot delete file \"%s\"", dent->d_name);
                    }
                    totalfiles++;
                }
            }
            offset += dent->d_reclen;
            dent = buffer + offset;
        }
    }
    fprintf(stderr, "Total files: %d\n", totalfiles);
    close(dirfd);
    free(buffer);

    exit(0);
}

Bien que cela ne combat pas le problème fondamental sous-jacent (beaucoup de fichiers, dans un système de fichiers qui fonctionne mal). Il est probable que ce soit beaucoup, beaucoup plus rapide que la plupart des alternatives affichées.

Par anticipation, il faut supprimer le répertoire affecté et le refaire après. Les répertoires ne font qu'augmenter en taille et peuvent rester peu performants même avec quelques fichiers à l'intérieur en raison de la taille du répertoire.

Edit: J'ai assez nettoyé cela. Ajout d'une option pour vous permettre de supprimer sur la ligne de commande au moment de l'exécution et supprimé un tas de trucs de Treewalk qui, honnêtement, était au mieux douteux. Il a également été démontré qu'il produit une corruption de la mémoire.

Vous pouvez maintenant faire dentls --delete /my/path

De nouveaux résultats. Basé sur un répertoire avec 1,82 million de fichiers.

## Ideal ls Uncached
$ time ls -u1 data >/dev/null

real    0m44.948s
user    0m1.737s
sys 0m22.000s

## Ideal ls Cached
$ time ls -u1 data >/dev/null

real    0m46.012s
user    0m1.746s
sys 0m21.805s


### dentls uncached
$ time ./dentls data >/dev/null
Total files: 1819292

real    0m1.608s
user    0m0.059s
sys 0m0.791s

## dentls cached
$ time ./dentls data >/dev/null
Total files: 1819292

real    0m0.771s
user    0m0.057s
sys 0m0.711s

J'ai été un peu surpris que cela fonctionne si bien!

82
Matthew Ife

Serait-il possible de sauvegarder tous les autres fichiers de ce système de fichiers vers un emplacement de stockage temporaire, de reformater la partition, puis de restaurer les fichiers?

31
jftuga

Il n'y a pas de limite par fichier de répertoire dans ext3 juste la limite d'inode du système de fichiers (je pense qu'il y a une limite sur le nombre de sous-répertoires cependant).

Vous pouvez toujours rencontrer des problèmes après avoir supprimé les fichiers.

Lorsqu'un répertoire contient des millions de fichiers, l'entrée de répertoire elle-même devient très volumineuse. L'entrée de répertoire doit être analysée pour chaque opération de suppression, et cela prend différentes quantités de temps pour chaque fichier, en fonction de l'emplacement de son entrée. Malheureusement, même après que tous les fichiers ont été supprimés, l'entrée de répertoire conserve sa taille. Ainsi, les opérations supplémentaires qui nécessitent l'analyse de l'entrée de répertoire prendront encore longtemps, même si le répertoire est maintenant vide. La seule façon de résoudre ce problème est de renommer le répertoire, d'en créer un nouveau avec l'ancien nom et de transférer tous les fichiers restants vers le nouveau. Supprimez ensuite celui renommé.

12
Alex J. Roberts

Je ne l'ai pas référencé, mais ce type l'a fait :

rsync -a --delete ./emptyDirectoty/ ./hugeDirectory/
8
Alix Axel

find n'a tout simplement pas fonctionné pour moi, même après avoir modifié les paramètres de l'ext3 fs comme suggéré par les utilisateurs ci-dessus. Consommé beaucoup trop de mémoire. Ce script PHP a fait l'affaire - utilisation rapide et insignifiante du processeur, 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 publié un rapport de bogue concernant ce problème avec find: http://savannah.gnu.org/bugs/?31961

4
Alexandre

Assurez-vous de faire:

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

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

3
karmawhore

J'ai récemment rencontré un problème similaire et je n'ai pas pu faire fonctionner la suggestion data=writeback De ring0 (peut-être en raison du fait que les fichiers se trouvent sur ma partition principale). En recherchant 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 data donnée à mount. J'ai combiné cela avec noatime et le volume avait dir_index Réglé, et cela semblait assez bien fonctionner. La suppression s'est en fait terminée sans que je doive le tuer, mon système est resté réactif et il est maintenant de nouveau opérationnel (avec journalisation activé) sans aucun problème.

3
Matthew Read

c'est une commande très lente. Essayer:

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

Est dir_index défini pour le système de fichiers? (tune2fs -l | grep dir_index) Sinon, activez-le. Il est généralement activé pour le nouveau RHEL.

2
sam

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

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

C'était un vieux 7200 tr/min hdd, et malgré le IO goulot d'étranglement et les pics de CPU, l'ancien serveur web a continué son service .

2
a_foaley

D'après ce dont 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 eu une fois où j'ai été confronté à un problème similaire (bien que mes estimations aient porté sur ~ 7h de suppression), à la fin est allé l'itinéraire suggéré par jftuga dans le premier commentaire .

1
Hubert Kario

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

rm -rf devrait être presque optimal pour un système de fichiers local (NFS serait différent). Mais à des millions de fichiers, 36 octets par nom de fichier et 4 par inode (une supposition, ne vérifiant pas la valeur pour ext3), c'est 40 * millions, à conserver dans RAM juste pour le répertoire.

À une supposition, vous détruisez la mémoire cache de 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 pendant que vous utilisez encore une autre partie, pour frapper à nouveau cette page du cache lors de la prochaine le fichier est supprimé. Le réglage des performances Linux n'est pas mon domaine, mais/proc/sys/{vm, fs}/contient probablement quelque chose de pertinent.

Si vous pouvez vous permettre des temps d'arrêt, vous pouvez envisager d'activer la fonction dir_index. Il fait passer l'index de répertoire de linéaire à quelque chose de beaucoup plus optimal pour la suppression dans les grands répertoires (b-arbres hachés). tune2fs -O dir_index ... suivi par e2fsck -D travaillerait. Cependant, même si je suis convaincu que cela aiderait avant il y a des problèmes, je ne sais pas comment la conversion (e2fsck avec le -D) fonctionne lorsqu'il s'agit d'un répertoire v.large existant. Sauvegardes + sucer-et-voir.

1
Phil P

Évidemment pas des pommes aux pommes ici, mais j'ai installé un petit test et j'ai fait ce qui suit:

100 000 fichiers de 512 octets créés dans un répertoire (dd et /dev/urandom en boucle); J'ai oublié de le chronométrer, mais il a fallu environ 15 minutes pour créer ces fichiers.

A 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,8 GHz (quelques centaines de Go IDE 7200 tr/min je pense; EXT3). Kernel 2.6.27.

1
gravyface

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

#!/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 Perl, peut-être encore plus rapide:

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

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

time find /test/directory | xargs rm 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
1
Janne Pikkarainen

Vous rencontrez probablement des problèmes de réécriture avec le répertoire. Essayez d'abord de supprimer les fichiers les plus récents. Regardez les options de montage qui différeront l'écriture différée sur le disque.

Pour une barre de progression, essayez d'exécuter quelque chose comme rm -rv /mystuff 2>&1 | pv -brtl > /dev/null

0
BillThor

Voici comment je supprime les millions de fichiers de trace qui peuvent parfois se rassembler sur un grand 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 quelque chose comme une heure par million de fichiers sur une configuration "typique" de 10 000 IOPS.

Il faudra souvent plusieurs minutes avant que les répertoires aient été analysés, la liste de fichiers initiale générée et le premier fichier supprimé. À partir de là, a. est répété pour chaque fichier supprimé.

Le retard provoqué par l'écho vers le terminal s'est avéré suffisamment long pour empêcher toute charge importante pendant la suppression.

0
Roy

D'accord, cela a été couvert de diverses manières dans le reste du fil, mais je pensais que je mettrais mes deux cents. Le responsable 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 entraîne un accès au disque partout lorsque vous vous dissociez. Les fichiers sont suffisamment petits pour que l'opération de dissociation ne saute probablement pas trop en mettant à zéro l'espace. Si vous lisez le répertoire puis triez par inode ascendant, vous obtiendrez probablement de meilleures performances. Donc, readdir dans ram (tri par inode) -> unlink -> profit.

L'inode est une approximation approximative ici, je pense .. mais en se basant sur votre cas d'utilisation, il pourrait être assez précis ...

0
MattyB

J'aurais probablement sorti un compilateur C et fait l'équivalent moral de votre script. Autrement dit, utilisez opendir(3) pour obtenir un descripteur de répertoire, puis utilisez readdir(3) pour obtenir le nom des fichiers, puis compilez les fichiers lorsque je les dissocie et de temps en temps j'imprime "% 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 de script Shell, c'est juste que j'ai l'habitude de devoir extraire le compilateur de temps en temps, soit parce qu'il n'y a pas de moyen propre de faire ce que je veux du Shell ou parce que bien que réalisable dans Shell, il est lent de cette façon.

0
Vatine

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

Cette question a 50 000 vues et pas mal de réponses, mais personne ne semble avoir évalué toutes les réponses. Il y a un lien vers un benchmark externe, mais celui-ci a plus de 7 ans et n'a pas regardé le programme fourni dans cette réponse: https://serverfault.com/a/328305/56529

Une partie de la difficulté ici est 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 différente (Ubuntu 18.04), un système de fichiers (ZFS) et un lecteur type (disque dur dans une configuration RAID10).

La configuration du test était identique pour chaque exécution:

# 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 de test:

rm -rf x: 30,43 s

find x/ -type f -delete: 29,79

Perl -e 'for(<*>){((stat)[9]<(unlink))}': 37,97 s

rsync -a --delete empty/ x: 25,11 s

(Ce qui suit est le programme de cette réponse , mais modifié pour ne rien imprimer ou attendre avant de supprimer des fichiers.)

./dentls --delete x: 29,74

La version rsync s'est avérée être la gagnante à chaque fois que je répétais le test, mais avec une marge assez faible. La commande Perl était plus lente que toute autre option sur mes systèmes.

Un peu choquant, le programme de la réponse la plus élevée à cette question s'est avéré être pas plus rapide sur mes systèmes qu'un simple rm -rf. Explorons pourquoi.

Tout d'abord, la réponse affirme que le problème est que rm utilise readdir avec une taille de tampon fixe de 32 Ko avec getdents. Cela ne s'est pas avéré être le cas sur mon système Ubuntu 18.04, qui utilisait un tampon 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 à listing les fichiers dans un grand répertoire, mais sans les supprimer (ce qui était la question). Il compare dentls à ls -u1, Mais un simple strace révèle que getdents est pas 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]

Cette commande ls fait un million d'appels à lstat, ce qui ralentit considérablement le programme. Les appels getdents ne totalisent que 0,455 seconde. Combien de temps les appels getdents prennent-ils 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 pour exécuter ces appels. Donc l'explication donnée dans cette réponse est en fait incorrecte - au moins pour les deux systèmes sur lesquels j'ai pu tester cela.

Il en va de même pour rm et dentls --delete. Alors que rm prend 0,42 seconde pour appeler getdents, dentls prend 0,53 seconde. Dans soit cas, la grande majorité du temps est consacrée à appeler unlink!

Donc, en bref, ne vous attendez pas à voir des accélérations massives exécutant dentls, à moins que votre système soit comme celui de l'auteur et ait beaucoup de frais généraux sur chaque getdents. Peut-être que les gens de la glibc l'ont considérablement accéléré au fil des ans depuis que la réponse a été écrite, et il faut maintenant un temps linéaire pour répondre à différentes tailles de mémoire 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.

0
adamf

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

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

0
marcoc