Le plugin hash_documents

Le nombre de documents dans le répertoire IMG/{$ext}/ du site peut devenir beaucoup trop important et avoir un impact sur les performances du système de fichiers du serveur. La solution proposée par ce plugin est de « hasher » le répertoire IMG/.

De manière à limiter le nombre d’inode dans chaque sous-dossier de IMG/, on répartit automatiquement les fichiers dans des sous-sous-répertoires. La structure passe ainsi de
IMG/mp3/fichier-son.mp3
à
IMG/mp3/a/b/c/fichier-son.mp3

Ce plugin s’occupe de tout pour vous.

En SPIP 2, une page ecrire/?exec=hash_documents permet d’aller hasher tous les documents déjà installés (mais il n’est pas indispensable de l’utiliser pour que ça fonctionne). On peut à tout moment revenir en arrière (grâce à cette même page). En SPIP3, elle est accessible via la page d’administration des plugins (ecrire/?exec=configurer_hasher).

Dans tous les cas, la désactivation du plugin n’empêche pas le site de fonctionner normalement.

Ci-dessous, les explications techniques :

Structure du hashage

le sous-répertoire a/b/c/ doit être calculé de manière à ce que la répartition des documents soit homogène et prévisible. On utilisera pour ce faire une fonction de hashage très simple consistant à prendre les n premiers caractères du md5 du nom du fichier.

md5 ayant une représentation hexa, le nombre de sous-répertoires ainsi créés sera de l’ordre de 16^n ; pour répartir correctement un million de documents, on a les possibilités suivantes :

— n=1 16 répertoires * 62500 docs
— n=2 256 rép * 3900 docs
— n=3 4096 rép * 244 docs

Dans cette implémentation, on utilise 3 niveaux de sous-répertoires : pour un fichier d’origine situé dans IMG/{$ext}/xxxx.ext, le hasher consiste à prendre les 3 premiers caractères (a, b, c) du md5(xxxx.ext), et déplacer le fichier dans IMG/$ext/a/b/c/xxxx.ext.

fonction : function hasher_adresser_document(xxxx.ext)

Déplacement d’un document

fonction : function hasher_deplacer_document($id_document) {}

Cette fonction a pour rôle de déplacer un document proprement de sa position non hashée vers sa position hashée. Tous les contrôles d’erreur sont mis en place afin de garantir que tout se passe bien : si la création du sous-répertoire échoue, par exemple, ou si la connexion à la base de données est tombée, il faut pouvoir reprendre.

Elle appelle hasher_adresser_document()

Conversion « batch »

fonction : function hasher_deplacer_n_documents($n) {}

Cette fonction prend les n documents non hashés les plus récents, et appelle hasher_deplacer_document() sur chacun d’eux. Elle renvoie un array() contenant les id_document des documents qu’elle a déplacés.

Elle peut servir à convertir l’existant (on l’appelle répétitivement jusqu’à épuisement du stock), ou juste après un upload (en SPIP 1.9.2, car en SPIP 2 le cas de l’upload est géré nativement via le pipeline ad hoc), ou un spip2spip, afin de hasher les documents qu’on vient d’ajouter [1].

Elle peut aussi être appelée depuis la page ecrire/?exec=hash_documents

Gestion de redirection

Lorsqu’un document a été déplacé, on souhaite qu’un hit sur son ancienne adresse renvoie vers la nouvelle adresse, avec un code HTTP 301 Moved Permanently (redirection définitive).

Ceci permet, d’une part, de gérer proprement la migration ; d’autre part, ça implique qu’il n’est pas indispensable de hasher les documents juste après l’upload, donc beaucoup de souplesse dans l’intégration.

On va pour ce faire ajouter un .htaccess dans le répertoire IMG/ qui lancera, sur les fichiers non trouvés, un script de redirection écrit en PHP ; ce dernier calculera l’adresse hashée du document demandé, vérifiera si ce document est présent, et en fonction du résultat renverra soit un code 301 vers ce document, soit un 404 Not Found.

Le script PHP est le fichier hash_404.php présent dans le plugin ; la seule petite difficulté pour son activation, c’est qu’il nécessite la création d’un .htaccess spécifique dans le répertoire IMG/ contenant le code suivant :

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
# Si spip est range a la racine du domaine
RewriteRule .* ../index.php?action=hash_404 [L] 
# Pour un mutualise, vaut mieux travailler en url absolue
#RewriteRule .* /index.php?action=hash_404 [L]

En fonction de la configuration technique de l’hébergeur, il faudra peut-être adapter un peu ce .htaccess.

Compatibilité

Il est important de noter que la structure proposée pour le code offre nativement une grande compatibilité, puisque, une fois les documents hashés, on peut retirer tout le code de hachage sans impact sur le fonctionnement.

On a pris soin de ne pas utiliser de fonctions « fragiles » de SPIP, qui changeraient d’une version à l’autre, afin que ce plugin soit compatible de SPIP 1.9.2 aux SPIP futurs.

La seule difficulté à prendre en compte est que le nommage des documents dans la table spip_documents a changé entre SPIP 1.9.2 et la série SPIP 2.0. En SPIP 1.9.2, en effet, spip_documents.fichier contenait une chaîne de la forme IMG/jpg/postcards.jpg, tandis qu’en SPIP 2 on a simplement jpg/postcards.jpg (le IMG/ a été supprimé).

Le plugin fonctionne nativement avec les deux représentations (avec ou sans IMG/).

Réversibilité

Les fonctions de hashage sont réversibles : un paramètre $rev permet de revenir à la structure « à plat ». Cela permet de revenir en arrière si finalement on ne souhaitait pas utiliser ce système, ou pour en utiliser un autre. Cela a aussi permis de tester le plugin, lors de son développement, en faisant des allers-retours.

Notes

[1Patch à appliquer aux fonctions qui ajoutent des fichiers dans spip_documents :

// hasher les documents
include_spip('hash_fonctions');
if (function_exists('hasher_deplacer_n_documents')) {
  hasher_deplacer_n_documents(10);
}

Discussion

10 discussions

  • 11

    Bonjour,
    Depuis le passage de SPIP à la version 4.0.2, le plugin ne fonctionne plus : les nouveaux documents sont stockés à la racine du répertoire de l’extension.
    De plus, étrangement, s’il est toujours possible de hasher et déhasher les anciens documents (ceux ajoutés avant le passage à la 4.0.2), cela n’est plus possible pour ceux ajouter après le passage (la liste des Id des documents modifiés est vides). Je dis « étrangement » parce que rien dans la base de données ne semble différencier les uns et les autres...

    • Je viens de tester avec spip 4.0.4 + plugin en v3.0.0 > pas de souci de mon côté.

    • Est-ce que ça pourrait venir de PHP 8.1 ? Comment le vérifier ?

    • Cela vient peut-être de PHP8.1 (mais c’était déjà le cas auparavant). Comment le vérifier ?

    • Ah c’est possible... je vais voir si j’arrive à me mettre une 8.1...

    • En 8.1 mais avec la version de dev de SPIP : ca marche.

      En 8.1 avec spip 4.1.0-alpha : ca marche

      En 8.1 avec spip 4.0.4 : j’ai trop de warning pour ne serait-ce qu’arriver à une page d’upload de document.

      A vu de nez, je dirais qu’à mon avis vue que spip 4 n’est pas censé être compatible php 8.1 mais uniquement 8.0, cela pourrait venir de là...

    • J’ai identifié un premier soucis : désormais, les logos apparaissent dans la table spip_documents. Or, le plugin ne les hashe pas.
      Lorsque, depuis la page de configuration, on souhaite hasher 100 documents, mais qu’il n’y a que des logos, le plugin ne hashe rien, alors qu’il peut y avoir des documents à hasher en 101è position.
      En revanche, je n’ai pas trouvé pourquoi l’adresse n’est pas hashée lorsqu’on ajoute un nouveau document.

    • Ah oui, car il y a un presupposé dans la regexp que le nom du dossier = le nom de l’extension.

      Bon, faut reussi à trovuer une regexp qui passe aussi pour les logos...

    • Pour le premier problème, peux tu tester la branche suivante :

      https://git.spip.net/spip-contrib-extensions/hash_documents/src/branch/bugs_gilles

      pour le second problème, difficile de dire, mais vu ce que je te disais sur les compats de spip 4 avec php 8 ce serait pas impossible que le pb viennent de là.

      Tu a accès aux logs PHP ?

    • Cela fonctionne : les logos sont désormais hashés.
      Pour le 2d, il semble avoir disparu lorsque j’ai installé cette branche, peut-être une mauvaise installation lors du passage à la 3.0.0 ?

    • Cela fonctionne : les logos sont désormais hashés.
      Pour le 2è problème, il semble avoir disparu avec cette branche.

    • OK,
      et bien c’est releasé sous le numero v3.0.1

    Répondre à ce message

  • 3

    Bonjour, le plugin ne fonctionne plus sous SPIP 4.0. Sa mise à jour est-elle prévue ?

    • Il faudrait tester en modifiant les bornes dans paquet.xnl pour voir si cela marche, et si c’est le alors on pourra modifier la version distribué. Sinon faudra que quelqu’un s’attelle à la tache d’une maj.

    • Après quelques recherches, il suffit de remplacer la fonction spip_fetch_array() par sql_fetch() dans le fichier hasher_fonctions.php

    • Je vais en profiter pour corriger tous les appels aux vieilles fonctions.

    Répondre à ce message

  • Pour compléter la documentation, pourrait-on préciser à partir de quand ce plugin devient intéressant. De l’ordre de 10kdocument ? 100kdocument ? 1MDocument ?

    Répondre à ce message

  • 3

    Ah, au fait, tant qu’on y est, serait-il envisageable de hasher aussi les logos, j’en ai beaucoup, puisque j’ai beaucoup de rubriques et articles...

    • Hasher les logos n’est pas prévu, mais sens-toi libre de le coder en option. En option seulement, car la caractéristique du plugin actuel est qu’on le désactive quand on veut sans perdre aucune fonctionnalité. Pour les logos c’est impossible en l’état actuel de SPIP.

    • Oui, je sais bien que les logos posent problème, avec leur stockage direct à la racine. Les passer en gestion comme des docs à part entière est sans doute nécessaire, et hash_documents pourra alors les prendre en charge... ;-)

    • Bonjour,

      Est-ce que avec la version spip 2.1 le plugin permet de hasher les logos ?

    Répondre à ce message

  • 16

    Bonjour

    Sur un site de photographe, j’ai un souci. Je ne peux plus afficher la page de vidage du cache. Enfin j’ai une page blanche. Ca bloque au moment du calcul de l’espace sur local/cache-vignettes. Dans ce dossier, il y a 600 dossiers differents. En utilisant la fonction calculer_taille_dossier de exec/admin_vider dans un fichier php séparé, j’ai reussi à aller une fois jusqu’au bout, il m’a trouvé plus de 2Go de données. Je ne sais pas ce qui bloque l’execution du script, le max_execution_time ?
    Si on regarde la fonction de calcul de la taille du cache normal, on voit que si y’a trop de fichiers on ne fait qu’une estimation de la taille.

    Ensuite en listant le contenu de IMG/jpg, je me rend compte qu’il y a dans les 56000 fichiers... J’envisage du coup d’installer le plugin Hash Document qui ne peut qu’améliorer les performances... Dans la doc du plugin il est écrit que la « page ecrire/ ?exec=hash_documents permet d’aller hasher tous les documents déjà installés »... Est ce-qu’on est sûr que cette requête ira jusqu’au bout de son exécution sans etre génée par le max_execution_time ?

    Je ne suis pas sur d’être clair ;-)

    • (je t’ai répondu sur la liste)

    • OK merci Fil... Je viens de tester, tout s’est déroulé sans problème...

      Par contre, la redirection via le .htaccess ne fonctionne dans le cas d’une mutualisation par domaine car le fichier /plugins/hash_documents/hash_404.php n’est pas accessible. J’ai testé en mettant ce fichier dans le sous dossier exec du plugin et en changeant dans le .htaccess :

      RewriteRule .* /ecrire/?exec=hash_404 [L]

      Et en ré-écrivant un peu les chemins dans ce même fichier... Ca fonctionne. Je peux essayer d’aller plus loin et rendre l’ensemble générique aux cas sans ou avec mutualisation, si tu penses que c’est une solution envisageable

    • Le passage en exec est certainement plus générique ; n’hésite pas à le commiter

    • quoique, à y réfléchir un peu plus, ça ne devrait pas être un « exec » (associé à l’espace privé), mais un « action ».

    • OK, c’est fait

      J’ai changé le contenu du .htaccess, du coup faudrait mettre l’article à jour

    • OK super, j’ai simplifié un peu ton code en révision 49003 et mis à jour la doc ; merci !

    • Bonjour,

      Tout d’abord bravo pour cet excellent plugin très souple et qui fonctionne à merveille !

      Je suis en train de mettre en place les redirections.
      (site installé à la racine d’un hébergement ovh)

      Avec le code de base : RewriteRule .* ../index.php ?action=hash_404 [L]
      J’obtiens une erreur « Fichier hash_404 introuvable »

      J’ai tenté une autre règle précisée dans hash_404.php :
      RewriteRule .* /ecrire/ ?action=hash_404 [L]
      et je tombe (naturellement ?) sur la page d’authentification de spip...

      Quelle est la bonne façon d’appeler (non connecté) la page hash_404. ?

    • Peut-être ajouter en haut du fichier .htaccess la ligne

      RewriteBase /
    • Merci pour la réponse.
      En fait le pb est autre et le htaccess de cette doc est bonne à priori.
      J’ai du corriger plusieurs choses dans le fichier action/hash_404.php :

      1 - Il faut mettre tout le code dans une fonction :

      function action_hash_404_dist(){
          [ici le code du fichier]
      }

      j’étais sous SPIP 2.1.12 et sans ça, SPIP renvoie une erreur en arrière plan avec le logo de l’écureuil et tout...

      2 - j’ai du remplacer les chemins des file_exists :

      file_exists('../'.$GLOBALS['meta']['dir_img'].$dest)

      remplacé par :

      file_exists('./'.$GLOBALS['meta']['dir_img'].$dest)

      (je ne comprend pas bien pourquoi. D’où le script est t-il vraiment lancé ?...)

      3 - Par ailleurs, je note une coquille : dans les 2 tests « file_exists » (selon que le fichier d’origine était un fichier hashé ou non), on peut voir une affectation à « dest1 » au lieu de « dest ». (« dest1 » ne servant à rien par la suite).

      Bon, ça fonctionne comme ça mais je crois que le chemin relatif est à réviser. Notamment, il risque de différer selon que la source est un fichier hashé ou non ...?)
      non ?

    • Hello

      Concernant le point 3, c’est ma faute. J’ai corrigé et commité

      Pour le point 1, je viens de retester, ca fonctionne correctement sans mettre le code dans une fonction. Et ca fonctionne toujours si on le met dans la fonction, ce que j’aurais aussi du faire quand j’ai déplacé le fichier dans le dossier action. Mais à l’époque je n’étais pas bon en « action »... Juste un peu plus maintenant ;-) Par contre comment ça fait pour fonctionner sans, la je ne pige pas, car spip est censé chercher la fonction lors de l’aiguillage. Fil peut etre peux tu nous éclairer ? Et me dire si je peux commiter cette modif aussi ?

      Enfin, le point 2 : chez moi ta proposition ne fonctionne pas. Ca me semble normal, vu qu’on part du dossier ecrire (/ecrire/ ?action=hash_404 [L]) et qu’il faut remonter d’un niveau pour aller dans IMG. Non ?

    • salut, merci pour ce retour, j’ai compris 2,3 trucs du coup.

      Point 3 : ok
      Point 1 : à suivre... (dans mes tests, j’avais bien le message prévu en cas de fichier non trouvé mais en plus et au dessous, une erreur de spip dans un cadre blanc... indiquant « Fichier hash_404 introuvable »)

      Point 2 : tu ne dois pas avoir la même chose que moi dans ton htaccess. En fait j’ai pris la règle de réécriture de la doc :
      RewriteRule .* ../index.php ?action=hash_404 [L]

      L’appel se fait donc depuis la racine du site. Ce qui est mieux à priori car si on le fait dans /ecrire, on a droit à une page d’authentification il me semble)

      Mais effectivement, il faudrait mettre d’équerre les exemples de règles de réécriture en commentaire dans le hash_404.php pour que ce soit conforme à cette doc.

    • Pour le point 1, j’avais bien comme tu dis, j’avais pas compris dans ton premier post.

      Concernant le point 3, le code à mettre dans le .htaccess est en commentaire dans le fichier action/hash_404.php

      Je mets cet article à jour

    • Ouille, crise de panique, plus rien ne fonctionnait...

      Donc, si tu peux confirmer le point 3 :

      -  dans le fichier action/hash_404.php, le test doit se faire par rapport à la racine du site :
      file_exists('./'.$GLOBALS['meta']['dir_img'].$dest)
      -  dans le fichier .htaccess, dans un cas classique, on peux travailler en relatif :
      RewriteRule .* ../index.php?action=hash_404 [L]
      mais dans le cas d’une mutu, ça ne fonctionne qu’en absolu (je viens de vérifier) :
      RewriteRule .* /index.php?action=hash_404 [L]

    • Donc, pour résumer, le fichier php se base bien sur le répertoire courant (./) qui correspond dans notre cas à la racine du site vu qu’on appelle le script depuis la racine (index.php ?action=hash_404).
      Et ça doit bien se passer comme ça pour tous je suppose ?
      et donc je crois qu’il y a qu’un seul cas pour le fichier php effectivement.

      Ensuite, pour appeler ce script dans de bonne condition, il faut l’appeler depuis la racine du site. Et la je ne suis pas spécialiste des règles de réécritures mais dans mon cas « simple » suivant :
      www.domaine.com/IMG/jpg/photo.jpg

      mon .htaccess se trouvant dans IMG, le contexte de la règle de réécriture est ce dossier IMG et c’est pourquoi on fait ../ pour chopper la racine du site. (je suppose car si le contexte est IMG/jpg, je ne comprend plus pourquoi ça marche chez moi :-) )

      Je ne connais pas les différences avec les sites mutualisés.
      Par contre, je viens de faire le test en absolue chez moi :
      RewriteRule .* /index.php ?action=hash_404 [L]

      et ça fonctionne aussi (moyennant 2 ou 3 refresh d’écran, car un changement de htaccess à la volée pose parfois soucis...)

      Donc ce que je pense au niveau du htaccess :
      -  soit le ../ permet de tomber sur la racine du site dans tous les cas et c’est le plus simple (mais tu sembles dire que ce n’est pas le cas dans les mutualisés - ou pb lié au changement de htaccess à la volée ??)
      -  soit tout le monde se base en absolue mais dans ce cas, il faut préciser dans la doc d’écrire :
      — > /index.php si le site est à la racine du domaine
      — > /rep1/rep2/rep3/ si le site est ailleurs sur le domaine

    • Hello

      J’ai commité les modifs. Je laisse les 2 exemples dans le .htaccess

    Répondre à ce message

  • 1

    Bonjour

    je voudrais tester ce plugin mais je ne trouve pas l’endroit où le télécharger. Suis-je mal réveillé ?

    Manu

    Répondre à ce message

  • 2

    Salut Fil

    Bon quand j’ai dis tout s’est déroulé sans problème... heu je me suis trompé... En fait environ 2000 fichiers sur les 55000 ont été déplacés dans les sous-répertoires mais le renommage en base ne s’est pas fait... Je pige pas bien comment mais bon, c’est une constatation... Donc j’ai modifié la fonction hasher_deplacer_document pour que si le fichier qu’on veut déplacer est déjà déplacé, on modifie juste le chemin en base... Ca m’a permis de remettre l’ensemble d’équerre...

    Je ne suis pas sur que ce soit une bonne idée de commiter cela, car faut quand même être sûr que les fichiers ont des noms uniques sinon, ca peut tout casser... Je garde pour moi, je laisse dans le code mais en commentaire ou je rajoute une case a cocher (avec explication) ?

    Répondre à ce message

  • 1

    oui le classement par date c’est l’ideal pour les gros sites, car en plus ca permet de localilser les documents anciens sur un serveur separe par exemple. avec quelque regle de htaccess, on peut rediriger le visiteur en fonction des dates d’images.

    Quand on a des centaines voir milliers de docs c’est tres vite indispendable d’utiliser plusieur serveurs dedie pour les images.

    • On pourrait ajouter une option « classement par date » à ce plugin, toute la mécanique est là.

      Mais quand tu parles de « plusieurs serveurs dédiés », je m’interroge. Est-ce qu’il s’agit de capacité disque ? Dans ce cas, personnellement, je recommanderais plutôt l’utilisation de serveur de type « cloud » (Amazon S3 pour ne pas le nommer). Je serais intéressé à avoir un exemple d’application auquel tu penses.

    Répondre à ce message

  • J’ai de mon côté patché SPIP pour créer des sous-répertoire par date :

    jpg/200901/
    jpg/200902/
    jpg/200903/

    etc...

    Je n’avais pas conaissance de ce plugin, mais dans le passé j’avais fait la même chose que je fais maintenant avec le hash comme le cache (en gros comme ce système mais à un seul niveau).

    Ce système par date n’est pas mal car il nous permet de regrouper plus ou moins les documents par articles (à moins qu’on upload des documents sur une très longue période), et ca peut aider à situer leur date d’upload, les articles correspondants, et retrouver les documents plus vite et intuitivement sur le disque.

    Si ca peut donner des idées...

    Benoit

    Répondre à ce message

  • 2

    Excellente nouveauté pour les sites avec beaucoup de docs !

    Je viens de le mettre en place sur un site SPIP 2, et il n’arrive pas à hasher 90 document sur les un peu plus de 3000 que j’ai. Tous ces 90 semblent avoir un nom comportant au moins un chiffre...

    Répondre à ce message

Ajouter un commentaire

Avant de faire part d’un problème sur un plugin X, merci de lire ce qui suit :

  • Désactiver tous les plugins que vous ne voulez pas tester afin de vous assurer que le bug vient bien du plugin X. Cela vous évitera d’écrire sur le forum d’une contribution qui n’est finalement pas en cause.
  • Cherchez et notez les numéros de version de tout ce qui est en place au moment du test :
    • version de SPIP, en bas de la partie privée
    • version du plugin testé et des éventuels plugins nécessités
    • version de PHP (exec=info en partie privée)
    • version de MySQL / SQLite
  • Si votre problème concerne la partie publique de votre site, donnez une URL où le bug est visible, pour que les gens puissent voir par eux-mêmes.
  • En cas de page blanche, merci d’activer l’affichage des erreurs, et d’indiquer ensuite l’erreur qui apparaît.

Merci d’avance pour les personnes qui vous aideront !

Par ailleurs, n’oubliez pas que les contributeurs et contributrices ont une vie en dehors de SPIP.

Qui êtes-vous ?
[Se connecter]

Pour afficher votre trombine avec votre message, enregistrez-la d’abord sur gravatar.com (gratuit et indolore) et n’oubliez pas d’indiquer votre adresse e-mail ici.

Ajoutez votre commentaire ici

Ce champ accepte les raccourcis SPIP {{gras}} {italique} -*liste [texte->url] <quote> <code> et le code HTML <q> <del> <ins>. Pour créer des paragraphes, laissez simplement des lignes vides.

Ajouter un document

Suivre les commentaires : RSS 2.0 | Atom