Performances cache <INCLURE> VS #INCLURE

Bonjour toutes et tous,

je viens de passer un peu de temps d’analyse sur la manière dont fonctionne le cache et j’avoue que la documentation me laisse perplexe. Aussi, j’aimerais votre avis pour corriger mes éventuelles erreurs de compréhension, et trancher une bonne fois pour toutes entre utiliser <INCLURE> ou #INCLURE.

La documentation

Essentiellement je me suis basé sur <INCLURE> d'autres squelettes - SPIP mais aussi Du php dans le squelette à la place de #SESSION ou #CACHE 0 - SPIP-Contrib

Qui dit essentiellement:

<INCLURE{fond=…}> et [(#INCLURE{fond=…})] ont donc des fonctionnements différents :

  • avec <INCLURE{fond=…}> chaque squelette inclus a un cache indépendant. Autrement dit, cette syntaxe provoque l’inclusion des pages à chaque visite d’un internaute, que celle-ci concerne une page déjà en cache ou non.
  • avec [(#INCLURE{fond=…})], la page principale appelante contient, en cache, l’intégralité du code généré, et les fichiers inclus n’ont pas de cache séparé.

et contrib spip:

Pour le choix entre <INCLURE> (inclusion avec cache indépendant) et #INCLURE (inclusion dans le cache du contexte d’appel) et pour l’usage de #SESSION voici une régle de base :

  • règle N°1 : utiliser <INCLURE> partout
  • règle N°2 : ne jamais utiliser #INCLURE ni #MODELE, surtout lorsque le squelette de l’inclusion est un peu complexe.
  • règle N°3 : utiliser #INCLURE ou #MODELE uniquement si on a besoin de conditionner l’affichage au moment de l’inclusion et utiliser les parties conditionnelles avant/après de la balise #INCLURE, ou besoin d’appliquer un filtre au résultat.

Mes tests

Je m’excuse d’avance pour ceux qui connaissant tout cela, mais ça pourra peut être servir à d’autres donc je vais quand même détailler un peu le truc.

Le code

J’utilise un squelette simplissime que j’appelle test12.html

hello squelette

  <INCLURE{fond=inc_test}>

qui inclut une noisette inc_test.html:

<br>
coucou noisette !!!!!!!!!!!!!!!!!!
<br>
Nombre = 
<?php echo random_int(1,42); ?>
<br>
Nous sommes le <time datetime='#DATE'>[(#DATE|date_iso)]</time>

#INCLURE statique

Pour ce test j’utilise donc [(#INCLURE{fond=inc_test})] dans mon squelette.
Je vide le cache et j’obtiens:

  • un squelette PHP skel/html_27fe7002f3ad6db46f7f78e9b6748c5d.php:
<?php

/*
 * Squelette : squelettes/test12.html
 * Date :      Thu, 25 Jan 2024 13:32:42 GMT
 * Compile :   Thu, 25 Jan 2024 13:34:52 GMT
 * Boucles :   
 */ 
//
// Fonction principale du squelette squelettes/test12.html
// Temps de compilation total: 0.197 ms
//

function html_27fe7002f3ad6db46f7f78e9b6748c5d($Cache, $Pile, $doublons = array(), $Numrows = array(), $SP = 0) {

        if (isset($Pile[0]["doublons"]) AND is_array($Pile[0]["doublons"]))
                $doublons = nettoyer_env_doublons($Pile[0]["doublons"]);

        $connect = '';
        $page = (
'hello squelette

  ' .
recuperer_fond( 'inc_test' , array(), array('compil'=>array('squelettes/test12.html','html_27fe7002f3ad6db46f7f78e9b6748c5d','',3,$GLOBALS['spip_lang'])), _request('connect') ?? ''));

        return analyse_resultat_skel('html_27fe7002f3ad6db46f7f78e9b6748c5d', $Cache, $page, 'squelettes/test12.html');
}
?>

  • ce code php permet de créer un fichier de cache cache/calcul/ba81/e6d5.cache qui contient le code SPIP transpilé (calculé dans le langage SPIP si j’ai bien suivi) et du code PHP:
<br>
coucou noisette !!!!!!!!!!!!!!!!!!
<br>
Nombre = 
<?php echo random_int(1,42); ?>
<br>
Nous sommes le <time datetime='2024-01-25 14:34:52'>2024-01-25T13:34:52Z</time>
  • et j’obtiens également le code pour toute ma page dans cache/calcul/93fe/3a17.cache qui inclut le cache précédent (au détail prêt que le PHP a été interprété!!!):
hello squelette

  <br>
coucou noisette !!!!!!!!!!!!!!!!!!
<br>
Nombre = 
21<br>
Nous sommes le <time datetime='2024-01-25 14:34:52'>2024-01-25T13:34:52Z</time>

Si je rappelle plusieurs fois la page mon nombre aléatoire et ma date ne change plus à moins de « calculer » la page à nouveau ce qui redéclenche une exécution dans mon squelette PHP. Spip regénère alors à nouveau les caches. Il y a une petite différence avec ce qui est écrit dans la doc

et les fichiers inclus n’ont pas de cache séparé

mais bref, c’est fonctionnellement identique et à part un peu plus de place prise dans le cache, la doc est conforme au résultat.

<INCLURE> dynamique

Pour ce test j’utilise donc <INCLURE{fond=inc_test}> dans mon squelette.
Je vide le cache et j’obtiens:

  • un squelette PHP légèrement différentskel/html_27fe7002f3ad6db46f7f78e9b6748c5d.php: (j’ai stippé les parties identiques, notez seulement l’apparition du echo avant recuperer_fond)
$page = (
'hello squelette

  ' .

'<'.'?php echo recuperer_fond( ' . argumenter_squelette('inc_test') . ', array(\'lang\' => ' . argumenter_squelette($GLOBALS["spip_lang"]) . '), array("compil"=>array(\'squelettes/test12.html\',\'html_27fe7002f3ad6db46f7f78e9b6748c5d\',\'\',3,$GLOBALS[\'spip_lang\'])), _request(\'connect\') ?? \'\');
?'.'>');
  • j’ai toujours le même cache pour ma noisette cache/calcul/ba81/e6d5.cache qui contient le code SPIP transpilé
  • en revanche le cache de la page a radicalement changé:
hello squelette

  <?php echo recuperer_fond( 'inc_test', array('lang' => 'fr'), array("compil"=>array('squelettes/test12.html','html_27fe7002f3ad6db46f7f78e9b6748c5d','',3,$GLOBALS['spip_lang'])), _request('connect') ?? '');
?>

On n’a plus de texte brut, mais un echo qui va aller rééxécuter le code contenu dans le cache de la première noisette. Avec ce sytème, mon nombre aléatoire fonctionne bien (à chaque appel PHP va réinterprêter random) mais ma date ne change touours pas à moins d’un nouveau calcul qui peut intervenir si:

  • on calcul la page à nouveau
  • le cache arrive en fin de vie
  • un évènement invalide le cache de spip (publication d’article, commentaire, modification, etc…)

Mesures

J’ai pris 250 vraies urls de mon site (avec des images et une vraie mise en page), et j’ai fait un curl multi threadé pour simuler 5 « hits » en simultané sur chaque article. Je prend le temps cumulé de ces 5 « internautes » pour chaque URL, et je moyenne le tout.
Dans le squelette de la page, j’ai soit mis uniquement des <INCLURE> soit des #INCLURE en fonction du test.
edit: j’ai réalisé ces mesures avec la compression des caches désactivée (je l’avais enlevé pour mes analyses, et oublié de la remettre pour les tests). Les tailles réelles de caches sont donc en vrai plus petites, mais ça ne change pas l’ordre des choses.

  • <INCLURE>:
    • Je commence avec un cache vide: 965ms en moyenne
    • Puis au 2ème passage: 184ms
    • le cache pèse 30Mo
  • #INCLURE:
    • Je commence avec un cache vide:: 600ms en moyenne
    • Puis au 2ème passage : 90ms
    • le cache pèse 48Mo

Je suis très surpris des différences très significatives de timings. C’est énorme je trouve. Bon je ne comprends pas vraiment pourquoi au 1er passage le #INCLURE est 300ms plus rapide alors que c’est plus de travail pour lui. Sur d’autres mesures avec moins d’urls je trouve un timing similaire à <inclure>. Passons.
En terme de service par contre, on va 2X plus vite avec un cache statique qu’en dynamique. Au prix de 50% de taille de cache en plus. L’explosion de la taille du cache s’explique facilement, par contre les multipes echo recuperer_fond() (qui font des accès disques si le cache est dans le filesystem) des caches noisettes en dynamique semblent assez pénalisants…

Mes questions

Merci d’avoir lu jusque là! :exploding_head:

Déjà, est ce que c’est quelque chose que vous constatez vous aussi? Est ce que mon test est biaisé quelque part?

Parce que du coup, quand je regarde la doc et spip contrib et que je vois:

règle N°1 : utiliser <INCLURE> partout

règle N°2 : ne jamais utiliser #INCLURE ni #MODELE, surtout lorsque le squelette de l’inclusion est un peu complexe.

Un abus de l’emploi des #INCLURE (statiques) risque donc d’augmenter le volume de cache et il faut éviter d’y avoir recours et ne les employer que quand on a pas le choix, c’est à dire

  • en cas d’affichage conditionnel [ avant(#INCLURE)apres]
  • ou en cas d’appel d’un filtre [(#INCLURE|filtrer)] .

et bien je suis assez surpris. Certes, le volume de cache est significativement plus important mais… à l’heure des disques SSD en To et des serveurs blindés de RAM, est il pertinent de continuer à recommander du cache dynamique? En statique, le gain pour le visiteur (et le référencement) est énorme!

Évidemment, si du code PHP traine dans les noisettes un cache statique peut être dangereux (ça dépend de ce qui est fait), mais du coup j’aurais plutôt tendance à inverser la logique non? Utilisez du cache statique partout SAUF si vous êtes coincés avec un besoin données dynamiques et dans ce cas là utilisez le cache dynamique.

Le seul bémol que je peux mettre à mon analyse car je n’ai pas testé ce sont les grosses tailles de cache si on utilise REDIS ou un cache en RAM. Le fait de ne pas partager les caches de noisettes entre 2 pages pourrait peut être être pesant sur un site à gros volume…

Bref, j’aimerais vraiment avoir vos retours, peut être que j’ai loupé un truc? Merci à vous!

cpol0

Le message est récent, je ne l’avais pas trouvé dans mes recherches mais je vois qu’on donne toujours ce conseil de <INCLURE> The SPIP Cache and dynamic PHP functions - #2 par JLuc
Il y a un truc que j’ai vraiment pas du capter :sweat_smile:

Faut pas t’excuser, c’est sympa de partager ça avec la communauté :slight_smile: Je suis certain que des personnes expertes du cache pourront répondre à tes interrogations !

Ce que je peux au moins te dire, c’est qu’en plus des balises #SESSION ou #AUTORISER ou avec du php dans le squelette, et certaines autres balises, il faut éviter #INCLURE également dès que tu as un #FORMULAIRE_xx dedans.

Et en fait… tu as très vite fait d’arriver dans une situation où, avec #INCLURE ça te génère un cache spécifique à pour une rédactrice ou admin qui est alors resservi pour un autre visiteur sur le site à tort.

Sur les performances, je n’ai jamais vraiment regardé la différence par contre.

Des tests de performances similaires aux tiens avaient été fait après l’invention des #INCLURE, avec des résultats similaires (ça doit pouvoir se trouver dans l’historique des listes).

Une inclusion statique est certainement parfois possible et possiblement préférable, et il serait intéressant de chercher à définir précisément dans quelles circonstances il est possible d’utiliser un #INCLURE.

Mais ça risque d’être très compliqué à définir et encore plus à documenter et à mettre en œuvre, pour les raisons indiquées par @marcimat et aussi de manière encore plus aigüe parce qu’un #INCLURE, donc une inclusion dite statique, statificie tous les <INCLURE> contenus.
Cf @Cerdic jadis « #INCLURE par construction transforme en texte/html tout ce qui est inclus (en perdant donc les partie dynamique) Il faut donc remplacer les #INCLURE par des <INCLURE> car vous avez sinon le risque de divulguer du contenu saisi par une personne à d’autres personnes… (ou alors le formulaire est inclus via un modèle perso ce qui produit le même résultat) »

Donc ça dépend de tout ce qui contenu dans l’arbre des inclusions…
On pourrait commencer à entrer dans le détail en disant genre
#INCLURE est ok si l’inclusion ne contient ni balise dynamique ni inclusion.
On pourrait probablement affiner avec
#INCLURE est ok si l’inclusion ne contient ni balise dynamique ni inclusion contenant des balises dynamiques ou des inclusions…
etc

Et encore faudrait vérifier tout ça…

C’est donc la recommandation réalistement praticable qui est recommandée et documentée.

oui en perf finale de service des pages en cache, un #INCLURE est potentiellement plus rapide qu’un <INCLURE> mais comme le dit la préconisation cela fait perdre tous les comportements dynamiques des squelettes (ie balises dynamiques et formulaires). C’est donc dangereux (fuite de données personelles) et source de bugs.

La préco reste donc : utiliser <INCLURE> partout et si tu as besoin de filtrer le résultat d’un squelette ou de faire un affichage conditionnel utiliser #INCLURE en t’assurant que tu tue pas un comportement dynamique.

Maintenant si tu maitrises ton site et que tu sais pertinemment que tu as aucun comportement dynamique et que personne ne va en ajouter à ton insu sur la durée de vie du site (exit donc un certain nombre de plugins…) tu peux tenter ta chance avec des #INCLURE, mais ne vient pas accuser SPIP de bug sur des comportements inattendus ou des fuites de données :slight_smile:

Même avec un <INCLURE>, tu as toujours un cache séparé entre un visiteur anonyme et un rédacteur/admin, et ce même si le contenu est strictement identique. Mais oui, j’imagine bien le cas d’une balise #SESSION au fin fond d’une noisette qui serait incluse (par erreur) statiquement générer un beau bazar.

Oui OK, c’est raccord avec ce que dit la doc. Si inclusion statique, pas de session, pas de formulaire, et oui ça va statufier ( :wink:) les noisettes en <INCLURE>.

Oui, tout à fait. Je concède que ça pourrait être un peu délicat à expliquer correctement dans une documentation. Et c’est certain qu’un <INCLURE> fonctionne à tous les coups sans avoir à se poser de question, donc je concède que dans une doc il soit plus simple de le recommander.
Au final, c’est plutôt le "ne jamais utiliser de #INCLURE qui serait à nuancer comme tu viens de le faire: « Si vous voulez vraiment optimiser à fond, il peut être intéressant d’utiliser #INCLURE si et seulement si vous remplissez les conditions suivantes… »

Nos réponses se sont croisées, mais OK bien noté!

Merci à tous pour vos réponses :pray:

Bonjour,
Il y a le cas du traitement des données. #SET{foo, #INCLURE{...}} qui marche très bien, mais y a-t-il une solution similaire avec <INCLURE> ?

Ça fut évoqué dans le ticket #5028 - <INCLURE> : parties conditionnelles et application de filtres - spip - SPIP on GIT qui partait de l’autre particularité des inclusions statiques : l’applicabilité d’un filtre et les parties conditionnelles avant / après.

Il y a en général, heureusement, des manières de faire autrement sinon ya longtemps que la question se serait posée ;
et il est également possible d’utiliser du PHP pour appeler <?php $foo = recuperer_fond ?> et faire ce qu’on veut avec le résultat ;
et ça pourrait d’ailleurs peut être faire l’objet d’une nouvelle macro (mais là je sais pas bien laquelle au juste) dans le plugin macrosession si qqn s’y colle.

Oups non, je crois pas que c’est possible en l’état de faire une macro qui équivale à un #SET{foo,<INCLURE>}.

Alors donc plus explicationnellement puisque cette discussion est citée : le #SET{foo,<INCLURE>} n’est pas possible car un <INCLURE> range un appel php dans le contenu du cache, au milieu du HTML et c’est d’ailleurs pour ça qu’on l’appelle « dynamique ».
Or les #SET et #GET font leur boulot à ce même moment, lors de la fabrication du cache, mais bien avant donc, que ce cache ne soit servi et que le PHP ne soit exécuté.

oui c’est exactement à ça que #INCLURE sert : quand on veut manipuler le résultat du calcul sous forme de texte pour le mettre dans une variable et/ou lui appliquer des filtres.

Mais donc ce faisant on perd tout le dynamisme, et c’est soit l’un soit l’autre, on ne peut pas avoir les 2

Et si le squelette qui fait le #INCLURE a lui-même un #CACHE{0} ?
(je me doute bien qu’en termes de webperf, c’est pas cool, mais en termes de mise à jour du #INCLURE ?)

Après un #INCLURE, il n’y a plus de #INCLURE, il n’y a que le résultat du #INCLURE. C’est pour ça que le service est rapide.
SPIP ne gère pas d’arborescence symbolique des inclusions, qu’elles soient statiques ou dynamiques. Il n’y a pas de gestion du cache « à part » en dehors du service des pages (et des inclusions dynamiques).
SPIP ne s’occupe que des pages qu’on lui demande de servir.

#DEBUT
Si le cache d’une page demandée est frais, c’est ce cache qui est servi direct et peu importe que le contenu vienne de HTML ou d’un #INCLURE statique dans un squelette quelque part : SPIP le sert. SPIP ne s’occupe pas à ce moment de la durée de cache des squelettes inclus statiquement puisqu’il n’y en a aucune trace.
Si ce cache contient du code php, soit parcequ’il y a du code PHP dans le squelette (pratique à proscrire en général), soit parce qu’il y a des <INCLURE> (ou d’autres balises dynamiques), ce PHP est exécuté et c’est cet appel qui à son tour gérera son cache et sa durée (GOTO DEBUT)

On pourrait s’attendre à ce que le résultat du #INCLURE ne soit jamais conservé et qu’il soit recalculé à chaque appel de page.
J’espère que c’est la bonne réponse, ça signifierai que j’ai bien compris :sweat_smile:

J’invite ceux qui veulent comprendre le cache ou tester une hypothèse à se créer un site de test sur un hébergement disposant de APCu (par exemple alwaysdata gratuit après activation APCu cf Configurer Alwaysdata pour SPIP), à y installer Memoization Memoization - Plugins SPIP et XRay XRay - Plugins SPIP.
Et je leur souhaite une bonne navigation dans les entrailles cachées de SPIP.

2 « J'aime »

Pour ceux qui utilisent docker (assez puissant combiné avec xdebug), si votre image PHP ne contient pas déjà APCU il faut rajouter dans votre Dockerfile:
RUN pecl install apcu && docker-php-ext-enable apcu

1 « J'aime »