rm w katalogu z milionami plików


104

Tło: serwer fizyczny, około dwóch lat, dyski SATA 7200 obr./min podłączone do karty RAID 3Ware, noatime montowany przez ext3 FS i dane = zamówione, nie pod szalonym obciążeniem, jądro 2.6.18-92.1.22.el5, czas pracy 545 dni . Katalog nie zawiera żadnych podkatalogów, tylko miliony małych (~ 100 bajtów) plików, a niektóre większe (kilka KB).

Mamy serwer, który w ciągu ostatnich kilku miesięcy trochę poszedł na kukułkę, ale zauważyliśmy go dopiero wtedy, gdy zaczął nie móc pisać do katalogu z powodu zbyt dużej liczby plików. W szczególności zaczął rzucać ten błąd w / var / log / messages:

ext3_dx_add_entry: Directory index full!

Na dysku tym pozostało wiele i-węzłów:

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

Zgaduję, że to oznacza, że ​​przekroczyliśmy limit liczby wpisów w samym pliku katalogu. Nie mam pojęcia, ile to plików, ale nie może to być, jak widać, więcej niż trzy miliony. Pamiętaj, że to nie jest dobre! Ale to pierwsza część mojego pytania: co to za górna granica? Czy można go przestroić? Zanim zacznę na mnie krzyczeć - chcę to wyciszyć ; ten ogromny katalog spowodował wiele problemów.

W każdym razie wyśledziliśmy problem w kodzie, który generował wszystkie te pliki, i naprawiliśmy go. Teraz utknąłem w usuwaniu katalogu.

Kilka opcji tutaj:

  1. rm -rf (dir)

    Najpierw tego spróbowałem. Zrezygnowałem i zabiłem go po półtora dnia bez widocznego uderzenia.

  2. unlink (2) w katalogu: Zdecydowanie warte rozważenia, ale pytanie brzmi, czy szybsze byłoby usunięcie plików w katalogu za pomocą fsck niż usunięcie za pomocą unlink (2). To znaczy w taki czy inny sposób muszę oznaczyć te i-węzły jako nieużywane. Zakłada się oczywiście, że mogę powiedzieć fsck, aby nie upuszczał wpisów do plików w / lost + found; w przeciwnym razie właśnie przeniosłem swój problem. Oprócz wszystkich innych obaw, po przeczytaniu o tym trochę więcej, okazuje się, że prawdopodobnie musiałbym wywołać niektóre wewnętrzne funkcje FS, ponieważ żaden z wariantów unlink (2), które mogę znaleźć, nie pozwoliłby mi po prostu beztrosko usunąć katalog z wpisami. Puchatek.
  3. while [ true ]; do ls -Uf | head -n 10000 | xargs rm -f 2>/dev/null; done )

    To jest właściwie skrócona wersja; prawdziwy, który uruchamiam, który po prostu dodaje raportowanie postępów i czyste zatrzymanie, gdy zabraknie plików do usunięcia, to:

    eksport i = 0;
    time (while [true]; do
      ls -Uf | głowa -n 3 | grep -qF '.png' || przerwa;
      ls -Uf | głowa -n 10000 | xargs rm -f 2> / dev / null;
      eksport i = $ (($ i + 10000));
      echo „$ i ...”;
    gotowy )

    To wydaje się działać całkiem dobrze. Pisząc to, usunęło 260 000 plików w ciągu ostatnich trzydziestu minut.

A teraz pytania:
  1. Jak wspomniano powyżej, czy można ograniczyć limit wpisów dla poszczególnych katalogów?
  2. Dlaczego usunięcie pojedynczego pliku, który był pierwszym z listy zwróconej przez ls -U, zajęło „prawdziwe 7m9.561s / użytkownik 0m0.001s / sys 0m0.001s” , a usunięcie pierwszych 10 000 wpisów za pomocą rozkaz w # 3, ale teraz idzie całkiem szczęśliwie? W tym przypadku usunął 260 000 w około trzydzieści minut, ale teraz zajęło kolejne piętnaście minut, aby usunąć kolejne 60 000. Dlaczego ogromne wahania prędkości?
  3. Czy jest lepszy sposób na zrobienie czegoś takiego? Nie przechowuj milionów plików w katalogu; Wiem, że to głupie i nie zdarzyłoby się to na moim zegarku. Googlowanie problemu i przeglądanie SF i SO oferuje wiele wariantów find, które nie będą znacznie szybsze niż moje podejście z kilku oczywistych powodów. Ale czy pomysł usuwania przez fsck ma jakieś nogi? A może coś zupełnie innego? Nie mogę się doczekać, aby usłyszeć myślenie „po wyjęciu z pudełka” (lub w „mało znanym” pudełku).
Dzięki za przeczytanie małej powieści; zadawaj pytania, a na pewno odpowiem. Zaktualizuję również pytanie o końcową liczbę plików i czas, przez jaki skrypt usuwania działał, kiedy już go posiadam.

Ostateczne wyjście skryptu !:

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

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

Tak więc trzy miliony plików usunięto w nieco ponad cztery godziny.


1
rm (GNU coreutils) 8.4 ma następującą opcję: „-v, --verbose wyjaśnia, co się dzieje” . Wyświetli wszystkie usuwane pliki.
Cristian Ciupitu

2
Właściwie byłby to dobry sposób na wykonanie paska postępu: ponieważ każdy plik miałby trzydzieści siedem znaków (36 + a '\ n'), mógłbym łatwo napisać parser do tego, a ponieważ printf () jest tani, a polecenie rm ma już załadowaną nazwę pliku, nie ma szczególnego ograniczenia wydajności. Wydaje się, że nie zaczynasz robić całego seksu, ponieważ i tak nigdy nie mogłem zmusić „rm” do zrobienia czegoś takiego. Ale może działać całkiem dobrze jako pasek postępu wewnątrz 10.000; może „.” na każde sto plików?
BMDan

8
rm -rfv | pv -l >/dev/null. pv powinno być dostępne w repozytorium EPEL .
Cristian Ciupitu

5
pv jest niesamowicie niesamowity. Pozostawiam za sobą ślad instalacji PV.
BMDan

Ostatnio miałem dokładnie ten sam problem. Dziękuję Ci!
richo

Odpowiedzi:


30

Opcja data=writebackmontowania zasługuje na wypróbowanie, aby zapobiec kronikowaniu systemu plików. Należy to zrobić tylko w czasie usuwania, istnieje jednak ryzyko, że serwer zostanie zamknięty lub ponownie uruchomiony podczas operacji usuwania.

Według tej strony ,

Niektóre aplikacje wykazują bardzo znaczną poprawę prędkości, gdy są używane. Na przykład można zauważyć poprawę prędkości (...), gdy aplikacje tworzą i usuwają duże wolumeny małych plików.

Opcja jest ustawiana w fstablub podczas operacji montowania, zastępując data=ordereddata=writeback. System plików zawierający pliki do usunięcia należy zamontować ponownie.


1
Mógł także wydłużyć czas z commit opcji : „Ta domyślna wartość (lub dowolna niska wartość) zaszkodzi wydajności, ale jest dobra dla bezpieczeństwa danych. Ustawienie jej na 0 będzie miało taki sam efekt, jak pozostawienie jej na wartości domyślnej (5 sekund Ustawienie bardzo dużych wartości poprawi wydajność ".
Cristian Ciupitu,

1
Zapisywanie wygląda gwiezdnie, z wyjątkiem dokumentacji, na którą patrzyłem ( gentoo.org/doc/en/articles/l-afig-p8.xml#doc_chap4 ) wyraźnie wspomina, że ​​nadal zapisuje metadane, które, jak zakładam, obejmują wszystkie dane zmiana (na pewno nie zmieniam żadnych danych w samych plikach). Czy moje rozumienie opcji jest nieprawidłowe?
BMDan

Wreszcie FYI, nie wspomniane w tym łączu, to fakt, że data = writeback może być ogromną luką bezpieczeństwa, ponieważ dane wskazywane przez dany wpis mogą nie zawierać danych zapisanych tam przez aplikację, co może spowodować awarię w przypadku ujawnienia starych, potencjalnie wrażliwych / prywatnych danych. Nie ma tu znaczenia, ponieważ włączamy to tylko tymczasowo, ale chciałem ostrzec wszystkich o tym zastrzeżeniu na wypadek, gdybyś ty lub inni, którzy natknęli się na tę sugestię, nie byli tego świadomi.
BMDan

commit: to całkiem sprytne! Dzięki za wskaźnik.
BMDan

2
data=writebacknadal zapisuje metadane przed zapisaniem ich w głównym systemie plików. Jak rozumiem, po prostu nie wymusza porządkowania między takimi rzeczami, jak pisanie mapy zasięgu i zapisywanie danych w tych zakresach. Być może istnieją też inne ograniczenia związane z porządkowaniem, które również się rozluźniają, jeśli zauważysz, że zyskujesz na tym. Oczywiście montaż bez dziennika może być jeszcze lepszą wydajnością. (Może to pozwolić na zmiany metadanych w pamięci RAM bez konieczności posiadania czegokolwiek na dysku przed zakończeniem operacji unlink).
Peter Cordes,

80

Chociaż główną przyczyną tego problemu jest wydajność ext3 w milionach plików, faktyczna główna przyczyna tego problemu jest inna.

Gdy trzeba wymienić katalog, wywoływany jest readdir () w katalogu, który daje listę plików. readdir to wywołanie posiksowe, ale używane tutaj prawdziwe wywołanie systemowe Linuksa nazywa się „getdents”. Getdents wyświetlają wpisy do katalogu, wypełniając bufor wpisami.

Problem polega głównie na tym, że readdir () używa stałego rozmiaru bufora 32 KB do pobierania plików. W miarę powiększania się katalogu (rozmiar rośnie wraz z dodawaniem plików) ext3 zwalnia i spowalnia pobieranie wpisów, a dodatkowy rozmiar bufora 32 kb readdir wystarcza tylko na ułamek wpisów w katalogu. Powoduje to, że readdir ciągle się zapętla i wywołuje drogie wywołanie systemowe w kółko.

Na przykład w katalogu testowym, który utworzyłem z ponad 2,6 milionami plików, uruchomienie „ls -1 | wc-l” pokazuje duży wynik strace wielu wywołań systemowych 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
...

Ponadto czas spędzony w tym katalogu był znaczący.

$ time ls -1 | wc -l
2616044

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

Metodą uczynienia tego bardziej wydajnym procesem jest ręczne wywoływanie getdents przy użyciu znacznie większego bufora. Znacząco poprawia to wydajność.

Teraz nie powinieneś samodzielnie wywoływać getdents, więc nie istnieje interfejs, aby normalnie z niego korzystać (sprawdź stronę get, aby zobaczyć getdents!), Ale możesz go wywoływać ręcznie i sprawić, że wywoływanie wywołań systemowych będzie bardziej wydajne.

To drastycznie zmniejsza czas potrzebny na pobranie tych plików. Napisałem program, który to robi.

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

Chociaż nie zwalcza to podstawowego problemu podstawowego (wiele plików w systemie plików, który działa słabo). Prawdopodobnie będzie to znacznie, znacznie szybciej niż wiele opublikowanych alternatyw.

Z góry należy usunąć katalog, którego dotyczy problem, i ponownie go później. Katalogi tylko zwiększają swój rozmiar i mogą pozostać słabej wydajności nawet z kilkoma plikami w środku ze względu na rozmiar katalogu.

Edycja: całkiem sporo to wyczyściłem. Dodano opcję pozwalającą na usunięcie w wierszu poleceń w czasie wykonywania i usunęło kilka elementów chodnika, co, szczerze mówiąc, było co najmniej wątpliwe. Wykazano również, że powoduje uszkodzenie pamięci.

Możesz teraz zrobić dentls --delete /my/path

Nowe wyniki. Na podstawie katalogu z 1,82 milionami plików.

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

Byłem trochę zaskoczony, że nadal działa tak dobrze!


1
Dwie drobne obawy: jedna, [256]prawdopodobnie powinna być [FILENAME_MAX], a druga, mój Linux (2.6.18 == CentOS 5.x) nie wydaje się zawierać wpisu d_type w katalogu dirent (przynajmniej zgodnie z getdents (2)).
BMDan,

1
Czy mógłbyś nieco rozwinąć kwestię przywracania równowagi w btree i dlaczego usunięcie w celu zapobiegania temu? Próbowałem googlować, niestety bezskutecznie.
ovgolovin

1
Ponieważ teraz wydaje mi się, że usuwamy po kolei, zmuszamy do ponownego zrównoważenia, usuwając liście z jednej strony i wychodząc z drugiej: en.wikipedia.org/wiki/B-tree#Rebalancing_after_deletion
ovgolovin

1
Mam nadzieję, że ci to nie przeszkadza. Ale wciąż zacząłem pytanie o usuwanie plików w kolejności stackoverflow.com/q/17955459/862380 , który wydaje się nie otrzymywać odpowiedzi wyjaśniającej problem z przykładem, który będzie zrozumiały dla zwykłych programistów. Jeśli masz czas i masz na to ochotę, czy mógłbyś na to spojrzeć? Może mógłbyś napisać lepsze wyjaśnienie.
ovgolovin,

2
To niesamowity kawałek kodu. Było to jedyne narzędzie, które udało mi się znaleźć na liście i usunąć około 11 000 000 (jedenaście milionów) plików sesji, które zgromadziły się w katalogu, prawdopodobnie przez kilka lat. Proces Plesk, który miał ich kontrolować, używając find i innych sztuczek w innych odpowiedziach tutaj, nie był w stanie ukończyć uruchomienia, więc pliki tylko się gromadziły. Jest to hołd dla drzewa binarnego, którego system plików używa do przechowywania katalogu, że sesje były w stanie w ogóle pracować - możesz utworzyć plik i odzyskać go bez opóźnień. Same oferty były bezużyteczne.
Jason

31

Czy można wykonać kopię zapasową wszystkich innych plików z tego systemu plików w tymczasowej lokalizacji do przechowywania, sformatować partycję, a następnie przywrócić pliki?


3
Naprawdę podoba mi się ta odpowiedź. Praktycznie rzecz biorąc, w tym przypadku nie, ale nie o tym bym pomyślał. Brawo!
BMDan

Dokładnie to, o czym myślałem. To jest odpowiedź na pytanie 3. Idealne, jeśli mnie zapytasz :)
Joshua

12

W ext3 nie ma limitu plików na katalog tylko limit i-węzłów systemu plików (myślę, że istnieje ograniczenie liczby podkatalogów).

Po usunięciu plików nadal możesz mieć problemy.

Gdy katalog zawiera miliony plików, sam wpis katalogu staje się bardzo duży. Wpis katalogu musi zostać przeskanowany dla każdej operacji usuwania, i zajmuje to różną ilość czasu dla każdego pliku, w zależności od tego, gdzie znajduje się jego wpis. Niestety nawet po usunięciu wszystkich plików pozycja katalogu zachowuje swój rozmiar. Tak więc dalsze operacje wymagające skanowania pozycji katalogu nadal potrwają długo, nawet jeśli katalog jest teraz pusty. Jedynym sposobem rozwiązania tego problemu jest zmiana nazwy katalogu, utworzenie nowego ze starą nazwą i przeniesienie pozostałych plików do nowego. Następnie usuń nazwę o zmienionej nazwie.


Rzeczywiście zauważyłem właśnie to zachowanie po usunięciu wszystkiego. Na szczęście już usunęliśmy katalog z „linii ognia”, więc mogłem go po prostu zapisać.
BMDan

2
To powiedziawszy, jeśli nie ma limitu plików dla poszczególnych katalogów, dlaczego dostałem „ext3_dx_add_entry: indeks katalogu pełny!” kiedy na tej partycji były jeszcze dostępne i-węzły? W tym katalogu nie było podkatalogów.
BMDan

3
hmm, zrobiłem trochę więcej badań i wydaje się, że istnieje limit liczby bloków, które może zająć katalog. Dokładna liczba plików zależy od kilku rzeczy, np. Długości nazwy pliku. Ten gossamer-threads.com/lists/linux/kernel/921942 wydaje się wskazywać, że przy blokach 4k powinieneś być w stanie mieć ponad 8 milionów plików w katalogu. Czy były to szczególnie długie nazwy plików?
Alex J. Roberts,

Każda nazwa pliku miała dokładnie 36 znaków.
BMDan

cóż, to ja z pomysłów :)
Alex J. Roberts


4

find po prostu nie działał dla mnie, nawet po zmianie parametrów ext3 fs, zgodnie z sugestią użytkowników powyżej. Spożywane zdecydowanie za dużo pamięci. Ten skrypt PHP rozwiązał problem - szybkie, nieznaczne użycie procesora, nieznaczne zużycie pamięci:

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

Wysłałem raport o błędzie dotyczący tego problemu z find: http://savannah.gnu.org/bugs/?31961


To mnie uratowało !!
jestro

3

Ostatnio napotkałem podobny problem i nie byłem w stanie uzyskać data=writebacksugestii ring0 do pracy (być może z powodu faktu, że pliki znajdują się na mojej głównej partycji). Badając obejścia, natknąłem się na to:

tune2fs -O ^has_journal <device>

Spowoduje to całkowite wyłączenie kronikowania, niezależnie od dataopcji mount. Połączyłem to z noatimegłośnością dir_indexustawioną i wydawało się, że działa całkiem dobrze. Usuwanie faktycznie się zakończyło bez konieczności jego zabijania, mój system pozostał responsywny, a teraz jest z powrotem gotowy do pracy (z włączonym kronikowaniem) bez żadnych problemów.


Chciałem zasugerować zamontowanie go jako ext2 zamiast ext3, aby uniknąć kronikowania operacji metadanych. To powinno zrobić to samo.
Peter Cordes,

3

Upewnij się, że:

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

co również powinno trochę przyspieszyć.


4
Dobra rozmowa, ale jest już zamontowana noatime, jak wspomniałem w nagłówku pytania. A nodiratime jest zbędny; patrz lwn.net/Articles/245002 .
BMDan

1
ppl powtórz tę mantrę „noatime, nodiratime, nodevatime, noreadingdocsatime”
poige 12.06.16

2

Bardzo powolne polecenie. Próbować:

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

rm -rf biegł przez półtora dnia, a ja w końcu go zabiłem, nie wiedząc, czy cokolwiek się udało. Potrzebowałem paska postępu.
BMDan

4
Jeśli chodzi o to, że rm jest bardzo wolny, „znajdź czas. -Delete” na plikach 30k: 0m0,357s / 0m0,019s / 0m0,337s rzeczywisty / użytkownik / sys. „czas (ls -1U | xargs rm -f)” na tych samych plikach: 0m0,366s / 0m0,025s / 0m0,340s. Co jest w zasadzie terytorium marginesu błędu.
BMDan

1
Mogłeś po prostu uruchomić, strace -r -p <pid of rm>aby dołączyć do już działającego procesu rm. Następnie możesz zobaczyć, jak szybko unlinkprzewijają się połączenia systemowe. ( -rkładzie czas od poprzedniego wywołania systemowego na początku każdej linii.)
Peter Cordes,

2

Czy dir_indexustawiony jest system plików? ( tune2fs -l | grep dir_index) Jeśli nie, włącz. Zwykle jest włączony dla nowego RHEL.


1
Tak, jest włączony, ale świetna sugestia!
BMDan

2

Kilka lat temu znalazłem katalog z 16 milionami plików XML w /systemie plików. Ze względu na krytyczność serwera użyliśmy następującego polecenia, którego ukończenie zajęło około 30 godzin :

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

Był to stary dysk twardy o prędkości 7200 obr / min i pomimo wąskiego gardła we / wy i skoków procesora stary serwer nadal działał.


1

Moją preferowaną opcją jest podejście newfs, już zasugerowane. Podstawowym problemem jest, jak już wspomniano, liniowy skan do usunięcia jest problematyczny.

rm -rfpowinien być prawie optymalny dla lokalnego systemu plików (NFS byłby inny). Ale przy milionach plików, 36 bajtów na nazwę pliku i 4 na i-węzeł (przypuszczenie, nie sprawdza wartości dla ext3), to 40 * milionów, które należy przechowywać w pamięci RAM tylko dla katalogu.

Zgadujesz, że przebijasz pamięć podręczną metadanych systemu plików w systemie Linux, tak że bloki dla jednej strony pliku katalogu są usuwane, gdy nadal używasz innej części, tylko po to, aby ponownie trafić na tę stronę pamięci podręcznej, gdy następna plik został usunięty. Strojenie wydajności Linuksa nie jest moim obszarem, ale / proc / sys / {vm, fs} / prawdopodobnie zawiera coś istotnego.

Jeśli możesz sobie pozwolić na przestoje, możesz rozważyć włączenie funkcji dir_index. Przełącza indeks katalogu z liniowego na coś znacznie bardziej optymalnego do usuwania w dużych katalogach (haszowane b-drzewa). tune2fs -O dir_index ...następnie e2fsck -Dbędzie działać. Chociaż jestem pewien, że to pomogłoby, zanim pojawią się problemy, nie wiem, jak działa konwersja (e2fsck z -D) podczas obsługi istniejącego katalogu v.large. Kopie zapasowe + ssać i widzieć.


1
pubbs.net/201008/squid/… sugeruje, że /proc/sys/fs/vfs_cache_pressuremoże to być wartość do użycia, ale nie wiem, czy sam katalog zalicza się do pamięci podręcznej strony (bo to jest to) lub do pamięci podręcznej i-węzłów (ponieważ pomimo tego, że nie jest i-węzeł, to metadane FS i z tego powodu są tam zawarte). Jak mówię, tuning maszyn wirtualnych Linux nie jest moim obszarem. Graj i zobacz, co pomaga.
Phil P

1

Oczywiście nie jabłka do jabłek tutaj, ale ustawiłem mały test i wykonałem następujące czynności:

Utworzono 100 000 512-bajtowych plików w katalogu ( ddi /dev/urandomw pętli); zapomniałem go zmierzyć, ale utworzenie tych plików zajęło około 15 minut.

Wykonano następujące czynności, aby usunąć wspomniane pliki:

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

100000

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

To jest pudełko Pentium 4 2.8GHz (chyba kilkaset GB IDE 7200 RPM; EXT3). Jądro 2.6.27.


Ciekawe, więc może fakt, że pliki były tworzone przez długi czas, jest istotny? Ale to nie powinno mieć znaczenia; pamięć podręczna bloków powinna zawierać wszystkie istotne bloki metadanych w pamięci RAM. Może to dlatego, że unlink (2) jest transakcyjny? Czy według Pana wyłączenie rejestrowania na czas RM byłoby potencjalnym (choć wprawdzie nieco niebezpiecznym) rozwiązaniem? Nie wygląda na to, że można po prostu całkowicie wyłączyć kronikowanie w zamontowanym systemie plików bez tune2fs / fsck / reboot, co w pewnym sensie nie udaje się.
BMDan

Nie mogę tego komentować, ale anegdotycznie (w różnych dyskusjach na temat NIX na przestrzeni lat), zawsze słyszałem, że rmjest to bardzo powolne w przypadku dużej liczby plików, stąd find -deleteopcja. Mając znak wieloznaczny w powłoce, rozwinie każdą dopasowaną nazwę pliku i zakładam, że istnieje ograniczony bufor pamięci, abyś mógł zobaczyć, jak to się stanie nieefektywne.
gravyface

1
rm byłby powolny, ponieważ szuka pliku według nazwy, co oznacza powtarzanie pozycji katalogu jeden po drugim, aż go znajdzie. Jednak w tym przypadku, ponieważ każdy przekazywany wpis jest (w tym momencie) pierwszym na liście (ls -U / ls -f), powinien być prawie tak szybki. To powiedziawszy, rm -rf <katalog>, który powinien był biec jak mistrz, był jak najbardziej powolny. Być może nadszedł czas, aby napisać łatkę do coreutils, aby przyspieszyć masowe usuwanie? Może to potajemnie globowanie / sortowanie w jakiś rekurencyjny sposób w celu zaimplementowania rm -rf? Takie niepewności są powodem, dla którego zadałem pytanie. ;)
BMDan

1
Uruchom ponownie komputer po uruchomieniu kroku tworzenia. Powinieneś dostać zauważalnie wolniejsze usuwanie.
Matt

1

Czasami Perl potrafi zdziałać cuda w takich przypadkach. Czy próbowałeś już, czy mały skrypt taki jak ten może przewyższyć bash i podstawowe polecenia powłoki?

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

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

Lub inne, może nawet szybsze podejście Perla:

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

EDYCJA: Właśnie wypróbowałem moje skrypty Perla. Im bardziej gadatliwy, robi coś dobrze. W moim przypadku próbowałem tego na serwerze wirtualnym z 256 MB pamięci RAM i pół milionem plików.

time find /test/directory | xargs rm wyniki:

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

w porównaniu do

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

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

Waham się wyobrażać sobie, co zrobiłoby to wywołanie glob (); Zakładam, że robi scandir (). Jeśli tak, zajmie to ZAWSZE powrót. Modyfikacja pierwszej sugestii, która nie wstępnie odczytuje wszystkich pozycji katalogu, może mieć pewne nogi; jednak w swojej obecnej formie również użyłby bezbożnej ilości procesora po prostu czytając wszystkie wpisy katalogu na raz. Częściowym celem tutaj jest dzielenie i podbijanie; kod ten nie różni się zasadniczo od 'rm -f * .png', pomimo problemów z rozszerzaniem powłoki. Jeśli to pomoże, w katalogu nie ma nic, czego nie chciałbym usunąć.
BMDan

Muszę spróbować więcej, jak tylko pójdę do pracy. Właśnie próbowałem utworzyć 100 000 plików w jednym katalogu i znaleźć kombinację + xargs + rm zajęło 7,3 sekundy, Perl + unlink (glob) ... kombinacja zakończyła się w 2,7 sekundy. Próbowałem tego kilka razy, wynik był zawsze taki sam. W pracy spróbuję z większą ilością plików.
Janne Pikkarainen

Nauczyłem się czegoś nowego podczas testowania tego. Przynajmniej w przypadku ext3 i ext4 sama pozycja katalogu pozostaje ogromna, nawet po usunięciu z niej wszystkich plików. Po kilku testach mój katalog / tmp / test zajmował 15 MB miejsca na dysku. Czy istnieje inny sposób na wyczyszczenie tego niż usunięcie katalogu i jego ponowne utworzenie?
Janne Pikkarainen

2
Nie, musisz go odtworzyć. Uderzyłem to, gdy miałem do czynienia z systemem pocztowym i folderem na odbiorcę oraz porządkami po poważnych problemach: nie ma innego wyjścia, jak stworzyć nowy katalog i przeszukiwać katalogi, a potem nukać stary. Możesz więc skrócić okno czasowe, gdy nie ma katalogu, ale go nie wyeliminować.
Phil P

Zauważ, że glob () posortuje wyniki, podobnie jak zwykle globbing powłoki, więc ponieważ masz tylko 100 000 plików, wszystko pasuje łatwo, a sortowanie jest szybkie. W przypadku znacznie większego katalogu chciałbyś opendir () / readdir () / closedir (), aby uniknąć sortowania. [Mówię normalnie dla powłoki, ponieważ zsh ma modyfikator globu, aby porządek sortowania był nieposortowany, co jest przydatne, gdy mamy do czynienia z dużą liczbą plików; *(oN)]
Phil P

1

Z tego, co pamiętam, usuwanie i-węzłów w systemach plików ext to O (n ^ 2), więc im więcej plików usuniesz, tym szybciej pójdzie reszta.

Był jeden raz, gdy napotkałem podobny problem (chociaż moje szacunki dotyczyły czasu usuwania ~ 7 godzin), w końcu poszła droga sugerowana przez jftuga w pierwszym komentarzu .


0

Cóż, to nie jest prawdziwa odpowiedź, ale ...

Czy można przekonwertować system plików na ext4 i sprawdzić, czy coś się zmieni?


Wygląda na to, że wykonanie tego „na żywo” wymaga fsck na zamontowanym systemie plików, co jest ... alarmujące. Masz lepszy sposób?
BMDan

System plików musi zostać odmontowany przed konwersją, tj. Przed niezbędnymi poleceniami tunefs.
marcoc

0

W porządku, zostało to omówione na różne sposoby w pozostałej części wątku, ale myślałem, że wrzucę moje dwa centy. Winowajcą wydajności w twoim przypadku jest prawdopodobnie readdir. Otrzymujesz listę plików, które niekoniecznie są sekwencyjne na dysku, co powoduje dostęp do dysku w dowolnym miejscu po rozłączeniu. Pliki są na tyle małe, że operacja rozłączenia prawdopodobnie nie przeskakuje zbyt wiele, zerując przestrzeń. Jeśli czytasz katalog, a następnie sortujesz według rosnącego i-węzła, prawdopodobnie uzyskasz lepszą wydajność. Więc readdir w ram (sortuj według i-węzła) -> unlink -> profit.

I-węzeł jest tutaj przybliżonym przybliżeniem ... ale na podstawie twojego przypadku użycia może być dość dokładny ...


1
Popraw mnie, jeśli się mylę, ale unlink (2) nie zeruje i-węzła, po prostu usuwa odniesienie do niego z katalogu. Jednak lubię chutzpę tego podejścia. Chcesz przeprowadzić kilka prób czasowych i sprawdzić, czy to prawda?
BMDan

0

Prawdopodobnie wybrałbym kompilator C i zrobiłbym moralny odpowiednik twojego skryptu. Oznacza to, że użyj, opendir(3)aby uzyskać uchwyt katalogu, następnie użyj, readdir(3)aby uzyskać nazwę plików, a następnie zsumować pliki, gdy je rozłączam, i od czasu do czasu drukuj „% d pliki usunięte” (i ewentualnie upływ czasu lub aktualny znacznik czasu).

Nie spodziewam się, że będzie zauważalnie szybszy niż wersja skryptu powłoki, po prostu od czasu do czasu muszę wyrywać kompilator, albo dlatego, że nie ma czystego sposobu robienia tego, co chcę z powłoki lub ponieważ chociaż jest to wykonalne w powłoce, w ten sposób jest nieproduktywnie wolne.


Mógł przynajmniej zacząć od modyfikacji kodu źródłowego rm z coreutils .
Cristian Ciupitu,

0

Prawdopodobnie występują problemy z przepisywaniem katalogu. Najpierw spróbuj usunąć najnowsze pliki. Sprawdź opcje montowania, które odłożą zapis na dysk.

Aby wyświetlić pasek postępu, spróbuj uruchomić coś takiego rm -rv /mystuff 2>&1 | pv -brtl > /dev/null


Jeśli chodzi o usuwanie najpierw najnowszych plików: ls -Ur? Jestem prawie pewien, że załadują wpisy katalogu, a następnie je odwrócą; Nie wierzę, że ls jest wystarczająco inteligentny, aby zacząć od końca listy wpisów do katalogu i przewinąć do początku. „ls -1” również prawdopodobnie nie jest świetnym pomysłem, ponieważ prawdopodobnie zajmie ponad 50 MB rdzenia i kilka minut; chcesz „ls -U” lub „ls -f”.
BMDan

Jest to prawdopodobnie praktyczne tylko wtedy, gdy nazwy plików zwiększają się według przewidywalnego wzorca. Jednak moja próba ls -1 potokowała do odwrócenia i potokowała do xargs. Użyj plików zamiast potoków, jeśli chcesz zobaczyć swoje wyniki pośrednie. Nie podałeś żadnych informacji na temat nazw plików. Wygenerujesz tupot w odwrotnej kolejności i usuniesz pliki przy użyciu wzorca. Być może trzeba będzie obsługiwać brakujące wpisy plików. Biorąc pod uwagę Twój komentarz dotyczący wymaganej pamięci, masz pojęcie o tym, że I / O wymaga przepisania katalogu.
BillThor,

0

Oto jak usuwam miliony plików śledzenia, które mogą czasem gromadzić się na dużym serwerze bazy danych Oracle:

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

Uważam, że powoduje to dość powolne usuwanie, które ma niewielki wpływ na wydajność serwera, zwykle coś w stylu godziny na milion plików przy „typowej” konfiguracji 10 000 IOPS.

Często zajmie to kilka minut, zanim zostaną zeskanowane katalogi, wygenerowana zostanie początkowa lista plików, a pierwszy plik zostanie usunięty. Odtąd a. jest powtarzane dla każdego usuniętego pliku.

Opóźnienie spowodowane przez echo na terminalu wykazało wystarczające opóźnienie, aby zapobiec znacznemu obciążeniu podczas usuwania.


Zostajesz zjedzony żywcem przez globbing. Jak o czymś więcej jak: 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'? Dodaj -deletedo ostatniego, aby faktycznie usunąć rzeczy; jak napisano, po prostu wyszczególnia, co by usunął. Zauważ, że jest to zoptymalizowane pod kątem okoliczności, w których masz wiele nieciekawych rzeczy w pobliskich katalogach; jeśli tak nie jest, możesz znacznie uprościć logikę.
BMDan,

find -delete powoduje zwykle zbyt wiele operacji we / wy i łatwo wpływa na wydajność produkcji. Być może z jonice.
Roy,

Jednak powoduje to, że wszystkie operacje wejścia / wyjścia są po prostu bardziej wydajne! Globbing jest na początku ładowany na przykład (tzn. Pełna lista plików jest generowana przed pierwszym rmzdarzeniem), więc masz stosunkowo wydajne operacje we / wy podczas uruchamiania, a następnie bolesne, rmniedziałające które prawdopodobnie nie powodują dużej ilości scandiroperacji we / wy, ale wymagają wielokrotnego przeglądania katalogu (nie powodując operacji we / wy, ponieważ zostały one już załadowane do pamięci podręcznej bloków; patrz także vfs_cache_pressure). Jeśli chcesz zwolnić, ionicejest to opcja, ale prawdopodobnie użyłbym sekund ułamkowych sleep.
BMDan

find /u*/app/*/diag -path '*/trace/*.tr' -execdir rm {} +uruchamiałby jeden rmna katalog, więc miałbyś mniej obciążenia procesora. Tak długo, jak masz mnóstwo czasu procesora na oszczędzanie, dławienie dysku IO przez rozwiązywanie całego rmprocesu dla każdej unlinkpracy, tak myślę, ale jest to brzydkie. perl ze snem na unlink byłby ładniejszy, gdyby spanie między rmcałymi katalogami naraz było zbyt duże ( -execdir sh -c ...może)
Peter Cordes,

-1

Możesz użyć funkcji równoległości „xargs”:

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

1
To nie pomoże. Wąskim gardłem jest słabe losowe we / wy na dysku. Wykonywanie równoległych operacji usuwania może nawet pogorszyć sytuację i zwiększyć obciążenie procesora.
Wim Kerkhoff,

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

1
Łał. Wydaje mi się, że to dość mocno wpisuje się w obóz „więcej niż jeden sposób na skórowanie kota”. Poważnie jednak z tym rodzajem i unikatem? W każdym razie „ls” sortuje domyślnie i mam nadzieję, że nazwy plików są unikalne. : /
BMDan

-2

tak naprawdę ten jest trochę lepszy, jeśli używana powłoka rozszerza linię poleceń:

ls|cut -c -4|sort|uniq|awk '{ print "echo " $1 ";rm -rf " $1 "*"}' |sh
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.