rm sur un répertoire avec des millions de fichiers

104

Contexte: serveur physique, âgé d'environ deux ans, disques SATA à 7 200 tr / min connectés à une carte RAID 3Ware, ext3 FS monté avec noatime et data = commandé, pas sous une charge folle, noyau 2.6.18-92.1.22.el5, temps de disponibilité 545 jours . Directory ne contient aucun sous-répertoire, mais des millions de petits fichiers (~ 100 octets), avec des fichiers plus volumineux (quelques Ko).

Nous avons un serveur qui est devenu un peu coucou au cours des derniers mois, mais nous ne l’avons remarqué que l’autre jour quand il a commencé à être incapable d’écrire dans un répertoire car il contenait trop de fichiers. Plus précisément, il a commencé à générer 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% /

Donc, je suppose que cela signifie que nous atteignons la limite du nombre d'entrées pouvant être dans le fichier répertoire lui-même. Aucune idée du nombre de fichiers, mais il ne peut pas y avoir plus de trois millions à peu près, comme vous pouvez le constater. Ce n'est pas bien, remarquez! Mais c’est la première partie de ma question: quelle est exactement cette limite supérieure? Est-ce accordable? Avant que je me crié dessus, je veux le régler vers le bas ; cet énorme répertoire a causé toutes sortes de problèmes.

Quoi qu'il en soit, nous avons repéré 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é ceci en premier. J'ai abandonné et je l'ai tué après une journée et demie sans impact perceptible.

  2. unlink (2) sur le répertoire: Cela mérite certainement d'être pris en compte, mais la question est de savoir s'il serait plus rapide de supprimer les fichiers du répertoire via fsck que de supprimer via unlink (2). C'est-à-dire que, d'une manière ou d'une autre, je dois marquer ces inodes comme inutilisés. Bien entendu, cela suppose que je puisse dire à fsck de ne pas supprimer les entrées dans les fichiers de / 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 FS internes, car aucune des variantes unlink (2) que je peux trouver ne me permettrait de supprimer allègrement. un répertoire avec des entrées. Caca.
  3. while [ true ]; do ls -Uf | head -n 10000 | xargs rm -f 2>/dev/null; done )

    C'est en fait la version abrégée; le vrai que je suis en train d’exécuter, qui ajoute quelques rapports de progression et un arrêt net lorsque nous sommes à court de fichiers à supprimer, est le suivant:

    exportation i = 0;
    time (while [true]; do
      ls -Uf | tête -n 3 | grep -qF '.png' || Pause;
      ls -Uf | tête -n 10000 | xargs rm -f 2> / dev / null;
      exportation i = $ (($ i + 10000));
      echo "$ i ...";
    terminé )

    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.

Maintenant, pour les questions:
  1. Comme mentionné ci-dessus, la limite d'entrée par répertoire est-elle réglable?
  2. Pourquoi a-t-il fallu "vrais 7m9.561s / utilisateur 0m0.001s / sys 0m0.001s" pour supprimer un fichier qui était le premier dans la liste renvoyée par ls -U, et il a fallu peut-être dix minutes pour supprimer les 10 000 premières entrées avec le commande en n ° 3, mais maintenant il roule assez bien? D'ailleurs, il en a supprimé 260 000 en une trentaine de minutes, mais il lui faut maintenant quinze minutes supplémentaires pour en supprimer 60 000 de plus. Pourquoi les énormes sautes de vitesse?
  3. Y a-t-il une meilleure façon de faire ce genre de chose? Pas stocker des millions de fichiers dans un répertoire; Je sais que c'est idiot, et cela ne serait pas arrivé sous ma montre. Googler le problème et regarder à travers SF et SO offre beaucoup de variations sur ce findqui ne va pas être beaucoup plus rapide que mon approche pour plusieurs raisons évidentes. Mais l'idée de delete-via-fsck a-t-elle des jambes? Ou quelque chose d'autre entièrement? Je suis impatient d'entendre des réflexions originales (ou dans la boîte non connue).
Merci d'avoir lu le petit roman; n'hésitez pas à poser des questions et je serai sûr de 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 une fois que je l'aurai.

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.

BMDan
la source
1
rm (GNU coreutils) 8.4 a cette option: "-v, --verbose explique ce qui est fait" . Il affichera tous les fichiers en cours de suppression.
Cristian Ciupitu
2
En fait, ce serait une façon élégante de faire une barre de progression: puisque chaque fichier aurait trente-sept caractères (36 + a '\ n'), je pourrais facilement écrire un analyseur pour cela, et comme printf () est cheap et la commande rm a déjà le nom du fichier chargé, il n’ya aucune pénalité de performance particulière. On dirait que je ne suis pas partant pour faire tout le tralala, étant donné que je ne pourrais jamais obtenir de "rm" quoi que ce soit de la sorte. Mais cela pourrait très bien fonctionner comme une barre de progression sur 10 000; peut-être un "." pour chaque cent fichiers?
BMDan
8
rm -rfv | pv -l >/dev/null. pv devrait être disponible dans le référentiel EPEL .
Cristian Ciupitu
5
pv est incroyablement génial. Je laisse une traînée d'installations PV dans mon sillage.
BMDan
J'ai eu exactement ce même problème récemment. Merci!
richo

Réponses:

30

L’ data=writebackoption de montage mérite d’être essayée afin d’empêcher la journalisation du système de fichiers. Cela ne doit être effectué que pendant la période de suppression. Cependant, 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 significative de la vitesse d'utilisation. Par exemple, des améliorations de la vitesse peuvent être constatées (...) lorsque les applications créent et suppriment de gros volumes de petits fichiers.

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

Ø de bague
la source
1
Il pourrait également augmenter le temps passé avec l' commit option suivante : "Cette valeur par défaut (ou toute valeur basse) nuira aux performances, mais elle est bonne pour la sécurité des données. Le réglage sur 0 aura le même effet que de le laisser au réglage par défaut (5 secondes). ). Le réglage sur de très grandes valeurs améliorera les performances ".
Cristian Ciupitu
1
L’écriture a l’air stellaire, à l’exception de la documentation que j’étais en train de consulter ( gentoo.org/doc/fr/articles/l-afig-p8.xml#doc_chap4 ) mentionne explicitement qu’il reste des métadonnées dans les journaux, ce qui, je suppose, inclut toutes les données que je suis. changer (je ne change certainement aucune donnée des fichiers eux-mêmes). Est-ce que ma compréhension de l'option est incorrecte?
BMDan
Enfin, pour information, ce lien ne mentionne pas le fait que data = writeback peut être un énorme trou de sécurité, car les données pointées par une entrée donnée peuvent ne pas contenir les données écrites par l'application, ce qui signifie qu'un crash pourrait en résulter dans les anciennes données potentiellement sensibles / privées exposées. Ce n’est pas un problème ici, car nous l’allumons seulement temporairement, mais je voulais alerter tout le monde de cette mise en garde au cas où vous-même ou d’autres personnes qui rencontriez cette suggestion n’étiez pas au courant.
BMDan
commet: c'est assez lisse! Merci pour le pointeur.
BMDan
2
data=writebackmet toujours les métadonnées en revue avant de les écrire dans le système de fichiers principal. Si je comprends bien, cela n’impose tout simplement pas d’ordre entre l’écriture d’une carte de l’étendue et l’écriture de données dans ces domaines. Peut-être y at-il d’autres contraintes d’ordre qui s’atténueront si vous en tirez un avantage. Bien entendu, un montage sans journal pourrait être encore plus performant. (Cela pourrait laisser les changements de métadonnées se produire dans la RAM, sans rien avoir besoin d’être sur le disque avant la fin de l’opération de dissociation).
Peter Cordes
80

Une cause majeure de ce problème est la performance ext3 avec des millions de fichiers, mais la cause première de ce problème est différente.

Quand un répertoire doit être répertorié, readdir () est appelé sur le répertoire, ce qui donne une liste de fichiers. readdir est un appel posix, mais le véritable appel système Linux utilisé ici s'appelle 'getdents'. Getdents liste les entrées du répertoire en remplissant un tampon avec des entrées.

Le problème tient principalement au fait que readdir () utilise une taille de tampon fixe de 32 Ko pour extraire des fichiers. Lorsqu'un répertoire devient de plus en plus grand (la taille augmente à mesure que des fichiers sont ajoutés), ext3 lit de plus en plus lentement pour extraire des entrées et la taille de mémoire tampon supplémentaire de 32 Ko de readdir est suffisante pour inclure une fraction des entrées du répertoire. Readdir est ainsi mis en boucle, invoquant l'appel système coûteux, encore et encore.

Par exemple, sur un répertoire de test que j'ai créé et contenant plus de 2,6 millions de fichiers, l'exécution de "ls -1 | wc-l" affiche une grande sortie strace 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 significatif.

$ 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 getdents manuellement avec un tampon beaucoup plus volumineux. Cela améliore considérablement les performances.

Maintenant, vous n'êtes pas censé appeler getdents vous-même manuellement, il n'y a donc pas d'interface pour l'utiliser normalement (consultez la page de manuel relative à getdents pour voir!), Mais vous pouvez l' appeler manuellement et rendre votre appel d'appel système plus efficace.

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). Cela risque d'être beaucoup, beaucoup plus rapide que la plupart des alternatives affichées.

En prévision, il convient de supprimer le répertoire affecté et de le refaire ensuite. La taille des répertoires n’augmente jamais et peut rester médiocre, même avec quelques fichiers, à cause de la taille du répertoire.

Edit: j'ai nettoyé cela un peu. Ajout d'une option pour vous permettre de supprimer sur la ligne de commande au moment de l'exécution et suppression d'un tas de trucs de promenade dans les arbres qui, honnêtement, rétrospectivement était au mieux discutable. Aussi a été montré pour produire une corruption de mémoire.

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

Nouveaux résultats Basé sur un répertoire de 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

Était un peu surpris cela fonctionne toujours si bien!

Matthew Ife
la source
1
Deux préoccupations mineures: l'une, [256]probablement [FILENAME_MAX], et deux, mon Linux (2.6.18 == CentOS 5.x) ne semble pas inclure une entrée d_type dans dirent (au moins selon getdents (2)).
BMDan
1
Pourriez-vous nous en dire un peu plus sur le rééquilibrage de l'arborescence et sur les raisons pour lesquelles une suppression dans l'ordre contribue à l'empêcher? J'ai essayé Google pour cela, malheureusement sans succès.
Ovgolovin
1
Parce que maintenant il me semble que si nous supprimons en ordre, nous forçons le rééquilibrage, que l' on enlève les feuilles d'un côté et laisser l'autre: en.wikipedia.org/wiki/B-tree#Rebalancing_after_deletion
ovgolovin
1
J'espère que je ne vous dérange pas avec ça. Mais j'ai quand même commencé à poser une question sur la suppression de fichiers dans l'ordre stackoverflow.com/q/17955459/862380 , qui ne semble pas recevoir de réponse qui explique le problème avec l'exemple, ce qui sera compréhensible pour les programmeurs ordinaires. Si vous avez le temps et si vous en avez envie, pourriez-vous vous renseigner? Peut-être que vous pourriez écrire une meilleure explication.
Ovgolovin
2
C'est un morceau de code incroyable. C'était le seul outil que je pouvais trouver capable de lister et de supprimer quelque 11 000 000 (onze millions) de fichiers de session qui s'étaient accumulés dans un répertoire, probablement sur plusieurs années. Le processus Plesk qui était censé les garder sous contrôle à l'aide de find et d'autres astuces dans d'autres réponses ici, n'a pas été en mesure de terminer une analyse. Les fichiers ont donc continué à s'accumuler. Il s'agit d'un hommage à l'arborescence binaire utilisée par le système de fichiers pour stocker le répertoire. Les sessions ont pu fonctionner. Vous pouvez créer un fichier et le récupérer sans délai. Juste les annonces étaient inutilisables.
Jason
31

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

Jftuga
la source
3
J'aime vraiment cette réponse, en fait. Sur le plan pratique, dans ce cas, non, mais ce n’est pas celui auquel j’aurais pensé. Bravo!
BMDan
Exactement ce que je pensais aussi. Ceci est une réponse pour la question 3. Idéal si vous me demandez :)
Joshua
12

Il n'y a pas de limite de fichiers par répertoire dans ext3, mais uniquement la limite d'inode du système de fichiers (je pense cependant qu'il existe une limite quant au nombre de sous-répertoires).

Vous pouvez toujours avoir des problèmes après la suppression des fichiers.

Lorsqu'un répertoire contient des millions de fichiers, l'entrée de répertoire elle-même devient très grande. L'entrée du répertoire doit être analysée pour chaque opération de suppression, ce qui prend différentes durées pour chaque fichier, en fonction de l'emplacement de l'entrée. Malheureusement, même après la suppression de tous les fichiers, l’entrée du répertoire conserve sa taille. Par conséquent, les opérations supplémentaires nécessitant l'analyse de l'entrée du répertoire prendront encore beaucoup de temps, même si le répertoire est maintenant vide. Le seul moyen de résoudre ce problème consiste à renommer le répertoire, à en créer un nouveau avec l'ancien nom et à transférer tous les fichiers restants dans le nouveau. Supprimez ensuite celui qui a été renommé.

Alex J. Roberts
la source
En effet, j'ai juste remarqué ce comportement après avoir tout supprimé. Heureusement, nous avions déjà supprimé le répertoire de la "ligne de tir", pour que je puisse le supprimer.
BMDan
2
Cela dit, s'il n'y a pas de limite de fichiers par répertoire, pourquoi ai-je obtenu "ext3_dx_add_entry: index du répertoire plein!" quand il y avait encore des inodes disponibles sur cette partition? Il n'y avait pas de sous-répertoires dans ce répertoire.
BMDan
3
hmm, j'ai fait un peu plus de recherches et il semble qu'il y ait une limite de blocs qu'un répertoire peut prendre. Le nombre exact de fichiers dépend de quelques facteurs, par exemple la longueur du nom de fichier. Ce gossamer-threads.com/lists/linux/kernel/921942 semble indiquer qu'avec des blocs de 4k, vous devriez pouvoir avoir plus de 8 millions de fichiers dans un répertoire. Étaient-ils des noms de fichiers particulièrement longs?
Alex J. Roberts
Chaque nom de fichier comportait exactement 36 caractères.
BMDan
eh bien c'est moi à court d'idées :)
Alex J. Roberts
8

Je ne l'ai pas évalué, mais ce type l'a fait :

rsync -a --delete ./emptyDirectoty/ ./hugeDirectory/
Alix Axel
la source
4

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

<?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

Alexandre
la source
Cela m'a sauvé!
Jestro
3

J'ai récemment fait face à un problème similaire et j'étais incapable d'obtenir la data=writebacksuggestion de ring0 de fonctionner (probablement parce que les fichiers se trouvent sur ma partition principale). En cherchant des solutions de contournement, je suis tombé sur ceci:

tune2fs -O ^has_journal <device>

Cela désactive complètement la journalisation, quelle que soit l' dataoption donnée à mount. J'ai combiné cela avec noatimeet le volume était dir_indexréglé, et cela semblait bien fonctionner. La suppression s’est en fait terminée sans que je n’aie besoin de la supprimer, mon système est resté réactif et il est maintenant remis en service (avec journalisation réactivée) sans aucun problème.

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

Assurez-vous de le faire:

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

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

Karmawhore
la source
4
Bon appel, mais il 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 .
BMDan
1
ppl répéter ce mantra "noatime, nodiratime, nodevatime, noreadingdocsatime"
poige le
2

La commande est très lente. Essayer:

find /dir_to_delete ! -iname "*.png" -type f -delete
bindbn
la source
rm -rf a fonctionné pendant un jour et demi et je l'ai finalement tué, sans jamais savoir s'il avait réellement accompli quelque chose. J'avais besoin d'une barre de progression.
BMDan
4
Pour que rm soit très lent, "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 fondamentalement un territoire de marge d'erreur.
BMDan
1
Vous auriez peut-être simplement tenté strace -r -p <pid of rm>de vous connecter au processus rm en cours d'exécution. Ensuite, vous pouvez voir à quelle vitesse unlinkdéfilent les appels système. ( -rplace le temps écoulé depuis l'appel système précédent au début de chaque ligne.)
Peter Cordes
2

Est dir_indexdéfini pour le système de fichiers? ( tune2fs -l | grep dir_index) Sinon, activez-le. C'est habituellement pour le nouveau RHEL.

sam
la source
1
Oui, il est activé, mais suggestion géniale!
BMDan
2

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

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

Il s’agissait d’un vieux disque dur à 7 200 tr / min et, malgré le goulot d’étranglement IO et les pics de processeur, l’ancien serveur Web poursuivait son service.

a_foaley
la source
1

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

rm -rfdevrait être presque optimal 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 estimation, ne vérifiant pas la valeur de ext3), cela représente 40 * millions, à conserver dans la RAM uniquement pour le répertoire.

En un rien de temps, vous écrasez 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 soient supprimés pendant que vous utilisez encore une autre partie. le fichier est supprimé. Le réglage des performances Linux n’est pas de mon ressort, 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 fonctionnalité dir_index. Il bascule l'index de répertoire de linéaire à quelque chose de beaucoup plus optimal pour la suppression dans les grands répertoires (arbres binaires hachés). tune2fs -O dir_index ...suivi de e2fsck -Dtravaillerait. Cependant, bien que je sois confiant que cela aiderait avant qu'il y ait des problèmes, je ne sais pas comment la conversion (e2fsck avec le -D) se comporte lorsqu'il s'agit d'un répertoire v.large existant. Sauvegardes + sucer-it-and-see.

Phil P
la source
1
pubbs.net/201008/squid/… suggère que ce /proc/sys/fs/vfs_cache_pressurepourrait être la valeur à utiliser, mais je ne sais pas si le répertoire lui-même compte pour le cache de page (parce que c'est ce qu'il est) ou le cache d'inode (car, même s'il n'est pas un inode, ce sont des métadonnées FS et regroupées pour cette raison). Comme je l'ai dit, le réglage de la machine virtuelle Linux n'est pas mon domaine. Jouez et voyez ce qui aide.
Phil P
1

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

Création de 100 000 fichiers de 512 octets dans un répertoire ( ddet /dev/urandomdans une boucle); oublié de 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’un boîtier Pentium 4 à 2,8 GHz (quelques centaines de Go IDE 7200 tr / min je pense; EXT3). Noyau 2.6.27.

surface de gravité
la source
Intéressant, le fait que les fichiers aient été créés sur une longue période est peut-être pertinent. Mais cela ne devrait pas avoir d'importance; le cache de blocs doit avoir tous les blocs de métadonnées pertinents dans la RAM. C'est peut-être parce que unlink (2) est transactionnel? À votre avis, le fait de désactiver la journalisation pendant la durée de l'entreprise serait-il une solution potentielle (certes quelque peu dangereuse)? Il ne semble pas que vous puissiez simplement désactiver entièrement la journalisation sur un système de fichiers monté sans tune2fs / fsck / reboot, ce qui va quelque peu à l'encontre du but recherché.
BMDan
Je ne peux pas en parler, mais de façon anecdotique (dans diverses discussions sur NIX au fil des ans), j'ai toujours entendu dire que la rmlenteur est horriblement lente pour un grand nombre de fichiers, d'où l' find -deleteoption. Avec un caractère générique sur le shell, il développera chaque nom de fichier recherché, et je suppose qu'il existe une mémoire tampon limitée pour cela, vous pouvez donc voir à quel point cela pourrait devenir inefficace.
Gravyface
1
rm serait lent car il cherche un fichier par son nom, ce qui signifie parcourir les entrées de répertoire une à une jusqu'à ce qu'il le trouve. Dans ce cas, cependant, étant donné que chaque entrée à remettre est (à ce stade) la première de la liste (ls -U / ls -f), elle devrait être presque aussi rapide. Cela dit, rm -rf <dir>, qui aurait dû fonctionner comme un champion, était aussi lent que possible. Peut-être qu'il est temps d'écrire un correctif à coreutils pour accélérer les suppressions massives? Peut-être qu'il s'agit de classer / classer secrètement de manière récursive afin d'implémenter rm -rf? Des incertitudes comme celle-ci sont la raison pour laquelle j'ai posé la question. ;)
BMDan
1
Redémarrez l'ordinateur après avoir exécuté l'étape de création. Vous devriez obtenir une suppression sensiblement plus lente.
Matt
1

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, peut-être même plus rapide, de Perl:

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

MODIFIER: Je viens d'essayer mes scripts 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 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
Janne Pikkarainen
la source
J'hésite à imaginer ce que cet appel glob () ferait; Je suppose que cela fait un scandir (). Si c'est le cas, il vous faudra FOREVER pour revenir. Une modification de la première suggestion qui ne prélève pas toutes les entrées de dir peut avoir des jambes; Cependant, dans sa forme actuelle, il utiliserait également une quantité de CPU impure en ne lisant que toutes les entrées du répertoire à la fois. Une partie de l’objectif ici est de diviser pour régner; ce code n'est pas fondamentalement différent de 'rm -f * .png', malgré les problèmes liés à l'expansion du shell. Si cela aide, il n'y a rien dans le répertoire que je ne voulais pas supprimer.
BMDan
Je dois essayer plus dès que je vais au travail. Je viens d'essayer de créer 100 000 fichiers dans un seul répertoire et de trouver la combinaison + xargs + rm prenant 7,3 secondes, la combinaison Perl + unlink (glob) ... terminée en 2,7 secondes. Essayé que plusieurs fois, le résultat était toujours le même. Au travail, je vais essayer avec plus de fichiers.
Janne Pikkarainen le
J'ai appris quelque chose de nouveau en testant cela. Au moins avec ext3 et ext4, l’entrée du répertoire reste énorme même après la suppression de tous les fichiers de cet emplacement. Après quelques tests, mon répertoire / tmp / test prenait 15 Mo d’espace disque. Existe-t-il un autre moyen de nettoyer cela autrement que de supprimer le répertoire et de le recréer?
Janne Pikkarainen le
2
Non, vous devez le recréer. J'arrive à cela lorsque je traite un système de messagerie et d'un dossier par destinataire et que des nettoyages sont effectués après des problèmes importants: il n'y a pas d'autre moyen que de créer un nouveau répertoire et de mélanger les répertoires, puis de supprimer l'ancien. Vous pouvez donc réduire la fenêtre temporelle lorsqu'il n'y a pas de répertoire, mais ne pas l'éliminer.
Phil P
Notez que glob () triera les résultats, tout comme le fait généralement le déplacement du shell. Par conséquent, comme vous n’avez que 100 000 fichiers, tout s’intègre facilement et le tri est rapide. Avec un répertoire beaucoup plus grand, vous voudriez opendir () / readdir () / closedir () juste pour éviter le tri. [Je dis normalement pour shell, puisque zsh a un modificateur glob pour rendre l’ordre de tri non trié, ce qui est utile pour traiter un grand nombre de fichiers; *(oN)]
Phil P
1

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

Une fois, j’ai été confronté à un problème similaire (bien que mes estimations aient pris environ 7 heures), c’est finalement le chemin suggéré par jftuga dans le premier commentaire .

Hubert Kario
la source
0

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?

Marcoc
la source
Il semble que faire "en direct" nécessite un fsck sur un système de fichiers monté, ce qui est ... alarmant. Vous avez un meilleur moyen?
BMDan
Le système de fichiers doit être démonté avant la conversion, c'est-à-dire avant les commandes tunefs nécessaires.
marcoc le
0

D'accord, cela a été couvert de différentes manières dans le reste du fil, mais je pensais que je mettrais mes deux sous. Le coupable de performance 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 supprimez le lien. Les fichiers sont suffisamment petits pour que l'opération de dissociation ne saute probablement pas trop au zéro. Si vous readdir puis triez par inode croissant, vous obtiendrez probablement de meilleures performances. Donc readdir dans la RAM (trier par inode) -> unlink -> profit.

Inode est une approximation approximative ici, mais je pense .. mais sur la base de votre cas d'utilisation, il pourrait être assez précis ...

MattyB
la source
1
Corrigez-moi si je me trompe, mais unlink (2) ne met pas l'inode à zéro, il supprime simplement la référence à celui-ci du répertoire. J'aime bien le chutzpah de cette approche. Envie de faire des essais et de voir si cela est vrai?
BMDan
0

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 utilisezreaddir(3) pour obtenir le nom des fichiers, puis compilez les fichiers au fur et à mesure que je les dissocie et imprime de temps en temps "% d fichiers supprimés" (et éventuellement le temps écoulé ou l'horodatage en cours).

Je ne m'attends pas à ce qu'il soit sensiblement plus rapide que la version du script shell, c'est simplement que je suis habitué à extraire le compilateur de temps en temps, soit parce qu'il n'y a pas de moyen propre de faire ce que je veux dans le shell ou parce que bien que réalisable en shell, il est lent de façon improductive.

Vatine
la source
Il pourrait au moins commencer par modifier le code source de rm de coreutils .
Cristian Ciupitu
0

Vous êtes probablement confronté à des problèmes de réécriture du répertoire. Essayez de supprimer les fichiers les plus récents en premier. Examinez les options de montage qui différeront l'écriture sur le disque.

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

BillThor
la source
En termes de suppression des fichiers les plus récents en premier: ls -Ur? Je suis à peu près sûr que cela chargerait les entrées dir, puis les inverserait; Je ne crois pas que ls soit assez intelligent pour commencer à la fin de la liste des entrées de dir et revenir en arrière au début. "ls -1" n’est probablement pas une bonne idée, car il faudrait probablement plus de 50 Mo de base et plusieurs minutes de fonctionnement; vous voudriez "ls -U" ou "ls -f".
BMDan
Cela n’est probablement pratique que si les noms de fichiers augmentent selon un modèle prévisible. Quoi qu'il en soit, vous pouvez essayer d'inverser ls-1, puis sur xargs. Utilisez des fichiers au lieu de tubes si vous voulez voir vos résultats intermédiaires. Vous n'avez fourni aucune information sur le nom du fichier. Vous générez le motif en sens inverse et supprimez les fichiers en utilisant le modèle. Vous devrez peut-être gérer les entrées de fichier manquantes. Compte tenu de votre commentaire sur la mémoire requise, vous avez une idée du nombre d’E / S nécessaires pour réécrire le répertoire.
BillThor
0

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 constate que cela entraîne une suppression assez lente qui a un impact faible sur les performances du serveur, généralement une heure par million de fichiers sur une configuration "typique" de 10 000 IOPS.

Plusieurs minutes sont nécessaires avant l'analyse des répertoires, la génération de la liste de fichiers initiale et la suppression du premier fichier. À partir de là, a. est répété pour chaque fichier supprimé.

Le retard causé par l'écho au terminal s'est avéré suffisamment retard pour empêcher toute charge significative pendant la suppression.

Roy
la source
Vous êtes mangé vif en bougeant. Que diriez-vous de quelque chose de plus semblable à 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':? Ajoutez -deleteau dernier pour supprimer réellement des choses; tel qu'il est écrit, il ne fait que répertorier ce qu'il supprimerait. Notez que ceci est optimisé pour des situations où vous avez beaucoup de choses sans intérêt dans les annuaires voisins; si ce n'est pas le cas, vous pouvez grandement simplifier la logique.
BMDan
find -delete a tendance à causer trop d'E / S et a facilement un impact sur les performances de production. Peut-être avec ionice.
Roy
Cela cause tout ce que je / O simplement en étant plus efficace! Pour votre exemple, le globbing est entièrement chargé au premier plan (c’est-à-dire que la liste complète des fichiers est générée avant que le premier ne rmse produise), ce qui vous permet d’avoir une entrée / sortie relativement efficace au démarrage, suivie de s douloureuses et hors d’ordre rm. cela ne cause probablement pas beaucoup d'E / S, mais implique de scandirparcourir le répertoire à plusieurs reprises (sans provoquer d'E / S car il a déjà été chargé dans le cache de blocs; voir aussi vfs_cache_pressure). Si vous souhaitez ralentir les choses, ionicec'est une option, mais j'utiliserais probablement une fraction de seconde sleep.
BMDan
find /u*/app/*/diag -path '*/trace/*.tr' -execdir rm {} +exécuterait un rmpar répertoire, de sorte que vous auriez moins de surcharge du processeur. Tant que vous disposez de beaucoup de temps processeur, réglez les entrées / sorties disque en forçant tout un rmprocessus pour chaque unlinktravail, je suppose, mais c'est moche. perl avec un sommeil par liaison serait plus agréable si dormir entre rmdes annuaires entiers à la fois est trop volumineux. ( -execdir sh -c ...peut - être)
Peter Cordes
-1

Vous pouvez utiliser les fonctionnalités de parallélisation 'xargs':

ls -1|xargs -P nb_concurrent_jobs -n nb_files_by_job rm -rf
Jeremy
la source
1
Cela ne va pas aider. Le goulot d'étranglement est la mauvaise E / S aléatoire sur le lecteur. Faire des suppressions parallèles pourrait aggraver la situation et augmenter simplement la charge du processeur.
Wim Kerkhoff
-2
ls|cut -c -4|sort|uniq|awk '{ print "rm -rf " $1 }' | sh -x
Karmawhore
la source
1
Sensationnel. Je suppose que cela tombe assez fermement dans le camp "plus qu’un moyen pour écorcher un chat". Sérieusement, avec le genre et l'uniq? Quoi qu'il en soit, "ls" est trié par défaut, et j'espère que les noms de fichiers sont uniques. : /
BMDan
-2

en fait, celui-ci est un peu meilleur si le shell que vous utilisez ne développe pas en ligne de commande:

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

la source