Une refonte du noyau de SPIP, optimisant son interface avec son langage d’implémentation, ses serveurs Web & SQL, et son système d’exploitation (V11)

Archive historique

Ceci est une archive périmée mais qui reste intéressante, parfois autant pour l’article que les commentaires associés.

ATTENTION : l’essentiel de cette contribution a été portée sur le CVS officiel de Spip.
Merci de consulter la version CVS, et son nouveau complément (article 639 et de se manifester sur spip-dev ou le forum de ce nouvel article.

Le travail de près d’un an ici décrit répond à plusieurs besoins, personnels ou exprimés sur spip-dev. Malgré une grande diversité d’interventions, un fil conducteur l’a motivé : le modèle d’exécution de Spip.
Celui-ci consiste en la chaîne de traductions suivante :

Squelettes en Spip --1--> Script en PHP --2--> Page en HTML & JavaScript

Mais l’implémentation actuelle intercale dans ce modèle théorique une phase 2bis consistant en une deuxième passe de PHP appliqué au résultat de la phase 2, afin d’y exécuter quelques balises <? qui s’y trouvent parfois. C’est d’autant plus gênant que le mécanisme de cache intervient après la phase 2, et non après la phase 2bis, ce qui conduit le serveur Web à recommencer à chaque requête une analyse du résultat de la phase 2.

En cherchant une nouvelle implémentation évitant ce travers sans diminuer les fonctionnalités de Spip, on a en fait abouti à une augmentation de celles-ci, et à un meilleur système de cache. L’implémentation proposée parvient à respecter le modèle théorique pour la plupart des squelettes standard. Les exceptions concernent quelques balises qui n’ont pas encore été réimplémentées selon le modèle théorique
(FORMULAIRE_INSCRIPTION FORMULAIRE_ECRIRE_AUTEUR
FORMULAIRE_SIGNATURE FORMULAIRE_SITE
MENU_LANG MENU_LANG_ECRIRE LOGIN_PRIVE LOGIN_PUBLIC URL_LOGOUT
)

Bien entendu, les squelettes contenant des balises <? font aussi exception.

Les réécrire pour profiter des nouvelles fonctionnalités n’est pas une obligation, mais elle peut en valoir la peine : la nouvelle version du squelette standard nouveautes, fournie en complément, est moitié plus petite que l’originale, en plus de s’exécuter plus vite.

Relation avec le serveur de base de données

Ce point a été le démarrage de ce travail et a été décrit dans d’anciennes contributions, à l’exception d’une nouvelle fonctionnalité dans les accès aux champs d’une part, et d’une interface permettant de paramétrer leur traitement par le traducteur Spip vers PHP d’autre part. En voici la liste complète.

-  reconnaissance des champs dans les critères de boucle. Par exemple, le critère {titre=#TITRE} sert à trouver dans la base un autre objet ayant même titre que l’objet de la boucle englobante. Cette absence avait provoqué l’introduction du critère « exclus » qui, avec cette innovation, peut s’exprimer tout simplement par {id_article !=#ID_ARTICLE}.

-  tout champ des tables SQL est accessible par la syntaxe standard #nom du champ en majuscules ; cette accessibilité fonctionne aussi bien pour le corps de la boucle
que pour les critères. Par exemple,

<BOUCLE0(ARTICLES){visites>5}{par visites}>#TITRE #VISITES<br/>
</BOUCLE0>

liste par nombres de visites croissants tous les articles ayant plus de 5 visites, avec leur score.

-  la balise boucle peut s’appliquer non seulement aux tables standards de Spip
(ARTICLES, RUBRIQUES, etc) mais à toute table accessible par la connexion au serveur SQL et préalablement déclarée au traducteur PHP.

De la sorte, s’il existe une table nommée matable1 ayant des champs nom et adresse, on peut écrire dans un squelette :

<BOUCLE_1(matable1)><br />#NOM #ADRESSE</BOUCLE_1>

et obtenir ce que les habitués de Spip devineront sans peine. La déclaration au
traducteur PHP consiste à allonger le tableau tables_principales présent dans le fichier inc_serialbase qui décrit déjà les tables standards :
il suffit de s’en inspirer. Ce fichier est également lu à l’installation de Spip
pour effectivement déclarer les tables SQL si elles ne l’étaient déjà.

-  les balises Spip, du point de vue de l’implémentation sont de deux sortes : celles qui sont exactement la valeur d’un champ d’une table SQL, et celles qui résultent d’un calcul en PHP. Il est possible d’étendre ce jeu de balises simplement en déclarant dans le fichier mes_fonctions une fonction nommée calcul_champ_nom_du_champ qui doit retourner le code PHP à insérer dans le squelette compilé (cette fonction sera appelé même s’il existe un champ homonyme dans une table SQL — que cette fonction pourra consulter si elle y tient).

On trouvera dans le fichier inc-index-squel les spécifications précises.

-  Spip permet de référencer à l’intérieur d’une boucle un champ d’une boucle englobante, sauf en cas d’homonymie ; cette restriction est ici levée grâce à la syntaxe nom_de_boucle :nom_de_champ, admise dans le corps comme dans les critères. Par exemple :

<BOUCLE0(RUBRIQUES)><BOUCLE1(ARTICLES){id_rubrique}>#0:TITRE  ...

référencera le titre de la rubrique, pas de l’article.

-  les paramètres comme 1,n-1 où n désigne le nombre d’éléments issus de la requête (voir les spécifications) sont reconnus.

-  lorsqu’un même champ est comparé (avec =) plusieurs fois à une constante on synthétise une clause IN et on s’assure que toutes les valeurs ont été rencontrées. Par exemple l’utilisation simultanée des deux critères {id_mot=16}{id_mot=2} permettent de repérer un objet ayant deux mots-clés (ou plus) ayant ces valeurs (normalement les critères de Spip sont réunis par un « ET » logique,
mais comme ce serait ici clairement impossible, on prend exceptionnellement
un OU). Ceci solutionne le bug 168 posté sur spip-commit, mais nécessite
des requêtes imbriquées, que MySQL n’offre qu’à partir de la 4.1.

-  tout ceci est réalisé dans un encombrement minimum du processus car le code du squelette est entièrement analysé avant la production de la requête SQL, qui se limite alors aux seuls champs effectivement utiles. Chaque requête retourne ainsi une sorte d’environnement lexical (à la Scheme pour les connaisseurs) ne nécessitant aucune recopie dans des variables globales ou autres. Cette requête est construite par une fonction spécifique intégrant plus en amont le préfixe des tables, et qui devrait faciliter le portage de Spip sur d’autres serveurs SQL.

En bref , la généralisation de l’usage des champs doit raréfier le besoin
de recourir à PHP pour en mémoriser les valeurs.

Relation avec le langage d’implémentation

Toujours dans l’optique d’éviter les inclusions de PHP autant que possible,
il est possible à présent :

-  de référencer dans un critère de boucle les valeurs des variables passées dans l’URL ou dans la liste des variables de la balise INCLURE lorsqu’il s’agit d’un squelette inclus. Ainsi, avec le critère {titre==$regexp}dans un squelette nommé partitre, l’URL partitre.php3?$regexp=*refonte*
retournera les articles comportant le mot refonte dans leur
titre. De même, le critère {par $tri} permet d’indiquer à travers l’URL l’ordre dans lequel les éléments de la table vont être affichés.

-  les champs étendus peuvent être imbriqués : [ 1 (#DESCRIPTIF) 2 [ 3 (#SURTITRE) 4 ] ] ne produira rien si DESCRIPTIF est vide, même si SURTITRE ne l’est pas.

-  la syntaxe nom de champ|filtre{argument2, argument3... } n’acceptait que des constantes comme arguments ;
elle accepte à présent des noms de champs (qui seront substitués à leur valeur)
et des variables passées dans l’URL ou la balise INCLURE. De la sorte, l’interpolation
dans un squelette de :

<?php mafonction('#TITRE', '#CHAPO', $_GET['monparam']) ?>

peut être remplacée par

[(#TITRE|mafonction{#CHAPO,$monparam})]

avec l’avantage que cette fonction sera appelée une seule fois, son résultat
étant mis en cache dès la fin de l’exécution du squelette, alors que l’autre écriture provoque un calcul postérieur à l’action du système de cache, n’en profitant donc pas.

-  la logique de cette raréfaction des balises

<?<code> suggère que chaque squelette soit décrit par 2 fichiers, l'un contenant exclusivement du HTML et les balises
Spip comme celle-ci, et l'autre contenant les fonctions PHP appelées à partir des filtres; c'est pourquoi cette extension de Spip, lorsqu'elle doit exécuter un squelette  {s} charge un éventuel fichier {s}<code>_fonctions

.
Par exemple, pour le squelette rubrique=22.html on chargera
rubrique=22_fonctions.php3 s’il existe.

L’ancienne écriture reste disponible car il existe des cas où le recalcul systématique est nécessaire, typiquement si l’on veut tenir compte de l’identité du demandeur. Hormis dans ces cas, la nouvelle écriture est à préférer.

Signalons également que s’il existe dans le répertoire des squelettes un fichier nommé mon-chercher.php3, celui-ci sera lu à la place de inc-chercher.php3 afin d’y trouver la définition de la fonction chercher_squelette retournant le squelette associé au document demandé.

Relations avec le serveur HTTP

Avec les améliorations ci-dessus, les squelettes interpolant du PHP seront rares, et il convient d’essayer d’extirper du traducteur de squelettes ses propres interpolations, afin de produire le plus fréquemment possible des fichiers 100% HTML que le serveur HTTP pourra renvoyer sans aucune nouvelle passe de PHP. Plusieurs situations sont à distinguer.

Passons rapidement sur le cas du surlignage : l’implémentation standard reposait sur un deuxième passe, qu’on a pu intégrer à la première sans difficulté, au besoin en rajoutant des balises <span class="spip_surligne"> qui sont ignorées
lorsqu’aucun surlignage n’est demandé.

Une famille de cas plus difficiles réside dans les balises d’identification (ADMIN, LOGIN etc).
C’est en fait ici qu’un ajout à la page cachée est inévitable. L’alternative théorique
à l’interpolation de PHP dans la page cachée consiste à traduire ces balises en un appel à une fonction JavaScript, qui sera définie dans un en-tête, recalculé
à chaque connexion, envoyé avec la page cachée. En d’autres termes, on
remplace une interpolation PHP confiée au serveur, par une interpolation JavaScript
confiée au client : ce transfert de charge est motivé par la lourdeur d’une deuxième
passe pour le serveur, alors que le client s’en acquitte sans passe supplémentaire,
cet appel à JavaScript n’étant qu’un parmi d’autres transmis pour d’autres raisons.

Cette solution séduisante théoriquement est malheureusement très délicate à
programmer. Actuellement, seule la balise la plus fréquente, FORMULAIRE_ADMIN, a été reprogrammée ainsi. Les autres, heureusement plus rares, restent à faire.

Enfin, le dernier cas concerne la balise INCLURE. L’implémentation standard interpolait un include PHP, au motif que les durées de vie des caches inclus
et de l’incluant pouvaient différer. La présente implémentation analyse le
script du squelette inclus pour en extirper le délai, et remonter cette information
aux niveaux supérieur de l’inclusion. On produit ainsi une page 100% HTML
mais respectant les délais.

Cette stratégie demande un espace disque un peu plus important (ce qui est inévitable puisque l’on veut mémoriser plus de résultats de calcul) et une regénération sera un plus lente pour un incluant de durée de vie plus longue que ses inclusions (mais en pratique c’est le cas inverse qui est le plus fréquent).

Cette stratégie a été effectivement implémentée pour les squelettes dont les scripts d’appels sont standard (affectations des variables $fond et $delai puis include du fichier inc_public), les autres scripts (rares) suggérant la nécessité d’une interpolation. Cette stratégie a été rendue possible par la réécriture du traducteur exposée plus haut, qui est totalement réentrant : n’affectant aucune variable globale, il se permet de s’appeler récursivement pour traduire un squelette sans avoir fini de traduire le squelette incluant.

Cette stratégie demande cependant une refonte du système de cache, qui en avait de toutes façons besoin.

Relations avec le système d’exploitation

Le système de caches standard de Spip n’est ni fiable, ni équitable, ni économe :
pour contrôler les accès simultanés de plusieurs processus à la collection de caches disponibles dans le système de fichiers, il pose un verrou dans le serveur SQL, lequel est inévitablement désynchronisé avec le système de fichiers, ce qui produit des bugs certains quoique difficilement reproductibles. L’interface avec le reste de Spip a aussi l’inconvénient de multiplier les accès disques pour créer une page, même quand celle-ci n’est pas destinée à être mémorisée sur le disque.

On propose ici une version reposant sur la primitive flock du système d’exploitation, et un suivi des dépendances des caches.

En ce qui concerne la compilation des squelettes, la méthode est simple : on pose un verrou sur son fichier source avant de regarder si la version compilée existe : on bloque ainsi les processus désirant compiler la même chose, sans bloquer les processus accédant aux caches pour d’autres raisons. Si la version compilée n’existe pas ou n’est plus d’actualité (source plus récente que la version compilée), on la crée. A ce stade, la
version compilée existe nécessairement et on libère le verrou : un processus éventuellement bloqué constatera alors l’existence de ce qu’il cherche au lieu de le calculer en même temps qu’un autre (comme le fait la version actuelle).

Les squelettes sont rangés dans le sous-répertoire s du répertoire des caches,
non plus au niveau supérieur, afin d’accéder plus rapidement à chaque sous-répertoire. Leur nom est le MD5 strict du nom de leur fichier source, inséré comme commentaire en première ligne pour les identifier facilement.

Pour les caches des pages HTML, le même principe reviendrait à dédoubler tous les fichiers de cache par un fichier de verrouillage spécifique, ce qui serait prohibitif. On a donc implémenté un algorithme plus compliqué :

-  le nom du fichier de cache est, au codage MD5 près, l’URL de la page demandée moins les paramètres insérés par Spip pour ses besoins propres (on notera au passage que les squelettes référençant des variables passées dans l’URL produiront des caches différents, ce qui est bien le but souhaité) ;

-  ce fichier se trouve dans un sous-sous-répertoire du répertoire nommé CACHE, le premier sous-répertoire étant nommé par la seizième lettre du MD5, et le deuxième sous-répertoire étant nommé par la durée de vie du cache (ce double niveau de hachage accélère les accès, mais aussi le nettoyage) ;

-  lorsqu’un processus demande si un cache existe, tous les accès aux fichiers de caches sont verrouillés (en posant un verrou en lecture sur un fichier annexe, savoir celui contenant cette pose) et on teste aussitôt si le cache existe ;

-  si le fichier n’existe pas, il est immédiatement créé, avec un contenu vide ;

-  à ce stade, le fichier existe nécessairement ; on demande un verrou non bloquant
sur lui ;

-  si le verrou ne peut être obtenu, c’est qu’un autre processus vient de le créer et s’occupe d’en calculer le contenu ; on libère le verrou sur le répertoire et on demande un verrou bloquant sur le fichier, afin d’endormir le processus jusqu’à libération du verrou par l’autre processus (ceci contraste avec la version actuelle ou deux processus peuvent calculer simultanément le même cache) ;

-  si le verrou est obtenu, on libère le verrou sur le répertoire ;

-  à ce stade, et dans les deux cas, d’autres processus peuvent à nouveau accéder aux autres caches mais pas à celui-là (en particulier, un cache en cours de construction n’empêche pas la construction des caches inclus ce qui est indispensable à la
stratégie d’inclusion définie à la section précédente) ;

-  si le fichier de cache est vide, c’est que le processus l’ayant créé a disparu sans finir son travail ; le processus en cours le reprend donc (situation très anomalique
du serveur : table des processus saturée etc) ;

-  la première ligne d’un fichier de cache non vide contient sa durée de vie (éventuellement héritée de ses inclusions) ; si elle est encore valable, le verrou est levé et le contenu du fichier est retourné ; sinon le verrou est conservé et le fichier remis à zéro ;

-  si le fichier de cache est vide, on en lance le calcul qu’on retournera en résultat, après écriture du fichier et libération de son verrou.

Lors de l’écriture du fichier de cache, le système rajoute comme première ligne la durée de vie du cache ainsi que l’indication html ou php selon que la page calculée nécessite ou non une deuxième passe de PHP. Dans le cas html, le répertoire de cache est donc un répertoire ressemblant à un banal site de pages statiques. On pourrait alors émettre un champ Content-Length et un Connexion : close pour prévenir plus rapidement le client que la page est achevée.

En plus de la gestion de ces accès concurrents, cette implémentation garde trace dans des tables SQL des dépendances entre caches et tables SQL standards, de sorte que lorsqu’une brève, un article ou une rubrique sont modifiés, tous les caches y faisant référence (directement ou par inclusion) sont immédiatement retirés : il n’est plus nécessaire de forcer le « voir en ligne » ou de vider explicitement tout le cache pour que le site tienne compte de ce changement. En fait, ce mode de fonctionnement existe en Spip standard pour les seuls forums (qui n’ont qu’un seul cache associé), il est donc ici généralisé.

Toutefois, il n’est pas possible d’avoir une condition nécessaire et suffisante (sans parler des pannes des serveurs) aussi le mécanisme de nettoyage périodique est conservé.
Il est cependant plus réactif puisque le nommage des répertoires par la durée de vie des caches qu’ils contiennent permet de supprimer les caches dès leur obsolescence, et non sur la base arbitraire d’un age supérieur à 14 jours.

Installation

Cette refonte se présente sous la forme d’une archive comprenant 17 fichiers nouveaux
et 23 nouvelles versions de fichiers existants, que voici classés par ordre de taille décroissante de modification (taille du diff rapporté à la taille du fichier) :

ecrire/inc_base.php3
inc-calcul-squel.php3
inc-champ-squel.php3
inc-public-global.php3
inc-forum.php3
inc-calcul.php3
ecrire/inc_surligne.php3
inc-cache.php3
ecrire/controle_forum.php3
inc-admin.php3
inc-debug-squel.php3
inc-public.php3
ecrire/inc_cron.php3
spip_cache.php3
spip_image.php3
inc-login.php3
ecrire/inc.php3
inc-debug.php3
inc-stats.php3
ecrire/naviguer.php3
ecrire/breves_voir.php3
ecrire/articles.php3
inc-formulaires.php3

Il suffit de déballer cette archive dans une installation de Spip 1.7.2 pour la voir fonctionner. Toutefois, si on veut profiter de la réactivité du système de cache (en particulier les forums, dont on a déjà l’habitude) il faut relancer une installation en détruisant le fichier ecrire/inc_connect.php3.

En plus de cette archive, on trouvera :

-  le fichier nouveautes.html équivalent à nouveautes-dist.html mais sans interpolation de PHP (le fichier inc_cron accepte néanmoins les deux versions par souci de compatibilité).

-  le fichier tablextra.php, à installer dans ecrire/ : c’est un script qui génère automatiquement le formulaire de remplissage des entrées d’une table SQL, et qui la remplit lorsque le formulaire est retourné. On lui donne dans l’URL le nom de la table et le fichier php qui contient sa description. Ainsi :

tablextra.php?table=spip_breves&file=inc_serialbase

provoquera la construction d’un formulaire de saisie fonctionnellement proche de ecrire/breve_edit et traitera le retour du formulaire comme ecrire/breve.

Discussion

Aucune discussion

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