API CacheLab 1. Action sur des caches ciblés

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’appliquer différentes actions sur des caches ciblés, au rang desquelles figure l’invalidation. Le ciblage se fait par une condition qui porte sur le chemin du squelette, sur la session ou sur le contexte ayant généré ce cache. Cela permet de décrire les caches ciblés et de leur appliquer l’action. Dans le cas d’une invalidation, seuls les caches le nécessitant sont invalidés, et aucun autre.

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

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 au moyen des fonctions d’API :
-  API d’action locale : les fonctions controler_invalideur et cachelab_cibler que décrit cet article.
-  API d’action globale (dev en cours) : ces fonctions interceptent les signaux d’invalidations de SPIP et permettent de les interpréter de la manière désirée globalement pour le site.

Fonction 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');)

Fonction 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 : le nom du champ clé primaire ciblée et id_objet : valeur ciblée de cette clé primaire. Ces 2 ci vont nécessairement ensemble. Par exemple :
cachelab_cibler('del', ['cle_objet'=>'id_article', 'id_objet'=>123]);

-  contexte : spécification d’un tableau de couples (clé_objet,id_objet). Seuls les caches dont le contexte inclue l’ensemble de ces couples (clé,valeur) sont ciblés. On utilisera par exemple le ciblage par contexte lorsqu’une noisette reçoit une simple référence dans son environnement sous la forme de 2 variables d’environnement : ’objet’ et ’id_objet’.

cachelab_cibler(
  'del',
  [ 'chemin'=>'inclure/documents',
    'contexte' => ['objet'=>'unetable', 'id_objet'=>$id_unetable]
  ]);

-  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 :

- $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.

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.

Rq 1 : Les conditions spécifiées se combinent. On peut, par exemple, spécifier un chemin, une valeur de contexte et un couple (cle_objet, id_objet) et seuls les caches vérifiant l’ensemble de ces critères seront ciblés.
Pour combiner des conditions de manière inclusive (chemin OU valeur de contexte OU id_objet précis) on peut faire des appels successifs à cachelab_cibler, ou bien (plus efficace) définir une nouvelle méthode de filtrage et l’appeler via plus.

Rq 2 : contexte est implémenté par un plus. C’est un exemple de la manière d’utiliser le paramétrage plus. Mais de ce fait, il n’est pas possible de combiner contexte avec un plus pour faire un test plus complexe en une seule passe (pour faire cela, on peut définir un nouveau “plus” qui combine le test du ’contexte’ et la contrainte spécifique ajoutée, et l’appeler sans en plus filtrer par contexte)..

Rq3 : les conditions déjà décrites se combinent par un ET (AND) logique et permettent de cibler aussi précisément que possible le jeu de cache ciblé. Il arrive qu’on ait besoin de cibler plusieurs jeux de caches. Pour cela on peut combiner plusieurs conditions de manière inclusive, c’est à dire avec un OU (OR) logique.
Il faut pour cela utiliser une entrée ’ou’ ou ’or’. C’est équivalent à appeler cachelab_cibler successivement pour chacune de ces conditions, mais c’est plus rapide puisque ça ne parcourera qu’une seule fois le cache.
Exemple pour une utilisation du plugin favori : avec l’appel à cachelab_cibler suivant, 2 jeux de caches seront ciblés :
-  les noisettes ayant ’favori’ dans leur chemin et recevant un certain id_favori dans leur environnement
-  les noisettes ayant ’mes_favoris’ dans leur chemin et un certain id_auteur dans leur environnement

$cible = cachelab_cibler(
  'del',
  [ 'ou' => [  
    ['chemin'=>'favori', 'cle_objet'=>'id_favori', 'id_objet'=>$id_favori],
    ['chemin'=>'mes_favoris', 'cle_objet'=>'id_auteur', 'id_objet'=>$id_auteur]  
 ]]);

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, 2 autres valeurs sont possible :

  • regexp : c’est alors un test d’expression régulière qui est utilisé, et qui permet une plus grande finesse de ciblage. Par exemple, si $condition['methode_chemin'] vaut inclu.e|liste.*admin, alors 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.
  • == ou egal ou ou equal : dans ce cas c’est un test d’égalité qui est fait. Cette valeur sera souvent combinée à l’option partie_chemin afin de préciser quelle partie du chemin doit être égale à la valeur indiquée.

-  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 des séparateurs / au niveau des dossiers et 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 et du nom de fichier;
  • 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 seulement, pas l’arborescence des dossiers qui y mènent.

-  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.

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.

La fonction cachelab_cibler renvoie un tableau de statistiques :

  • 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
  • chrono : le temps total du filtrage, en millisecondes.
  • 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_clean] => 0
[nb_candidats] => 2325
[chrono] => 10.415 ms
Filtrage du chemin par ’regexp’ :
[nb_cible] => 29
[nb_clean] => 0
[nb_candidats] => 1546
[chrono] => 81.342 ms

Extension : définir ses propres actions

Pour définir une nouvelle action nouvelleaction, il faudra définir une fonction cachelab_appliquer_nouvelleaction($action, $cle, $data=null, $options='', &$return=null) sur le modèle de la fonction cachelab_appliquer livrée dans le plugin.
Cette fonction sera appelée tour à tour par cachelab_cibler pour chacun des caches ciblés.
-  Elle devra appliquer 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.
-  Elle renverra true si elle a pu appliquer l’action et false sinon.
-  La fonction pourra éventuellement ajouter un résultat au tableau $return passé en référence. Ce tableau pourra être récupéré dans le champ ’return’ de la valeur de retour de cachelab_cibler.

Efficacité et coût pour le CPU

Sur un hébergement où chaque site dispose de son propre espace pour les caches APC, les durées de filtrage ne sont pas pénalisantes. Surtout qu’elles ne se font que lorsqu’il y a une invalidation. Le coût du ciblage de l’invalidation est compensé par le bien meilleur taux de cache : en pratique on atteint assez rapidement un taux de cache de 97% à 99,9%. SPIP n’est alors sollicité pour l’évaluation d’un squelette que lorsqu’il y a réellement besoin de le calculer ou de le mettre à jour.

-  Le filtrage par regexp est évidemment plus long que par strpos. Et l’interrogation du contexte est un peu plus lente puisqu’il faut accéder au contenu des caches.

-  Le filtrage par le contenu du contexte est plus coûteux si les données du cache sont crypté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. De plus, leur nombre peut être beaucoup plus grand puisque les caches de tous les sites se cumulent. Dans ce cas, le parcours va prendre plus de temps et ça peut devenir inacceptable. (Voir todo : “Sous-listes de caches”).

Logs d’informations sur le fonctionnement

Plusieurs fichiers de logs sont produits soit systématiquement, soit à la demande par passage de paramètre aux fonctions de l’API, soit de manière paramétrable globalement par des constantes.

Ces informations permettent de suivre globalement le fonctionne du cachelab, et d’optimiser éventuellement le ciblage, par un meilleur choix des conditions de ciblage, du découpage des noisettes ou du choix des arguments reçus par ces noisettes ciblées.

La constante LOG_CACHELAB_CHRONO, lorsqu’elle vaut true demande de loger un maximum d’information.

Le fichier cachelab_chrono.log contient alors les logs détaillés des temps de ciblage : la condition, le nombre de cache ciblé, le nombre total de cache à ce moment et la durée du ciblage.

info: cachelab_cibler(del) en 47.324 ms (0 caches sur 17660)
Array ( [chemin] => facette|liste/article )

info: cachelab_cibler(del) en 264.248 ms (0 caches sur 17660)
Array (
  [ou] => Array (
    [0] => Array (
      [cle_objet] => id_article
      [id_objet] => 17714 )
    [1] => Array ( [chemin] => facette|liste/article)
))

Le fichier cachelag_toomany_del.log indique quelles ciblages renvoient un grand nombre de caches. On y trouve la condition ciblant les caches, la durée du ciblage, le nombre de caches ciblés et le nombre total de cache à ce moment.

Le seuil déclanchant ce log peut être paramétré par la constante

LOG_CACHELAB_TOOMANY_DEL. Par défaut c'est 100.

Exemple :
<code>
 ### cachelab_cibler(del) en 186.538 ms (109 caches sur 14263)
Array (
[cle_objet] => id_article
[id_objet] => 17986)

### cachelab_cibler(del) en 64.819 ms (127 caches sur 17871)
Array ([chemin] => facette|liste/article)

Le fichier cachelag_slow_del.log indique quelles ciblages renvoient un grand nombre de caches. On y trouve la condition ciblant les caches, la durée du ciblage, le nombre de caches ciblés et la durée du ciblage.

Le seuil déclanchant ce log peut être paramétré par la constante LOG_CACHELAB_SLOW. Par défaut c’est 100ms.

Exemple :

INFO: cachelab_cibler(del) en 461.381 ms (16 caches sur 16901)
Array (
    [cle_objet] => id_annonce
    [id_objet] => 17588 )

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 ? »

Cachelab n’est prévu que pour APCu.

Cependant, des devs seraient bienvenus : contrairement à XRay qui en son état actuel est trés lié à APC, CacheLab n’appelle qu’une seule fonction spécifique à APC : celle qui permet de lister l’ensemble des caches. Il serait possible d’étendre memoization pour disposer optionnellement d’une méthode iterate (c’est d’ailleurs fait dans un fork du plugin Memoization. Sur option, Memoïzation tiendrait à jour une liste des caches et alors cacheLab pourrait tout de suite fonctionner avec les autres systèmes de caches. Il serait certainement possible de faire la même chose pour memcache, XCache et redis.

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

Les fonctions décrites sur cette page sont stables et éprouvées. Les éventuels bugs détectés sont gérés sur le bugtracker et d’éventuels développements futurs sont imaginés sur Compléments CacheLab et todo.
Une seconde partie du plugin est fonctionnelle, mais susceptible d’évoluer (API CacheLab 2. Actions globales par type d’invalidation)

  • Quels sont les résultats obtenus ?

Sur le site de dev, 100% des invalidations du cache sont optimisées. Il n’y a donc plus d’invalidation totale du cache, sauf si on le désire et qu’on en fait la demande explicitement. Le taux de hit du cache SPIP est passé de en moyenne 40% à plus de 99% après quelques heures ou jours de fonctionnement (aprés le fix du bug de contamination du sessionnement).

Voir aussi


-  En prime : CacheLab étend #CACHE et INCLURE
-  API CacheLab 2. Actions globales par type d’invalidation
-  Compléments CacheLab et todo
-  XRay, un explorateur des caches SPIP
-  Plugin ’macrosession’ : usage optimisé et extension des données de session

updated on 6 April 2020

Discussion

3 discussions

  • J’ai ajouté la doc sur les fichiers de logs produits et les constantes pour leur paramétrage.

    Reply to this message

  • Nouveau (v1.1.0) : cibler plusieurs jeux de caches d’un coup

    Rq3 : les conditions déjà décrites se combinent par un ET (AND) logique et permettent de cibler aussi précisément que possible le jeu de cache ciblé. Il arrive qu’on ait besoin de cibler plusieurs jeux de caches. Pour cela, on devait appeler plusieurs fois de suite cachelab_cibler. Avec cette nouvelle version, on peut combiner plusieurs conditions de manière inclusive, c’est à dire avec un OU (OR) logique. Il faut pour cela utiliser une entrée ’ou’ ou ’or’. C’est équivalent à appeler cachelab_cibler successivement pour chacune de ces conditions, mais c’est plus rapide puisque ça ne parcoure qu’une seule fois le cache.

    Exemple : avec l’appel à cachelab_cibler suivant, 2 jeux de caches seront ciblés :
    -  les noisettes ayant ’favori’ dans leur chemin et recevant un certain id_favori dans leur environnement
    -  les noisettes ayant ’mes_favoris’ dans leur chemin et un certain id_auteur dans leur environnement

    $cible = cachelab_cibler(
      'del',
      [ 'ou' => [  
        ['chemin'=>'favori', 'cle_objet'=>'id_favori', 'id_objet'=>$id_favori],
        ['chemin'=>'mes_favoris', 'cle_objet'=>'id_auteur', 'id_objet'=>$id_auteur]  
     ]]);

    Reply to this message

  • Nouveau : passage en v1.0.0

    Avec de nouvelles valeurs possibles pour methode_chemin : “==” ou “egal” ou “equal” . Ce fonctionnement sera souvent combiné à l’option partie_chemin afin de préciser quelle est partie du chemin qui est testée. Avec “==” ou “egal” ou “equal”, c’est un test d’égalité qui est fait entre la valeur ciblée pour le chemin et la partie_chemin des caches testés.

    (Par défaut, c’est strpos : il suffit que le chemin contienne la partie spécifiée)

    Reply to this message

Ajouter un commentaire

Who are you?
[Log in]

To show your avatar with your message, register it first on gravatar.com (free et painless) and don’t forget to indicate your Email addresse here.

Enter your comment here

This form accepts SPIP shortcuts {{bold}} {italic} -*list [text->url] <quote> <code> and HTML code <q> <del> <ins>. To create paragraphs, just leave empty lines.

Add a document

Follow the comments: RSS 2.0 | Atom