La gestion du cache est un élément essentiel pour garantir des performances optimales de Lutece.
Nombre d'opérations de construction des pages sont coûteuses en CPU. On peut citer notamment toutes les opérations mettant en oeuvre de la transformation XML/XSL ou du parsing ou des accès à la base données. Sans cache, Lutece ne pourrait supporter que quelques utilisateurs avec des temps de réponse très médiocres. Faire appel au cache est donc absolument nécessaire !
Les données stockées dans le cache occupent de l'espace mémoire donc leur volume est un paramètre a garder toujours à l'esprit.
Par ailleurs, les données du cache ne sont qu'un reflet des données réelles. Dans certains cas on tiendra à ce qu'elles soient toujours en phase avec les données réelles : le cache devra être "invalidé" à chaque modification d'une donnée ou celles-ci ne sont pas "cachées". Dans d'autres cas, c'est la durée de vie des objets en cache qui donnera la limite acceptable pour le délai de mise à jour des données.
Ce sont toutes ces considérations qu'il s'agit de discuter dans cet article pour configurer Lutece dans une optique de performances maximales.
L'algorithme de base d'une gestion de cache est relativement trivial. Le cache est un grand réservoir en mémoire dans lequel on stocke des objets dont chacun est identifié par une clé unique calculée. Le modèle de collection utilisé en général pour stocker un cache est une Map.
L'algorithme de récupération d'un objet est donc le suivant :
La construction de la clé de cache est donc un aspect important de la gestion.
Par ailleurs, les services de Lutece s'appuient sur le produit Ehcache qui permet une gestion plus fine des caches en introduisant des notions telles que :
nombre maximum d'objets, durée de vie des objets, stockage sur disque ...
Lutece offre une interface centralisée de gestion de tous les services de cache, accessible à l'administrateur du site.
Cette interface liste les services de cache installés, leur statut, les principales options de configuration et des éléments dynamiques d'utilisation (nombre d'objets, mémoire occupée, ...).
Les dispositifs de cache sont présents à plusieurs niveaux et ils peuvent apporter des gains pour accéder à des ressources lourdes à calculer ou à charger.
On distingue deux principaux niveaux.
Ces services sont implémentés sous forme de filtre de servlet. C'est à dire qu'ils interceptent tout appel HTTP à une ressource (jsp, js, images, ...).
Ces services sont basés sur la brique Ehcache-Web. Les clés de cache sont construites à partir des éléments de la requête HTTP : method, uri path, querystring.
Ces services fournissent d'autres optimisations :
Ces services assurent le cache des différents objets Lutece : page, portlet, menus, arborescence du site, document.
Deux fichiers situés dans le répertoire WEB-INF/conf/ permettent de gérer la configuration des caches :
Ce fichier contient le paramétrage par défaut des caches.
# Default cache configuration
lutece.cache.default.maxElementsInMemory=10000
lutece.cache.default.eternal=flase
lutece.cache.default.timeToIdleSeconds=10000
lutece.cache.default.timeToLiveSeconds=10000
lutece.cache.default.overflowToDisk=true
lutece.cache.default.diskPersistent=true
lutece.cache.default.diskExpiryThreadIntervalSeconds=120
lutece.cache.default.maxElementsOnDisk=10000
caches.dat
Ce fichier contient le statut et le paramétrage spécifique des caches au démarrage de la Webapp.
#Caches status file
#Sun Mar 27 03:06:48 CEST 2011
SiteMapService.enabled=1
MyPortalWidgetService.enabled=1
PortalMenuService.enabled=1
DocumentResourceServletCache.enabled=1
PageCacheService.enabled=1
PortletCacheService.enabled=1
MyPortalWidgetContentService.enabled=1
PageCachingFilter.enabled=1
StaticFilesCachingFilter.enabled=1
StaticFilesCachingFilter.timeToLiveSeconds=1000000
La racine des propriétés est le nom des caches (avec les espaces supprimés si le nom original en contient). La propriété enabled indique si le cache est activé (valeur=1) ou désactivé (valeur=0). Si cette propriété est absente, le cache est activé par défaut.
Les autres propriétés sont les mêmes paramètres de cache que ceux du fichier caches.properties. La valeur indiquée dans ce fichier surchargera la valeur par défaut définie dans caches.properties.
A partir de la version 4.1 ces infos sont sauvegardées en base dans le Datastore. Les fichiers caches.properties et caches.dat ne servent que pour l'initialisation des valeurs.
Comme indiqué en introduction, il n'y a pas de configuration universelle. Il s'agit de trouver les meilleurs compromis entre fraicheur des données, performances et consommation de ressources mémoire.
Voici les principales configurations.
En développement, il n'y a pas d'enjeux de performances et il est préférable de suivre la mise à jour des données sans effet de cache. La configuration à retenir est donc de désactiver tous les caches.
StaticFilesCachingFilter.enabled=0
PageCachingFilter.enabled=0
PageCacheService.enabled=0
PortletCacheService.enabled=0
PortalMenuService.enabled=0
SiteMapService.enabled=0
il est possible également de désactiver l'ensemble des caches en modifiant une propriété system au niveau du lancement de la VM Java.
java -Dnet.sf.ehcache.disabled=true
Le cache de premier niveau peut être activé à la fois sur les ressources statiques (images, css, scripts) mais aussi sur les pages du portail dont notamment Portal.jsp.
La durée de vie du cache des ressources statiques peut être configurée à une semaine soit 604800 secondes. Celle des pages JSP peut être configurée à une heure soit 3600 secondes
StaticFilesCachingFilter.enabled=1
StaticFilesCachingFilter.timeToLiveSeconds=604800
PageCachingFilter.enabled=1
PageCachingFilter.timeToLiveSeconds=3600
Les caches de deuxième niveau (pages, portlets, menu,...) ne sont pas obligatoires dans ce cas. Si ils sont activés, la durée de vie des objets doit être cohérente avec celle du cache de premier niveau, soit dans le cas présent la valeur à retenir serait également une heure.
PageCacheService.enabled=0
PortletCacheService.enabled=0
PortalMenuService.enabled=0
SiteMapService.enabled=0
DocumentResourceServletCache.enabled=1
La personnalisation empêche d'utiliser le cache de premier niveau pour les pages JSP. En effet la même page ne s'affichant pas de la même manière en fonction de l'utilisateur connecté, il n'est pas possible de servir une page à partir de sa seule adresse.
Pour assurer un cache efficace des pages, il est alors nécessaire de s'appuyer sur les caches de second niveau qui peuvent gérer dans leurs clés l'identifiant de l'utilisateur.
StaticFilesCachingFilter.enabled=1
StaticFilesCachingFilter.timeToLiveSeconds=604800
PageCachingFilter.enabled=0
PageCacheService.enabled=1
PortletCacheService.enabled=1
PortalMenuService.enabled=1
SiteMapService.enabled=1
DocumentResourceServletCache.enabled=1
Dans ce type de configuration, les identifiants des utilisateurs étant dans la clé de cache, il faut impérativement tenir compte du nombre d'utilisateurs pour tous les aspects de dimensionnement. Il peut être envisagé de créer des clés de cache spécifiques en fonction du mode de personnalisation pour réduire le nombre d'objets et les performances.
Dans un contexte où la mémoire disponible est faible et/ou la taille des ressources est importante, il convient de dimensionner correctement le paramètre maxElementsInMemory. Si le nombre d'objets en cache dépasse cette valeur, les objets les moins récemment utilisés (eviction policy LRU Least Recently Used) seront déplacés vers un cache disque, limitant ainsi les risques de dépassement mémoire.
Pour développer un service de cache intégré à Lutece il suffit d'étendre la classe abstraite AbstractCacheableService.
public class MyCacheService extends AbstractCacheableService
{
private static final String SERVICE_NAME = "My Cache Service";
public MyCacheService()
{
initCache();
}
public String getName( )
{
return SERVICE_NAME;
}
public MyResource getResource( String strId )
{
MyResource r = getFromCache( strId );
if( r == null )
{
r = getResourceFromSource( strId );
putInCache( strId , r );
}
return r;
}
}
Pour rendre les clés faciles à lire, la norme retenue est de concaténer les éléments sous la forme [ key1:value1][key2:value2]...[user:id].
Par ailleurs, ces constructions étant présumées être fortement sollicitées, on utilisera une concaténation avec un StringBuilder
.
Voici une implémentation typique respectant ces normes :
private String getCacheKey( String strId , LuteceUser user )
{
StringBuilder sbKey = new StringBuilder();
sbKey.append( "[res:" ).append( strId ).append( "][user:").append( user.getName() ).append("]");
return sbKey.toString();
}
Certains services tels que PageService
acceptent l'injection par le biais du contexte Spring d'une classe implémentant l'interface ICacheKeyService
permettant de générer des clés de cache à partir d'une map de paramètres et de l'objet user.
La classe DefaultCacheKeyService
propose une implémentation par défaut de cette interface. L'interface prévoit de définir pour la génération de la clé :
- une liste de paramètres à utiliser pour générer la clé. Il s'agit d'une mesure de sécurité pour empêcher qu'un générateur utilisant des paramètres fictifs dans les urls ne génère autant de clés de cache qui viendrait à saturer la mémoire.
- une liste de paramètres à ignorer. Certains paramètres peuvent ne pas être pertinents dans la construction et peuvent générer des doublons dans le cache. Il convient donc de les éliminer en les déclarant dans cette liste.
Voici l'exemple d'injection de CacheKeyService
dans un contexte Spring pour PageService
:
<bean id="pageCacheKeyService" class="fr.paris.lutece.portal.service.cache.DefaultCacheKeyService" >
<property name="allowedParametersList" >
<list>
<value>page_id</value>
</list>
</property>
</bean>
<bean id="portletCacheKeyService" class="fr.paris.lutece.portal.service.cache.DefaultCacheKeyService" >
<property name="ignoredParametersList" >
<list>
<value>page-id</value>
<value>site-path</value>
</list>
</property>
</bean>
<bean id="pageService" >
<property name="pageCacheKeyService" ref="pageCacheKeyService" />
<property name="portletCacheKeyService" ref="portletCacheKeyService" />
</bean>
Il y a une erreur de communication avec le serveur Booktype. Nous ne savons pas actuellement où est le problème.
Vous devriez rafraîchir la page.