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 : 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 cache (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.

L’appel est simple puisqu’ il y a 3 arguments : action, condition, options.

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. Lorsque cette doc ne le précise pas, c’est elle qui est supposée utilisée.
-  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 :

-  chemin : spécification du chemin des squelettes ciblés. C’est la plus simple et la plus courante des conditions utilisée.
Par défaut, c’est une chaîne constituée des morceaux de chemins ciblés, séparés par un | s’il y en a plusieurs. Cette chaine sera décomposée et les différents morceaux de chemins seront cherchés avec strpos.
Exemple : admin|liste ciblera tous les caches des squelettes dont le chemin ou le nom contient ’admin’ ou ’liste’.
On peut indiquer des dossiers et sous-dossiers : liste/article

-  cle_objet : le nom de l’argument d’inclusion (par exemple un nom de clé primaire) et id_objet : la valeur ciblée de cette clé. Ces 2 couples (index,valeur) vont nécessairement ensemble.
Exemple : cachelab_cibler('del', ['cle_objet'=>'id_article', 'id_objet'=>123]); effacera tous les caches présentant ’id_article=123’ dans les arguments de leur appel.

Les arguments d’inclusions ciblés peuvent ne pas être des clé primaires.
Exemple : cachelab_cibler('del', ['cle_objet'=>'couleur', 'id_objet'=>'bleu']); invalidera les caches des inclusions ayant « couleur => bleu » dans leur contexte. Dans ce cas, la valeur ciblée peut être d’un autre type (à confirmer) que int ou string.

Depuis la version 1.6.1 du plugin, il est également possible d’indiquer, pour id_objet, une suite de valeurs cibles, séparées par des ’|’ comme pour le ciblage par le chemin. Ainsi, cachelab_cibler('del', ['cle_objet'=>'id_article', 'id_objet'=>'11|21|31'); effacera tous les caches dont les contextes contiennent un id_article valant 11, 21 ou 31.

-  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]
  ]);

-  session : seuls sont ciblés les squelettes sessionnés pour la session indiquée.
La valeur spécifiée n’est pas un id_auteur, mais un identifiant de session (nombre hexa de 8 caractères, tel que renvoyé par spip_session() ou trouvé dans $GLOBALS['cache_utilise_session']).
Une valeur spéciale : ’courante’ permet de spécifier la session courante.
C’est ainsi que cachelab_cibler('del', ['chemin'=>'liste', 'session'=>'courante']) effacera tous les caches des listes sessionnées pour la session courante.

-  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. La manière de créer une nouvelle méthode de filtrage est détaillée sur la page Extensions : nouveaux ciblages et gestion des caches périmés.

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

Il arrive qu’on ait besoin de cibler plusieurs jeux de caches, c’est à dire qu’on veut combiner plusieurs conditions de manière inclusive, par un OU (OR) logique. Par exemple : chemin OU valeur de contexte OU id_objet précis. Pour combiner des conditions de manière inclusive, 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,
-  ou bien faire appel au critère de condition ou. 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 la liste des caches.

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

Lorsqu’on cible avec une action del, il n’est en général pas utile d’utiliser le retour de la fonction... sauf pour la surveillance et l’optimisation.

La fonction cachelab_cibler renvoie en effet le tableau des statistiques récoltées lors du parcours des caches. Ces données permettent d’étudier le cache, de connaître le coût de l’invalidation ciblée (en ms) et éventuellement d’aller plus loin dans l’optimisation.

Le tableau renvoyé contient les index suivants :

  • 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

On peut aussi définir ses propres actions : voir Extensions : nouveaux ciblages et gestion des caches périmés.

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. On pourrait envisager des développements pour améliorer les temps d’accés sur ce type d’hébergement (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 :

 ### 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 prennent longtemps à se faire. 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_truc
    [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é ? »

La réponse dépend des conditions et options utilisées. Avec la simple option 'chemin'=>'content', les squelettes des sous-dossiers sont aussi invalidés puisque leur chemin contient le chemin ciblé. Mais plusieurs options permettent d’obtenir le ciblage voulu :

1) Une option de filtrage permet de n’invalider que les squelettes d’un dossier ciblé : 'partie_chemin' => 'dossier'. Ça répond assez précisément à la demande. Voir doc plus haut.

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 de filtrage contentseul qui cible spécifiquement ce dossier.
-  créer 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 pour l’appeler, il suffira alors de passer une condition avec plus=contentseul.

  • « 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 qui a permis le développement de cachelab, 100% des invalidations du cache sont optimisées.
-  Il n’y a plus aucune invalidation totale du cache, sauf si on le désire et qu’on en fait la demande explicitement.
-  Seuls les caches qu’il faut réellement invalider sont invalidés.

Avec cachelab, 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). C’est à dire qu’aucun calcul n’est fait inutilement.

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

Discussion

Une discussion

  • Nouveau dans la V1.6.1 : On peut cibler les caches dont un argument d’inclusion figure dans une suite de valeurs cibles, séparées par des ’|’ comme pour le ciblage par le chemin. Ainsi, cachelab_cibler('del', ['cle_objet'=>'id_article', 'id_objet'=>'11|21|31'); effacera tous les caches dont les contextes contiennent un id_article valant 11, 21 ou 31.

    Les spécifiants de condition ’cle_objet’ et ’id_objet’ étaient initialement conçus et documentés comme s’appliquant à des clés primaires, mais ils peuvent servir en fait pour tout argument d’inclusion, clé primaire ou pas, y compris pour des arguments d’inclusion qui ne sont pas des champs de la table (car c’est au niveau de l’inclusion d’un cache que le ciblage se fait et non dans la relation à une table).

    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