rm su una directory con milioni di file


104

Sfondo: server fisico, circa due anni, unità SATA 7200-RPM connesse a una scheda RAID 3Ware, no3 tempo montato su ext3 FS e dati = ordinati, non sotto carico, kernel 2.6.18-92.1.22.el5, uptime 545 giorni . La directory non contiene alcuna sottodirectory, solo milioni di file piccoli (~ 100 byte), con alcuni più grandi (pochi KB).

Abbiamo un server che è diventato un po 'cuculo nel corso degli ultimi mesi, ma l'abbiamo notato solo l'altro giorno quando ha iniziato a non essere in grado di scrivere in una directory perché conteneva troppi file. In particolare, ha iniziato a lanciare questo errore in / var / log / messages:

ext3_dx_add_entry: Directory index full!

Il disco in questione ha molti inode rimanenti:

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

Quindi suppongo che ciò significhi che abbiamo raggiunto il limite di quante voci possono essere nel file di directory stesso. Non ho idea di quanti file sarebbero, ma non può essere più, come puoi vedere, di circa tre milioni. Non che va bene, intendiamoci! Ma questa è la prima parte della mia domanda: che cos'è esattamente quel limite superiore? È sintonizzabile? Prima di sgridato-voglio sintonizzare giù ; questa enorme directory ha causato tutti i tipi di problemi.

Ad ogni modo, abbiamo rintracciato il problema nel codice che stava generando tutti quei file e lo abbiamo corretto. Ora sono bloccato con l'eliminazione della directory.

Alcune opzioni qui:

  1. rm -rf (dir)

    L'ho provato prima io. Ho rinunciato e l'ho ucciso dopo che era corso per un giorno e mezzo senza alcun impatto evidente.

  2. unlink (2) nella directory: vale sicuramente la pena considerare, ma la domanda è se sarebbe più veloce eliminare i file all'interno della directory tramite fsck che eliminarli tramite unlink (2). Cioè, in un modo o nell'altro, devo contrassegnare quegli inode come inutilizzati. Ciò presuppone, ovviamente, che posso dire a fsck di non rilasciare le voci nei file in / lost + found; altrimenti, ho appena spostato il mio problema. Oltre a tutte le altre preoccupazioni, dopo aver letto un po 'di più su questo, si scopre che probabilmente dovrei chiamare alcune funzioni interne di FS, poiché nessuna delle varianti unlink (2) che riesco a trovare mi permetterebbe di cancellare solo allegramente una directory con le voci al suo interno. Pooh.
  3. while [ true ]; do ls -Uf | head -n 10000 | xargs rm -f 2>/dev/null; done )

    Questa è in realtà la versione abbreviata; quello vero che sto correndo, che aggiunge solo alcuni rapporti sui progressi e un punto fermo quando finiamo i file da eliminare, è:

    esportazione i = 0;
    tempo (while [true]; do
      ls -Uf | capo -n 3 | grep -qF '.png' || rompere;
      ls -Uf | head -n 10000 | xargs rm -f 2> / dev / null;
      esporta i = $ (($ i + 10000));
      echo "$ i ...";
    fatto )

    Questo sembra funzionare piuttosto bene. Mentre scrivo, negli ultimi trenta minuti circa ha eliminato 260.000 file.

Ora, per le domande:
  1. Come accennato in precedenza, è possibile regolare il limite di voci per directory?
  2. Perché ci sono voluti "7m9.561s reali / utente 0m0.001s / sys 0m0.001s" per eliminare un singolo file che era il primo nell'elenco restituito ls -Ue ci sono voluti forse dieci minuti per eliminare le prime 10.000 voci con il comando in # 3, ma ora si trascina abbastanza felicemente? Del resto, ha eliminato 260.000 in circa trenta minuti, ma ora sono necessari altri quindici minuti per eliminarne altri 60.000. Perché le enormi oscillazioni di velocità?
  3. C'è un modo migliore per fare questo genere di cose? Non archiviare milioni di file in una directory; So che è sciocco e non sarebbe successo sul mio orologio. Cercare su Google il problema e guardare attraverso SF e SO offre molte varianti findche non saranno significativamente più veloci del mio approccio per diverse ragioni evidenti. Ma l'idea di eliminare-via-fsck ha delle gambe? O qualcos'altro interamente? Sono ansioso di sentire il pensiero fuori dagli schemi (o dentro-non-ben-conosciuto-scatola).
Grazie per aver letto il piccolo romanzo; sentiti libero di porre domande e sarò sicuro di rispondere. Aggiornerò anche la domanda con il numero finale di file e per quanto tempo è stato eseguito lo script di eliminazione dopo averlo visualizzato.

Output dello script finale !:

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

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

Quindi, tre milioni di file eliminati in poco più di quattro ore.


1
rm (GNU coreutils) 8.4 ha questa opzione: "-v, --verbose spiega cosa si sta facendo" . Verranno visualizzati tutti i file che vengono eliminati.
Cristian Ciupitu,

2
In realtà, sarebbe un modo semplice per fare una barra di avanzamento: poiché ogni file sarebbe lungo trentasette caratteri (36 + a '\ n'), potrei facilmente scrivere un parser per questo, e poiché printf () è economico e il comando rm ha già caricato il nome del file, non ci sono penalità di prestazioni speciali. Sembra un non-principiante per aver fatto l'intero shebang, dal momento che non avrei mai potuto "fare" qualcosa del genere, comunque. Ma potrebbe funzionare abbastanza bene come barra di avanzamento intra-10.000; forse un "." per ogni cento file?
BMDan,

8
rm -rfv | pv -l >/dev/null. pv dovrebbe essere disponibile nel repository EPEL .
Cristian Ciupitu,

5
pv è incredibilmente fantastico. Lascio una scia di installazioni fotovoltaiche sulla mia scia.
BMDan,

Recentemente ho avuto lo stesso identico problema. Grazie!
Richo,

Risposte:


30

L' data=writebackopzione mount merita di essere provata, al fine di impedire l'inserimento nel journal del file system. Questo dovrebbe essere fatto solo durante il tempo di eliminazione, tuttavia esiste un rischio se il server viene arrestato o riavviato durante l'operazione di eliminazione.

Secondo questa pagina ,

Alcune applicazioni mostrano un miglioramento della velocità molto significativo quando viene utilizzato. Ad esempio, i miglioramenti della velocità possono essere visti (...) quando le applicazioni creano ed eliminano grandi volumi di piccoli file.

L'opzione è impostata in fstabo durante l'operazione di montaggio, sostituendo data=orderedcon data=writeback. Il file system contenente i file da eliminare deve essere rimontato.


1
Potrebbe anche aumentare il tempo commit dall'opzione : "Questo valore predefinito (o qualsiasi valore basso) comprometterà le prestazioni, ma è buono per la sicurezza dei dati. Impostandolo su 0 avrà lo stesso effetto di lasciarlo sul valore predefinito (5 secondi ). Impostarlo su valori molto grandi migliorerà le prestazioni ".
Cristian Ciupitu,

1
Il writeback sembra stellare, tranne la documentazione che stavo guardando ( gentoo.org/doc/en/articles/l-afig-p8.xml#doc_chap4 ) menziona esplicitamente che continua a pubblicare metadati su riviste, che presumo includa tutti i dati che sto cambiando (non sto certamente cambiando alcun dato nei file stessi). La mia comprensione dell'opzione è errata?
BMDan,

Infine, FYI, non menzionato in quel link è che dato = writeback può essere un enorme buco di sicurezza, dal momento che i dati indicati da una data voce potrebbero non avere i dati scritti lì dall'app, il che significa che potrebbe verificarsi un arresto anomalo nel vecchio, i dati possibilmente sensibili / privati ​​esposti. Non è un problema qui, dal momento che lo stiamo solo attivando temporaneamente, ma volevo avvisare tutti di quell'avvertimento nel caso in cui tu o altri che incontriate quel suggerimento non foste consapevoli.
BMDan,

commettere: è piuttosto elegante! Grazie per il puntatore.
BMDan,

2
data=writebackancora registra i metadati prima di scriverli nel filesystem principale. A quanto ho capito, semplicemente non impone l'ordinamento tra cose come scrivere una mappa di estensione e scrivere dati in quelle estensioni. Forse ci sono anche altri vincoli di ordine che rilassa, se hai visto un guadagno perfetto da questo. Naturalmente, il montaggio senza il diario potrebbe essere prestazioni ancora più elevate. (Potrebbe consentire che i cambiamenti dei metadati avvengano nella RAM, senza la necessità di avere nulla sul disco prima del completamento dell'operazione di scollegamento).
Peter Cordes,

80

Mentre una delle principali cause di questo problema è la prestazione ext3 con milioni di file, la causa principale di questo problema è diversa.

Quando è necessario elencare una directory, readdir () viene chiamato nella directory che produce un elenco di file. readdir è una chiamata posix, ma la vera chiamata di sistema Linux utilizzata qui si chiama 'getdents'. Getdents elenca le voci della directory riempiendo un buffer di voci.

Il problema è principalmente dovuto al fatto che readdir () utilizza una dimensione del buffer fissa di 32 KB per recuperare i file. Man mano che una directory diventa sempre più grande (la dimensione aumenta man mano che vengono aggiunti i file) ext3 diventa sempre più lento nel recuperare le voci e la dimensione aggiuntiva del buffer di 32 KB di readdir è sufficiente per includere solo una frazione delle voci nella directory. Questo fa sì che readdir si ripeti ripetutamente e invochi ripetutamente la costosa chiamata di sistema.

Ad esempio, in una directory di test che ho creato con oltre 2,6 milioni di file all'interno, l'esecuzione di "ls -1 | wc-l" mostra un output di grandi dimensioni di molte chiamate di sistema 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
...

Inoltre, il tempo trascorso in questa directory è stato significativo.

$ time ls -1 | wc -l
2616044

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

Il metodo per rendere questo un processo più efficiente consiste nel chiamare manualmente getdents con un buffer molto più grande. Questo migliora significativamente le prestazioni.

Ora, non dovresti chiamare manualmente getdents, quindi non esiste alcuna interfaccia per usarlo normalmente (controlla la pagina man per vedere getdents!), Tuttavia puoi chiamarlo manualmente e rendere la tua chiamata di sistema più efficiente.

Ciò riduce drasticamente il tempo necessario per recuperare questi file. Ho scritto un programma che fa questo.

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

Mentre questo non combatte il problema fondamentale sottostante (molti file, in un filesystem che funziona male). È probabile che sia molto, molto più veloce di molte delle alternative pubblicate.

Come previsto, si dovrebbe rimuovere la directory interessata e rifarla dopo. Le directory aumentano sempre di dimensioni e possono rimanere scarsamente performanti anche con alcuni file all'interno a causa delle dimensioni della directory.

Modifica: ho ripulito un bel po '. Aggiunta un'opzione per permetterti di eliminare dalla riga di comando in fase di esecuzione e rimosso un sacco di roba da treewalk che, onestamente, guardando indietro era discutibile nella migliore delle ipotesi. Inoltre è stato dimostrato che produce il danneggiamento della memoria.

Ora puoi farlo dentls --delete /my/path

Nuovi risultati Basato su una directory con 1,82 milioni di file.

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

È stato un po 'sorpreso che funzioni ancora così bene!


1
Due problemi minori: uno, [256]probabilmente dovrebbe essere [FILENAME_MAX], e due, il mio Linux (2.6.18 == CentOS 5.x) non sembra includere una voce d_type in dirent (almeno secondo getdents (2)).
BMDan,

1
Potresti per favore approfondire un po 'il riequilibrio di btree e perché la cancellazione al fine aiuta a prevenirlo? Ho provato a cercarlo su Google, purtroppo senza successo.
Ovgolovin,

1
Perché ora mi sembra che stiamo eliminando in ordine, forziamo il riequilibrio, mentre rimuoviamo le foglie da un lato e lasciamo dall'altro: en.wikipedia.org/wiki/B-tree#Rebalancing_after_deletion
ovgolovin

1
Spero di non disturbarti con queste cose. Ma ho comunque iniziato una domanda sull'eliminazione dei file in ordine stackoverflow.com/q/17955459/862380 , che sembra non ricevere una risposta che spieghi il problema con l'esempio, che sarà comprensibile per i normali programmatori. Se hai tempo e ti senti così, potresti guardarci dentro? Forse potresti scrivere una spiegazione migliore.
Ovgolovin,

2
Questo è un fantastico pezzo di codice. È stato l'unico strumento che sono riuscito a trovare in grado di elencare ed eliminare circa 11.000.000 (undici milioni) di file di sessione che erano stati creati in una directory, probabilmente nel corso di alcuni anni. Il processo di Plesk che avrebbe dovuto tenerli sotto controllo usando find e altri trucchi in altre risposte qui, non è stato in grado di completare una corsa, quindi i file hanno continuato ad accumularsi. È un tributo all'albero binario che il filesystem usa per archiviare la directory, che le sessioni sono state in grado di funzionare: potresti creare un file e recuperarlo senza ritardi. Solo le inserzioni erano inutilizzabili.
Jason,

31

Sarebbe possibile eseguire il backup di tutti gli altri file da questo file system in un percorso di archiviazione temporaneo, riformattare la partizione e quindi ripristinare i file?


3
Mi piace davvero questa risposta, in realtà. In pratica, in questo caso, no, ma non è quello a cui avrei pensato. Bravo!
BMDan,

Esattamente anche quello a cui stavo pensando. Questa è una risposta alla domanda 3. Ideale se me lo chiedi :)
Giosuè,

12

In ext3 non esiste un limite per file di directory solo per il limite dell'inode del filesystem (penso che ci sia un limite al numero di sottodirectory).

Potresti avere ancora problemi dopo aver rimosso i file.

Quando una directory ha milioni di file, la voce della directory stessa diventa molto grande. La voce della directory deve essere scansionata per ogni operazione di rimozione e ciò richiede diversi periodi di tempo per ogni file, a seconda di dove si trova la sua voce. Sfortunatamente anche dopo che tutti i file sono stati rimossi la voce della directory mantiene le sue dimensioni. Quindi ulteriori operazioni che richiedono la scansione della voce della directory impiegheranno ancora molto tempo anche se la directory è vuota. L'unico modo per risolvere il problema è rinominare la directory, crearne una nuova con il vecchio nome e trasferire tutti i file rimanenti in quella nuova. Quindi eliminare quello rinominato.


In effetti, ho notato proprio questo comportamento dopo aver eliminato tutto. Fortunatamente, avevamo già rimosso la directory dalla "linea di fuoco", per così dire, così ho potuto semplicemente dirlo.
BMDan,

2
Detto questo, se non esiste un limite per file per directory, perché ho ricevuto "ext3_dx_add_entry: indice directory pieno!" quando c'erano ancora inode disponibili su quella partizione? Non c'erano sottodirectory all'interno di questa directory.
BMDan,

3
hmm ho fatto un po 'più di ricerca e sembra che ci sia un limite di numero di blocchi che una directory può occupare. Il numero esatto di file dipende da alcune cose, ad esempio la lunghezza del nome file. Questo gossamer-threads.com/lists/linux/kernel/921942 sembra indicare che con blocchi 4k dovresti essere in grado di avere più di 8 milioni di file in una directory. Erano nomi di file particolarmente lunghi?
Alex J. Roberts,

Ogni nome file aveva esattamente 36 caratteri.
BMDan,

beh, sono io a corto di idee :)
Alex J. Roberts,


4

find semplicemente non ha funzionato per me, anche dopo aver modificato i parametri di ext3 fs come suggerito dagli utenti sopra. Consumato troppa memoria. Questo script PHP ha funzionato: un utilizzo della CPU veloce e insignificante, un utilizzo insignificante della memoria:

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

Ho pubblicato una segnalazione di bug relativa a questo problema con find: http://savannah.gnu.org/bugs/?31961


Questo mi ha salvato !!
jestro,

3

Di recente ho riscontrato un problema simile e non sono riuscito a far funzionare il data=writebacksuggerimento di ring0 (probabilmente a causa del fatto che i file si trovano sulla mia partizione principale). Durante la ricerca di soluzioni alternative mi sono imbattuto in questo:

tune2fs -O ^has_journal <device>

Ciò disattiverà completamente il journaling, indipendentemente datadall'opzione assegnata mount. L'ho combinato con noatimeil volume dir_indeximpostato e sembrava funzionare abbastanza bene. L'eliminazione in realtà è terminata senza che io abbia bisogno di ucciderla, il mio sistema è rimasto reattivo e ora è di nuovo attivo e funzionante (con l'inserimento nel journal) senza problemi.


Stavo per suggerire di montarlo come ext2 anziché ext3, per evitare l'inserimento nel journal delle operazioni sui metadati. Questo dovrebbe fare lo stesso.
Peter Cordes,

3

Assicurati di fare:

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

che dovrebbe accelerare un po 'anche le cose.


4
Buona chiamata, ma è già montato mezzogiorno, come ho già detto nell'intestazione della domanda. E il nodiratime è ridondante; vedi lwn.net/Articles/245002 .
BMDan,

1
ppl ripeti questo mantra "noatime, nodiratime, nodevatime, noreadingdocsatime"
poige

2

È un comando molto lento. Provare:

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

rm -rf ha funzionato per un giorno e mezzo e alla fine l'ho ucciso, senza mai sapere se avesse effettivamente realizzato qualcosa. Avevo bisogno di una barra di avanzamento.
BMDan,

4
Per quanto riguarda il fatto che rm sia molto lento, "time find. -Delete" su file 30k: 0m0.357s / 0m0.019s / 0m0.337s real / user / sys. "time (ls -1U | xargs rm -f)" su quegli stessi file: 0m0.366s / 0m0.025s / 0m0.340s. Che è fondamentalmente un territorio con margine di errore.
BMDan,

1
Potresti aver appena corso strace -r -p <pid of rm>per collegarti al processo rm già in esecuzione. Quindi puoi vedere quanto velocemente unlinkpassano le chiamate di sistema. ( -rinserisce il tempo trascorso dalla precedente chiamata di sistema all'inizio di ogni linea.)
Peter Cordes

2

È dir_indeximpostato per il filesystem? ( tune2fs -l | grep dir_index) In caso contrario, abilitalo. Di solito è acceso per il nuovo RHEL.


1
Sì, è abilitato, ma fantastico suggerimento!
BMDan,

2

Un paio di anni fa, ho trovato una directory con 16 milioni di file XML nel /filesystem. A causa della critica del server, abbiamo usato il seguente comando che ha impiegato circa 30 ore per terminare:

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

Era un vecchio hdd a 7200 rpm e, nonostante il collo di bottiglia di IO e i picchi di CPU, il vecchio server web ha continuato il suo servizio.


1

La mia opzione preferita è l'approccio newfs, già suggerito. Il problema di base è, come già notato, la scansione lineare per gestire la cancellazione è problematica.

rm -rfdovrebbe essere quasi ottimale per un filesystem locale (NFS sarebbe diverso). Ma a milioni di file, 36 byte per nome file e 4 per inode (un'ipotesi, non verificando il valore per ext3), sono 40 * milioni, da conservare nella RAM solo per la directory.

A indovinare, stai distruggendo la memoria cache dei metadati del filesystem in Linux, in modo che i blocchi per una pagina del file di directory vengano espulsi mentre stai ancora usando un'altra parte, solo per colpire di nuovo quella pagina della cache quando la prossima il file è cancellato. L'ottimizzazione delle prestazioni di Linux non è la mia area, ma / proc / sys / {vm, fs} / probabilmente contiene qualcosa di rilevante.

Se puoi permetterti i tempi di inattività, potresti considerare di attivare la funzione dir_index. Cambia l'indice di directory da lineare a qualcosa di molto più ottimale per la cancellazione in grandi directory (hash con b-tree). tune2fs -O dir_index ...seguito da e2fsck -Davrebbe funzionato. Tuttavia, anche se sono sicuro che ciò possa aiutare prima che ci siano problemi, non so come si comporta la conversione (e2fsck con il -D) quando si ha a che fare con una directory v.large esistente. Backup + succhia-e-vedi.


1
pubbs.net/201008/squid/… suggerisce che /proc/sys/fs/vfs_cache_pressurepotrebbe essere il valore da utilizzare, ma non so se la directory stessa conta per la cache della pagina (perché è quello che è) o la cache dell'inode (perché, nonostante non sia un inode, sono metadati FS e raggruppati lì per questo motivo). Come ho già detto, l'ottimizzazione della VM Linux non è la mia area. Gioca e vedi cosa aiuta.
Phil P,

1

Ovviamente non mele alle mele qui, ma ho impostato un piccolo test e ho fatto quanto segue:

Creato 100.000 file da 512 byte in una directory ( dde /dev/urandomin un ciclo); ho dimenticato di cronometrarlo, ma ci sono voluti circa 15 minuti per creare quei file.

Esegui le seguenti operazioni per eliminare detti file:

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

100000

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

Questa è una scatola Pentium 4 da 2,8 GHz (credo che duecento GB IDE 7200 RPM; EXT3). Kernel 2.6.27.


Interessante, quindi forse è rilevante il fatto che i file siano stati creati per un lungo periodo di tempo? Ma questo non dovrebbe importare; la cache dei blocchi dovrebbe avere tutti i blocchi di metadati pertinenti nella RAM. Forse è perché unlink (2) è transazionale? Secondo te, la disattivazione del journaling per la durata della rm sarebbe una soluzione potenziale (anche se certamente un po 'pericolosa)? Non sembra che tu possa semplicemente disattivare il journaling su un filesystem montato senza tune2fs / fsck / reboot, che in qualche modo vanifica lo scopo.
BMDan,

Non posso commentarlo, ma aneddoticamente (in varie discussioni su NIX nel corso degli anni), ho sempre sentito che rmè terribilmente lento su un gran numero di file, quindi l' find -deleteopzione. Con un jolly sulla shell, espanderà ogni nome di file abbinato e suppongo che ci sia un buffer di memoria limitato per quello, in modo da poter vedere come sarebbe diventato inefficiente.
gravyface,

1
rm sarebbe lento perché sta cercando un file per nome, il che significa che scorre le voci della directory una alla volta fino a quando non lo trova. In questo caso, tuttavia, poiché ogni voce che viene consegnata è (a quel punto) la prima nell'elenco (ls -U / ls -f), dovrebbe essere quasi altrettanto veloce. Detto questo, rm -rf <dir>, che avrebbe dovuto correre come un campione, è stato lento come potrebbe essere. Forse è il momento di scrivere una patch per coreutils per accelerare le eliminazioni di massa? Forse sta segretamente sconvolgendo / ordinando in qualche modo ricorsivo per implementare rm -rf? Incertezze come questa sono il motivo per cui ho posto la domanda. ;)
BMDan,

1
Riavvia il computer dopo aver eseguito il passaggio di creazione. Dovresti ottenere un'eliminazione notevolmente più lenta.
Matt,

1

A volte Perl può fare miracoli in casi come questo. Hai già provato se un piccolo script come questo potrebbe superare i comandi bash e shell di base?

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

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

O un altro, forse anche più veloce, approccio Perl:

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

EDIT: Ho appena provato i miei script Perl. Quanto più prolisso fa qualcosa di giusto. Nel mio caso l'ho provato con un server virtuale con 256 MB di RAM e mezzo milione di file.

time find /test/directory | xargs rm i risultati:

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

rispetto a

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

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

Esito a immaginare cosa farebbe quella chiamata glob (); Presumo che faccia uno scandalo (). Se è così, ci vorrà SEMPRE per tornare. Una modifica del primo suggerimento che non pre-legge tutte le voci dir potrebbe avere delle gambe; tuttavia, nella sua forma attuale, anch'essa userebbe una quantità ingenua di CPU semplicemente leggendo tutte le voci della directory in una sola volta. Parte dell'obiettivo qui è dividere e conquistare; questo codice non è sostanzialmente diverso da 'rm -f * .png', nonostante i problemi con l'espansione della shell. Se aiuta, non c'è nulla nella directory che non volevo eliminare.
BMDan,

Devo provare di più non appena riesco a lavorare. Ho appena provato a creare 100000 file in una singola directory e ho trovato che la combinazione + xargs + rm ha impiegato 7,3 secondi, la combinazione Perl + unlink (glob) ... è terminata in 2,7 secondi. Provato un paio di volte, il risultato è sempre stato lo stesso. Al lavoro lo proverò con più file.
Janne Pikkarainen,

Ho imparato qualcosa di nuovo durante questo test. Almeno con ext3 ed ext4 la voce della directory stessa rimane enorme anche dopo aver eliminato tutti i file da lì. Dopo un paio di test la mia directory / tmp / test occupava 15 MB di spazio su disco. Esiste un altro modo per ripulirlo oltre a rimuovere la directory e ricrearla?
Janne Pikkarainen,

2
No, devi ricrearlo. Lo colpisco quando ho a che fare con un sistema di posta e una cartella per destinatario e ripuliscono dopo problemi significativi: non c'è altro modo, se non quello di creare una nuova directory e mescolare le directory, quindi nuocere a quella vecchia. Quindi puoi ridurre la finestra temporale quando non c'è directory, ma non eliminarla.
Phil P,

Nota che glob () ordinerà i risultati, proprio come fa normalmente il globbing della shell, quindi poiché hai solo 100k file, tutto si adatta facilmente e l'ordinamento è veloce. Con una directory molto più grande, vorresti opendir () / readdir () / closedir () solo per evitare l'ordinamento. [Dico normalmente per shell, poiché zsh ha un modificatore glob per rendere ordinamento non ordinato, il che è utile quando si ha a che fare con un numero elevato di file; *(oN)]
Phil P,

1

Da quello che ricordo l'eliminazione degli inode nel filesystem ext è O (n ^ 2), quindi più file si eliminano, più velocemente andrà il resto.

C'è stata una volta che ho dovuto affrontare un problema simile (anche se le mie stime hanno esaminato il tempo di cancellazione di circa 7 ore), alla fine è stato seguito il percorso suggerito da Jftuga nel primo commento .


0

Bene, questa non è una vera risposta, ma ...

Sarebbe possibile convertire il filesystem in ext4 e vedere se le cose cambiano?


Sembra che fare questo "live" richieda un fsck su un filesystem montato, il che è ... allarmante. Hai un modo migliore?
BMDan,

Il filesystem deve essere smontato prima della conversione, cioè prima dei necessari comandi tunefs.
marcoc,

0

Bene, questo è stato coperto in vari modi nel resto del thread, ma ho pensato di aggiungere due centesimi. Il colpevole delle prestazioni nel tuo caso è probabilmente readdir. Si sta recuperando un elenco di file che non sono necessariamente sequenziali sul disco e che causano l'accesso al disco ovunque quando si scollega. I file sono abbastanza piccoli che l'operazione di scollegamento probabilmente non salta troppo azzerando lo spazio. Se leggi readdir e poi ordina per inode crescente, probabilmente otterrai prestazioni migliori. Quindi readdir in ram (ordina per inode) -> scollega -> profitto.

Inode è un'approssimazione approssimativa qui penso .. ma basandosi sul tuo caso d'uso potrebbe essere abbastanza preciso ...


1
Correggimi se sbaglio, ma unlink (2) non azzera l'inode, rimuove semplicemente il riferimento ad esso dalla directory. Mi piace la faccia tosta di questo approccio, però. Ti interessa fare delle prove a cronometro e vedere se è vero?
BMDan,

0

Probabilmente avrei tirato fuori un compilatore in C e fatto l'equivalente morale della tua sceneggiatura. Cioè, usa opendir(3)per ottenere un handle di directory, quindi usa readdir(3)per ottenere il nome dei file, quindi aggiusta i file mentre li scollego e ogni tanto stampa "% d file cancellati" (e possibilmente tempo trascorso o timestamp corrente).

Non mi aspetto che sia notevolmente più veloce della versione dello script della shell, è solo che sono abituato a dover strappare il compilatore di tanto in tanto, sia perché non c'è modo pulito di fare ciò che voglio dalla shell o perché sebbene fattibile in shell, è improduttivamente lento in quel modo.


Poteva almeno iniziare modificando il codice sorgente di rm da coreutils .
Cristian Ciupitu,

0

Probabilmente stai riscontrando problemi di riscrittura con la directory. Prova a eliminare prima i file più recenti. Guarda le opzioni di mount che rimandano il writeback al disco.

Per una barra di avanzamento prova a eseguire qualcosa di simile rm -rv /mystuff 2>&1 | pv -brtl > /dev/null


In termini di eliminazione prima dei file più recenti: ls -Ur? Sono abbastanza sicuro che caricare le voci dir, quindi invertirle; Non credo che sia abbastanza intelligente da iniziare alla fine dell'elenco delle voci dir e tornare indietro fino all'inizio. "ls -1" probabilmente non è una grande idea, dal momento che occorrerebbero probabilmente più di 50 MB di core e diversi minuti; vorresti "ls -U" o "ls -f".
BMDan,

È probabilmente pratico solo se i nomi dei file aumentano in modo prevedibile. Comunque tu provi ls -1 reindirizzato su reverse, e reindirizzato su xargs. Usa i file anziché le pipe se vuoi vedere i tuoi risultati intermedi. Non hai fornito alcuna informazione sulla denominazione dei file. Genererai lo scalpiccio al contrario e cancelleresti i file usando il modello. Potrebbe essere necessario gestire voci di file mancanti. Dato il tuo commento sulla memoria richiesto, hai un'idea dell'I / O richiesto per riscrivere la directory.
BillThor,

0

Ecco come elimino i milioni di file di traccia che a volte possono essere raccolti su un server di database Oracle di grandi dimensioni:

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

Trovo che ciò si traduca in una cancellazione abbastanza lenta che ha un basso impatto sulle prestazioni del server, di solito qualcosa sulla falsariga di un'ora per milione di file su una configurazione "tipica" 10.000 IOPS.

Occorreranno spesso alcuni minuti prima che le directory vengano scansionate, generato l'elenco di file iniziale e il primo file venga eliminato. Da lì in poi, a. viene ripetuto per ogni file eliminato.

Il ritardo causato dall'eco al terminale ha dimostrato un ritardo sufficiente a prevenire qualsiasi carico significativo durante la cancellazione.


Ti viene mangiato vivo da un trauma. Che ne dici di qualcosa di più simile 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':? Aggiungi -deleteall'ultimo per eliminare effettivamente le cose; come scritto, elenca solo ciò che eliminerebbe. Nota che questo è ottimizzato per le circostanze in cui hai molte cose poco interessanti nelle directory vicine; in caso contrario, è possibile semplificare notevolmente la logica.
BMDan,

find -delete tende a causare troppi I / O e influisce facilmente sulle prestazioni di produzione. Forse con ionice.
Roy,

Sta causando tutto questo I / O semplicemente essendo più efficiente, però! Il globbing è tutto front-caricato per il tuo esempio (vale a dire, l'elenco completo dei file viene generato prima del primo rmaccada), in modo da avere relativamente efficiente I / O all'avvio da quella, seguita da dolorose, out-of-order rms che probabilmente non causano molto I / O, ma implicano a scandircamminare ripetutamente nella directory (non causando I / O perché è già stato caricato nella cache dei blocchi; vedi anche vfs_cache_pressure). Se desideri rallentare le cose, ioniceè un'opzione, ma probabilmente userò secondi frazionari sleep.
BMDan,

find /u*/app/*/diag -path '*/trace/*.tr' -execdir rm {} +eseguiresti uno rmper directory, quindi avresti meno sovraccarico della CPU. Finché hai un sacco di tempo da dedicare alla CPU, rallentando l'IO del disco eseguendo un intero rmprocesso per ogni unlinklavoro, immagino, ma è brutto. perl con un sonno per scollegamento sarebbe più bello se dormire tra rmintere directory alla volta fosse troppo pieno. ( -execdir sh -c ...forse)
Peter Cordes,

-1

È possibile utilizzare le funzionalità di parallelizzazione 'xargs':

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

1
Questo non aiuta. Il collo di bottiglia è la scarsa I / O casuale sull'unità. L'eliminazione parallela potrebbe peggiorare ulteriormente e aumentare il carico della CPU.
Wim Kerkhoff,

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

1
Wow. Immagino che rientri abbastanza saldamente nel campo "più di un modo per scuoiare un gatto". Seriamente, però, con l'ordinamento e l'unicq? "ls" ordina per impostazione predefinita, comunque, e spero sicuramente che i nomi dei file siano unici. : /
BMDan

-2

in realtà, questo è un po 'meglio se la shell che usi fa l'espansione della riga di comando:

ls|cut -c -4|sort|uniq|awk '{ print "echo " $1 ";rm -rf " $1 "*"}' |sh
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.