rm en un directorio con millones de archivos


104

Antecedentes: servidor físico, aproximadamente dos años de antigüedad, unidades SATA de 7200 RPM conectadas a una tarjeta RAID 3Ware, ext3 FS montado noatime y data = ordenado, no bajo carga loca, kernel 2.6.18-92.1.22.el5, tiempo de actividad 545 días . El directorio no contiene ningún subdirectorio, solo millones de archivos pequeños (~ 100 bytes), con algunos más grandes (algunos KB).

Tenemos un servidor que se ha vuelto un poco loco en el transcurso de los últimos meses, pero solo lo notamos el otro día cuando comenzó a no poder escribir en un directorio debido a que contenía demasiados archivos. Específicamente, comenzó a arrojar este error en / var / log / messages:

ext3_dx_add_entry: Directory index full!

El disco en cuestión tiene muchos inodos restantes:

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

Supongo que eso significa que llegamos al límite de cuántas entradas pueden estar en el archivo de directorio. No tengo idea de cuántos archivos serían, pero no puede ser más, como puede ver, más de tres millones. ¡No es que eso sea bueno, eso sí! Pero esa es la primera parte de mi pregunta: ¿cuál es exactamente ese límite superior? ¿Es sintonizable? Antes de gritar en Las-Quiero ajustarlo hacia abajo ; Este enorme directorio causó todo tipo de problemas.

De todos modos, rastreamos el problema en el código que estaba generando todos esos archivos, y lo hemos corregido. Ahora estoy atascado con la eliminación del directorio.

Algunas opciones aquí:

  1. rm -rf (dir)

    Intenté esto primero. Me di por vencido y lo maté después de que hubiera funcionado durante un día y medio sin ningún impacto perceptible.

  2. unlink (2) en el directorio: Definitivamente vale la pena considerarlo, pero la pregunta es si sería más rápido eliminar los archivos dentro del directorio a través de fsck que eliminarlos a través de unlink (2). Es decir, de una forma u otra, tengo que marcar esos inodes como no utilizados. Esto supone, por supuesto, que puedo decirle a fsck que no suelte entradas a los archivos en / lost + found; de lo contrario, acabo de mover mi problema. Además de todas las otras preocupaciones, después de leer un poco más sobre esto, resulta que probablemente tenga que llamar a algunas funciones internas de FS, ya que ninguna de las variantes de Unlink (2) que puedo encontrar me permitiría eliminar alegremente un directorio con entradas en el mismo. Pooh
  3. while [ true ]; do ls -Uf | head -n 10000 | xargs rm -f 2>/dev/null; done )

    Esta es en realidad la versión abreviada; el verdadero que estoy ejecutando, que solo agrega algunos informes de progreso y una parada limpia cuando nos quedamos sin archivos para eliminar, es:

    exportar i = 0;
    tiempo (mientras [verdadero]; hacer
      ls -Uf | cabeza -n 3 | grep -qF '.png' || rotura;
      ls -Uf | cabeza -n 10000 | xargs rm -f 2> / dev / null;
      exportar i = $ (($ i + 10000));
      echo "$ i ...";
    hecho )

    Esto parece estar funcionando bastante bien. Mientras escribo esto, ha eliminado 260,000 archivos en los últimos treinta minutos más o menos.

Ahora, para las preguntas:
  1. Como se mencionó anteriormente, ¿se puede ajustar el límite de entrada por directorio?
  2. ¿Por qué tardó "7m9.561s reales / usuario 0m0.001s / sys 0m0.001s" para eliminar un solo archivo que fue el primero en la lista devuelto por ls -U, y tardó tal vez diez minutos en eliminar las primeras 10.000 entradas con el comando en el n. ° 3, pero ahora se está transportando muy felizmente. Para el caso, eliminó 260,000 en aproximadamente treinta minutos, pero ahora le tomó otros quince minutos eliminar 60,000 más. ¿Por qué los enormes cambios de velocidad?
  3. ¿Hay una mejor manera de hacer este tipo de cosas? No almacenar millones de archivos en un directorio; Sé que es una tontería, y no habría sucedido en mi reloj. Buscar en Google el problema y mirar SF y SO ofrece muchas variaciones findque no van a ser significativamente más rápidas que mi enfoque por varias razones evidentes. Pero, ¿la idea delete-via-fsck tiene piernas? ¿O algo completamente diferente? Estoy ansioso por escuchar el pensamiento fuera de la caja (o dentro de la caja no conocida).
Gracias por leer la pequeña novela; No dude en hacer preguntas y me aseguraré de responder. También actualizaré la pregunta con el número final de archivos y cuánto tiempo se ejecutó el script de eliminación una vez que tengo eso.

Salida del guión final !:

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

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

Entonces, tres millones de archivos eliminados en poco más de cuatro horas.


1
rm (GNU coreutils) 8.4 tiene esta opción: "-v, --verbose explica lo que se está haciendo" . Mostrará todos los archivos que se están eliminando.
Cristian Ciupitu

2
En realidad, esa sería una buena manera de hacer una barra de progreso: dado que cada archivo tendría treinta y siete caracteres (36 + a '\ n'), podría escribir fácilmente un analizador para eso, y dado que printf () es barato y el comando rm ya tiene el nombre del archivo cargado, no hay penalización de rendimiento especial. Parece un no-iniciador para hacer todo el shebang, ya que nunca podría hacer que "rm" haga algo así, de todos modos. Pero podría funcionar bastante bien como una barra de progreso intra-10,000; quizás un "." por cada cien archivos?
BMDan

8
rm -rfv | pv -l >/dev/null. pv debería estar disponible en el repositorio EPEL .
Cristian Ciupitu

55
PV es abrumadoramente increíble. Dejo un rastro de instalaciones de pv a mi paso.
BMDan

Tuve exactamente el mismo problema recientemente. ¡Gracias!
richo

Respuestas:


30

La data=writebackopción de montaje merece ser probada, para evitar el registro en diario del sistema de archivos. Esto debe hacerse solo durante el tiempo de eliminación; sin embargo, existe el riesgo de que el servidor se apague o reinicie durante la operación de eliminación.

De acuerdo con esta página ,

Algunas aplicaciones muestran una mejora de velocidad muy significativa cuando se usa. Por ejemplo, se pueden ver mejoras de velocidad (...) cuando las aplicaciones crean y eliminan grandes volúmenes de archivos pequeños.

La opción se establece en fstabo durante la operación de montaje, reemplazando data=orderedcon data=writeback. El sistema de archivos que contiene los archivos que se eliminarán debe volver a montarse.


1
También podría aumentar el tiempo de la commit opción : "Este valor predeterminado (o cualquier valor bajo) perjudicará el rendimiento, pero es bueno para la seguridad de los datos. Establecerlo en 0 tendrá el mismo efecto que dejarlo en el valor predeterminado (5 segundos ". Establecerlo en valores muy grandes mejorará el rendimiento".
Cristian Ciupitu

1
La reescritura parece estelar, excepto que la documentación que estaba viendo ( gentoo.org/doc/en/articles/l-afig-p8.xml#doc_chap4 ) menciona explícitamente que todavía publica metadatos, que supongo que incluyen todos los datos que estoy cambiando (ciertamente no estoy cambiando ningún dato en los archivos mismos). ¿Mi comprensión de la opción es incorrecta?
BMDan

Por último, para su información, no mencionado en ese enlace es el hecho de que data = writeback puede ser un gran agujero de seguridad, ya que los datos señalados por una entrada determinada pueden no tener los datos que la aplicación escribió allí, lo que significa que podría producirse un bloqueo en los datos antiguos, posiblemente sensibles / privados expuestos. No es una preocupación aquí, ya que solo lo estamos encendiendo temporalmente, pero quería alertar a todos sobre esa advertencia en caso de que usted u otras personas que se crucen con esa sugerencia no lo supieran.
BMDan

commit: eso es bastante hábil! Gracias por la anotación.
BMDan

2
data=writebacktodavía registra metadatos antes de escribirlos en el sistema de archivos principal. Según tengo entendido, simplemente no impone el orden entre cosas como escribir un mapa de extensión y escribir datos en esas extensiones. Tal vez hay otras restricciones de orden que también relaja, si viste una ganancia de rendimiento de esto. Por supuesto, montar sin el diario en absoluto podría ser un rendimiento aún mayor. (Puede permitir que los cambios de metadatos sucedan en la RAM, sin necesidad de tener nada en el disco antes de que se complete la operación de desvinculación).
Peter Cordes

80

Si bien una de las principales causas de este problema es el rendimiento ext3 con millones de archivos, la causa raíz real de este problema es diferente.

Cuando un directorio necesita ser listado, se llama a readdir () en el directorio que produce una lista de archivos. readdir es una llamada posix, pero la llamada real del sistema Linux que se usa aquí se llama 'getdents'. Los solicitantes enumeran las entradas del directorio llenando un búfer con entradas.

El problema se debe principalmente al hecho de que readdir () usa un tamaño de búfer fijo de 32Kb para recuperar archivos. A medida que un directorio se hace más y más grande (el tamaño aumenta a medida que se agregan archivos) ext3 se vuelve más y más lento para buscar entradas y el tamaño de búfer adicional de 32 Kb de readdir solo es suficiente para incluir una fracción de las entradas en el directorio. Esto hace que readdir se repita una y otra vez e invoque la costosa llamada del sistema una y otra vez.

Por ejemplo, en un directorio de prueba que creé con más de 2.6 millones de archivos dentro, ejecutar "ls -1 | wc-l" muestra una gran salida de muchas llamadas al 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
...

Además, el tiempo que pasó en este directorio fue significativo.

$ time ls -1 | wc -l
2616044

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

El método para hacer de este un proceso más eficiente es llamar a getdents manualmente con un búfer mucho más grande. Esto mejora el rendimiento significativamente.

Ahora, se supone que no debe llamar a getdents manualmente, por lo que no existe una interfaz para usarlo normalmente (consulte la página del manual para ver getdents), sin embargo, puede llamarlo manualmente y hacer que la invocación de llamadas de su sistema sea mucho más eficiente.

Esto reduce drásticamente el tiempo que lleva recuperar estos archivos. Escribí un programa que hace esto.

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

Si bien esto no combate el problema fundamental subyacente (muchos archivos, en un sistema de archivos que funciona mal en él). Es probable que sea mucho, mucho más rápido que muchas de las alternativas publicadas.

Como previsión, uno debe eliminar el directorio afectado y rehacerlo después. Los directorios solo aumentan de tamaño y pueden seguir teniendo un bajo rendimiento, incluso con algunos archivos dentro debido al tamaño del directorio.

Editar: he limpiado esto bastante. Se agregó una opción para permitirle eliminar en la línea de comandos en tiempo de ejecución y eliminó un montón de cosas del paseo del árbol que, sinceramente, mirar hacia atrás era cuestionable en el mejor de los casos. También se demostró que produce daños en la memoria.

Ahora puedes hacer dentls --delete /my/path

Nuevos resultados Basado en un directorio con 1.82 millones de archivos.

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

Me sorprendió que esto todavía funcione tan bien.


1
Dos preocupaciones menores: una, [256]probablemente debería ser [FILENAME_MAX], y dos, mi Linux (2.6.18 == CentOS 5.x) no parece incluir una entrada d_type en dirent (al menos según getdents (2)).
BMDan

1
¿Podría por favor elaborar un poco sobre el reequilibrio de btree y por qué la eliminación en orden ayuda a prevenirlo? Intenté buscarlo en Google, desafortunadamente fue en vano.
ovgolovin

1
Porque ahora me parece que si estamos eliminando en orden,
forzamos el

1
Espero no molestarte con este asunto. Pero aún así comencé una pregunta sobre la eliminación de archivos en orden stackoverflow.com/q/17955459/862380 , que parece no recibir una respuesta que explique el problema con el ejemplo, lo que será comprensible para los programadores comunes. Si tiene tiempo y se siente así, ¿podría investigarlo? Tal vez podrías escribir una mejor explicación.
ovgolovin

2
Esta es una increíble pieza de código. Fue la única herramienta que pude encontrar capaz de enumerar y eliminar unos 11,000,000 (once millones) de archivos de sesión que se habían acumulado en un directorio, probablemente durante algunos años. El proceso de Plesk que se suponía que los mantendría bajo control usando find y otros trucos en otras respuestas aquí, no pudo completar una ejecución, por lo que los archivos seguían acumulándose. Es un tributo al árbol binario que el sistema de archivos utiliza para almacenar el directorio, que las sesiones pudieron funcionar en absoluto: podría crear un archivo y recuperarlo sin demora. Solo los listados eran inutilizables.
Jason

31

¿Sería posible hacer una copia de seguridad de todos los demás archivos de este sistema de archivos en una ubicación de almacenamiento temporal, formatear la partición y luego restaurar los archivos?


3
Realmente me gusta esta respuesta, en realidad. Como cuestión práctica, en este caso, no, pero no es una en la que hubiera pensado. ¡Bravo!
BMDan

Exactamente lo que estaba pensando también. Esta es una respuesta a la pregunta 3. Ideal si me preguntas :)
Joshua

12

No hay límite por archivo de directorio en ext3, solo el límite de inodo del sistema de archivos (aunque creo que hay un límite en el número de subdirectorios).

Es posible que aún tenga problemas después de eliminar los archivos.

Cuando un directorio tiene millones de archivos, la entrada del directorio se vuelve muy grande. La entrada del directorio tiene que ser escaneada para cada operación de eliminación, y eso toma varias cantidades de tiempo para cada archivo, dependiendo de dónde se encuentre su entrada. Desafortunadamente, incluso después de que se hayan eliminado todos los archivos, la entrada del directorio conserva su tamaño. Por lo tanto, las operaciones adicionales que requieren escanear la entrada del directorio aún llevarán mucho tiempo, incluso si el directorio ahora está vacío. La única forma de resolver ese problema es cambiar el nombre del directorio, crear uno nuevo con el nombre anterior y transferir los archivos restantes al nuevo. Luego borre el renombrado.


De hecho, noté solo este comportamiento después de eliminar todo. Afortunadamente, ya habíamos sacado el directorio de la "línea de fuego", por así decirlo, así que podría simplemente leerlo.
BMDan

2
Dicho esto, si no hay un límite de archivo por directorio, ¿por qué recibí "ext3_dx_add_entry: índice de directorio lleno!" cuando todavía había inodos disponibles en esa partición? No había subdirectorios dentro de este directorio.
BMDan

3
Hmm, hice un poco más de investigación y parece que hay un límite de número de bloques que un directorio puede ocupar. El número exacto de archivos depende de algunas cosas, por ejemplo, la longitud del nombre de archivo. Este gossamer-threads.com/lists/linux/kernel/921942 parece indicar que con bloques de 4k debería poder tener más de 8 millones de archivos en un directorio. ¿Eran nombres de archivo particularmente largos?
Alex J. Roberts

Cada nombre de archivo tenía exactamente 36 caracteres de longitud.
BMDan

bueno, ese es mi falta de ideas :)
Alex J. Roberts


4

find simplemente no funcionó para mí, incluso después de cambiar los parámetros de ext3 fs como lo sugirieron los usuarios anteriores. Consumió demasiada memoria. Este script PHP hizo el truco: uso de CPU rápido e insignificante, uso de memoria insignificante:

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

Publiqué un informe de error con respecto a este problema con find: http://savannah.gnu.org/bugs/?31961


Esto me salvó !!
jestro

3

Recientemente me enfrenté a un problema similar y no pude obtener la data=writebacksugerencia de ring0 para trabajar (posiblemente debido al hecho de que los archivos están en mi partición principal). Mientras investigaba soluciones, me topé con esto:

tune2fs -O ^has_journal <device>

Esto apagará el diario por completo, independientemente de la dataopción que le des mount. Combiné esto con noatimey el volumen se había dir_indexestablecido, y parecía funcionar bastante bien. La eliminación en realidad terminó sin que tuviera que matarla, mi sistema siguió respondiendo y ahora está funcionando nuevamente (con el diario nuevamente) sin problemas.


Iba a sugerir montarlo como ext2 en lugar de ext3, para evitar registrar en diario las operaciones de metadatos. Esto debería hacer lo mismo.
Peter Cordes

3

Asegúrate de hacer:

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

lo que debería acelerar un poco las cosas también.


44
Buena decisión, pero ya está montada en noatime, como mencioné en el encabezado de la pregunta. Y nodiratime es redundante; ver lwn.net/Articles/245002 .
BMDan

1
ppl repita este mantra "noatime, nodiratime, nodevatime, noreadingdocsatime"
poige

2

Es un comando muy lento. Tratar:

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

rm -rf corrió durante un día y medio, y finalmente lo maté, sin saber si realmente había logrado algo. Necesitaba una barra de progreso.
BMDan

44
En cuanto a que rm es muy lento, "time find. -Delete" en archivos de 30k: 0m0.357s / 0m0.019s / 0m0.337s real / user / sys. "time (ls -1U | xargs rm -f)" en esos mismos archivos: 0m0.366s / 0m0.025s / 0m0.340s. Que es básicamente un territorio de margen de error.
BMDan

1
Podría haber ejecutado strace -r -p <pid of rm>para adjuntar al proceso rm que ya se está ejecutando. Luego puede ver qué tan rápido unlinkpasan las llamadas del sistema. ( -rpone el tiempo desde la llamada al sistema anterior al comienzo de cada línea.)
Peter Cordes

2

¿Está dir_indexconfigurado para el sistema de archivos? ( tune2fs -l | grep dir_index) Si no, habilítelo. Suele estar activado para el nuevo RHEL.


1
Sí, está habilitado, ¡pero una sugerencia increíble!
BMDan

2

Hace un par de años, encontré un directorio con 16 millones de archivos XML en el /sistema de archivos. Debido a la crítica del servidor, utilizamos el siguiente comando que tardó aproximadamente 30 horas en finalizar:

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

Era un viejo disco duro de 7200 rpm , y a pesar del cuello de botella de IO y los picos de CPU, el viejo servidor web continuó su servicio.


1

Mi opción preferida es el enfoque newfs, ya sugerido. El problema básico es, nuevamente como ya se señaló, el escaneo lineal para manejar la eliminación es problemático.

rm -rfdebería ser casi óptimo para un sistema de archivos local (NFS sería diferente). Pero en millones de archivos, 36 bytes por nombre de archivo y 4 por inodo (una suposición, sin verificar el valor de ext3), eso es 40 * millones, que se mantendrán en la RAM solo para el directorio.

Supongo que está agotando la memoria caché de metadatos del sistema de archivos en Linux, por lo que los bloques para una página del archivo de directorio se eliminan mientras todavía está usando otra parte, solo para golpear esa página del caché nuevamente la próxima vez El archivo es eliminado. El ajuste del rendimiento de Linux no es mi área, pero / proc / sys / {vm, fs} / probablemente contenga algo relevante.

Si puede permitirse el tiempo de inactividad, puede considerar activar la función dir_index. Cambia el índice del directorio de lineal a algo mucho más óptimo para su eliminación en directorios grandes (hashed b-trees). tune2fs -O dir_index ...seguido por e2fsck -Dfuncionaría. Sin embargo, aunque estoy seguro de que esto ayudaría antes de que haya problemas, no sé cómo se realiza la conversión (e2fsck con el -D) cuando se trata de un directorio v.large existente. Copias de seguridad + suck-it-and-see.


1
pubbs.net/201008/squid/… sugiere que ese /proc/sys/fs/vfs_cache_pressurepodría ser el valor a utilizar, pero no sé si el directorio en sí cuenta para la memoria caché de la página (porque eso es lo que es) o la memoria caché de inodo (porque, a pesar de no ser un inodo, son metadatos FS y agrupados allí por esa razón). Como digo, el ajuste de Linux VM no es mi área. Juega y mira lo que ayuda.
Phil P

1

Obviamente no manzanas con manzanas aquí, pero configuré una pequeña prueba e hice lo siguiente:

Creó 100,000 archivos de 512 bytes en un directorio ( ddy/dev/urandom en un bucle); olvidé cronometrarlo, pero me llevó aproximadamente 15 minutos crear esos archivos.

Ejecutó lo siguiente para eliminar dichos archivos:

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

100000

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

Esta es una caja Pentium 4 2.8GHz (un par de cientos de GB IDE 7200 RPM, creo; EXT3). Kernel 2.6.27.


Interesante, entonces ¿quizás sea relevante el hecho de que los archivos fueron creados durante un largo período de tiempo? Pero eso no debería importar; la memoria caché de bloques debe tener todos los bloques de metadatos pertinentes en la RAM. ¿Quizás es porque unlink (2) es transaccional? En su opinión, ¿apagar el diario durante la duración de la rm sería una solución potencial (aunque ciertamente algo peligrosa)? No parece que pueda desactivar el diario por completo en un sistema de archivos montado sin tune2fs / fsck / reiniciar, lo que de alguna manera frustra el propósito.
BMDan

No puedo comentar sobre eso, pero anecdóticamente (en varias discusiones de NIX a lo largo de los años), siempre he escuchado que eso rmes terriblemente lento en una gran cantidad de archivos, de ahí la find -deleteopción. Con un comodín en el shell, expandirá cada nombre de archivo coincidente, y supongo que hay un búfer de memoria limitado para eso, por lo que podría ver cómo eso se volvería ineficiente.
gravyface

1
rm sería lento porque está buscando un archivo por nombre, lo que significa iterar a través de las entradas del directorio una por una hasta que lo encuentre. En este caso, sin embargo, dado que cada entrada que se entrega es (en ese punto) la primera de la lista (ls -U / ls -f), debería ser casi tan rápida. Dicho esto, rm -rf <dir>, que debería haber corrido como un campeón, fue lo más lento posible. ¿Quizás es hora de escribir un parche en coreutils para acelerar las eliminaciones masivas? Tal vez es secretamente globbing / sorting de alguna manera recursiva para implementar rm -rf? Incertidumbres como esta son las razones por las que hice la pregunta. ;)
BMDan

1
Reinicie la máquina después de ejecutar el paso de creación. Debería obtener una eliminación notablemente más lenta.
Matt

1

A veces, Perl puede hacer maravillas en casos como este. ¿Ya has probado si un script pequeño como este podría superar a bash y los comandos básicos del shell?

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

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

U otro enfoque de Perl, quizás incluso más rápido:

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

EDITAR: Acabo de probar mis scripts de Perl. Cuanto más detallado se hace algo bien. En mi caso, probé esto con un servidor virtual con 256 MB de RAM y medio millón de archivos.

time find /test/directory | xargs rm resultados:

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

comparado con

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

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

Dudo en imaginarme qué haría esa llamada glob (); Supongo que hace un scandir (). Si es así, eso tomará SIEMPRE para regresar. Una modificación de la primera sugerencia que no lea previamente todas las entradas dir podría tener algunas patas; sin embargo, en su forma actual, también usaría una cantidad impía de CPU al leer todas las entradas del directorio a la vez. Parte del objetivo aquí es dividir y conquistar; Este código no es fundamentalmente diferente de 'rm -f * .png', a pesar de los problemas con la expansión de shell. Si ayuda, no hay nada en el directorio que no quisiera eliminar.
BMDan

Tengo que intentar más en cuanto llegue al trabajo. Solo intenté crear 100 000 archivos en un solo directorio y encontrar la combinación + xargs + rm tomó 7.3 segundos, Perl + unlink (glob) ... la combinación terminó en 2.7 segundos. Intenté ese par de veces, el resultado fue siempre el mismo. En el trabajo intentaré esto con más archivos.
Janne Pikkarainen

Aprendí algo nuevo mientras probaba esto. Al menos con ext3 y ext4, la entrada del directorio en sí sigue siendo enorme incluso después de eliminar todos los archivos de allí. Después de un par de pruebas, mi directorio / tmp / test estaba ocupando 15 MB de espacio en disco. ¿Hay otra forma de limpiar eso que no sea eliminar el directorio y volver a crearlo?
Janne Pikkarainen

2
No, necesitas recrearlo. Llegué a esto cuando lidié con un sistema de correo y una carpeta por destinatario y limpiezas después de problemas importantes: no hay otra forma, que no sea crear un nuevo directorio y barajar los directorios, y luego eliminar el antiguo. Entonces puede reducir la ventana de tiempo cuando no hay un directorio, pero no eliminarlo.
Phil P

Tenga en cuenta que glob () clasificará los resultados, de la misma manera que lo hace normalmente el globing de shell, por lo que debido a que solo tiene 100k archivos, todo encaja fácilmente y la clasificación es rápida. Con un directorio mucho más grande, querrás abrir opendir () / readdir () / closedir () solo para evitar el tipo. [Digo normalmente para shell, ya que zsh tiene un modificador global para hacer que el orden de clasificación no esté ordenado, lo cual es útil cuando se trata de grandes cantidades de archivos; *(oN)]
Phil P

1

Por lo que recuerdo, la eliminación de inodes en los sistemas de archivos ext es O (n ^ 2), por lo que cuantos más archivos elimine, más rápido irá el resto.

Hubo una vez que me enfrenté a un problema similar (aunque mis estimaciones miraron el tiempo de eliminación de ~ 7h), al final fue la ruta sugerida por jftuga en el primer comentario .


0

Bueno, esta no es una respuesta real, pero ...

¿Sería posible convertir el sistema de archivos a ext4 y ver si las cosas cambian?


Parece que hacer esto "en vivo" requiere un fsck en un sistema de archivos montado, lo cual es ... alarmante. ¿Tienes una mejor manera?
BMDan

El sistema de archivos debe desmontarse antes de la conversión, es decir, antes de los comandos tunefs necesarios.
marcoc

0

De acuerdo, esto se ha cubierto de varias maneras en el resto del hilo, pero pensé en tirar mis dos centavos. El culpable de rendimiento en su caso es probablemente readdir. Está obteniendo una lista de archivos que no son necesariamente secuenciales en el disco, lo que está causando acceso al disco en todo el lugar cuando se desvincula. Los archivos son lo suficientemente pequeños como para que la operación de desvinculación probablemente no salte demasiado poniendo a cero el espacio. Si lee y luego ordena por inodo ascendente, probablemente obtendrá un mejor rendimiento. Entonces readdir en ram (ordenar por inodo) -> desvincular -> beneficio.

Inode es una aproximación aproximada aquí, creo ... pero basándome en su caso de uso podría ser bastante preciso ...


1
Corrígeme si me equivoco, pero unlink (2) no pone a cero el inodo, solo elimina la referencia del directorio. Sin embargo, me gusta el chutzpah de este enfoque. ¿Le gustaría ejecutar algunas pruebas de tiempo y ver si es verdad?
BMDan

0

Probablemente habría sacado un compilador de C y hecho el equivalente moral de tu guión. Es decir, use opendir(3)para obtener un identificador de directorio, luego use readdir(3)para obtener el nombre de los archivos, luego registre los archivos a medida que los desvío y de vez en cuando imprima "% d archivos eliminados" (y posiblemente el tiempo transcurrido o la marca de tiempo actual).

No espero que sea notablemente más rápido que la versión del script de shell, es solo que estoy acostumbrado a tener que extraer el compilador de vez en cuando, ya sea porque no hay una manera limpia de hacer lo que quiero del shell o porque Si bien es factible en shell, es improductivamente lento de esa manera.


Al menos podría comenzar modificando el código fuente de rm de coreutils .
Cristian Ciupitu

0

Es probable que tenga problemas de reescritura con el directorio. Intente eliminar primero los archivos más nuevos. Mire las opciones de montaje que diferirán la reescritura en el disco.

Para una barra de progreso intente ejecutar algo como rm -rv /mystuff 2>&1 | pv -brtl > /dev/null


En términos de eliminar primero los archivos más nuevos: ls -Ur? Estoy bastante seguro de que cargaría las entradas de directorio y luego las revertiría; No creo que ls sea lo suficientemente inteligente como para comenzar al final de la lista de entradas del directorio y retroceder hasta el principio. "ls -1" probablemente tampoco sea una gran idea, ya que probablemente tomaría más de 50 MB de núcleo y varios minutos en ejecutarse; querrías "ls -U" o "ls -f".
BMDan

Es probable que solo sea práctico si los nombres de los archivos aumentan en un patrón predecible. Sin embargo, puede intentar ls -1 canalizado para invertir, y canalizado a xargs. Use archivos en lugar de tuberías si desea ver sus resultados intermedios. No ha proporcionado ninguna información sobre el nombre del archivo. Generaría el patrón en reversa y eliminaría los archivos usando el patrón. Es posible que deba manejar las entradas de archivos faltantes. Dado su comentario sobre la memoria requerida, tiene una idea de la necesidad de E / S para reescribir el directorio.
BillThor

0

Así es como elimino los millones de archivos de rastreo que a veces se pueden reunir en un gran servidor de base de datos Oracle:

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

Me parece que esto da como resultado una eliminación bastante lenta que tiene un bajo impacto en el rendimiento del servidor, generalmente algo así como una hora por millón de archivos en una configuración "típica" de 10,000 IOPS.

A menudo tomará varios minutos antes de que se hayan escaneado los directorios, se haya generado la lista de archivos inicial y se elimine el primer archivo. De allí en adelante, a. se repite para cada archivo eliminado.

La demora causada por el eco en el terminal ha demostrado ser una demora suficiente para evitar cualquier carga significativa mientras la eliminación está progresando.


Estás siendo comido vivo por los globos. ¿Qué tal algo más 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':? Agregue -deleteal último para eliminar realmente las cosas; tal como está escrito, solo enumera lo que eliminaría. Tenga en cuenta que esto está optimizado para circunstancias en las que tiene muchas cosas sin interés en directorios cercanos; Si ese no es el caso, puede simplificar mucho la lógica.
BMDan

find -delete tiende a causar demasiadas E / S y afecta fácilmente el rendimiento de producción. Quizás con ionice.
Roy

Sin embargo, está causando toda esa E / S simplemente por ser más eficiente. El englobamiento es toda carga frontal por su ejemplo (es decir, se genera la lista completa de los archivos antes de la primera rmsucede), por lo que tiene relativamente eficiente de E / S al arrancar de eso, seguido por dolorosa, fuera de orden rms eso probablemente no cause mucha E / S, sino que implique scandirrecorrer el directorio repetidamente (sin causar E / S porque ya se ha cargado en el caché de bloques; ver también vfs_cache_pressure). Si desea ralentizar las cosas, ionicees una opción, pero probablemente usaría s de fracción de segundo sleep.
BMDan

find /u*/app/*/diag -path '*/trace/*.tr' -execdir rm {} +ejecutaría uno rmpor directorio, por lo que tendría menos sobrecarga de CPU. Supongo que siempre que tenga toneladas de tiempo de CPU libre, acelere la E / S del disco al bifurcar todo un rmproceso para cada unlinktrabajo, pero es feo. Perl con un sueño por desvinculación sería mejor si dormir entre rmdirectorios completos a la vez es demasiado explosivo. ( -execdir sh -c ...quizás)
Peter Cordes

-1

Puede usar las funciones de paralelización de 'xargs':

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

1
Esto no ayudará. El cuello de botella es la pobre E / S aleatoria en el disco. Hacer eliminaciones paralelas podría empeorarlo y solo aumentar la carga de la CPU.
Wim Kerkhoff

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

1
Guau. Supongo que eso cae con bastante firmeza en el campamento "más de una forma de desollar un gato". ¿En serio, sin embargo, con el género y el uniq? "ls" se ordena de manera predeterminada, de todos modos, y espero que los nombres de archivo sean únicos. : /
BMDan

-2

en realidad, este es un poco mejor si el shell que usas sí expande la línea de comandos:

ls|cut -c -4|sort|uniq|awk '{ print "echo " $1 ";rm -rf " $1 "*"}' |sh
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.