Ticket #540 (new new feature)

Opened 5 months ago

Last modified 2 months ago

jCache

Reported by: sylvain261 Assigned to: sylvain261
Priority: normal Milestone: Jelix 1.1 beta 2
Component: jelix:utils Version: 1.0.3
Severity: normal Keywords:
Cc: Php version:
Review: review- Hosting Provider:
Documentation needed: 0 Blocking: 151

Description

L'objectif de jCache serait de permettre de réaliser (sur le serveur) du cache de tout contenu quel qu'il soit, à partir du moment où il est sérialisable et donc stockable sous forme de chaine.

Cela serait utile pour (par exemple) :

  • Mettre en cache le résultat d'un appel à un service externe (web service ou autre)
  • Mettre en cache le résultat d'une requête SQL très lentes à s'exécuter

D'une façon générale, il s'agit donc de pouvoir mettre en cache n'importe quelle donnée, à partir du moment où le chargement de cette donnée est coûteuse en performance pour le site.
A plus long terme on peut imaginer que jZone soit revue pour exploiter jCache, on peut également envisager s'en servir pour du cache sur l'intégralité de la réponse (à voir en fonction de l'issu du ticket #4).

En terme d'implémentation, j'envisage :

  1. Que l'utilisation de jCache soit des plus simplistes, cad qu'on aurait une seule méthode statique get qui renverrait le contenu du cache et qui mettrait à jour le cache si nécessaire. Cette fonction recevrait en paramètre :
    • Qqch permettant d'identifier le contenu caché, qui pourra être soit une string, soit un tableau de string. L'utilisation d'un tableau permettant de ranger le cache sous forme arborescente et donc d'en faciliter la gestion, la configuration et son vidage manuel. (par exemple on pourrait avoir comme identifiant de cache array(0=>'fr', 1=>'article', 2=>'hall-of-fame') auquel cas au niveau du stockage on aurait un répertoire /fr/article/hall-of-fame.
    • Le nom de la fonction chargeant les données à cacher. Ce pourra être soit un simple nom de fonction, soit un tableau avec nom de classe + nom de méthode (pour les méthodes statiques) soit un tableau avec object + nom de méthode (pour les méthodes non statiques)
    • Un tableau de paramètres à passer à la fonction/méthode chargeant les données à cacher
      //Un appel à jCache ressemblerait donc à :
      jCache::get(array('article', '1'), 'loadArticle', array(1));
      //ou encore :
      jCache::get('article1', array('myClassName', 'loadArticle'), array(1));
      //ou encore :
      jCache::get('article1', array(&$myObject, 'loadArticle'), array(1));
      
  2. Une configuration du cache entièrement déclarative via defaultConfig.ini.php où on aurait la possibilité
    • De désactiver le cache de façon globale
    • De préciser le TTL du cache de façon globale ou par type de contenu (compte tenu qu'on a une organisation arborescente des données cachée, l'idée et de potentiellement pouvoir définir une durée de cache différentes pour les différentes entrées de l'arborescence)
    • De préciser le driver à utiliser (cf ci dessous) de façon globale ou par type de contenu (même principe que pour le TTL)
  3. Un système de lock et d'attente évitant que le cache soit raffraichit par plusieurs process simultanés
  4. Une méthode clear permettant de supprimer tout ou partie du cache.
  5. Un système de driver (file ou db) permettant dans le cas de db de partager une donnée en cache entre plusieurs frontaux load balancés.

Merci à tous ceux que ça intéressera de me donner leur feedback sur les objectifs et l'approche pour l'implémentation.

Attachments

patchtestapp.diff (16.3 kB) - added by samtahRam on 05/19/08 10:44:11.
application test jCache
patchlib.diff (39.5 kB) - added by samtahRam on 05/19/08 10:48:19.
plugin jCache
jcache-v2.diff (56.5 kB) - added by laurentj on 06/15/08 10:24:35.
mise à jour des patchs précédents

Change History

04/22/08 11:31:33 changed by laurentj

Je ne comprend pas l'usage des arguments "nom de fonction" et "arguments de fonction" à jCache::get

Pour le reste, je suis ok :-)

04/22/08 11:32:53 changed by laurentj

  • blocking set to 151.

04/22/08 13:19:51 changed by sylvain261

L'idée est d'utiliser call_user_func http://www.php.net/manual/en/function.call-user-func.php pour appeller soit la fonction, soit la méthode statique, soit la méthode non statique

04/22/08 13:58:53 changed by Lipki

Ca marcherai pour le cache du plugin image :)
(ouais je parle que de ce plugin, mais c'est le seul validé a mon actif alors :) ...)

04/22/08 14:15:11 changed by laurentj

sylvain: je me suis mal exprimé. Tu n'as pas du tout expliqué l'interet de donner un nom de fonction. Elle sert à quoi cette fonction ?

04/23/08 08:22:44 changed by sylvain261

Bon apparemment j'ai pas bien expliqué la vision que j'avais de jCache.

Le principe est que, si je veut ajouter du cache pour le code suivant :

$data = myClassName::loadArticle(1);

La seule chose que j'ai à faire c'est de changer la façon dont j'appelle la méthode dont je veut cacher le résultat. Çà devient alors :

$data = jCache::get('article1', array('myClassName', 'loadArticle'), array(1));

L'idée est que ce n'est pas à la fonction chargeant les données (ici loadArticle) de se préoccuper des problématiques de cache mais au controller (ou autre) qui appelle cette fonction. En effet, savoir si les choses doivent être cachée ou pas dépendent uniquement du contexte d'appel de la méthode et pas de l'implémentation de la méthode elle même (par exemple une même méthode peut être cachée dans le front mais pas dans le back). C'est donc jCache qui gère tout çà pour nous. C'est pour cela qu'on passe en paramètre à jCache tout ce qui est nécessaire pour lui permettre de faire appel à la fonction chargeant les données lorsqu'il est nécessaire de rafraîchir le cache.

Lipki : Oui, y a pas de raison pour que ce ne soit pas applicable. Par contre il faudra bien sûr adapter un peu ton plugin.

04/23/08 12:29:38 changed by laurentj

mouai.. mais ma classe n'a pas forcément une méthode statique. Et peut être aussi qu'il faut l'instancier avec des paramètres.

Ça pourrait être une utilisation, mais j'aimerai aussi une API plus conventionnelle, avec une méthode set, qu'on puisse utiliser comme ceci :

  $content = jCache::get('article1');
  if($content == null) {
     $content = $myObject->loadArticle('1');
     jCache::set('article1', $content);
  }

On peut ainsi l'utiliser dans de plus grand nombre de cas, plutôt que de restreindre à l'appel d'une méthode statique. Ce sera par exemple plus simple à utiliser dans jZone je pense.

Bref, il faut avoir le choix entre un jCache::get('article1', array(...)..), et un jCache::get('article1');

04/23/08 12:32:11 changed by laurentj

idée de drivers : [memcache], ou encore les fonctions de cache de APC &co il me semble etc.. Regarder leur api et voir si c'est compatible avec l'API que l'on propose dans jCache.

04/23/08 18:56:56 changed by sylvain261

mouai.. mais ma classe n'a pas forcément une méthode statique. Et peut être aussi qu'il faut l'instancier avec des paramètres. => Ça sera pas forcément une méthode statique qu'on appellera ca pourra être une fonction ou une méthode non statique d'un objet déjà instancié.

Ce qui m'ennuie avec cette approche plus conventionnelle c'est la façon (du coup manuelle) dont on va gérer le lock. Il est à mon sens impératif d'avoir ce mécanisme de lock qui évite que 2 process fasse en même temps le chargement de données nécessaire au rafraîchissement du cache. Avec cette approche conventionnelle, pour avoir une gestion du lock il faudrait alors faire :

  $content = jCache::get('article1');
  if($content == null) {
     jCache::lock('article1');
     $content = $myObject->loadArticle('1');
     jCache::set('article1', $content);
     jCache::unlock('article1');
  }

cad qu'on aurait à poser le lock à la main avant de charger les données puis lever le lock à la main une fois les données chargée. (le get se chargeant d'attendre si la donnée cachée est lockée et se chargeant de forcer la libération du lock si l'attente dure trop longtemps).
Qu'est ce que tu en penses ?
On fait les 2 ?

Par ailleurs je me pose la question de comment implémenter le lock... Soit au niveau de chaque driver (mais certains drivers de le permettrons probablement pas (memcache) soit de façon unique quelque soit le driver, à l'aide de fichier sur le disque mais du coup mon lock n'est plus partagé entre mes frontaux...

04/23/08 19:02:34 changed by laurentj

  • component changed from jelix to jelix:utils.

Pourquoi gerer le lock à la main ? La méthode set ne pourrais pas le faire ? Pourquoi mettre un lock avant de lire l'article ?

04/24/08 07:57:50 changed by sylvain261

Comme je l'indiquais, sur les site à forte charge il est fréquent d'avoir plusieurs process simultanés qui demande la rafraîchissement du cache, du coup, sans lock, on charge les données plusieurs fois simultanément. Ce n'est pas viable si ce chargement de données et relativement consommateur en ressource. J'ai rencontré cette problématique pour un site qui fait 1.2 million de pages vues par jour et aujourd'hui je s'apprête à le porter sous Jelix, il m'est donc indispensable d'avoir ce mécanisme de lock évitant que 2 process simultanés décident de charger les données en même temps. (A l'époque où j'avais rencontré ce pb, l'alternative qu'on avait mis en place c'était de rafraîchir le cache via des cron et non pas via le front)

05/04/08 23:54:41 changed by laurentj

Propose ton patch, mais j'aimerais les deux manières d'utilisation. Je ne veux pas qu'on soit obliger d'utiliser une classe ou une fonction.

Je viens aussi de penser à un truc, on pourrait avoir un boolean à jCache::get disant qu'il faut locker si il n'y a pas de cache. Et lors de l'appel du jCache::set, ça enlève le lock automatiquement. Et il faut prévoir un timeout pour le lock, au cas où le lock est mis mais qu'il y a une exception ou autre avant le set, ce qui empeche de lever le lock.

05/19/08 10:44:11 changed by samtahRam

  • attachment patchtestapp.diff added.

application test jCache

05/19/08 10:48:19 changed by samtahRam

  • attachment patchlib.diff added.

plugin jCache

05/19/08 10:52:19 changed by samtahRam

  • review set to review?.
  • component changed from jelix:utils to jelix:plugins.

Bonjour, Sylvain m'a confié la tâche de poster le plugin concernant le cache de jelix et de correspondre avec vous en son absence. mon e-mail est t.ramaroson@neov.net. Merci de votre attention

06/15/08 10:15:14 changed by laurentj

  • review changed from review? to review-.
  • component changed from jelix:plugins to jelix:utils.

Je suis en train de relire le patch :-)

  • Y a t-il une raison particulière de pouvoir utiliser un driver spécifique pour chaque type de donnée à mettre en cache ? aurais-tu un exemple montrant l'utilité de cette fonctionnalité ?
  • Vu que jCacheSettings ne contient qu'une méthode, ne serait-ce pas mieux de mettre cette méthode directement dans jCache ?
  • je n'ai pas trop compris la raison, dans le driver file, pourquoi ne pas utiliser jFile::removeDir ? si jFile::removeDir lance une exception pour des problèmes de permission, je ne comprend pas pourquoi ça ne serait pas le cas avec la version du plugin.

Il y a cette gestion de lock qui me chiffone :

  • Comment ce lock sera géré par un driver comme memcache par ex ? peut être par une deuxième variable dans memcache 'lock:$id' ?
  • il y a aussi des cas absurdes. ex : on veut mettre en cache un enregistrement que l'on récupère via une requête SQL. Seulement si on choisi le driver db, avec les histoires des locks, on a au minimum 2 requêtes SQL à chaque lecture du cache. Bref, l'utilisation du cache peut finalement être plus gourmand que d'aller executer directement le code métier sans utiliser de cache. Je pense qu'il faut optimiser cette methode get : ne gérer le lock que si on donne une fonction en argument. Si le développeur ne donne pas de fonction, ça veut dire qu'il gère lui même le lock, donc pas besoin de faire toutes ces vérifications de lock à la lecture. M'enfin il n'en reste pas moins que cette gestion de lock avec le driver db ne me satisfait pas trop. N'y a-t-il pas plutôt un moyen dans les bases de donner de locker les enregistrement en lecture, ce qui fait que la base de donnée, quand on fait un select sur cet enregistrement, c'est elle qui attend que l'enregistrement soit délocké pour le lire ? Je pense que ça serait plus efficace. Voir si il n'y a pas ça dans mysql, pgsql et sqlite..

Je vais fournir un nouveau patch, j'y ai fait quelques corrections et au moins il s'appliquera sans erreur sur le trunk ;-)

06/15/08 10:24:35 changed by laurentj

  • attachment jcache-v2.diff added.

mise à jour des patchs précédents

06/15/08 10:28:41 changed by laurentj

Changement dans le nouveau patch :

  • adaptation sur le trunk courant
  • le dao est fourni dans le module jelix et non un module jcache
  • renommage des sections de config : cache et cacheAreas.
  • des corrections dans _checkCacheId, _getFormatArea et le driver file : erreurs de syntaxe dans les regexps, et améliorations pour plus d'efficacité.

07/02/08 14:57:11 changed by bballizlife

Well this ticket seems to have moved fast at first, but is now abandonned...

Does the last patch near to be review+ ? Are there a lot to do more ?

07/02/08 15:30:01 changed by sylvain261

I need to discuss with Laurent in order to move ahead. but..not so much time currently. I think we can plan the final implementation of jCache on the end of this month.

07/02/08 15:40:49 changed by bballizlife

Don't wrooy, we're all a little bit short in time ;) If this ticket could end this summer that would be great !

07/07/08 11:56:42 changed by laurentj

  • milestone set to Jelix 1.1 beta 2.
Download in other formats: Comma-delimited Text Tab-delimited Text RSS Feed