Trucos para trabajar con un gran número de archivos pequeños

La idea del artículo surgió espontáneamente de una discusión en los comentarios de un artículo. «Algo sobre inode».

Trucos para trabajar con un gran número de archivos pequeños

El hecho es que la peculiaridad interna del funcionamiento de nuestros servicios es el almacenamiento de una enorme cantidad de archivos pequeños. En este momento tenemos alrededor de cientos de terabytes de esos datos. Y nos encontramos con algunas obvias e inesperadas dificultades y hemos logrado superarlas.

Por eso comparto nuestra experiencia, tal vez le sirva a alguien.

Primer problema: «No hay espacio disponible en el dispositivo»

Como se mencionó en el artículo anteriormente citado, el problema es que hay bloques libres en el sistema de archivos, pero los inodes se han agotado.

Se puede verificar el número de inodes usados y libres con el comando df -ih:

Trucos para trabajar con un gran número de archivos pequeños

No voy a resumir el artículo, en breve, en el disco hay tanto bloques para datos como bloques para metainformación, que son los inodes (nodo de índice). Su cantidad se establece al inicializar el sistema de archivos (hablando de ext2 y sus sucesores) y no cambia después. El equilibrio entre bloques de datos e inodes se calcula a partir de datos promedio, en nuestro caso, cuando hay muchos archivos pequeños, el equilibrio debe inclinarse hacia un mayor número de inodes.

En Linux ya se han previsto opciones con diferentes equilibrados, y todas estas configuraciones precalculadas se encuentran en el archivo /etc/mke2fs.conf.
Por lo tanto, al inicializar el sistema de archivos mediante mke2fs, se puede especificar el perfil necesario.

Aquí hay algunos ejemplos del archivo:

    small = {
        blocksize = 1024
        inode_size = 128
        inode_ratio = 4096
    }

    big = {
        inode_ratio = 32768
    }

    largefile = {
        inode_ratio = 1048576
        blocksize = -1
    }

Se puede elegir la opción de uso deseada con la opción «-T» al invocar a mke2fs. También se pueden especificar manualmente los parámetros necesarios si no hay una solución lista.

Más detalles se describen en los manuales para mke2fs.conf y mke2fs.

Una característica que no se mencionó en el artículo anteriormente citado es que se puede establecer el tamaño del bloque de datos. Es evidente que para archivos grandes tiene sentido utilizar un tamaño de bloque mayor, mientras que para archivos pequeños, uno menor.

Sin embargo, es importante tener en cuenta una característica interesante, como la arquitectura del procesador.
En su momento, pensé que necesitaba un tamaño de bloque más grande para grandes archivos de fotos. Esto ocurrió en un entorno doméstico, en un almacenamiento de archivos de WD en arquitectura ARM. Sin pensarlo mucho, configuré el tamaño de bloque a 8k o 16k en lugar del estándar de 4k, habiendo medido previamente el ahorro. Todo funcionó perfectamente hasta que el propio almacenamiento falló, aunque el disco seguía funcionando. Al colocar el disco en una computadora normal con un procesador Intel estándar, recibí una sorpresa: tamaño de bloque no soportado. Los datos estaban ahí, todo bien, pero no se podían leer. Los procesadores i386 y similares no pueden trabajar con tamaños de bloque que no correspondan al tamaño de la página de memoria, que es exactamente 4k. En resumen, terminó usando utilidades del espacio de usuario, todo fue lento y triste, pero recuperamos los datos. Para quienes estén interesados, busquen el nombre de la utilidad. fuseext2. La moraleja: o pensar en todos los casos de antemano, o no pretender ser un superhéroe y usar configuraciones estándar para hogares.

UPD. Según el comentario del usuario berez , aclaro que para i386 el tamaño del bloque no debe exceder 4k, pero no tiene que ser necesariamente 4k; es decir, se permiten 1k y 2k.

Así que, así es como resolvimos los problemas.

Primero, nos encontramos con el problema cuando un disco de varios terabytes se llenó de datos, y no podíamos reorganizar la configuración del sistema de archivos.

En segundo lugar, necesitábamos una solución urgente.

Como resultado, llegamos a la conclusión de que era necesario cambiar el equilibrio, reduciendo el número de archivos.
Para reducir el número de archivos, decidimos agruparlos en un solo archivo comprimido. Teniendo en cuenta nuestra especificidad, agrupamos en un solo archivo todos los archivos de un determinado período y llevamos a cabo la compresión mediante una tarea cron diariamente durante la noche.

Elegimos un archivo zip. En los comentarios del artículo anterior se sugería tar, pero hay una dificultad: no tiene un índice y los archivos dentro están dispuestos secuencialmente (no es por nada que «tar» es una abreviatura de «Tape Archive», un legado de las unidades de cinta), es decir, si se necesita leer un archivo al final del archivo, hay que leer todo el archivo, ya que no hay desplazamientos para cada archivo en relación con el comienzo del archivo. Por lo tanto, esta es una operación lenta. En zip todo es mucho mejor: tiene ese índice y desplazamientos de archivos dentro del archivo, y el tiempo de acceso a cada archivo no depende de su ubicación. En nuestro caso, se podía establecer la opción de compresión «0», ya que todos los archivos ya habían sido comprimidos previamente en gzip.

Los clientes recuperan archivos a través de nginx, y de acuerdo con la API antigua, simplemente se especifica el nombre del archivo, por ejemplo así:

http://www.server.com/hydra/20170416/0453/3bd24ae7-1df4-4d76-9d28-5b7fcb7fd8e5

Para descomprimir archivos sobre la marcha, encontramos y conectamos el módulo nginx-unzip-module (https://github.com/youzee/nginx-unzip-module) y configuramos dos upstreams.

Como resultado, la configuración fue la siguiente:

Trucos para trabajar con un gran número de archivos pequeños

Los dos hosts en la configuración eran como sigue:

server {
  listen *:8081;

  location / {
    root      /home/filestorage;
  }
}

server {
  listen *:8082;

  location ~ ^/hydra/(d+)/(\d+)/(.*)$ {
    root      /home/filestorage;
    file_in_unzip_archivefile "/home/filestorage/hydra/$1/$2.zip";
    file_in_unzip_extract "$2/$3";
    file_in_unzip;
  }
}

Y la configuración de los upstreams en el nginx superior:

upstream storage {
  server server.com:8081;
  server server.com:8082;
}

Cómo funciona:

  • El cliente va a nginx frontal
  • Nginx frontal intenta entregar el archivo del primer upstream, es decir, directamente desde el sistema de archivos
  • Si no hay archivo, intenta entregar desde el segundo upstream, que intenta encontrar el archivo dentro del archivo

El segundo problema: otra vez "No space left on device"

Este es el segundo problema que encontramos cuando hay muchos archivos en el directorio.
Estamos intentando crear un archivo, el sistema se queja de que no hay espacio. Cambiamos el nombre del archivo y de nuevo intentamos crearlo.

Se logra.

Se ve aproximadamente así:

Trucos para trabajar con un gran número de archivos pequeños

La verificación de inodes no dio nada: hay muchos libres.
La verificación de espacio — lo mismo.
Pensamos que podría haber demasiados archivos en el directorio, que existe un límite para esto, pero no es así: Número máximo de archivos por directorio: ~1.3 × 10^20

Y se puede crear un archivo si se cambia el nombre.
Conclusión: el problema está en el nombre del archivo.

Investigaciones posteriores mostraron que el problema está en el algoritmo de hashing al construir el índice del directorio, con un gran número de archivos hay colisiones con todas las consecuencias resultantes. Se puede leer más aquí: https://ext4.wiki.kernel.org/index.php/Ext4_Disk_Layout#Hash_Tree_Directories

Se puede desactivar esta opción, pero... buscar un archivo por nombre puede volverse impredeciblemente lento al revisar todos los archivos.

 tune2fs -O "^dir_index" /dev/sdb3

En general, como solución temporal puede funcionar.

Moraleja: tener muchos archivos en un directorio suele ser malo. No se debe hacer así.

Normalmente en tales casos se crean subdirectorios, ya sea por las primeras letras del nombre del archivo o por otros parámetros, como las fechas; en la mayoría de los casos esto ayuda.
Pero el número total de archivos pequeños sigue siendo malo, incluso si se dividen en directorios; entonces, ver la primera problemática.

Tercera problema: cómo ver la lista de archivos si son muchos.

En nuestra situación, donde tenemos muchos archivos, de una manera u otra nos hemos enfrentado al problema de cómo ver el contenido del directorio.

La solución estándar es el comando Muestra el contenido del directorio. Si no se proporciona una ruta, se muestra el contenido del directorio actual..
Bien, veamos qué ocurre con 4772098 archivos:


$ time ls /home/app/express.repository/offercache/ >/dev/null

real	0m30.203s
user	0m28.327s
sys	0m1.876s

30 segundos... puede ser demasiado. Y la mayor parte del tiempo se gasta procesando archivos en el espacio de usuario, no en el funcionamiento del núcleo.

Pero hay una solución:


$ time find /home/app/express.repository/offercache/ >/dev/null

real	0m3.714s
user	0m1.998s
sys	0m1.717s

3 segundos. 10 veces más rápido.
¡Hurra!

UPD.

Una solución aún más rápida del usuario berez — desactivar la ordenación en Muestra el contenido del directorio. Si no se proporciona una ruta, se muestra el contenido del directorio actual.


time ls -U /home/app/express.repository/offercache/ >/dev/null
real	0m2.985s
user	0m1.377s
sys	0m1.608s

Cuarta problema: alto LA al trabajar con archivos.

Periódicamente surge la situación en la que es necesario copiar un montón de archivos de una máquina a otra. Sin embargo, a menudo el LA aumenta drásticamente, ya que todo depende del rendimiento de los discos mismos.

Lo más sensato que uno quiere es usar SSD. Es realmente genial. La única cuestión es el costo de los SSD de varios terabytes.

Pero si los discos son ordinarios, hay que copiar archivos, y esto además es un sistema de producción, donde la sobrecarga lleva a quejas de los clientes? Hay al menos dos herramientas útiles: nice y ionice.

nice — reduce la prioridad del proceso, por lo que el programador asigna más cuantas de tiempo a otros procesos, más prioritarios.
En nuestra práctica ha ayudado establecer nice al máximo (19 — es la prioridad mínima, -20 (menos 20) — la máxima).

ionice — por lo tanto, corrige la prioridad de entrada/salida (programación de I/O)

Si tienes RAID y necesita sincronizarse de repente (después de un reinicio fallido o tras la sustitución de un disco), en algunas situaciones tiene sentido reducir la velocidad de sincronización para que el resto de los procesos puedan funcionar de manera más o menos adecuada. Para esto, puedes usar el siguiente comando:


echo 1000 > /proc/sys/dev/raid/speed_limit_max

Problema cinco: Cómo sincronizar archivos en tiempo real

Tenemos las mismas enormes cantidades de archivos que necesitamos respaldar en un segundo servidor para evitar... Los archivos se escriben constantemente, por lo que, para minimizar las pérdidas, hay que copiarlos lo más rápido posible.

Solución estándar: Rsync sobre SSH.

Es una buena opción, a menos que necesites hacerlo cada pocos segundos. Y hay muchos archivos. Incluso si no los copias, necesitas entender de alguna manera qué ha cambiado, y comparar varios millones de archivos consume tiempo y carga los discos.

Es decir, necesitamos saber de inmediato qué hay que copiar, sin iniciar una comparación cada vez.

La salvación es lsyncd. Lsyncd — Demonio de Sincronización en Vivo (Espejo). También funciona a través de rsync, pero monitorea adicionalmente el sistema de archivos en busca de cambios utilizando inotify y fsevents, y comienza a copiar solo aquellos archivos que han aparecido o cambiado.

Problema seis: cómo entender quién está sobrecargando los discos

Probablemente todos lo sepan, pero para completar el panorama: hay un comando para monitorear el subsistema de disco iotop —similar a top, pero muestra los procesos que utilizan los discos de manera más activa.

Trucos para trabajar con un gran número de archivos pequeños

Por cierto, el viejo y querido top también permite entender si hay problemas con los discos o no. Para esto, hay dos parámetros que son los más adecuados: Carga Promedio y IOwait.

Trucos para trabajar con un gran número de archivos pequeños

El primero muestra cuántos procesos están en la cola de servicio, generalmente más de 2 ya indica que algo no está bien. Durante las copias de respaldo a los servidores, permitimos hasta 6-8; después de eso, la situación se considera anómala.

El segundo — cuánto está ocupado el procesador con las operaciones de disco. IOwait >10% — razón para preocuparse, aunque en nuestros servidores con perfiles de carga específicos, a veces es estable entre 40-50%, y eso es realmente la norma.

Con esto concluiré, aunque seguramente hay muchos aspectos que no hemos tenido que enfrentar, estaré encantado de recibir comentarios y descripciones de casos reales interesantes.

Fuente: habr.com

Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS 🔥 Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS | ProHoster