rm in einem Verzeichnis mit Millionen von Dateien


104

Hintergrund: Physischer Server, ungefähr zwei Jahre alt, SATA-Laufwerke mit 7200 U / min, die an eine 3Ware-RAID-Karte angeschlossen sind, ext3 FS-Mounted Noatime und Daten = bestellt, nicht unter verrückter Last, Kernel 2.6.18-92.1.22.el5, Betriebszeit 545 Tage . Directory enthält keine Unterverzeichnisse, nur Millionen kleiner (~ 100 Byte) Dateien, einige größere (ein paar KB).

Wir haben einen Server, der in den letzten Monaten ein bisschen kuckuck gegangen ist, aber wir haben ihn erst neulich bemerkt, als er anfing, nicht in ein Verzeichnis schreiben zu können, weil er zu viele Dateien enthielt. Insbesondere hat es begonnen, diesen Fehler in / var / log / messages auszulösen:

ext3_dx_add_entry: Directory index full!

Auf der fraglichen Festplatte sind noch viele Inodes vorhanden:

Filesystem            Inodes   IUsed   IFree IUse% Mounted on
/dev/sda3            60719104 3465660 57253444    6% /

Ich schätze, das heißt, wir haben das Limit erreicht, wie viele Einträge in der Verzeichnisdatei selbst sein können. Keine Ahnung, wie viele Dateien das sein würden, aber wie Sie sehen können, können es nicht mehr als drei Millionen sein. Nicht dass das gut wäre, wohlgemerkt! Aber das ist Teil einer meiner Fragen: Was genau ist diese Obergrenze? Ist es einstellbar? Bevor ich angeschrien werde - ich möchte es leiser stellen ; Dieses riesige Verzeichnis verursachte alle möglichen Probleme.

Wie auch immer, wir haben das Problem in dem Code aufgespürt, der all diese Dateien generiert hat, und wir haben es korrigiert. Jetzt stecke ich beim Löschen des Verzeichnisses fest.

Ein paar Möglichkeiten hier:

  1. rm -rf (dir)

    Ich habe es zuerst versucht. Ich gab es auf und tötete es, nachdem es anderthalb Tage ohne erkennbare Auswirkungen gelaufen war.

  2. Unlink (2) im Verzeichnis: Auf jeden Fall eine Überlegung wert, aber die Frage ist, ob es schneller wäre, die Dateien im Verzeichnis über fsck zu löschen, als über Unlink (2). Das heißt, auf die eine oder andere Weise muss ich diese Inodes als unbenutzt markieren. Dies setzt natürlich voraus, dass ich fsck anweisen kann, keine Einträge in den Dateien in / lost + found abzulegen. ansonsten habe ich gerade mein problem verschoben. Neben all den anderen Bedenken muss ich wahrscheinlich einige interne FS-Funktionen aufrufen, nachdem ich etwas mehr darüber gelesen habe, da keine der unlink (2) -Varianten, die ich finden kann, es mir erlauben würde, nur munter zu löschen ein Verzeichnis mit Einträgen darin. Pooh.
  3. while [ true ]; do ls -Uf | head -n 10000 | xargs rm -f 2>/dev/null; done )

    Dies ist eigentlich die gekürzte Version; Die eigentliche Version, die ich gerade ausführe und die nur einige Fortschrittsberichte und einen sauberen Stopp hinzufügt, wenn die zu löschenden Dateien ausgehen, ist:

    export i = 0;
    time (while [wahr]; do
      ls -Uf | head -n 3 | grep -qF '.png' || brechen;
      ls -Uf | head -n 10000 | xargs rm -f 2> / dev / null;
      exportiere i = $ (($ i + 10000));
      echo "$ i ...";
    getan )

    Dies scheint ziemlich gut zu funktionieren. Während ich das schreibe, hat es in den letzten 30 Minuten ungefähr 260.000 Dateien gelöscht.

Nun zu den Fragen:
  1. Ist, wie oben erwähnt, das Limit für Verzeichniseinträge einstellbar?
  2. Warum hat es "echte 7m9.561s / Benutzer 0m0.001s / sys 0m0.001s" gedauert, um eine einzelne Datei zu löschen, die die erste in der Liste war, die von zurückgegeben wurde ls -U, und es hat vielleicht zehn Minuten gedauert , um die ersten 10.000 Einträge mit der zu löschen? Kommando in # 3, aber jetzt schleppt es sich ziemlich glücklich voran? Im Übrigen wurden 260.000 in ungefähr 30 Minuten gelöscht, aber es dauerte jetzt weitere 15 Minuten, um weitere 60.000 zu löschen. Warum die großen Geschwindigkeitsschwankungen?
  3. Gibt es einen besseren Weg, um so etwas zu tun? Speichern Sie nicht Millionen von Dateien in einem Verzeichnis. Ich weiß, dass das albern ist, und es wäre auf meiner Uhr nicht passiert. Das Problem zu googeln und durch SF und SO zu schauen, bietet eine Menge Variationen find, die aus mehreren offensichtlichen Gründen nicht wesentlich schneller sein werden als mein Ansatz. Aber hat die Delete-via-Fsck-Idee irgendwelche Beine? Oder etwas ganz anderes? Ich bin gespannt darauf, über den Tellerrand (oder innerhalb der nicht bekannten Box) zu denken.
Vielen Dank, dass Sie den kleinen Roman gelesen haben. Fühlen Sie sich frei, Fragen zu stellen und ich werde sicher antworten. Ich werde auch die Frage mit der endgültigen Anzahl von Dateien aktualisieren und wie lange das Löschskript ausgeführt wurde, wenn ich das habe.

Letzte Skriptausgabe !:

2970000...
2980000...
2990000...
3000000...
3010000...

real    253m59.331s
user    0m6.061s
sys     5m4.019s

Also drei Millionen Dateien in etwas mehr als vier Stunden gelöscht.


1
rm (GNU coreutils) 8.4 hat diese Option: "-v, --verbose erkläre was gemacht wird" . Es werden alle Dateien angezeigt, die gelöscht werden.
Cristian Ciupitu

2
Eigentlich wäre das eine gute Möglichkeit, einen Fortschrittsbalken zu erstellen: Da jede Datei siebenunddreißig Zeichen lang ist (36 + ein '\ n'), könnte ich leicht einen Parser dafür schreiben, und da printf () ist billig und der Befehl rm hat bereits den Namen der Datei geladen, es gibt keine besonderen Leistungseinbußen. Scheint ein Nichtstarter zu sein, wenn man den ganzen Shebang macht, da ich sowieso niemals "rm" dazu bringen könnte, so etwas zu machen. Aber es könnte durchaus als Fortschrittsbalken innerhalb von 10.000 funktionieren. vielleicht ein "." für alle hundert Dateien?
BMDan

8
rm -rfv | pv -l >/dev/null. pv sollte im EPEL- Repository verfügbar sein .
Cristian Ciupitu

5
pv ist überwältigend toll. Ich hinterlasse eine Spur von PV-Installationen.
BMDan

Ich hatte vor kurzem genau das gleiche Problem. Dankeschön!
Richo

Antworten:


30

Die data=writebackMount-Option sollte ausprobiert werden, um ein Journaling des Dateisystems zu verhindern. Dies sollte nur während der Löschzeit erfolgen. Es besteht jedoch das Risiko, dass der Server während des Löschvorgangs heruntergefahren oder neu gestartet wird.

Nach dieser Seite ,

Einige Anwendungen zeigen eine sehr deutliche Geschwindigkeitsverbesserung, wenn sie verwendet werden. Beispielsweise können Geschwindigkeitsverbesserungen festgestellt werden (...), wenn Anwendungen große Mengen kleiner Dateien erstellen und löschen.

Die Option wird entweder in fstaboder während des Montagebetriebes, ersetzt data=orderedmit data=writeback. Das Dateisystem mit den zu löschenden Dateien muss neu gemountet werden.


1
Er könnte auch die Zeit von der commit Option verlängern : "Dieser Standardwert (oder ein beliebiger niedriger Wert) beeinträchtigt die Leistung, ist aber gut für die Datensicherheit. Das Setzen auf 0 hat den gleichen Effekt wie das Belassen des Standardwerts (5 Sekunden) ). Wenn Sie einen sehr großen Wert einstellen, wird die Leistung verbessert ".
Cristian Ciupitu

1
Rückschreiben sieht hervorragend aus, außer in der Dokumentation , die ich mir angesehen habe ( gentoo.org/doc/en/articles/l-afig-p8.xml#doc_chap4 ), wird explizit erwähnt, dass es immer noch Metadaten von Journalen enthält, von denen ich annehme , dass sie alle Daten enthalten, die ich bin Ändern (Ich ändere bestimmt keine Daten in den Dateien selbst). Ist mein Verständnis der Option falsch?
BMDan

Zu guter Letzt wird in diesem Link nicht erwähnt, dass data = writeback eine große Sicherheitslücke darstellen kann, da Daten, auf die durch einen bestimmten Eintrag verwiesen wird, möglicherweise nicht die Daten enthalten, die von der App dort geschrieben wurden, was zu einem Absturz führen kann in den alten, möglicherweise sensiblen / privaten Daten ausgesetzt. Keine Sorge, da wir es nur vorübergehend einschalten, aber ich wollte alle auf diese Einschränkung hinweisen, falls Sie oder andere, die auf diesen Vorschlag stoßen, es nicht merken.
BMDan

begehen: das ist ziemlich schlau! Danke für den Hinweis.
BMDan

2
data=writebackDie Metadaten der Journale bleiben erhalten, bevor sie in das Hauptdateisystem geschrieben werden. So wie ich es verstehe, erzwingt es einfach keine Reihenfolge zwischen Dingen wie dem Schreiben einer Extent-Map und dem Schreiben von Daten in diese Extents. Vielleicht gibt es noch andere Ordnungsbeschränkungen, die es auch lockert, wenn Sie einen perfekten Gewinn daraus ziehen. Selbstverständlich könnte die Montage ohne den Zapfen noch leistungsfähiger sein. (Es kann vorkommen, dass die Metadatenänderungen nur im RAM vorgenommen werden, ohne dass vor Abschluss des Unlink-Vorgangs etwas auf der Festplatte gespeichert werden muss.)
Peter Cordes

80

Während eine Hauptursache für dieses Problem die Leistung von ext3 mit Millionen von Dateien ist, ist die eigentliche Hauptursache für dieses Problem anders.

Wenn ein Verzeichnis aufgelistet werden muss, wird readdir () für das Verzeichnis aufgerufen, das eine Liste von Dateien liefert. readdir ist ein posix-Aufruf, aber der echte Linux-Systemaufruf, der hier verwendet wird, heißt 'getdents'. Getdents listet Verzeichniseinträge auf, indem ein Puffer mit Einträgen gefüllt wird.

Das Problem ist hauptsächlich darauf zurückzuführen, dass readdir () eine feste Puffergröße von 32 KB verwendet, um Dateien abzurufen. Wenn ein Verzeichnis größer und größer wird (die Größe nimmt zu, wenn Dateien hinzugefügt werden), wird ext3 langsamer und langsamer, um Einträge abzurufen, und die zusätzliche 32-KB-Puffergröße von readdir reicht nur aus, um einen Bruchteil der Einträge in das Verzeichnis aufzunehmen. Dies führt dazu, dass readdir immer wieder eine Schleife durchläuft und den teuren Systemaufruf immer wieder aufruft.

Zum Beispiel zeigt "ls -1 | wc-l" in einem Testverzeichnis, das ich mit mehr als 2,6 Millionen Dateien erstellt habe, eine große Ausgabe vieler getdent-Systemaufrufe.

$ 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
...

Außerdem war die in diesem Verzeichnis verbrachte Zeit erheblich.

$ time ls -1 | wc -l
2616044

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

Die Methode, um dies effizienter zu gestalten, besteht darin, getdents manuell mit einem viel größeren Puffer aufzurufen. Dies verbessert die Leistung erheblich.

Jetzt sollten Sie getdents nicht selbst manuell aufrufen, daher gibt es keine Schnittstelle für die normale Verwendung (sehen Sie in der Manpage nach, ob getdents angezeigt werden!). Sie können es jedoch manuell aufrufen und den Systemaufruf effizienter gestalten.

Dadurch wird die Zeit zum Abrufen dieser Dateien drastisch reduziert. Ich habe ein Programm geschrieben, das dies tut.

/* 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);
}

Dies bekämpft zwar nicht das zugrunde liegende Grundproblem (viele Dateien in einem Dateisystem, das eine schlechte Leistung erbringt). Es ist wahrscheinlich viel, viel schneller als viele der Alternativen, die veröffentlicht werden.

Aus Vorsichtsgründen sollte man das betroffene Verzeichnis entfernen und es anschließend neu erstellen. Verzeichnisse nehmen immer nur an Größe zu und können aufgrund der Größe des Verzeichnisses auch mit wenigen darin enthaltenen Dateien eine schlechte Leistung aufweisen.

Edit: Ich habe das ziemlich aufgeräumt. Es wurde eine Option hinzugefügt, die es Ihnen ermöglicht, zur Laufzeit auf der Befehlszeile zu löschen, und ein paar Baumstamm-Elemente entfernt, die, ehrlich gesagt, im besten Fall fragwürdig waren. Auch wurde gezeigt, Speicherbeschädigung zu produzieren.

Sie können jetzt tun dentls --delete /my/path

Neue Ergebnisse. Basierend auf einem Verzeichnis mit 1,82 Millionen Dateien.

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

War irgendwie überrascht, dass das immer noch so gut funktioniert!


1
Zwei kleinere Bedenken: Eins [256]sollte wahrscheinlich sein [FILENAME_MAX], und zwei, mein Linux (2.6.18 == CentOS 5.x) scheint keinen d_type-Eintrag in dirent zu enthalten (zumindest laut getdents (2)).
BMDan

1
Könnten Sie bitte ein wenig über das Neuausbalancieren von Btrees erläutern und warum hilft das Löschen, um dies zu verhindern? Ich habe es mit Googeln versucht, leider ohne Erfolg.
Ovgolovin

1
Denn nun scheint es mir , wenn wir in der richtigen Reihenfolge löschen, zwingen wir Rebalancing, wie wir Blätter auf der einen Seite und lassen auf der anderen Seite zu entfernen: en.wikipedia.org/wiki/B-tree#Rebalancing_after_deletion
ovgolovin

1
Ich hoffe, ich störe Sie nicht mit dieser Angelegenheit. Trotzdem begann ich eine Frage zum Löschen von Dateien in der angegebenen Reihenfolge ( stackoverflow.com/q/17955459/862380) , die anscheinend keine Antwort enthält, die das Problem mit dem Beispiel erklärt, das für normale Programmierer verständlich ist. Wenn Sie Zeit haben und Lust dazu haben, können Sie es sich ansehen? Vielleicht könntest du eine bessere Erklärung schreiben.
Ovgolovin

2
Dies ist ein erstaunliches Stück Code. Es war das einzige Tool, das ich finden konnte, um 11.000.000 (elf Millionen) Sitzungsdateien aufzulisten und zu löschen, die sich wahrscheinlich über einige Jahre in einem Verzeichnis angesammelt hatten. Der Plesk-Prozess, mit dem sie mithilfe von Suchen und anderen Tricks in anderen Antworten unter Kontrolle gehalten werden sollten, konnte einen Durchlauf nicht abschließen, sodass die Dateien weiterhin aufgebaut wurden. Es ist eine Hommage an den Binärbaum, den das Dateisystem zum Speichern des Verzeichnisses verwendet und mit dem die Sitzungen überhaupt arbeiten konnten. Sie konnten eine Datei erstellen und diese ohne Verzögerung abrufen. Nur Inserate waren unbrauchbar.
Jason

31

Ist es möglich, alle anderen Dateien aus diesem Dateisystem in einem temporären Speicherort zu sichern, die Partition neu zu formatieren und die Dateien dann wiederherzustellen?


3
Diese Antwort gefällt mir wirklich sehr gut. In diesem Fall praktisch gesehen nein, aber ich hätte nicht daran gedacht. Bravo!
BMDan

Genau das habe ich auch gedacht. Dies ist eine Antwort auf Frage 3. Ideal, wenn Sie mich fragen :)
Joshua

12

In ext3 gibt es keine Beschränkung pro Verzeichnisdatei, nur die Beschränkung des Dateisystem-Inodes (ich glaube, es gibt eine Beschränkung für die Anzahl der Unterverzeichnisse).

Möglicherweise haben Sie nach dem Entfernen der Dateien weiterhin Probleme.

Wenn ein Verzeichnis Millionen von Dateien enthält, wird der Verzeichniseintrag selbst sehr groß. Der Verzeichniseintrag muss nach jedem Entfernungsvorgang durchsucht werden. Dies dauert für jede Datei unterschiedlich lange, je nachdem, wo sich ihr Eintrag befindet. Leider behält der Verzeichniseintrag auch nach dem Entfernen aller Dateien seine Größe. Weitere Vorgänge, die das Durchsuchen des Verzeichniseintrags erfordern, dauern daher auch dann noch lange, wenn das Verzeichnis jetzt leer ist. Die einzige Möglichkeit, dieses Problem zu lösen, besteht darin, das Verzeichnis umzubenennen, ein neues mit dem alten Namen zu erstellen und alle verbleibenden Dateien auf das neue zu übertragen. Dann löschen Sie die umbenannte.


In der Tat bemerkte ich genau dieses Verhalten, nachdem ich alles gelöscht hatte. Zum Glück hatten wir das Verzeichnis schon sozusagen aus der "Schusslinie" entfernt, so dass ich es einfach rmdiren konnte.
BMDan

2
Das heißt, wenn es keine Beschränkung für Verzeichnisdateien gibt, warum habe ich dann "ext3_dx_add_entry: Verzeichnisindex voll!" Wann waren auf dieser Partition noch Inodes verfügbar? In diesem Verzeichnis befanden sich keine Unterverzeichnisse.
BMDan

3
hmm ich habe ein bisschen mehr recherchiert und es scheint, dass es eine Begrenzung der Anzahl von Blöcken gibt, die ein Verzeichnis aufnehmen kann. Die genaue Anzahl der Dateien hängt von einigen Faktoren ab, z. B. der Länge des Dateinamens. Diese gossamer-threads.com/lists/linux/kernel/921942 scheint darauf hinzudeuten, dass Sie mit 4k-Blöcken mehr als 8 Millionen Dateien in einem Verzeichnis haben sollten. Waren das besonders lange Dateinamen?
Alex J. Roberts

Jeder Dateiname war genau 36 Zeichen lang.
BMDan

Nun, das bin ich aus Ideen :)
Alex J. Roberts


4

find hat bei mir einfach nicht funktioniert, auch nach dem ändern der ext3 fs parameter wie von den benutzern oben vorgeschlagen. Verbraucht viel zu viel Speicher. Dieses PHP-Skript hat den Trick getan - schnelle, unbedeutende CPU-Auslastung, unbedeutende Speicherauslastung:

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

Ich habe einen Fehlerbericht zu diesem Problem mit find gepostet: http://savannah.gnu.org/bugs/?31961


Das hat mich gerettet !!
Jestro

3

Ich hatte kürzlich ein ähnliches Problem und konnte den data=writebackVorschlag von ring0 nicht zum Laufen bringen (möglicherweise aufgrund der Tatsache, dass sich die Dateien auf meiner Hauptpartition befinden). Bei der Suche nach Problemumgehungen bin ich auf Folgendes gestoßen:

tune2fs -O ^has_journal <device>

Dadurch wird die Aufzeichnung vollständig deaktiviert, unabhängig von der dataOption, die Sie aktivieren mount. Ich kombinierte dies mit noatimeund die Lautstärke war dir_indexeingestellt, und es schien ziemlich gut zu funktionieren. Der Löschvorgang wurde tatsächlich abgeschlossen, ohne dass ich ihn beenden musste. Mein System reagierte weiterhin und ist jetzt ohne Probleme wieder einsatzbereit (mit aktivierter Journalfunktion).


Ich wollte vorschlagen, es als ext2 anstelle von ext3 zu mounten, um ein Journaling der Metadatenoperationen zu vermeiden. Dies sollte das gleiche tun.
Peter Cordes

3

Stellen Sie sicher, dass Sie:

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

das sollte die Sache auch ein bisschen beschleunigen.


4
Guter Anruf, aber es ist schon noatime gemountet, wie ich im Header zur Frage erwähnte. Und nodiratime ist überflüssig; siehe lwn.net/Articles/245002 .
BMDan

1
ppl wiederhole dieses Mantra "noatime, nodiratime, nodevatime, noreadingdocsatime"
poige

2

Es ist ein sehr langsamer Befehl. Versuchen:

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

rm -rf rannte anderthalb Tage und ich tötete es schließlich, ohne jemals zu wissen, ob es tatsächlich etwas bewirkt hatte. Ich brauchte eine Fortschrittsanzeige.
BMDan

4
Um sehr langsam zu sein, "time find. -Delete" für 30k-Dateien: 0m0.357s / 0m0.019s / 0m0.337s real / user / sys. "time (ls -1U | xargs rm -f)" für dieselben Dateien: 0m0.366s / 0m0.025s / 0m0.340s. Welches ist im Grunde Marge-of-Error-Gebiet.
BMDan

1
Sie könnten einfach ausgeführt werden strace -r -p <pid of rm>, um eine Verbindung zum bereits ausgeführten rm-Prozess herzustellen. Dann können Sie sehen, wie schnell unlinkSystemaufrufe vorbeirollen. ( -rStellt die Zeit seit dem vorherigen Systemaufruf am Anfang jeder Zeile ein.)
Peter Cordes

2

Ist dir_indexfür das Dateisystem eingestellt? ( tune2fs -l | grep dir_index) Wenn nicht, aktivieren Sie es. Normalerweise ist es für neue RHEL aktiviert.


1
Ja, es ist aktiviert, aber ein toller Vorschlag!
BMDan

2

Vor ein paar Jahren habe ich ein Verzeichnis mit 16 Millionen XML- Dateien im /Dateisystem gefunden. Aufgrund der Kritik des Servers haben wir den folgenden Befehl verwendet, der ungefähr 30 Stunden dauerte :

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

Es war eine alte Festplatte mit 7200 U / min , und trotz des E / A-Engpasses und der CPU-Spitzen setzte der alte Webserver seinen Dienst fort.


1

Meine bevorzugte Option ist der bereits vorgeschlagene newfs-Ansatz. Das Grundproblem ist wiederum, wie bereits erwähnt, die lineare Abtastung, um das Löschen zu handhaben, problematisch.

rm -rfsollte für ein lokales Dateisystem nahezu optimal sein (NFS wäre anders). Aber bei Millionen von Dateien, 36 Bytes pro Dateiname und 4 Bytes pro Inode (eine Schätzung, die den Wert für ext3 nicht überprüft), sind das 40 * Millionen, die nur für das Verzeichnis im RAM gespeichert werden müssen.

Vermutlich zerschlagen Sie den Metadaten-Cache des Dateisystems in Linux, sodass Blöcke für eine Seite der Verzeichnisdatei gelöscht werden, während Sie noch einen anderen Teil verwenden, um diese Seite des Caches beim nächsten Mal erneut aufzurufen Datei wird gelöscht. Linux Performance Tuning ist nicht mein Bereich, aber / proc / sys / {vm, fs} / enthält wahrscheinlich etwas Relevantes.

Wenn Sie sich Ausfallzeiten leisten können, können Sie die Funktion dir_index aktivieren. Es schaltet den Verzeichnisindex von linear auf etwas weitaus Optimaleres zum Löschen in großen Verzeichnissen (gehashte B-Bäume). tune2fs -O dir_index ...gefolgt von e2fsck -Dwürde funktionieren. Obwohl ich zuversichtlich bin, dass dies helfen würde, bevor es Probleme gibt, weiß ich nicht, wie die Konvertierung (e2fsck mit dem -D) ausgeführt wird, wenn ein vorhandenes v.large-Verzeichnis verwendet wird. Backups + Wahnsinn.


1
pubbs.net/201008/squid/… schlägt vor, dass /proc/sys/fs/vfs_cache_pressuredies der zu verwendende Wert sein könnte, aber ich weiß nicht, ob das Verzeichnis selbst für den Seiten-Cache (weil es so ist) oder für den Inode-Cache (weil es kein Inode ist) gilt Inode sind es FS-Metadaten, die aus diesem Grund dort gebündelt sind. Wie gesagt, Linux VM Tuning ist nicht mein Bereich. Spielen und sehen, was hilft.
Phil P

1

Natürlich keine Äpfel zu Äpfeln hier, aber ich habe einen kleinen Test gemacht und folgendes gemacht:

Erstellt 100.000 512-Byte-Dateien in einem Verzeichnis ( ddund /dev/urandomin einer Schleife). Ich habe die Zeit vergessen, aber es dauerte ungefähr 15 Minuten, um diese Dateien zu erstellen.

Führen Sie die folgenden Schritte aus, um diese Dateien zu löschen:

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

100000

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

Dies ist eine Pentium 4-Box mit 2,8 GHz (einige hundert GB IDE mit 7200 U / min, glaube ich; EXT3). Kernel 2.6.27.


Interessant, vielleicht ist die Tatsache, dass die Dateien über einen langen Zeitraum erstellt wurden, relevant? Aber das sollte keine Rolle spielen. Der Blockcache sollte alle relevanten Metadatenblöcke im RAM enthalten. Vielleicht liegt es daran, dass unlink (2) transaktionell ist? Wäre es Ihrer Einschätzung nach eine potenzielle (wenn auch etwas gefährliche) Lösung, das Journaling für die Dauer der Laufzeit auszuschalten? Es sieht nicht so aus, als ob Sie das Journaling auf einem gemounteten Dateisystem ohne ein tune2fs / fsck / reboot vollständig ausschalten können, was den Zweck etwas zunichte macht.
BMDan

Ich kann das nicht kommentieren, aber anekdotisch (in verschiedenen NIX-Diskussionen im Laufe der Jahre) habe ich immer gehört, dass rmdas bei einer großen Anzahl von Dateien schrecklich langsam ist, daher die find -deleteOption. Mit einem Platzhalter in der Shell wird jeder übereinstimmende Dateiname erweitert, und ich gehe davon aus, dass es dafür einen begrenzten Speicherpuffer gibt, sodass Sie sehen können, wie ineffizient das werden würde.
Gravyface

1
rm wäre langsam, da nach einer Datei mit Namen gesucht wird. Dies bedeutet, dass die Verzeichniseinträge einzeln durchlaufen werden, bis sie gefunden werden. Da in diesem Fall jedoch jeder Eintrag, der übergeben wird, (zu diesem Zeitpunkt) der erste in der Liste ist (ls -U / ls -f), sollte er fast genauso schnell sein. Das heißt, rm -rf <dir>, das wie ein Champion hätte laufen sollen, war so langsam wie möglich. Vielleicht ist es an der Zeit, einen Patch für coreutils zu schreiben, um massive Löschvorgänge zu beschleunigen? Vielleicht ist es geheimes Globbing / Sortieren auf eine rekursive Art und Weise, um rm -rf zu implementieren? Unsicherheiten wie diese sind der Grund, warum ich die Frage gestellt habe. ;)
BMDan

1
Starten Sie den Computer neu, nachdem Sie den Erstellungsschritt ausgeführt haben. Sie sollten einen merklich langsameren Löschvorgang erhalten.
Matt

1

Manchmal kann Perl in solchen Fällen Wunder wirken. Haben Sie bereits versucht, ob ein kleines Skript wie dieses Bash und die grundlegenden Shell-Befehle übertreffen könnte?

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

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

Oder ein anderer, vielleicht sogar noch schnellerer Perl-Ansatz:

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

EDIT: Ich habe gerade meine Perl-Skripte ausprobiert. Je ausführlicher man etwas richtig macht. In meinem Fall habe ich dies mit einem virtuellen Server mit 256 MB RAM und einer halben Million Dateien versucht.

time find /test/directory | xargs rm Ergebnisse:

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

verglichen mit

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

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

Ich zögere, mir vorzustellen, was der Aufruf von glob () bewirken würde. Ich nehme an, es macht einen Skandir (). Wenn ja, wird es für immer dauern, bis wir zurückkehren. Eine Änderung des ersten Vorschlags, bei der nicht alle Verzeichniseinträge vorgelesen wurden, kann einige Abschnitte enthalten. In seiner jetzigen Form würde es jedoch auch eine unheilige Menge an CPU verbrauchen, wenn nur alle Verzeichniseinträge auf einmal gelesen würden. Ein Teil des Ziels besteht darin, zu teilen und zu erobern. Dieser Code unterscheidet sich trotz Problemen mit der Shell-Erweiterung nicht grundlegend von 'rm -f * .png'. Wenn es hilft, gibt es nichts in dem Verzeichnis, das ich nicht löschen wollte.
BMDan,

Ich muss mehr versuchen, sobald ich zur Arbeit komme. Ich habe gerade versucht, 100 000 Dateien in einem einzigen Verzeichnis zu erstellen. Die Suche nach + xargs + rm dauerte 7,3 Sekunden. Perl + unlink (glob) ... Kombination war in 2,7 Sekunden fertig. Ich habe es ein paar Mal versucht, aber das Ergebnis war immer dasselbe. Bei der Arbeit werde ich das mit mehr Dateien versuchen.
Janne Pikkarainen

Beim Testen habe ich etwas Neues gelernt. Zumindest mit ext3 und ext4 bleibt der Verzeichniseintrag selbst nach dem Löschen aller Dateien von dort riesig. Nach einigen Tests beanspruchte mein Verzeichnis / tmp / test 15 MB Festplattenspeicher. Gibt es eine andere Möglichkeit, das zu bereinigen, als das Verzeichnis zu entfernen und neu zu erstellen?
Janne Pikkarainen

2
Nein, Sie müssen es neu erstellen. Ich traf dies, wenn ich mich mit einem Mail-System und Ordner pro Empfänger befasste und nach bedeutenden Problemen aufräumte: Es gibt keine andere Möglichkeit, als ein neues Verzeichnis zu erstellen und die Verzeichnisse zu mischen und dann das alte zu löschen. So können Sie das Zeitfenster verringern, wenn kein Verzeichnis vorhanden ist, aber nicht entfernen.
Phil P

Beachten Sie, dass glob () die Ergebnisse ähnlich wie bei Shell-Globbing sortiert. Da Sie also nur 100.000 Dateien haben, passt alles problemlos und die Sortierung ist schnell. Bei einem viel größeren Verzeichnis sollten Sie opendir () / readdir () / closedir () verwenden, um die Sortierung zu vermeiden. [Ich sage normalerweise für Shell, da zsh einen Glob-Modifikator hat, der die Sortierreihenfolge unsortiert, was nützlich ist, wenn mit einer großen Anzahl von Dateien gearbeitet wird. *(oN)]
Phil P

1

Soweit ich mich erinnere, ist das Löschen von Inodes in ext-Dateisystemen O (n ^ 2). Je mehr Dateien Sie löschen, desto schneller wird der Rest ausgeführt.

Es gab ein einziges Mal, dass ich mit einem ähnlichen Problem konfrontiert war (obwohl meine Schätzungen eine Löschzeit von ~ 7 Stunden betrachteten), und am Ende ging die von JFTUGA vorgeschlagene Route im ersten Kommentar .


0

Nun, das ist keine richtige Antwort, aber ...

Wäre es möglich, das Dateisystem nach ext4 zu konvertieren und zu sehen, ob sich die Dinge ändern?


Es scheint, dass dies "live" ein fsck auf einem gemounteten Dateisystem erfordert, was ... alarmierend ist. Hast du einen besseren Weg?
BMDan

Das Dateisystem muss vor der Konvertierung, dh vor den erforderlichen Befehlen tunefs, ausgehängt werden.
Marcoc

0

Okay, das wurde im Rest des Threads auf verschiedene Arten abgedeckt, aber ich dachte, ich würde meine zwei Cent einwerfen. Der Leistungsschuldige in Ihrem Fall ist wahrscheinlich readdir. Sie erhalten eine Liste von Dateien zurück, die auf der Festplatte nicht unbedingt in irgendeiner Weise sequentiell sind, wodurch beim Trennen der Verknüpfung überall auf die Festplatte zugegriffen wird. Die Dateien sind so klein, dass der Vorgang zum Aufheben der Verknüpfung wahrscheinlich nicht zu weit aus dem Speicherplatz herausspringt. Wenn Sie readdir lesen und dann nach aufsteigendem Inode sortieren, erhalten Sie wahrscheinlich eine bessere Leistung. Also readdir in ram (sortiere nach inode) -> unlink -> profit.

Inode ist hier eine grobe Annäherung, denke ich .. aber basierend auf Ihrem Anwendungsfall könnte es ziemlich genau sein ...


1
Korrigieren Sie mich, wenn ich falsch liege, aber Unlink (2) setzt den Inode nicht auf Null, sondern entfernt lediglich den Verweis darauf aus dem Verzeichnis. Ich mag jedoch die Chuzpe dieses Ansatzes. Möchtest du ein paar Zeitfahren machen und sehen, ob es zutrifft?
BMDan

0

Ich hätte wahrscheinlich einen C-Compiler herausgepeitscht und das moralische Äquivalent Ihres Skripts erstellt. Das heißt, verwenden Sie opendir(3), um ein Verzeichnis-Handle abzurufen, und dann readdir(3), um den Namen der Dateien abzurufen. Führen Sie dann beim Aufheben der Verknüpfung eine Zusammenfassung der Dateien durch und drucken Sie ab und zu "% d Dateien gelöscht" (und möglicherweise die abgelaufene Zeit oder den aktuellen Zeitstempel).

Ich erwarte nicht, dass es merklich schneller ist als die Shell-Skript-Version. Ich bin es nur gewohnt, hin und wieder den Compiler rauszureißen, entweder weil es keine saubere Möglichkeit gibt, das zu tun, was ich von der Shell will, oder weil Obwohl es in der Shell machbar ist, ist es auf diese Weise unproduktiv langsam.


Er könnte zumindest damit beginnen, den Quellcode von rm aus coreutils heraus zu modifizieren .
Cristian Ciupitu

0

Wahrscheinlich treten beim Umschreiben des Verzeichnisses Probleme auf. Versuchen Sie zuerst, die neuesten Dateien zu löschen. Sehen Sie sich die Mount-Optionen an, mit denen das Zurückschreiben auf die Festplatte verzögert wird.

Versuchen Sie für eine Fortschrittsanzeige etwas wie rm -rv /mystuff 2>&1 | pv -brtl > /dev/null


In Bezug auf das Löschen der neuesten Dateien zuerst: ls -Ur? Ich bin mir ziemlich sicher, dass die Verzeichniseinträge geladen und dann umgekehrt werden. Ich glaube nicht, dass ls klug genug ist, um am Ende der Verzeichniseintragsliste zu beginnen und den Weg zurück zum Anfang zurückzuspulen. "ls -1" ist wahrscheinlich auch keine gute Idee, da es wahrscheinlich mehr als 50 MB Core und mehrere Minuten dauern würde, bis es ausgeführt wird. Sie möchten "ls -U" oder "ls -f".
BMDan

Es ist wahrscheinlich nur praktisch, wenn die Dateinamen in vorhersagbaren Mustern zunehmen. Wie auch immer, Sie haben versucht, ls -1 umzukehren und xargs weiterzuleiten. Verwenden Sie Dateien anstelle von Pipes, wenn Sie Ihre Zwischenergebnisse sehen möchten. Sie haben keine Informationen zur Benennung von Dateien angegeben. Sie würden das Muster in umgekehrter Reihenfolge erzeugen und die Dateien anhand des Musters löschen. Möglicherweise müssen Sie fehlende Dateieinträge behandeln. Angesichts Ihres Kommentars zum erforderlichen Speicher haben Sie eine Vorstellung davon, welche E / A erforderlich sind, um das Verzeichnis neu zu schreiben.
BillThor

0

So lösche ich die Millionen von Tracedateien, die manchmal auf einem großen Oracle-Datenbankserver gesammelt werden können:

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

Ich stelle fest, dass dies zu einer relativ langsamen Löschung führt, die sich nur geringfügig auf die Serverleistung auswirkt, normalerweise in der Größenordnung von einer Stunde pro Million Dateien bei einem "typischen" 10.000-IOPS-Setup.

Oft dauert es mehrere Minuten, bis die Verzeichnisse durchsucht, die erste Dateiliste erstellt und die erste Datei gelöscht wurde. Von da an und weiter, a. wird für jede gelöschte Datei zurückgemeldet.

Die Verzögerung, die durch das Echo des Terminals verursacht wird, hat sich als ausreichend verzögert erwiesen, um eine signifikante Belastung während des Löschvorgangs zu verhindern.


Du wirst lebendig gefressen, indem du herumläufst. Wie wäre es mit etwas mehr wie 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':? In -deletezum letzten tatsächlich Dinge löschen; Wie geschrieben, listet es nur auf, was es löschen würde. Beachten Sie, dass dies für Situationen optimiert ist, in denen Sie viele uninteressante Dinge in nahe gelegenen Verzeichnissen haben. Ist dies nicht der Fall, können Sie die Logik erheblich vereinfachen.
BMDan

find -delete verursacht in der Regel zu viele E / A-Vorgänge und beeinträchtigt leicht die Produktionsleistung. Vielleicht mit ionice.
Roy

Es verursacht all diese E / A, indem es einfach effizienter ist! Der Globbing ist alles von vorne geladen für Ihr Beispiel (das heißt, die vollständige Liste der Dateien erzeugt wird , bevor die erste rmFall ist ), so dass Sie sich von dem relativ effizientem I / O beim Start haben, gefolgt von schmerzhafter, out-of-order rms Das verursacht wahrscheinlich nicht viel E / A, erfordert aber scandirdas wiederholte Durchlaufen des Verzeichnisses (ohne E / A zu verursachen, da dies bereits in den Blockcache geladen wurde; siehe auch vfs_cache_pressure). Wenn Sie etwas verlangsamen möchten, ioniceist dies eine Option, aber ich würde wahrscheinlich Sekundenbruchteile verwenden sleep.
BMDan

find /u*/app/*/diag -path '*/trace/*.tr' -execdir rm {} +würde eine rmpro Verzeichnis ausführen , damit Sie weniger CPU-Overhead haben. Solange Sie Tonnen an CPU-Zeit übrig haben, drosseln Sie die Festplatten-E / A, indem Sie einen ganzen rmProzess für jede unlinkArbeit forken, denke ich, aber es ist hässlich. perl mit einem sleep per unlink wäre schöner, wenn das schlafen zwischen rmganzen verzeichnissen zu platzen wäre. ( -execdir sh -c ...vielleicht)
Peter Cordes

-1

Sie könnten 'xargs'-Parallelisierungsfunktionen verwenden:

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

1
Das wird nicht helfen. Der Engpass ist die schlechte zufällige E / A auf dem Laufwerk. Parallele Löschvorgänge können die Situation noch verschlimmern und die CPU-Auslastung erhöhen.
Wim Kerkhoff

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

1
Beeindruckend. Ich denke, das fällt ziemlich fest in das Lager "Mehr als eine Art, eine Katze zu häuten". Im Ernst, mit der Sorte und der Einzigartigkeit? "ls" wird sowieso standardmäßig sortiert, und ich hoffe, dass die Dateinamen eindeutig sind. : /
BMDan

-2

Tatsächlich ist dieses etwas besser, wenn die von Ihnen verwendete Shell eine Befehlszeilenerweiterung ausführt:

ls|cut -c -4|sort|uniq|awk '{ print "echo " $1 ";rm -rf " $1 "*"}' |sh
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.