Data Injection Markup Language
Gestion de cache pour le processeur DIML
Auteur : V.G. FREMAUX
E.I.S.T.I. / Cergy, France
Laboratoire de Recherche Appliquée
Nouvelles Technologies de l'Information et de la Communication
Version : 1.0 / Septembre 2003
0. Objet de ce document
Cette spécification définit le besoin en termes de cache pour l'accélération des performances d'un serveur Web motorisé par un processeur ESSI.
1. Motivation
La composition dynamique de documents apporte une grande productivité aux développeurs de contenus diffusés par Internet, au détriment du coût unitaire en traitement pour servir ces documents. Un mécanisme de cache permet à un serveur de servir une version calculée à l'avance, pendant une certaine plage de temps pendant laquelle l'information contenue dans la page calculée peut être considérée comme toujours valide. La notion de cache est ici identique à celle des Proxies-Caches HTTP.
2. Définitions
- Expiration
- L'expiration d'une page est le délai au bout duquel une page doit être considérée comme non valide.
- Cache total ou PostCache
- Un cache total mémorise la résolution finale d'un document dynamique, à une adresse tenant compte de la totalité du contexte dynamique qui a servi à produire la page.
- Cache partiel
- Un cache partiel cache un document partiellement résolu, dans lequel une sélection des appels dynamiques a été traitée, alors que le reste des appels, intégrant des données à durée de validité plus courte, demeure sous une forme interprétable.
- Champ dynamique
- Par opposition, le champ dynamique d'un site DIML est l'ensemble des insructions DIML dont le résultat ne peut être déterminé qu'au moment de l'appel, soit parce que la valeur est imprévisible (donnée ouverte), soit parce qu'elle est trop volatile.
Une variable du champ dynamique peut ne contenir que du HTML littéral, par exemple, s'il s'agit d'une donnée issue d'une base de données. Le caractère dynamique du symbole signifie seulement qu'il ne peut disparaître dans une copie de cache partiel.
- Champ ouvert
- Un champ de requête est dit "ouvert" lorsque la valeur qu'il peut prendre ne peut être définie à l'avance dans une liste finie de valeurs connues.
C'est le cas d'un champ d'entrée de texte.
- Champ statique
- Le champ statique d'un site DIML est l'ensemble des instructions DIML qui peut être assimilé à une sélection paramétrique de plusieurs modèles de la même page. Il détermine la frontière entre la partie complètement dynamique des données à diffuser, et des variantes semi-statiques dont la durée de vie est beaucoup plus longue, ou dont le nombre peut être parfaitement déterminé.
Une instruction à valeur statique peut très bien contenir d'autres symboles à valeur dynamique. Le caractère statique de l'instruction provoque sa disparition d'une copie de cache partiel. Il ne signifie pas que son contenu est purement "littéral" au sens de la construction dynamique.
- Symbole terminal
- Une variable ou un template DIML qui ne contient aucun appel DIML récursif. Son contenu est considéré comme un e constante au sens du processus de construction dynamique de la page.
- Symbole non terminal
- Une variable ou un template DIML qui ne contient des appels ou instructions DIML.
3. Problèmes posés
La gestion d'un cache pour un processeur de page dynamiques pose les problèmes fondamentaux suivants :
3.1 Degré de cache
Le degré de cache détermine quelle est la nature de l'objet caché.
- Un cache total va résoudre la totalité de la page dynamique en une page HTML complètement statique. Lorsque la page est très fortement paramétrique, un même URI peut produire un très grand nombre de pages statiques équivalentes (en fait une par "solution" de la chaîne CGI de requête). Le nombre est quasiement infini lorsque la requête véhicule des champs "ouverts".
- Un cache partiel va produire un ensemble de versions simplifiées de la page dynamique, c'est à dire un jeu de pages dynamiques dans lesquelles certains choix ont déjà été résolus par rapport à la page originale.
3.2 Organisation du cache
Un mécanisme de cache pour le moteur ESSI doit disposer d'un espace de stockage temporaire des fichiers cachés. Il existe une discussion fondamentale sur l'organisation de ce cache.
3.2.1 Cache à plat
Le principe du cache à plat conduit à stocker toutes les entrées de cache dans un seul et unique répertoire. Une entrée de cache doit cependant avoir une clef unique, quelque soit sa position dans le site original. Cette clef unique ne peut être le nom de fichier original, car sa valeur n'est pas unique dans le système de fichiers physique. Il est donc nécessaire de générer un nom complexe, identifiant le fichier, mais également la partie de chemin d'accès physique garantissant l'identification unique de la ressource. Le nom de ressource cachée devra également prendre en compte le contexte dynamique qui à produit le contenu.
Etant donné le nombre de composantes d'identification du fichier de cache, et la longueur potentielle de ce nom, il semble difficilement possible d'envisager un stockage à plat des fichiers cachés.
3.2.2 Cache arborescent
Le cache arborescent reproduit une arborescence de stockage physique image de l'arborescence logique des URI à partir de la racine HTTP. L'avantage de cette méthode est que la complexité identifiante du chemin d'accès est assurée par le système de fichiers. L'inconvénient de cette méthode est qu'il faut gérer la création des chemins manquant dans le cache : il n'est évidemment pas question de reproduire a priori la structure logique du site.
Les noms de fichiers de cache sont déduits des noms de fichiers originaux, ce qui assure une lisibilité humaine de la structure du cache.
3.3 Optimisation du cache
Tous les paramètres de contexte CGI ne sont pas nécessairement significatifs pour la sélection d'une ressource cachée, notamment, lorsqu'une stratégie de cache partiel est utilisée. Le paramétrage du cache doit pouvoir permettre de préciser le "champ statique" du site d'une façon suffisamment précise pour discriminer jusqu'où la résolution dynmamique de la page doit aller pour optimiser la taille du cache et la réactivité du moteur.
Sur un cache total, la détermination du champ statique permet de déterminer quel est le jeu de fichiers "différents" à stocker sous forme de ressources cachées. Ainsi deux "solutions" distinctes de la chaîne CGI de requête convergeront vers la même expresion de la page. C'est par exemple le cas dans les sites disposant d'une variable CGI de session, qui n'influe pas sur ce que la page produit.
3.4 Validité et réactivité de cache
3.4.1 Pertinence de contenu
Le cache permet de stocker une version pré-calculée d'une solution de l'URL complète de la ressource cachée. Il est important de pouvoir fournir à l'utilisateur la version la plus "à jour" de cette solution.
Une solution devient invalide lorsque la fabrication complète de la page donne un résultat différent de sa version cachée. Ceci arrive lorsqu'une des ressources composant la page a été mise à jour. Le mécanisme de cache devra donc disposer d'un moyen de calculer la pertinence de sa version caché, au regard des ressources originales qui produisent cette solution.
3.4.1 Temporalité de ressource
Attribuer une durée de vie à une ressource est utile pour fournir au cache un système de contrôle de son volume. Si le cache est limité en taille, il est en effet nécessaire de pouvoir choisir quelles ressources doivent être éliminées. La datation des copies cachées est un bon moyen pour rationaliser ce choix : Le cache commencera à éliminer les ressources "déclarées" obsolètes.
Note : Une ressource obsolète d'un point de vue de la date de calcul peut cependant être parfaitement valide du point de vue contenu. Les deux problèmes sont indépendants.
4. Moteur de cache
Le moteur de cache pour processeur ESSI suppose une refonte complète de l'algorithme de résolution des pages DIML.
4.1 Discussion sur les procédés
4.1.1 Processus du cache total
Le processus de cache total ne nécessite pas de refonte du moteur. Son processus est simple et répond au processus ci-dessous :
La stratégie de gestion d'un cache partiel est beaucoup plus délicate que cette du cache total. En effet, un cache partiel effectue une résolution partielle des fichiers dynamiques pour constituer une nouvelle copie plus simple à exécuter, mais tout aussi dynamique :
4.1.2 Algorithme à double étage
Selon l'algorithme à double étage, l'action de cacher entraîne une résolution de la page en deux phases distinctes selon le schéma fonctionnel suivant :
La première phase effectue la résolution partielle, dont le résuiltat peut être mis en cache selon une stratégie d'indexation particulière, qui élimine la partie du contexte appartenant au champ dynamique du document.
La deuxième phase effectue une résolution complète du document intermédiaire générée par la pré-résolution, ou de sa copie cachée, en faisant intervenir la partie dynamique du contexte CGI d'entrée.
Discussion
La résolution à double étage demande une refonte du moteur interne. Sa fonction d'analmyse doit déterminer pour chaque instruction rencontrée l'opportunité de résoudre ou non.
La résolution à double étage peut apparaitre comme une perte importante de réactivité du processeur dans son ensemble. Cependant, il est probable que la phase de pré-résolution exécute moins d'instructions que si le document avait été entièrement résolu. La phase de post-compilation exécutant les instructions restantes. Selon toute attente, la surconsommation de temps d'exécution ne doit pas être très importante, en regard du gain apporté par une résolution beaucoup plus rapide des pages cachées.
La résolution à double étage soulève des problèmes de détermination du champ statique, et notemment lors de l'exécution d'overscripting externe. En effet, un script externe est susceptible de créer des symboles qui sont essentiels pour la construction correcte du document, que ce soit à partir de sa copie originale comme à partir de sa copie cachée. Lors de la première résolution, les overscripts doivent être exécutés pour pouvoir conserver la cohérence de la table des symboles qui permet la poursuite des choix du pré-processing. Cependant, ils doivent être réexécutés également lors du post-processing, comme ils seraient exécutés lors de la résolution de la copie de cache. Ceci conduit à exécuter deux fois les mêmes scripts et conduira souvent des incohérences lorsque des données sont modifiées ou écrites en bases de données. Il est, selon nos expérimentations impossible de :
- Déterminer si un overscript devrait être exécuté entièrement au pré-processing (overscript à effet statique) ou entièrement au post-processing (overscript à effet dynamique). Le plus souvent les overscripts sont hybrides.
- Formaliser une identification des symboles générés par overscipt, pour isoler un champ statique.
- Imposer une dissociation manuelle du code d'overscript qui serait une contrainte de développement trop complexe pour le développeur.
4.1.3 Algorithme à résolution parallèle
L'algorithme à résolution parallèle est fondamentalement différent au niveau de la structure du moteur. En effet, les deux versions (copie finale et copie cache) sont créées en même temps, dans le même processus récursif de résolution du langage DIML.
Le processus est le suivant :
Discussion
La résolution parallèle permet de ne gérer qu'un seule table des symboles et résout les problèmes de cohérence liés à l'overscripting.
La résolution parallèle permet un gain en coût processus lors de la construction de la copie cachée. Par contre, elle est moins performante lors d'une résolution de cache que l'algorithme à double étage.
4.2 Discussion sur la résolution partielle
4.2.1 Détermination du champ statique
Pour effectuer une résolution partielle, il est important de pouvoir déterminer quelles sont les instructions qui peuvent être résolues et lesquelles doivent être laissées en l'état.
Malgré nos efforts et un temps considérable d'expérimentation, une détermination "implicite" du champ statique se révèle totalement impossible dans le contexte complètement récursif et conditionel du DIML. Nous allons le démontrer par un contre-exemple simple :
Soit la séquence de DIML suivante :
<%if (%FORM::lang% eq "") %>
<%invoke script="detectLanguage.pl" %>
<%else %>
<%set %pref% = %FORM::lang% %>
<%set %FORM::lang% = "en" %>
<%endif %>
...
<%%FORM::lang%%>
Le paradoxe peut s'exprimer ainsi :
Admettons que la variable FORM::lang fasse partie du champ statique, c'est à dire, nous permette de cacher une version de cache pour chaque langue effective, y compris pour la langue nulle. Admettons que le champ de l'instruction "invoke" soit dynamique. Nous aurions pour la page nulle une balise non résolue d'invocation de script, qui serait à même (dansle meilleur des cas) de pouvoir détecter et fixer la langue. Malheureusement, FORM::lang étant dans le champ statique, toutes les occurrences de FORM::lang ont disparu. La détection de langue ne sert plus à rien.
Admettons maintenant que FORM::lang fasse partie du champ dynamique. L'alternative nepeut être résolue, puisque la variable de test est supposée être mobile. A ce moment, il est impossible de déterminer quelle alternative va être choisie. L'analyse de tout le reste de la page est néanmoins assujetti à la bonne détermination de la valeur de FORM::lang. La stratégie de cache implicite échoue encore.
Nous avons rencontré de nombreuses situations de ce genre en essayant diverses stratégies de cache sur des fichiers complexes tels que le site DIML lui même. Toutes ces situations nous ont amené à la conclusion qu'il était préférable de laisser au développeur de pages la rationalité du choix pour savoir si une séquence devait être résolue ou non.
5. Stratégie de cache
5.1 Cache total
5.1.1 Stratégie
La stratégie de cache total effectue un stockage temporaire de la version finalisée d'un document, indexée par décomposition de l'URL de la ressource selon le processus suivant :
- Pour la partie "chemin d'accès", il est utilisé une stratégie de cache arborescent qui reproduit la partie "chemin logique" à partir de la racine "ESSI::CACHE_ROOT". Le chemin logique est évalué à partir de la racine physique du site SITE_ROOT_PATH.
- Pour la partie "nom de fichier", le nom de fichier original est préservé.
- Pour la partie "contexte", un suffixe magique (digest) généré au moment du stockage de la copie de cache d'une ressource.
5.1.2 Exceptions de cache
Même dans le cas d'une stratégie totale, toutes les URL d'entrée ne peuvent être indexées. Seront considérées comme des exceptions de cache :
- Les requêtes effectuant des téléchargements de type mime multipart/form-data
- Les requêtes présentant des paramètres de cartes-images serveur
5.1.3 Réduction du champ d'indexation
Afin de diminuer le nombre d'objets stockés dans le cache, on admet que le contexte peut être divisé en un champ statique et un champ dynamique.
Le champ statique contient les variables déterminant une variation statique du contenu, c'est à dire, qui influe sur l'information qui doit être présentée à l'écran. C'est le cas de la plupart des paramètres CGI dans des sites de publication, dont le but et de paramétrer la sortie HTML.
Le champ dynamique contient des données qui n'influent pas sur la sortie. C'est le cas par exemple de certaines variables de session, ou de certaines variables d'identification (il faut pour cela que la session ne serve qu'à l'identification d'une visite, et n'influence pas des paramètres visuels). On peut considérer alors que la page diffusée sera identique quelles que soient les valeurs des variables du champ dynamique. Elles sont évidemment bien moins nombreuses.
5.1.4 Cas particulier des requêtes à ancres
Les portions "ancres" des URL sont ignorées dans la résolution de ressource cachée. Les ancres sont reportées à l'identique, dans la mesure ou l'URL de base de la ressource n'est pas affectée par le cache (c'est en effet seulement le contenu de l'URL qui ne provient pas de la même source physique).
5.2 Cache partiel
5.2.1 Stratégie
Le cache partiel permet de résoudre plus finement le besoin de cache d'un site hautement dynamique. En effet, la plupart des instructions peuvent être résolues en pré-compilation de cache, surtout lorsqu'il s'agit d'instructions qui composent la structure d'une page.
Par contre, toutes les instructions faisant appel, affichant ou provoquant une interaction avec un système très dynamique tel une base de données peuvent être marqués comme faisant partie du champ dynamique.
La différence est subtile et parfois, les deux aspects seront intimement interpénétrés (comme dans les composants WCT) par exemple. Il pourra ne pas toujours être aisé de séparer le champ dynamique du champ statique.
La discussion devient ardue par exemple, dans des sites disposant d'un moteur de publication de contenu, par lequel l'essentiel du contenu est stocké sous forme d'enregistrements de bases de données. Malgré cet aspect a priori dynamique, nombreux sont les sites basés sur ces technologies dont le contenu peut être considéré comme statique de fait. Dans ce cas, prégénérer les pages résultant de l'activation du contenu par ces moteurs peut être considéré comme une manoeuvre statique d'un cache partiel.
5.2.2 Marquage explicite du champ dynamique
Pour des sites majoritairement statiques, il est préférable que le champ dynamique soit explicitement indiqué par le développeur. Le mode de résolution initial du processeur sera donc statique par défaut ( $main::CACHE_DEFAULT = "static" dans le fichier de configuration du processeur ). Le module de cache du processeur DIML fournit par la suite deux instructions simples pour contredire ce défaut et marquer les plages dynamiques :
<%cache off%>
... section dynamique ...
<%cache on%>
5.2.3 Marquage explicite du champ statique
Pour des sites majoritairement dynamiques, il serait préférable de pouvoir explicitement renseigner le processeur que certaines séquences peuvent être pré-resolues sans risque pour la copie de cache de manquer de données. Dans ce cas, la configuration initiale devra donner une préférence dynamique ( $main::CACHE_DEFAULT = "dynamic" ) dans le fichier de configuration du processeur, puis utiliser les instructions du module de cache pour explicitement forcer une séquence statique :
<%cache on%>
... section statique pré-résolue ...
<%cache off%>
5.2.4 Surcharge de la politique courante
Dans certaines portions statiques, certains symboles doivent conserver une valeur dynamique. Cette valeur qui leur est attribuée permet d'éviter un usage abusif des clauses cache dans une section très majoritairement statique (et vice versa).
Par exemple, dans une page documentaire, l'essentiel de la composition du document peut être résolue de amnière statique, alors que les paramètres CGI de contexte (session, langue, section, etc.) doivent pouvoir continuer à être transmis derrière chaque URL ou dans chaque formulaire.
Certain symboles peuvent donc revêtir d'un caractère ("statique" ou "dynamique") "forcé" (par exemple, la variable de session "FORM::session" dans ce site est résolument dynamique, quelle que soit la séquence considérée. Par cet artifice, le cache ne pourra stocker qu'une seule page de cache pour le document présent, même si la navigation de personnes différentes attaquent ce document par des URL sensiblement différentes.
5.2.5 Déclaration du champ dynamique
La déclaration du champ dynamique doit rester souple. Nous avons donc choisi de lui donner une souplesse "au répertoire près".
Déclarer un champ dynamique, c'est déclarer la liste des symboles (templates ou variables) dont les appels ne DOIVENT PAS être résolus.
6. Paramètres du cache
6.1 Temporisation du cache
La temporisation du cache est nécessaire pour la stratégie de cache total, dans laquelle les fichiers cachés sont une "photographie" d'une ressource figée à l'instant de sa "prise de vue". Comme la stratégie totale ne laisse aucune latitude pour qu'une copie cachée conserve une partie dynamique, la seule possibilité pour un utilisateur de voir une version suffisamment récente d'une ressource est de temporiser la validité d'une copie de cache.
Les objets cachés ont une certaine durée de vie. Cette durée de vie devra pouvoir être globalement établie pour l'ensemble du volume à diffuser, mais devra pouvoir être contredite localement.
La durée de cache s'exprime selon la pratique de la directive Expire: du serveur Apache :
cacheDuration ::= "M"|"A" seconds
seconds ::= [0-9]+
6.2 Contrôle de taille du cache
6.2.1 Principes
La taille du cache doit pouvoir être globalement limitée à une valeur fixe. Dans l'alternative, le cache doit pouvoir proposer une ou plusieurs méthodes de contrôle de la taille par nettoyage automatique de ressources. Ce nettoyage peut être systématique, sur la base du délai d'expiration des ressources (une ressource expirée n'a plus de raison de demeurer dans le cache, puisqu'elle devra être régénérée lors d'une prochaine activation de l'URL correspondante). Cependant, une telle politique peut entrainer un surcoût en process pour des volumes importants (ou très paramétriques). Une politique plus "backoffice" peut être alors préférable.
Le contrôle de la taille du cache nécessite le comptage permanent des octets des objets déposés dans le cache. Pour maintenir les performances du cache dans une fourchette avantageuse, on évitera de procéder à sa détermination par analyse des volumes. Une variable de comptage doit donc être maintenue dans l'espace du cache.
4.2.2 Politique de nettoyage en cas de saturation de cache
Malgré la mise en oeuvre d'un mécanisme de nettoyage régulier des fichiers obsolètes, il est possible dans certaines situations que le fonctionnement du site soit incompatible avec la limite physique absolue de taille. Il est nécessaire de prescrire un algorithme d'élimination de ressources.
Cet algorithme doit être capable d'éliminer les ressources suffisantes pour pouvoir continuer à stocker les copies de cache nécessaires, sans trop amputer la réactivité du moteur. Il devra donc définir les méta données qui permettent d'effectuer une sélection rapide des objets à supprimer afin de libérer la place nécessaire.
7. Notes d'implémentation
L'implémentation de cache remet en cause l'architecture du processeur ESSI d'une manière assez fondamentale. En effet l'instauration d'un cache partiel entraîne la modification de comportement du moteur d'analyse syntaxique du DIML suivat les règles elliptiques qui ont été définies au chapitre 5.
All material is copyleft V.G. FREMAUX (EISTI France) 1999 to 2003 except explicitly mentioned
|