rm em um diretório com milhões de arquivos


104

Histórico: servidor físico, com cerca de dois anos, unidades SATA de 7200-RPM conectadas a uma placa 3Ware RAID, ext3 FS montado noatime e dados = pedidos, sem carga louca, kernel 2.6.18-92.1.22.el5, tempo de atividade 545 dias . O diretório não contém subdiretórios, apenas milhões de arquivos pequenos (~ 100 bytes), com alguns maiores (alguns KB).

Temos um servidor que ficou um pouco covarde ao longo dos últimos meses, mas só o notamos outro dia quando começou a não conseguir gravar em um diretório por conter muitos arquivos. Especificamente, ele começou a lançar esse erro em / var / log / messages:

ext3_dx_add_entry: Directory index full!

O disco em questão possui muitos inodes restantes:

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

Então, acho que isso significa que atingimos o limite de quantas entradas podem estar no próprio arquivo de diretório. Não faço ideia de quantos arquivos seriam, mas não pode ser mais do que três milhões, como você pode ver. Não que isso seja bom, lembre-se! Mas essa é uma parte da minha pergunta: exatamente qual é esse limite superior? É sintonizável? Antes de eu chegar gritado-Quero ajustá-lo para baixo ; esse diretório enorme causou todo tipo de problemas.

Enfim, rastreamos o problema no código que estava gerando todos esses arquivos e o corrigimos. Agora estou preso em excluir o diretório.

Algumas opções aqui:

  1. rm -rf (dir)

    Eu tentei isso primeiro. Desisti e matei depois que durou um dia e meio, sem qualquer impacto perceptível.

  2. unlink (2) no diretório: definitivamente vale a pena considerar, mas a questão é se seria mais rápido excluir os arquivos dentro do diretório via fsck do que excluir via unlink (2). Ou seja, de uma forma ou de outra, tenho que marcar esses inodes como não utilizados. Isso pressupõe, é claro, que eu posso dizer ao fsck para não soltar entradas nos arquivos em / lost + found; caso contrário, acabei de mudar meu problema. Além de todas as outras preocupações, depois de ler um pouco mais sobre isso, provavelmente eu teria que chamar algumas funções internas do FS, pois nenhuma das variantes de desvinculação (2) que posso encontrar me permitiria excluir alegremente um diretório com entradas. Pooh.
  3. while [ true ]; do ls -Uf | head -n 10000 | xargs rm -f 2>/dev/null; done )

    Esta é realmente a versão abreviada; o real que estou executando, que apenas adiciona alguns relatórios de progresso e uma parada limpa quando ficamos sem arquivos para excluir, é:

    exportar i = 0;
    time (while [true]; faça
      ls -Uf | cabeça -n 3 | grep -qF '.png' || pausa;
      ls -Uf | cabeça -n 10000 | xargs rm -f 2> / dev / null;
      exportar i = $ (($ i + 10000));
      eco "$ i ...";
    feito )

    Isso parece estar funcionando muito bem. Enquanto escrevo isso, ele excluiu 260.000 arquivos nos últimos trinta minutos.

Agora, para as perguntas:
  1. Como mencionado acima, o limite de entrada por diretório é ajustável?
  2. Por que demorou "7m9.561s / usuário 0m0.001s / sys 0m0.001s" reais para excluir um único arquivo que foi o primeiro na lista retornado ls -Ue demorou talvez dez minutos para excluir as primeiras 10.000 entradas com o comando comando no # 3, mas agora ele está indo muito feliz? Na verdade, ele excluiu 260.000 em cerca de trinta minutos, mas agora são necessários mais quinze minutos para excluir mais 60.000. Por que as enormes oscilações de velocidade?
  3. Existe uma maneira melhor de fazer esse tipo de coisa? Não armazene milhões de arquivos em um diretório; Eu sei que isso é bobagem, e isso não teria acontecido no meu relógio. Pesquisar no Google e analisar SF e SO oferece muitas variações findque não serão significativamente mais rápidas do que minha abordagem por várias razões evidentes. Mas a idéia delete-via-fsck tem alguma perna? Ou algo completamente diferente? Estou ansioso para ouvir o pensamento fora da caixa (ou dentro da caixa não é bem conhecida).
Obrigado por ler o pequeno romance; sinta-se à vontade para fazer perguntas e responderei. Também atualizarei a pergunta com o número final de arquivos e por quanto tempo o script de exclusão foi executado depois que o tiver.

Saída final do script !:

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

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

Assim, três milhões de arquivos excluídos em pouco mais de quatro horas.


1
O rm (GNU coreutils) 8.4 tem esta opção: "-v, --verbose explica o que está sendo feito" . Ele exibirá todos os arquivos que estão sendo excluídos.
Cristian Ciupitu 27/09/10

2
Na verdade, seria uma maneira interessante de fazer uma barra de progresso: como cada arquivo teria trinta e sete caracteres (36 + a '\ n'), eu poderia escrever facilmente um analisador para isso, e como printf () é cheap e o comando rm já tem o nome do arquivo carregado, não há penalidade de desempenho especial. Parece um iniciante por fazer todo o trabalho, já que eu nunca conseguia "rm" fazer algo assim, de qualquer maneira. Mas poderia funcionar muito bem como uma barra de progresso intra-10.000; talvez um "." para cada cem arquivos?
BMDan

8
rm -rfv | pv -l >/dev/null. O pv deve estar disponível no repositório EPEL .
Cristian Ciupitu 27/09/10

5
PV é esmagadoramente impressionante. Deixo um rastro de instalações fotovoltaicas no meu caminho.
BMDan

Eu tive exatamente esse mesmo problema recentemente. Obrigado!
Richo

Respostas:


30

A data=writebackopção de montagem merece ser tentada, a fim de impedir o registro no diário do sistema de arquivos. Isso deve ser feito apenas durante o tempo de exclusão, no entanto, existe um risco se o servidor estiver sendo desligado ou reiniciado durante a operação de exclusão.

De acordo com esta página ,

Algumas aplicações mostram uma melhoria muito significativa da velocidade quando é usada. Por exemplo, melhorias de velocidade podem ser vistas (...) quando os aplicativos criam e excluem grandes volumes de arquivos pequenos.

A opção é configurada dentro fstabou durante a operação de montagem, substituindo data=orderedpor data=writeback. O sistema de arquivos que contém os arquivos a serem excluídos precisa ser remontado.


1
Ele também pode aumentar o tempo com a commit opção : "Esse valor padrão (ou qualquer valor baixo) prejudicará o desempenho, mas é bom para a segurança dos dados. A configuração para 0 terá o mesmo efeito que deixá-lo no padrão (5 segundos) A configuração para valores muito grandes melhorará o desempenho ".
Cristian Ciupitu 26/09/10

1
O write-back parece estelar, exceto a documentação que eu estava visualizando ( gentoo.org/doc/en/articles/l-afig-p8.xml#doc_chap4 ) menciona explicitamente que ainda registra metadados, que eu presumo incluir todos os dados que estou mudando (certamente não estou alterando nenhum dado nos arquivos). O meu entendimento da opção está incorreto?
BMDan 27/09/10

Por fim, FYI, não mencionado nesse link, é o fato de que data = write-back pode ser uma grande falha de segurança, pois os dados apontados por uma determinada entrada podem não ter os dados que foram gravados pelo aplicativo, o que significa que pode ocorrer uma falha nos dados antigos, possivelmente sensíveis / privados, sendo expostos. Não é uma preocupação aqui, já que estamos apenas ativando temporariamente, mas eu queria alertar a todos sobre essa ressalva, caso você ou outras pessoas que se deparassem com essa sugestão não estivessem cientes.
BMDan 27/09/10

commit: isso é bem liso! Obrigado pelo ponteiro.
BMDan 27/09/10

2
data=writebackainda registra os metadados antes de gravá-los no sistema de arquivos principal. Pelo que entendi, simplesmente não impõe a ordem entre coisas como escrever um mapa de extensão e gravar dados nessas extensões. Talvez haja outras restrições de pedidos que relaxam também, se você perceber um ganho de desempenho com isso. Obviamente, montar sem o diário pode ter um desempenho ainda maior. (Isso pode permitir que as alterações de metadados aconteçam na RAM, sem a necessidade de ter nada no disco antes da conclusão da operação de desvinculação).
Peter Cordes

80

Embora uma das principais causas desse problema seja o desempenho ext3 com milhões de arquivos, a causa raiz real desse problema é diferente.

Quando um diretório precisa ser listado, readdir () é chamado no diretório que produz uma lista de arquivos. O readdir é uma chamada posix, mas a chamada real do sistema Linux que está sendo usada aqui é chamada de 'getdents'. Os getdents listam as entradas do diretório preenchendo um buffer com as entradas.

O problema se deve principalmente ao fato de que o readdir () usa um tamanho de buffer fixo de 32 KB para buscar arquivos. À medida que um diretório se torna cada vez maior (o tamanho aumenta à medida que os arquivos são adicionados), o ext3 fica cada vez mais lento para buscar entradas e o tamanho do buffer de 32 KB do readdir adicional é suficiente apenas para incluir uma fração das entradas no diretório. Isso faz com que o readdir faça um loop repetidamente e invoque a chamada cara do sistema repetidamente.

Por exemplo, em um diretório de teste que criei com mais de 2,6 milhões de arquivos, executar "ls -1 | wc-l" mostra uma grande saída de rastreio de muitas chamadas de 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
...

Além disso, o tempo gasto nesse diretório foi significativo.

$ time ls -1 | wc -l
2616044

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

O método para tornar esse processo mais eficiente é chamar getdents manualmente com um buffer muito maior. Isso melhora significativamente o desempenho.

Agora, você não deve chamar os getdents manualmente, para que não exista uma interface para usá-lo normalmente (verifique a página de manual para obter os getdents!), No entanto, você pode chamá-lo manualmente e tornar a chamada de sistema mais eficiente.

Isso reduz drasticamente o tempo necessário para buscar esses arquivos. Eu escrevi um programa que faz isso.

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

Enquanto isso não combate o problema fundamental subjacente (muitos arquivos, em um sistema de arquivos com baixo desempenho). É provável que seja muito, muito mais rápido do que muitas das alternativas postadas.

Como uma previsão, deve-se remover o diretório afetado e refazê-lo depois. Os diretórios apenas aumentam de tamanho e podem permanecer com desempenho fraco, mesmo com alguns arquivos internos devido ao tamanho do diretório.

Edit: Eu limpei isso um pouco. Adicionada uma opção para permitir que você exclua na linha de comando em tempo de execução e removeu um monte de coisas na calçada que, honestamente, olhando para trás, era questionável na melhor das hipóteses. Também foi mostrado para produzir corrupção de memória.

Agora você pode fazer dentls --delete /my/path

Novos resultados. Baseado em um diretório com 1,82 milhão de arquivos.

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

Fiquei surpreso que isso ainda funcione tão bem!


1
Duas pequenas preocupações: uma, [256]provavelmente deveria ser [FILENAME_MAX], e duas, meu Linux (2.6.18 == CentOS 5.x) parece não incluir uma entrada d_type no dirent (pelo menos de acordo com getdents (2)).
BMDan

1
Você poderia elaborar um pouco sobre o reequilíbrio do btree e por que a exclusão em ordem ajuda a evitá-lo? Eu tentei pesquisar no Google por isso, infelizmente sem sucesso.
Ovgolovin

1
Porque agora parece-me que estamos excluindo em ordem, forçamos o reequilíbrio, à medida que removemos as folhas de um lado e as do outro: en.wikipedia.org/wiki/B-tree#Rebalancing_after_deletion
ovgolovin

1
Espero não incomodá-lo com esse assunto. Mas ainda assim comecei uma pergunta sobre a exclusão de arquivos em ordem stackoverflow.com/q/17955459/862380 , que parece não receber uma resposta que explique o problema com o exemplo, o que será compreensível para programadores comuns. Se você tiver tempo e sentir-se assim, poderia investigar? Talvez você possa escrever uma explicação melhor.
Ovgolovin

2
Este é um pedaço incrível de código. Foi a única ferramenta que consegui listar e excluir 11.000.000 (onze milhões) de arquivos de sessão criados em um diretório, provavelmente ao longo de alguns anos. O processo do Plesk que deveria mantê-los sob controle usando o find e outros truques em outras respostas aqui, não conseguiu concluir uma execução, portanto os arquivos continuavam se acumulando. É uma homenagem à árvore binária que o sistema de arquivos usa para armazenar o diretório, que as sessões foram capazes de funcionar - você pode criar um arquivo e recuperá-lo sem demora. Apenas listagens eram inutilizáveis.
Jason

31

Seria possível fazer backup de todos os outros arquivos deste sistema de arquivos em um local de armazenamento temporário, reformatar a partição e restaurar os arquivos?


3
Eu realmente gosto desta resposta, na verdade. Por uma questão prática, neste caso, não, mas não é algo em que eu teria pensado. Bravo!
BMDan

Exatamente o que eu estava pensando também. Esta é uma resposta para a pergunta 3. Ideal se você me perguntar :)
Joshua

12

Não existe um limite de arquivo por diretório no ext3, apenas o limite de inode do sistema de arquivos (acho que existe um limite no número de subdiretórios).

Você ainda pode ter problemas após remover os arquivos.

Quando um diretório possui milhões de arquivos, a própria entrada do diretório se torna muito grande. A entrada do diretório deve ser varrida para todas as operações de remoção e isso leva várias quantidades de tempo para cada arquivo, dependendo de onde sua entrada está localizada. Infelizmente, mesmo depois que todos os arquivos foram removidos, a entrada do diretório mantém seu tamanho. Portanto, outras operações que exigem a verificação da entrada do diretório ainda levarão muito tempo, mesmo que o diretório esteja vazio agora. A única maneira de resolver esse problema é renomear o diretório, criar um novo com o nome antigo e transferir os arquivos restantes para o novo. Em seguida, exclua o renomeado.


Na verdade, notei esse comportamento depois de excluir tudo. Felizmente, nós já removemos o diretório da "linha de fogo", por assim dizer, para que eu pudesse controlá-lo.
BMDan

2
Dito isto, se não há limite de arquivos por diretório, por que obtive "ext3_dx_add_entry: índice de diretório cheio!" quando ainda havia inodes disponíveis nessa partição? Não havia subdiretórios dentro deste diretório.
BMDan

3
hmm, pesquisei um pouco mais e parece que há um limite de número de blocos que um diretório pode ocupar. O número exato de arquivos depende de algumas coisas, como o tamanho do nome do arquivo. Este gossamer-threads.com/lists/linux/kernel/921942 parece indicar que, com blocos de 4k, você poderá ter mais de 8 milhões de arquivos em um diretório. Eles eram nomes de arquivos particularmente longos?
Alex J. Roberts

Cada nome de arquivo tinha exatamente 36 caracteres.
precisa saber é o seguinte

bem, isso é me fora de ideias :)
Alex J. Roberts


4

O find simplesmente não funcionou para mim, mesmo depois de alterar os parâmetros do ext3 fs, conforme sugerido pelos usuários acima. Consumiu muita memória. Este script PHP fez o truque - uso rápido e insignificante da CPU, uso insignificante de memória:

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

Publiquei um relatório de bug sobre esse problema com o find: http://savannah.gnu.org/bugs/?31961


Isso me salvou !!
jestro

3

Recentemente, enfrentei um problema semelhante e não consegui fazer a data=writebacksugestão do ring0 funcionar (possivelmente devido ao fato de os arquivos estarem na minha partição principal). Enquanto pesquisava soluções alternativas, deparei-me com isso:

tune2fs -O ^has_journal <device>

Isso desativará completamente o diário, independentemente da dataopção a ser fornecida mount. Combinei isso com noatimeo volume dir_indexdefinido e parecia funcionar muito bem. A exclusão realmente terminou sem que eu precisasse matá-la, meu sistema permaneceu responsivo e agora está de volta a funcionar (com o diário novamente) sem problemas.


Eu sugeriria montá-lo como ext2 em vez de ext3, para evitar o registro no diário das operações de metadados. Isso deve fazer o mesmo.
Peter Cordes

3

Certifique-se de fazer:

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

o que também deve acelerar um pouco as coisas.


4
Boa chamada, mas já está montado noatime, como mencionei no cabeçalho da pergunta. E o nodiratime é redundante; consulte lwn.net/Articles/245002 .
BMDan 27/09/10

1
ppl repetir esse mantra "noatime, nodiratime, nodevatime, noreadingdocsatime"
poige

2

É um comando muito lento. Experimentar:

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

rm -rf correu por um dia e meio, e eu finalmente o matei, sem nunca saber se realmente havia conseguido alguma coisa. Eu precisava de uma barra de progresso.
BMDan

4
Quanto a ser muito lento, "time find. -Dete" em 30k arquivos: 0m0.357s / 0m0.019s / 0m0.337s real / user / sys. "time (ls -1U | xargs rm -f)" nesses mesmos arquivos: 0m0.366s / 0m0.025s / 0m0.340s. Que é basicamente território com margem de erro.
BMDan

1
Você poderia simplesmente executar strace -r -p <pid of rm>para se conectar ao processo rm já em execução. Então você pode ver com que rapidez as unlinkchamadas do sistema estão passando. ( -rColoca o tempo desde que a chamada de sistema anterior, no início de cada linha.)
Peter Cordes

2

Está dir_indexdefinido para o sistema de arquivos? ( tune2fs -l | grep dir_index) Caso contrário, ative-o. Geralmente está ativado para o novo RHEL.


1
Sim, está ativado, mas sugestão incrível!
precisa saber é o seguinte

2

Alguns anos atrás, encontrei um diretório com 16 milhões de arquivos XML no /sistema de arquivos. Devido à crítica do servidor, usamos o seguinte comando que levou cerca de 30 horas para concluir:

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

Era um disco rígido antigo de 7200 rpm e, apesar do gargalo de E / S e dos picos de CPU, o servidor da web antigo continuou seu serviço.


1

Minha opção preferida é a abordagem newfs, já sugerida. O problema básico é, novamente, como já observado, a verificação linear para lidar com a exclusão é problemática.

rm -rfdeve estar próximo do ideal para um sistema de arquivos local (o NFS seria diferente). Mas em milhões de arquivos, 36 bytes por nome de arquivo e 4 por inode (um palpite, sem verificar o valor do ext3), são 40 * milhões, para serem mantidos na RAM apenas para o diretório.

Suponho que você esteja debulhando a memória cache de metadados do sistema de arquivos no Linux, para que os blocos de uma página do arquivo de diretório sejam eliminados enquanto você ainda estiver usando outra parte, apenas para acessar a página do cache novamente na próxima arquivo é excluído. O ajuste de desempenho do Linux não é minha área, mas / proc / sys / {vm, fs} / provavelmente contém algo relevante.

Se você puder pagar o tempo de inatividade, considere ativar o recurso dir_index. Ele muda o índice do diretório de linear para algo muito mais ideal para exclusão em diretórios grandes (árvores com hash b). tune2fs -O dir_index ...seguido por e2fsck -Diria funcionar. No entanto, embora eu esteja confiante de que isso ajudaria antes que -Docorram problemas, não sei como a conversão (e2fsck com the ) é executada ao lidar com um diretório v.large existente. Backups + suck-it-and-see.


1
pubbs.net/201008/squid/… sugere que esse /proc/sys/fs/vfs_cache_pressurepode ser o valor a ser usado, mas não sei se o próprio diretório conta para o cache da página (porque é isso que é) ou para o cache do inode (porque, apesar de não ser um inode, são metadados FS e agrupados por esse motivo). Como eu disse, o ajuste da VM do Linux não é minha área. Jogue e veja o que ajuda.
Phil P

1

Obviamente não maçãs para maçãs aqui, mas eu configurei um pequeno teste e fiz o seguinte:

Criou 100.000 arquivos de 512 bytes em um diretório ( dde /dev/urandomem um loop); esqueci de cronometrar, mas levou cerca de 15 minutos para criar esses arquivos.

Execute o seguinte para excluir os arquivos mencionados:

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

100000

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

Esta é uma caixa Pentium 4 de 2,8 GHz (algumas centenas de GB de IDE 7200 RPM, eu acho; EXT3). Kernel 2.6.27.


Interessante, talvez o fato de os arquivos serem criados por um longo período seja relevante? Mas isso não deveria importar; o cache do bloco deve ter todos os blocos de metadados pertinentes na RAM. Talvez seja porque unlink (2) é transacional? Na sua opinião, desativar o registro no diário pela duração da empresa seria uma solução potencial (embora admitida que seja um pouco perigosa)? Não parece que você pode simplesmente desativar o registro em diário inteiramente em um sistema de arquivos montado sem um tune2fs / fsck / reboot, o que de certa forma prejudica o objetivo.
BMDan 27/09/10

Não posso comentar sobre isso, mas, anedoticamente (em várias discussões do NIX ao longo dos anos), sempre ouvi dizer que rmé terrivelmente lento em um grande número de arquivos, daí a find -deleteopção. Com um curinga no shell, ele expandirá cada nome de arquivo correspondente, e eu suponho que haja um buffer de memória limitado para isso, para que você possa ver como isso seria ineficiente.
Gravyface 28/09/10

1
rm seria lento porque está procurando um arquivo pelo nome, o que significa percorrer as entradas do diretório, uma a uma, até encontrá-lo. Nesse caso, no entanto, como cada entrada que está sendo entregue é (nesse ponto) a primeira da lista (ls -U / ls -f), deve ser quase a mesma velocidade. Dito isto, rm -rf <dir>, que deveria ter funcionado como um campeão, foi o mais lento possível. Talvez seja hora de escrever um patch no coreutils para acelerar exclusões maciças? Talvez seja secretamente globbing / classificação de alguma maneira recursiva, a fim de implementar rm -rf? Incertezas como essa são o motivo de eu fazer a pergunta. ;)
BMDan 30/09/10

1
Reinicie a máquina depois de executar a etapa de criação. Você deve obter uma exclusão visivelmente mais lenta.
Matt

1

Às vezes, Perl pode fazer maravilhas em casos como este. Você já tentou se um script pequeno como esse poderia superar o bash e os comandos básicos do shell?

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

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

Ou outra abordagem, talvez ainda mais rápida, do Perl:

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

Edição: Eu apenas tentei meus scripts Perl. Quanto mais detalhado alguém faz algo certo. No meu caso, tentei isso com um servidor virtual com 256 MB de RAM e meio milhão de arquivos.

time find /test/directory | xargs rm resultados:

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

comparado com

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

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

Hesito em imaginar o que aquela chamada glob () faria; Eu suponho que faz um scandir (). Se assim for, isso vai levar para sempre voltar. Uma modificação da primeira sugestão que não pré-lê todas as entradas de dir pode ter algumas pernas; no entanto, em sua forma atual, ele também usaria uma quantidade profana de CPU apenas lendo todas as entradas de diretório de uma só vez. Parte do objetivo aqui é dividir e conquistar; esse código não é fundamentalmente diferente de 'rm -f * .png', apesar dos problemas com a expansão do shell. Se ajudar, não há nada no diretório que eu não queira excluir.
BMDan 27/09/10

Tenho que tentar mais assim que chegar ao trabalho. Eu apenas tentei criar 100 000 arquivos em um único diretório e a combinação + xargs + rm levou 7,3 segundos, a combinação Perl + unlink (glob) ... terminou em 2,7 segundos. Tentei isso duas vezes, o resultado sempre foi o mesmo. No trabalho, tentarei isso com mais arquivos.
Janne Pikkarainen

Aprendi algo novo ao testar isso. Pelo menos com ext3 e ext4, a própria entrada do diretório permanece enorme, mesmo após a exclusão de todos os arquivos de lá. Após alguns testes, meu diretório / tmp / test estava ocupando 15 MB de espaço em disco. Existe outra maneira de limpar isso além de remover o diretório e recriá-lo?
Janne Pikkarainen

2
Não, você precisa recriá-lo. Eu acertei isso ao lidar com um sistema de correio, pasta por destinatário e limpezas após problemas significativos: não há outra maneira, a não ser criar um novo diretório e embaralhar os diretórios, e depois remover o antigo. Assim, você pode reduzir a janela de tempo quando não houver diretório, mas não eliminá-lo.
Phil P

Observe que glob () classificará os resultados, da mesma forma que o globbing do shell normalmente, portanto, como você possui apenas 100k arquivos, tudo se ajusta facilmente e a classificação é rápida. Com um diretório muito maior, você deseja opendir () / readdir () / closedir () apenas para evitar a classificação. [Eu digo normalmente para o shell, já que o zsh possui um modificador glob para classificar a ordem de classificação, o que é útil ao lidar com um grande número de arquivos; *(oN)]
Phil P

1

Pelo que me lembro, a exclusão de inodes nos sistemas de arquivos ext é O (n ^ 2); portanto, quanto mais arquivos você excluir, mais rápido o restante será.

Houve uma vez em que me deparei com um problema semelhante (embora minhas estimativas parecessem um tempo de exclusão de ~ 7h), no final a rota sugerida pelo jftuga no primeiro comentário .


0

Bem, esta não é uma resposta real, mas ...

Seria possível converter o sistema de arquivos para ext4 e ver se as coisas mudam?


Parece que fazer isso "ao vivo" requer um fsck em um sistema de arquivos montado, o que é ... alarmante. Tem uma maneira melhor?
BMDan

O sistema de arquivos deve ser desmontado antes da conversão, ou seja, antes dos comandos tunefs necessários.
marcoc

0

Tudo bem, isso foi abordado de várias maneiras no restante do tópico, mas eu pensei em colocar meus dois centavos. O culpado de desempenho no seu caso provavelmente é o readdir. Você está recebendo de volta uma lista de arquivos que não são necessariamente seqüenciais no disco, o que está causando acesso ao disco em todo o lugar quando você desvincula. Os arquivos são pequenos o suficiente para que a operação de desvinculação provavelmente não salte demais zerando o espaço. Se você ler e depois classificar por inode crescente, provavelmente obterá melhor desempenho. Então readdir em ram (classificar por inode) -> desvincular -> lucro.

Inode é uma aproximação aproximada aqui, eu acho .. mas com base no seu caso de uso, pode ser bastante preciso ...


1
Corrija-me se eu estiver errado, mas desvincular (2) não zera o inode, apenas remove a referência a ele do diretório. Eu gosto do chutzpah dessa abordagem, no entanto. Gostaria de executar alguns testes de tempo e ver se isso é verdade?
BMDan 30/09/10

0

Eu provavelmente teria escolhido um compilador C e feito o equivalente moral do seu script. Ou seja, use opendir(3)para obter um identificador de diretório, use readdir(3)para obter o nome dos arquivos e, em seguida, registre os arquivos conforme eu os desassocio e de vez em quando imprima "% d arquivos excluídos" (e possivelmente o tempo decorrido ou o carimbo de data / hora atual).

Eu não espero que seja visivelmente mais rápido que a versão do shell script, é só que eu tenho que rasgar o compilador de vez em quando, porque não há uma maneira limpa de fazer o que eu quero do shell ou porque enquanto praticável com casca, é improdutivamente lento dessa maneira.


Ele poderia pelo menos começar modificando o código fonte da rm a partir do coreutils .
Cristian Ciupitu 26/09/10

0

Você provavelmente está tendo problemas de reescrita no diretório. Tente excluir os arquivos mais recentes primeiro. Veja as opções de montagem que adiarão o write-back para o disco.

Para uma barra de progresso, tente executar algo como rm -rv /mystuff 2>&1 | pv -brtl > /dev/null


Em termos de excluir os arquivos mais recentes primeiro: ls -Ur? Tenho certeza de que carregaria as entradas dir e depois as inverteu; Eu não acredito que o ls seja inteligente o suficiente para começar no final da lista de entradas do diretório e voltar no início. "ls -1" também provavelmente não é uma ótima idéia, pois provavelmente levaria mais de 50 MB de núcleo e vários minutos para ser executado; você deseja "ls -U" ou "ls -f".
BMDan 27/09/10

Provavelmente, só é prático se os nomes dos arquivos aumentarem em um padrão previsível. No entanto, você pode tentar ls -1 canalizado para reverter e canalizado para xargs. Use arquivos em vez de pipes se desejar ver seus resultados intermediários. Você não forneceu nenhuma informação sobre a nomeação de arquivos. Você geraria o padrão em sentido inverso e excluiria os arquivos usando o padrão. Pode ser necessário lidar com entradas de arquivos ausentes. Dado o seu comentário sobre a memória necessária, você tem uma idéia da necessidade de E / S para reescrever o diretório.
BillThor

0

Aqui está como eu excluo os milhões de arquivos de rastreamento que às vezes podem se reunir em um grande servidor de banco de dados Oracle:

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

Acho que isso resulta em uma exclusão bastante lenta que tem baixo impacto no desempenho do servidor, geralmente algo como uma hora por milhão de arquivos em uma configuração "típica" de 10.000 IOPS.

Geralmente, leva alguns minutos para que os diretórios sejam verificados, a lista de arquivos inicial gerada e o primeiro arquivo seja excluído. A partir daí, a. é ecoado para cada arquivo excluído.

O atraso causado pelo eco no terminal provou ser um atraso suficiente para evitar qualquer carga significativa enquanto a exclusão está em andamento.


Você está sendo comido vivo por globbing. Que tal algo mais como 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':? Adicione -deleteao último para realmente excluir as coisas; conforme escrito, apenas lista o que excluiria. Observe que isso é otimizado para circunstâncias em que você tem muitas coisas desinteressantes em diretórios próximos; se não for esse o caso, você pode simplificar bastante a lógica.
BMDan

find -delete tende a causar muita E / S e afeta facilmente o desempenho da produção. Talvez com ionice.
Roy

Está causando toda essa E / S simplesmente por ser mais eficiente! O englobamento é tudo front-carregado para o seu exemplo (isto é, a lista completa dos arquivos é gerado antes do primeiro rmacontece), então você tem relativamente eficiente I / O na inicialização disso, seguido por dolorosa, out-of-order rms isso provavelmente não causa muita E / S, mas envolve scandirandar repetidamente no diretório (não causando E / S porque já foi carregado no cache do bloco; veja também vfs_cache_pressure). Se você quiser desacelerar as coisas, ioniceé uma opção, mas eu provavelmente usaria segundos fracionários sleep.
BMDan

find /u*/app/*/diag -path '*/trace/*.tr' -execdir rm {} +executaria um rmpor diretório, para que você tivesse menos sobrecarga da CPU. Contanto que você tenha muito tempo de CPU disponível, limitando a E / S do disco, bifurcando um rmprocesso inteiro para todas as unlinkobras, eu acho, mas é feio. o perl com uma suspensão por desvinculação seria melhor se a suspensão entre rmdiretórios inteiros por vez estiver muito cheia. ( -execdir sh -c ...Talvez)
Peter Cordes

-1

Você pode usar os recursos de paralelização 'xargs':

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

1
Isso não vai ajudar. O gargalo é a pobre E / S aleatória na unidade. Fazer exclusões paralelas pode piorar ainda mais e apenas aumentar a carga da CPU.
Wm Kerkhoff

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

1
Uau. Eu acho que isso se encaixa bastante no campo "mais de uma maneira de esfolar um gato". Sério, porém, com o tipo e o uniq? "ls" é ordenado por padrão, e espero que os nomes dos arquivos sejam únicos. : /
BMDan 30/09/10

-2

na verdade, este é um pouco melhor se o shell que você usa expandir a linha de comando:

ls|cut -c -4|sort|uniq|awk '{ print "echo " $1 ";rm -rf " $1 "*"}' |sh
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.