Lister un répertoire de plusieurs millions de fichiers efficacement

Antoine Lépée

Le coup classique. On commence à stocker quelques fichiers dans un dossier sur un disque. Puis, avec le temps et l’augmentation du traffic, ce qui ne devait stocker “que” quelque milliers de fichiers se retrouve à en détenir plusieurs millions. Le problème avec ce genre de situation, c’est qu’un simple inventaire devient mission impossible. Et pourtant !

cover

Lorsque vous faites un ls dans un répertoire le process va commencer par établir une liste de tous les fichiers présents dans le répertoire. Puis essaiera de les sortir sur STDOUT ordonnés par ordre alphabétique. Là, on sent le truc venir. Bien que ça ne pose aucun problème en règle générale, avant même de commencer à output le résultat, ls va charger en mémoire l’intégralité des chemins des fichiers qu’il essaie de lister.

La suite, est évidemment, une occupation en RAM délirante et un CPU qui prend le tarif de sa vie (un excellent moyen de crasher une machine) et le mieux pire là dedans, c’est que vous ne verrez pas apparaître, ne serait-ce que, la première ligne du résultat.

Find? a new sherif in town?

Une solution est d’utiliser find, qui, à l’inverse de ls travaille de façon incrémentale.

❯ find . -maxdepth 1

L’ennui c’est que find prendra beaucoup de temps à executer la commande à cause des tests qu’il fait pour matcher les fichiers que l’on recherche

Back to LS

Si l’on revient sur ls rapidement, et que l’on s’interesse à son man, on découvre quelques options qui pourraient nous aider.

     -1      (The numeric digit ``one''.)  Force output to be one entry per line.  This is the default
             when output is not to a terminal.
     -f      Output is not sorted.  This option turns on the -a option.

Le -1 c’est bonus, on affiche simplement le nom du fichier sur une ligne complète

ls -1
2018-01-25-Obtenir-un-tableau-dobjets-indexe-par-id-avec-pdo.md
2018-01-25-aller-plus-loin-avec-beberlei-assert.md
2018-01-25-composer-and-symfony-polyfill.md
2018-02-06-Libérer-de-lespace-disque-avec-docker-system-prune.md
2018-02-06-behat-phpunit-bootstrap-zero-config.md
2018-02-06-travailler-avec-une-version-non-publiee-dune-librarie-avec-composer.md
2018-02-09-Utiliser-les-methodes-d-intersection-par-valeurs.md
2018-02-09-afficher-du-code-dans-les-textarea-github.md
2018-02-13-ouvrir-une-issue-github-depuis-le-code.md
2018-02-20-git-diff-github-style.md
2018-03-12-taches-asynchrones-avec-zsh.md
2018-03-28-antigen-le-gestionnaire-de-paquets-pour-zsh.md
2018-04-22-diviser-une-chaine-de-caractères-en-es6.md
2018-05-30-voir-lhistorique-des-modifications-dune-methode-avec-git.md
2018-07-18-lister-un-gros-repertoire.md

On l’a vu plus haut, l’inconvénient de ls est qu’il charge en mémoire avant de faire des opérations de tri. Grâce au flag -f on s’affranchit de cette contrainte et ls retournera les résultats au fur et à mesure sur STDOUT

ls -f1
.
..
2018-02-20-git-diff-github-style.md
2018-02-13-ouvrir-une-issue-github-depuis-le-code.md
2018-04-22-diviser-une-chaine-de-caractères-en-es6.md
2018-03-28-antigen-le-gestionnaire-de-paquets-pour-zsh.md
2018-01-25-Obtenir-un-tableau-dobjets-indexe-par-id-avec-pdo.md
...

Et pourquoi s’arrêter là ! Une autre astuce, est d’invoquer le binaire directement, afin de s’assurer qu’aucune conf obscure ne vienne faire des opérations que l’on aurai pas anticipé (genre le truc qui met de jolies couleurs sur les outputs, ou encore les variables d’environnement ayant un impact sur ls).

\/bin/ls -f1
.
..
2018-02-20-git-diff-github-style.md
2018-02-13-ouvrir-une-issue-github-depuis-le-code.md
2018-04-22-diviser-une-chaine-de-caractères-en-es6.md
2018-03-28-antigen-le-gestionnaire-de-paquets-pour-zsh.md
2018-01-25-Obtenir-un-tableau-dobjets-indexe-par-id-avec-pdo.md
...

Enfin, vu que l’on parle de millions d’entrées, plutôt que d’output sur STDOUT on va gentillement envoyer tout ça dans un fichier texte.

\/bin/ls -f1 > ./mon-fichier.txt

Victoire ! Non seulement ça fonctionne mais en plus c’est “presque sans douleur”. La limitation avec cette approche c’est les I/O qui prennent très très cher. Si vous avez un process genre NGinx à côté, pensez à bien le monitorer.

Le conseil du sage

Moyen mnémotechnique pour s’en souvenir : -f1, parce que ça trace 🏎 !

Back