Embora uma das principais causas desse problema seja o desempenho do 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 gera uma lista de arquivos. readdir é uma chamada posix, mas a chamada real do sistema Linux que está sendo usada aqui é chamada de 'getdents'. Getdents lista as entradas do diretório preenchendo um buffer com entradas.
O problema é principalmente o fato de que o readdir () usa um tamanho de buffer fixo de 32Kb para buscar arquivos. À medida que um diretório fica maior e maior (o tamanho aumenta à medida que os arquivos são adicionados), o ext3 fica mais lento e lento para buscar entradas e o tamanho adicional do buffer de 32 Kb do readdir é 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 cara chamada do sistema repetidamente.
Por exemplo, em um diretório de teste criado com mais de 2,6 milhões de arquivos, a execução de "ls -1 | wc-l" mostra uma grande saída de strace 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 neste diretório foi significativo.
$ time ls -1 | wc -l
2616044
real 0m20.609s
user 0m16.241s
sys 0m3.639s
O método para tornar isso um processo mais eficiente é chamar getdents manualmente com um buffer muito maior. Isso melhora significativamente o desempenho.
Agora, você não deve chamar getdents manualmente, então não existe nenhuma interface para usá-lo normalmente (verifique a página de manual de getdents para ver!), mas você pode chamá-lo manualmente e fazer sua chamada de chamada do sistema é muito 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);
}
Embora isso não combata o problema fundamental subjacente (muitos arquivos, em um sistema de arquivos que executa mal nele). É provável que seja muito, muito mais rápido do que muitas das alternativas postadas.
Como premeditação, deve-se remover o diretório afetado e refazê-lo depois. Os diretórios só aumentam de tamanho e podem permanecer com desempenho insatisfatório mesmo com alguns arquivos internos devido ao tamanho do diretório.
Editar: eu limpei isso um pouco. Adicionado uma opção para permitir que você apague na linha de comando em tempo de execução e removeu um monte de coisas de árvores 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ões 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 funcionasse tão bem!