Carnet Wiki

API CacheLab : cibler les caches invalidés

Version 118 — il y a 5 jours — moicubitus

Doc bien avancée - 2e niveau d’API à finir

Chaque fois qu’un utilisateur crée ou modifie un objet (article, forum, favori, etc), SPIP invalide tout le cache. Tous les caches en mémoire devront ensuite être calculés de nouveau au lieu d’être utilisés... même s’ils sont encore valables. Parfois, c’est rageant de ne plus pouvoir bénéficier d’aucun de ces caches, alors qu’on vient juste d’ajouter une virgule dans un texte ou qu’un internaute vient de poster un forum dans un coin lointain du site, ou juste un « J’aime »...

CacheLab fournit une API permettant d’invalider des caches ciblés ou de leur appliquer d’autres actions. Le ciblage se fait par une condition qui porte sur le chemin du squelette ou sur le contexte ayant généré ce cache. Cela permet de décrire toutes les situations et d’appliquer l’invalidation ciblée appropriée, qui invalide tous les caches le nécessitant, et aucun autre, ou le moins possible.

Cette partie de CacheLab doit être utilisé avec le plugin memoization activé avec le cache APC ou APCu.

Voir aussi
-  Introduction au plugin CacheLab
-  CacheLab étend #CACHE et INCLURE
-  Compléments CacheLab et todo

CacheLab n’installe aucune stratégie d’invalidation, car ces stratégies dépendent de chaque site, mais CacheLab donne le moyen de le faire (assez) facilement, au moyen des fonctions d’API :
-  API d’action locale : controler_invalideur et cachelab_cibler
-  API d’action globale : ces fonctions interceptent les signaux d’invalidations de SPIP et permettent de les interpréter de la manière désirée pour le site.

API : controler_invalideur

Dans SPIP, la fonction qui invalide le cache est suivre_invalideur.
Cette fonction invalide TOUT le cache. Il est toutefois possible de la désactiver totalement ou de la désactiver pour certains types d’objets. C’est ce que facilite la fonction controler_invalideur.

Les arguments sont :

action
-  stop : arrêter le fonctionnement par défaut d’invalidation des caches par suivre_invalideur.
-  go : reprendre le fonctionnement par défaut des invalidations.
-  select : spécifier les types d’objet dont la modification est invalidante (les autres n’invalident pas).

objets_invalidants : non spécifié, ou liste des objets invalidants. Cet argument sert uniquement pour l’action select : c’est la liste (array) des types d’objets qui continuent à invalider globalement le cache.

Cette fonction peut être appelée dans le code php
-  soit dans le mes_options du plugin, pour totalement supprimer l’invalidation SPIP des caches (avec controler_invalideur('stop');) et ensuite cibler au cas par cas
-  soit au cas par cas. Par exemple, dans le traitement d’un formulaire, on pourra stoper l’invalidation juste avant d’appeler l’API SPIP d’enregistrement d’une modification, afin de préserver le cache, puis cibler précisément l’invalidation adéquate avec cachelab_cibler, puis restaurer le fonctionnement normal de l’invalidation avec (avec controler_invalideur('go');)

API : cachelab_cibler

Cette fonction permet de réaliser une action sur un certain nombre de caches, ciblés en fonctions de conditions qui portent sur leur chemin, sur l’environnement du squelette à l’origine de ce cacche (variables de contexte), sur le contenu du cache lui-même ou sur ses métadonnées.

C’est la fonction principale qui doit être appelée pour faire une invalidation ciblée, lorsqu’on a désactivé l’invalidation totale au moyen de controler_invalideur.

Les arguments en sont :

action : l’action à réaliser sur les caches filtrés. Les actions sont en anglais.
-  del : détruire les caches ciblés. En effet, SPIP 3 ne peut pas "invalider" spécifiquement un cache : pour s’en débarrasser, il faut le supprimer.
-  list : renvoie la liste des clés des caches ciblés.
-  list_html : renvoie la liste des contenus HTML des caches ciblés, indexés par leur clé. Attention, cela peut être volumineux. On peut limiter le nombre de résultats en spécifiant $option['nb'].
-  clean : parcourir les caches et supprimer les caches périmés (pour SPIP). Toute action, sauf ’pass’ le fait déjà, sauf option contraire.
-  pass : parcourir les caches mais ne rien faire, pas même la suppression nettoyage des caches périmés (pour SPIP) qui est faite par défaut pour les autres actions.
-  get : renvoyer le premier cache ciblé (métadonnées et contenu).
-  get_html : renvoyer le contenu du premier cache ciblé (html généré à partir du squelette SPIP)

conditions : l’énoncé des conditions que doit satisfaire un cache. Les conditions sont en français.
cachelab_cibler applique l’action indiquée à tous les caches qui satisfont à la fois la condition sur la session (si cette condition est présente) ET sur le chemin du squelette (s’il y en a une) ET sur le contexte (s’il y en a une). La condition sur le chemin n’est évaluée que si la condition sur la session est satisfaite (si présente).La condition sur l’objet n’est évaluée que si la condition sur la session et sur le chemin sont satisfaits (si présents).

$condition est un tableau dont les entrées peuvent être :

-  session : 1 ou ’courante’ pour la session courante, ou un identifiant de session, tel qu’utilisé en suffixe des noms de cache. Seuls les squelettes sessionnés pour l’auteur courant (ou spéficié) sont ciblés.

-  chemin : spécification du chemin des squelettes ciblés. Par défaut, c’est une chaîne constituée des morceaux de chemins ciblés, séparés par un |, et que la fonction strpos cherchera. Exemple : admin|liste ciblera tous les caches des squelettes dont le chemin ou le nom contient ’admin’ ou ’liste’. Il peut y avoir des dossiers et sous-dossiers : liste/article

-  cle_objet : la clé primaire ciblée
-  id_objet : valeur ciblée de la clé primaire. Ces 2 ci vont nécessairement ensemble.

-  contexte : spécification d’un ensemble de couples (clé_objet,id_objet). Seuls les caches dont le contexte inclue l’ensemble de ces couples (clé,valeur) sont ciblés.

-  plus : spécification d’une méthode de filtrage autre que celles incorporées dans le plugin. Cela permet des extensions personnalisées pour des filtrages adaptés à un squelette particulier.
Lorsque $conditions['plus'] vaut monfiltrage, le webmestre doit définir une fonction cachelab_ciblercache_monfiltrage($action, $conditions, $options, $cle, &$data, &$stats) qui renvoie true ou false selon que le cache vérifie ou non le filtrage désiré.
Les arguments en sont :

<blockquote class="spip">

- $action, conditions, options : les paramètres reçus par cachelab_cibler
-  $cle : la clé du cache APC à tester
-  $data : la valeur du cache APC à tester, y compris les métadonnées. Cet argument est passé par référence (pour efficacité et pour permettre de modifier le cache ou les métadonnées).
-  $stats : le tableau de stats en cours. Cet argument est passé par référence et peut être modifié par la nouvelle fonction de filtrage, pour incrémenter les statistiques, augmenter les listes de caches et éventuellement pour renvoyer des résultats complémentaires.

</blockquote>

Ce filtrage dédié est appelée après les filtrages par session, chemin et cle_objet s’ils sont spécifiés.
Le filtrage par contexte, proposé d’origine dans CacheLab est un exemple d’extension. En effet, cette méthode de filtrage est implémentée au moyen d’une fonction cachelab_ciblercache_contexte.

options : tableau facultatif d’options à passer à l’action ou au filtrage. Ces options permettent de modifier le filtrage ou d’enrichir les résultats renvoyés par la fonction.

-  methode_chemin : Par défaut, les chemins sont reconnus avec la fonction php strpos. Toutefois, si on passe $options['methode_chemin'] vaut regexp, alors c’est un test d’expression régulière qui est utilisé, et qui permet une plus grande finesse de ciblage. Exemple de valeur pour $condition['methode_chemin'] dans ce cas : inclu.e|liste.*admin. Dans ce dernier cas, les chemins de squelettes contenant ’inclure’ ou ’include’ seront ciblés, ainsi que tous les chemins de squelette dans un dossier ’liste’ et ayant ’admin’ dans le nom... ou ayant ’liste’ puis ’admin’ dans leur nom.

-  partie_chemin : permet de spécifier quelle partie du chemin est testée. Le chemin complet, c’est le chemin du squelette depuis le dossier de squelettes (comme pour un inclure_spip), avec en prime en suffixe l’identifiant de session (un hexa qui peut difficilement matcher avec un nom de squelette et donc qui ne gêne pas). Les différentes valeurs possibles sont : tout ou chemin : teste l’entièreté du chemin ;dossier : teste le répertoire du squelette seulement. Comme ça, des squelettes dans les sous-dossiers ne sont pas ciblés ; fichier : teste le nom de fichier du squelette.

-  list et chrono : permettent de récupérer les listes de caches et la durée du filtrage, pour aller plus loin et optimiser le ciblage.

-  clean : par défaut, cachelab détruit les caches SPIP périmés que APCCache garde encore en mémoire, dés qu’il en rencontre un. Ce comportement est désactivé lorsque $options a un index ’clean’ vide.

RQ : Définir les constantes php CACHELAB_CLEAN et CACHELAB_CHRONO permet de changer le fonctionnement par défaut de ces options.

Préciser le diagnostic
La plupart du temps, il n’est pas utile d’utiliser le retour de la fonction, mais cela permet d’étudier le cache, connaître le coût de l’invalidation ciblée (en ms) et éventuellement d’aller plus loin.

(Doc non stabilisée) : La fonction cachelab_cibler renvoie un tableau de statistiques globales sur le filtrage :

  • nb_candidats : le nombre de caches candidats (corrects et valides)
  • nb_cible : le nombre de caches ciblés par les conditions spécifiées
  • nb_clean : le nombre de caches vidés car périmés par APC ou invalidés car trop vieux vu la durée du cache SPIP
  • nb_no_data : le nombre de caches sans données. 0 normalement.
  • nb_not_array : le nombre d’erreurs "data not array". 0 normalement.
  • chrono : le temps total du filtrage, en millisecondes. Pour obtenir cette sortie, le tableau d’options passé à la fonction doit contenir une entrée chrono vraie.
  • l_cible : liste des clés des caches ciblés. Pour obtenir ces sorties, le tableau d’options passé à la fonction doit contenir une entrée list vraie.

Exemple de résultats renvoyé :

Filtrage du chemin par ’strpos’ :
[nb_cible] => 8
[nb_not_array] => 0
[nb_no_data] => 0
[nb_clean] => 0
[nb_candidats] => 2325
[nb_alien] => 0
[chrono] => 10.415 ms
Filtrage du chemin par ’regexp’ :
[nb_cible] => 29
[nb_not_array] => 0
[nb_no_data] => 0
[nb_clean] => 0
[nb_candidats] => 1546
[nb_alien] => 0
[chrono] => 81.342 ms

Coût pour le CPU

Les durées de filtrage sont très raisonnables et ne sont pas pénalisantes, dans la mesure où elles ne se font que lorsqu’il y a une invalidation. Le filtrage par regexp est un peu plus long que par strpos. Et l’interrogation du contexte est un peu plus lente puisqu’il faut accéder aux données.

Le filtrage par le contenu du contexte serait plus coûteux si les données du cache étaient codées par mémoization, comme c’est nécessaire lorsque le cache APCu est partagé entre utilisateurs. Si les données sont codées, le commit 111634 indique qu’il faut 1 ou 2µs pour chaque décodage. Du coup il faudrait entre 10 et 20ms supplémentaire pour parcourir 10000 caches. Cela reste très acceptable. Cependant, lorsque les caches APC de plusieurs sites indépendants sont mutualisés et qu’ils sont tous accessibles à chaque site (ainsi que ça semble le cas sur nursit par exemple), alors leur nombre peut être beaucoup plus grand. Dans ce cas, le parcours va prendre plus de temps. S’il y a 10 sites qui mutualisent leur cache APC et qu’il y a 10 fois plus de caches, alors au lieu de 81.342 ms ce sera 810 ms ce qui est significatif (voir todo).

Extension : définir ses propres actions

Pour définir une nouvelle action nouvelleaction, il faudra définir une fonction cachelab_applique_nouvelleaction($action, $cle, $data=null, $options='', &$return=null) sur le modèle de la fonction cachelab_applique livrée dans le plugin.
Cette fonction sera appelée tour à tour par cachelab_cibler pour chacun des caches ciblés.
-  Elle devra appliquera l’action nouvelleaction sur le cache dont la clé est reçue en paramètre $cle et dont le contenu est $data s’il est déjà connu (dans le cas où le filtre ne porte pas seulement sur le chemin mais aussi sur le contexte).
-  Les options de ciblage sont $options.
-  La fonction pourra éventuellement ajouter un résultat au tableau $return qui sera renvoyé par la fonction cachelab_cibler.
-  Elle renverra true si elle a pu appliquer l’action et false sinon.

API globale

Alors que le premier niveau d’API est assez bien stabilisé, il n’y a que les bases de ce 2ème niveau d’API globale. À terme, il devrait bénéficier de compléments pour automatiser le ciblage des invalidations, ou le paramétrer via un clic pour les sites simples.

Le niveau d’API globale intercepte les invalidations du cache quelque soit leur origine (code de SPIP ou des plugins), en surchargeant la fonction suivre_invalideur . CacheLab interprète alors le signal d’invalidation reçu et permet d’appliquer une stratégie d’invalidation ciblée spécifiquement pour ce signal.

Il y a un petit nombre de types de signaux d’invalidation. À chaque forme de signal est associé un typesignal :
-  Pour chaque type d’objet d’un site, il y a un signal d’invalidation de la forme id="objet/id_objet", qui est activé lorsqu’un crée, modifie ou détruit un objet de ce type. Par exemple id="article/1234". Par simplification on dit parfois aussi que le signal est alors "article/1234". Dans cet exemple, le typesignal est article
-  Certains autres signaux correspondent à des événements plus qu’à des objets : recalcul lors d’une demande via var_mode=recalcul. Là, le typesignal est recalcul.
-  Certaines modifications d’objets génèrent un signal exotique : le signal favori/article/1234 indique qu’une invalidation est demandée pour cause de changement d’un favori portant sur l’article 1234. Dans le cas des favoris, un autre signal est également généré à la suite : favori/auteur/98 qui indique qu’un favori concernant l’auteur 98 a été créé, modifié ou supprimé. Dans cet exemple, le typesignal est favori

Rq : Les signaux d’invalidations ne sont pas utilisés dans le noyau de SPIP et ne servent que pour d’éventuels plugins comme CacheLab. Il n’est donc pas exclu que certains plugins provoquant des invalidations ne renseignent pas du tout ou pas correctement ce signal : dans ce cas il faudra les corriger pour qu’ils fournissent un signal suffisamment descriptif du motif et du contexte d’invalidation.

Pour mettre en oeuvre ce 2e niveau d’API, le webmestre doit définir une stratégie d’invalidation pour chacun des types de signaux, au moyen d’une fonction dédiée dans laquelle il appellera les fonctions du 1er niveau : controler_invalideur et cachelab_cibler avec les paramètres $action='del' et avec les condition et options permettant d’invalider précisément les caches qui deviennent périmés.

Pour traiter le signal "typesignal/...", il faut définir une fonction cachelab_suivre_invalideur_typesignal ($signal, $modif).
Les arguments sont :
-  $signal : le signal reçu.
-  $modif : a priori c’est true.
La fonction renvoie true s’il faut ensuite poursuivre encore les traitements standards d’invalidation, ou false, si le signal a été entièrement traité et qu’il n’y a plus rien à faire.

En général pour un signal "unobjet/id_unobjet", cette fonction appelera cachelab_cibler('del', array('objet'=>$objet, 'id_objet'=>$id_objet)); pour invalider tous les caches des squelettes ayant cet objet précis dans leur environnement. Il se peut, toutefois, que cela ne suffise pas, notamment lorsque cet objet peut figurer dans des listes : ces listes ne reçoivent pas les id des objets contenus mais des indications génériques (par exemple : "afficher 5 objets à partir du 10e").

Si les squelettes sont bien rangés, il se peut que les noisettes de "listes d’objet" soient rangées dans un dossier "liste", et alors il sera possible d’appeler également :
cachelab_cibler('del', array('chemin'=>'liste'));. Et sinon, il faudra changer le rangement de vos squelettes pour pouvoir les facilement avec cachelab... par exemple en mettant vos listes dans un dossier "liste".

Exemples :

// gérer une demande d'invalidation concernant un point GIS
function cachelab_suivre_invalideur_gis($cond, $modif) {
	include_spip ('inc/cachelab');
	cachelab_cibler('del', array ('chemin'=>'json_')); // cibler gis_json, gis_article, etc
}
// gérer une demande d'invalidation concernant un document
function cachelab_suivre_invalideur_document($cond, $modif) {
	include_spip ('inc/cachelab');
	cachelab_cibler('del', array ('chemin'=>'documents'));
}

Ce 2e niveau d’API permet donc de mettre en place une stratégie d’invalidation plus globale : chaque fonction répond à un signal d’invalidation spécifique, mais pour tout le site et quelque soit l’origine de l’invalidation de cet objet, et non formulaire par formulaire.

Questions et réponses

  • « Dans des inclures imbriqués, est-il possible d’invalider certains inclures de la branche sans forcement invalider tout les inclures enfants ? Par exemple en zcore il y a content > liste Si j’invalide content, liste sera-t-il invalidé ? »

3 réponses :
1) Les options permettent de n’invalider que les squelettes d’un dossier ciblé : il suffit de passer 'partie_chemin' => 'dossier'

2) Une autre manière de faire serait de passer methode_chemin=regexp et donner comme chemin une expression régulière qui exclue les squelettes dans les sous dossiers. Par exemple : chemin=content\/[^\/]*$

3) Une autre manière de faire serait de créer une extension ’contentseul’ et passer plus=contentseul pour appeler une fonction cachelab_ciblercache_contentseul qui opérerait le filtrage, par exemple avec la même regexp que ci dessus ou bien ainsi :

function cachelab_ciblercache_contentseul($action, $conditions, $options, $cle, &$data, &$stats) {
   return (strpos($cle,'content') and (substr_count($cle, '/') < 2));
}
  • « Et memcache, XCache et redis ? »

Contrairement à XRay qui est trés lié à APC, CacheLab n’appelle qu’une seule fonction spécifique à APC : celle qui permet de lister l’ensemble des caches. Cette fonction n’existe pas, à ma connaissance, pour les autres systèmes de caches. Il serait néanmoins possible d’étendre memoization pour disposer optionnellement d’une méthode get_list qui renvoie la liste de tous les caches. Il faudrait ajouter une option selon laquelle Memoïzation tiendrait à jour une liste des caches. Et alors cacheLab pourrait tout de suite fonctionner avec les autres systèmes de caches.

  • Quel est l’état de dev du plugin ?

Ça marche. L’API est opérationnelle sur au moins un site avec APC cache. Les extensions de #CACHE et var_mode=cache ont été testées sur quelques sites.
Les éventuels bugs détectés et les développements futurs à court ou moyen terme sont suivis sur Compléments CacheLab et todo.

  • Quels sont les résultats obtenus ?

Sur le site de dev, toutes les invalidations du cache ont été ciblées. Le taux de hit du cache SPIP est passé de en moyenne 40% à 88% et même à 99% avec le fix du bug de contamination du sessionnement.

Voir aussi

-  Compléments CacheLab et todo
-  XRay, un explorateur des caches SPIP
-  CacheLab étend #CACHE et INCLURE
-  Plugin ’macrosession’ : usage optimisé et extension des données de session

Retour à la version courante

Toutes les versions