Astuces pour travailler avec un grand nombre de petits fichiers

L'idée de cet article est née spontanément d'une discussion dans les commentaires d'un article. «Quelques informations sur l'inode».

Astuces pour travailler avec un grand nombre de petits fichiers

En effet, la spécificité interne du fonctionnement de nos services est de stocker un nombre considérable de petits fichiers. Actuellement, nous avons environ des centaines de téraoctets de ces données. Nous avons rencontré quelques pièges évidents et moins évidents et avons réussi à naviguer à travers eux.

C'est pourquoi je partage notre expérience, cela pourrait être utile à quelqu'un.

Premier problème : «Aucun espace disponible sur le périphérique»

Comme mentionné dans l'article ci-dessus, le problème est qu'il y a des blocs libres dans le système de fichiers, mais les inodes sont épuisés.

Le nombre d'inodes utilisés et libres peut être vérifié avec la commande df -ih:

Astuces pour travailler avec un grand nombre de petits fichiers

Je ne vais pas résumer l'article, en bref, sur le disque, il y a à la fois des blocs pour les données directement, ainsi que des blocs pour les métadonnées, appelés inodes (noeud d'index). Leur nombre est défini lors de l'initialisation du système de fichiers (il s'agit d'ext2 et de ses descendants) et ne change pas par la suite. L'équilibre entre les blocs de données et les inodes est calculé à partir des données moyennes, dans notre cas, avec de nombreux petits fichiers, cet équilibre doit pencher du côté du nombre d'inodes — il doit y en avoir plus.

Linux a déjà prévu des options avec un équilibre différent, et toutes ces configurations prédéfinies se trouvent dans le fichier /etc/mke2fs.conf.
Ainsi, lors de l'initialisation initiale du système de fichiers via mke2fs, vous pouvez spécifier le profil nécessaire.

Voici quelques exemples extraits du fichier :

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

    big = {
        inode_ratio = 32768
    }

    largefile = {
        inode_ratio = 1048576
        blocksize = -1
    }

Vous pouvez choisir l'option d'utilisation souhaitée avec l'option «-T» lors de l'appel de mke2fs. Il est également possible de définir manuellement les paramètres nécessaires s'il n'y a pas de solution prête.

Davantage de détails sont décrits dans les manuels pour mke2fs.conf et mke2fs.

Une caractéristique non abordée dans l'article ci-dessus est qu'il est possible de définir la taille des blocs de données. Évidemment, pour les grands fichiers, il est logique d'avoir une plus grande taille de bloc, pour les petits fichiers — une plus petite.

Cependant, il convient de prendre en compte une caractéristique intéressante : l'architecture du processeur.
Un jour, je me suis rendu compte que j'avais besoin d'une taille de bloc plus grande pour les gros fichiers photo. C'était dans un contexte domestique, sur un serveur de fichiers domestique WD basé sur l'architecture ARM. Sans trop réfléchir, j'ai défini la taille de bloc sur 8k ou 16k au lieu de 4k, après avoir mesuré les économies réalisées. Tout allait bien jusqu'à ce que le serveur tombe en panne, alors que le disque était toujours fonctionnel. En plaçant le disque dans un ordinateur classique avec un processeur Intel ordinaire, j'ai eu la surprise de découvrir que la taille de bloc n'était pas prise en charge. Les données étaient là, tout allait bien, mais il était impossible de les lire. Les processeurs i386 et similaires ne peuvent pas travailler avec des tailles de bloc qui ne correspondent pas à la taille de la page mémoire, qui est exactement de 4k. En somme, cela a fini par nécessiter l'utilisation d'outils de l'espace utilisateur, tout était lent et triste, mais nous avons récupéré les données. Pour ceux que cela intéresse — recherchez le nom de l'outil fuseext2. La morale de l'histoire : soit anticiper tous les cas à l'avance, soit ne pas se prendre pour un super-héros et utiliser les paramètres standard pour les utilisateurs moyens.

UPD. Suite à la remarque d'un utilisateur berez , je précise que pour i386, la taille du bloc ne doit pas dépasser 4k, mais elle n'a pas besoin d'être rigoureusement de 4k, donc des tailles de 1k et 2k sont acceptables.

Alors, comment avons-nous résolu les problèmes ?

Tout d'abord, nous avons rencontré le problème lorsque le disque multi-térabyte était plein de données, et que nous ne pouvions pas modifier la configuration du système de fichiers.

Deuxièmement, une solution immédiate était nécessaire.

En fin de compte, nous en sommes venus à la conclusion qu'il fallait rétablir l'équilibre en réduisant le nombre de fichiers.
Pour diminuer le nombre de fichiers, il a été décidé de regrouper les fichiers dans une seule archive. Compte tenu de notre spécificité, nous avons regroupé dans une seule archive tous les fichiers sur une certaine période, et nous avons effectué l'archivage via une tâche cron chaque nuit.

Nous avons choisi le zip. Dans les commentaires de l'article précédent, un tar était proposé, mais il y a une difficulté : il n'a pas de table des matières, et les fichiers y sont stockés de manière séquentielle (ce n'est pas par hasard que « tar » est l'abréviation de « Tape Archive », un héritage des lecteurs à bande), c'est-à-dire que si vous devez lire un fichier à la fin de l'archive, vous devez lire toute l'archive, car il n'y a pas de décalages pour chaque fichier par rapport au début de l'archive. Cela rend l'opération longue. Avec le zip, tout est beaucoup mieux : il a cette table des matières et des décalages de fichiers à l'intérieur de l'archive, et le temps d'accès à chaque fichier ne dépend pas de sa position. De plus, dans notre cas, nous pouvions définir l'option de compression à « 0 », car tous les fichiers avaient déjà été compressés avec gzip.

Les clients récupèrent les fichiers via nginx, et selon l'ancienne API, il suffit d'indiquer simplement le nom du fichier, par exemple :

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

Pour décompresser les fichiers à la volée, nous avons trouvé et connecté le module nginx-unzip-module (https://github.com/youzee/nginx-unzip-module) et configuré deux upstreams.

Le résultat a donné cette configuration :

Astuces pour travailler avec un grand nombre de petits fichiers

Les deux hôtes dans les paramètres étaient comme suit :

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;
  }
}

Et la configuration des upstreams sur le nginx en amont :

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

Comment ça fonctionne :

  • Le client va sur nginx frontal
  • Le nginx frontal essaie de renvoyer le fichier du premier upstream, c'est-à-dire directement du système de fichiers
  • S'il n'y a pas de fichier — il essaie de le renvoyer du deuxième upstream, qui essaie de trouver le fichier à l'intérieur de l'archive

Deuxième problème : encore « No space left on device »

C'est le deuxième problème auquel nous avons été confrontés lorsque le répertoire contenait trop de fichiers.
Nous essayons de créer un fichier, le système se plaint qu'il n'y a pas de place. Nous changeons le nom du fichier et essayons à nouveau de le créer.

Cela fonctionne.

Ça ressemble à peu près à ça :

Astuces pour travailler avec un grand nombre de petits fichiers

La vérification des inodes n'a rien donné — il y en a beaucoup de libres.
La vérification de l'espace — c'est la même chose.
Nous avons pensé qu'il y avait peut-être trop de fichiers dans le répertoire, et il y a une limite à cela, mais encore une fois non : Maximum number of files per directory: ~1.3 × 10^20

Et on peut créer un fichier si l'on change le nom.
Conclusion — le problème réside dans le nom du fichier.

Des recherches supplémentaires ont montré que le problème était lié à l'algorithme de hachage lors de la construction de l'index du répertoire ; avec un grand nombre de fichiers, des collusions se produisent avec toutes les conséquences qui en découlent. Vous pouvez en lire plus ici : https://ext4.wiki.kernel.org/index.php/Ext4_Disk_Layout#Hash_Tree_Directories

Cette option peut être désactivée, mais la recherche de fichiers par nom peut devenir imprévisible et très longue lors de l'énumération de tous les fichiers.

 tune2fs -O "^dir_index" /dev/sdb3

En général, cela peut fonctionner comme solution temporaire.

Moralité : avoir beaucoup de fichiers dans un dossier est généralement une mauvaise idée. Il ne faut pas faire cela.

Dans de tels cas, il est habituel de créer des sous-dossiers, soit par les premières lettres du nom de fichier, soit selon d'autres paramètres, comme les dates ; dans la plupart des cas, cela aide.
Mais le nombre total de petits fichiers reste un problème, même s'ils sont organisés dans des dossiers - reportez-vous au premier problème.

Troisième problème : comment voir la liste des fichiers quand il y en a beaucoup.

Dans notre situation, où nous avons beaucoup de fichiers, nous avons de toute façon été confrontés au problème de visualiser le contenu du répertoire.

La solution standard est la commande. ls.
D'accord, voyons ce que cela donne avec 4 772 098 fichiers :


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

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

30 secondes… ça fait beaucoup. En effet, la plupart du temps est consommée par le traitement des fichiers dans l'espace utilisateur, et non par le travail du noyau.

Mais il existe une solution :


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

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

3 secondes. 10 fois plus rapide.
Hourra !

Mise à jour.

Une solution encore plus rapide d'un utilisateur berez – désactiver le tri avec ls


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

Quatrième problème : haute charge LA lors du travail avec des fichiers.

Il arrive parfois qu'il soit nécessaire de copier un grand nombre de fichiers d'une machine à une autre. Cela entraîne souvent une augmentation significative de la charge LA, car cela dépend fortement de la performance des disques eux-mêmes.

Ce que l'on souhaite généralement, c'est utiliser des SSD. C'est vraiment génial. La seule question est le coût des SSD multi-téraoctets.

Mais si les disques sont normaux, il faut copier les fichiers, et c'est un système de production où une surcharge entraîne des clients mécontents ? Il y a au moins deux outils utiles : nice et ionice.

nice – réduit la priorité du processus, permettant ainsi au planificateur de répartir plus de quanta de temps aux autres processus prioritaires.
Dans notre pratique, nous avions l'habitude de fixer le niveau de nice au maximum (19 - c'est la priorité minimale, -20 (moins 20) - la priorité maximale).

ionice – ajuste donc la priorité du système d'entrée/sortie (I/O scheduling).

Si vous utilisez RAID et qu'il a soudainement besoin de se synchroniser (après un redémarrage échoué ou pour restaurer un tableau RAID après le remplacement d'un disque), il peut être judicieux, dans certaines situations, de réduire la vitesse de synchronisation pour permettre aux autres processus de fonctionner de manière plus adéquate. Voici une commande qui peut aider :


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

Problème cinq : Comment synchroniser des fichiers en temps réel

Nous avons encore d'énormes quantités de fichiers à sauvegarder sur un deuxième serveur pour éviter… Les fichiers sont constamment écrits, donc pour minimiser les pertes, il faut les copier aussi rapidement que possible.

Solution standard : Rsync sur SSH.

C'est une bonne option, sauf si cela doit être fait toutes les quelques secondes. Et il y a beaucoup de fichiers. Même si nous ne les copions pas, nous devons quand même savoir ce qui a changé, et comparer plusieurs millions de fichiers prend du temps et impose une charge sur les disques.

C'est-à-dire que nous devons savoir immédiatement ce qu'il faut copier, sans lancer une comparaison à chaque fois.

La solution est — lsyncd. Lsyncd — Démon de synchronisation en direct (Mirror). Il fonctionne également via rsync, mais surveille en plus le système de fichiers pour détecter les changements en utilisant inotify et fsevents, et il lance la copie uniquement pour les fichiers qui ont été ajoutés ou modifiés.

Problème six : comment savoir qui charge les disques

Cela doit être connu de tous, mais pour une vue d'ensemble : pour surveiller le sous-système de disque, il existe une commande iotop — semblable à top, mais qui montre les processus utilisant le disque de manière la plus active.

Astuces pour travailler avec un grand nombre de petits fichiers

Au fait, l'ancien bon top permet aussi de comprendre si nous avons des problèmes avec les disques ou non. Pour cela, deux paramètres sont les plus appropriés : Load Average et IOwait.

Astuces pour travailler avec un grand nombre de petits fichiers

Le premier indique combien de processus sont en attente d'être servis, généralement plus de 2 — cela indique déjà que quelque chose ne va pas. Lors d'une copie active sur des serveurs de sauvegarde, nous tolérons jusqu'à 6-8, au-delà de cela, la situation est considérée comme anormale.

Le second — combien le processeur est occupé par les opérations de disque. IOwait >10% — c'est un motif de préoccupation, bien que sur nos serveurs avec un profil de charge spécifique, cela soit régulièrement de 40-50%, et c'est vraiment la norme.

Je vais m'arrêter ici, bien qu'il y ait sûrement de nombreux points avec lesquels nous n'avons pas eu à deal, j'attends avec impatience vos commentaires et vos descriptions de cas réels intéressants.

Source : habr.com

Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS 🔥 Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS | ProHoster