• Aucun résultat trouvé

Am´elioration des performances par ajout d’un cache adaptable

8.5 Exemple 2 : serveur web

8.5.2 Am´elioration des performances par ajout d’un cache adaptable

Comanche, se voulant extrˆemement simple, n’int`egre pas de m´ecanisme pour mettre en cache le contenu des fichiers qu’il lit. Afin d’am´eliorer ses performances, nous d´ecidons donc d’introduire un nouveau composant adaptatif qui ajoute cette fonctionnalit´e.

Les performances du composant cache d´ependent essentiellement de la quantit´e de m´emoire qu’il est autoris´e `a utiliser pour stocker le contenu des fichiers lus pr´ec´edemment. Si cette quantit´e est trop faible, le syst`eme ne tirera pas pleinement partie de la pr´esence d’un cache, et si elle est trop importante, les performances risquent d’ˆetre encore moins bonnes. En effet, si le cache utilise trop de m´emoire le syst`eme d’exploitation devra avoir recours `a de la m´emoire virtuelle sur disque, et donc beaucoup plus lente (ph´enom`ene de « trashing »). La quantit´e de m´emoire allou´ee au composant cache ne peut donc pas ˆetre une constante, mais doit ˆetre d´etermin´ee en fonction de la quantit´e de m´emoire disponible sur le syst`eme hˆote. Or, cette quantit´e varie au cours du temps, le serveur http n’´etant pas le seul programme en cours d’ex´ecution sur le syst`eme. Si par exemple un utilisateur d´emarre une application gourmande en m´emoire alors que le cache utilise une grande partie de la m´emoire syst`eme, le syst`eme d’exploitation devra d´eplacer une partie de la m´emoire utilis´ee par le cache sur le disque dur, beaucoup plus lent, pour pouvoir fournir la m´emoire n´ecessaire au nouveau programme.

Notre premier sc´enario d’adaptation pour cette application va donc consister `a rendre adaptable la quantit´e de m´emoire allou´ee au composant cache afin de garantir de bonnes performances en toutes circonstances.

Architecture initiale. Dans Comanche, la gestion des requˆetes http elles-mˆemes est trait´ee par un composant request-dispatcher. Ce dernier poss`ede une interface cliente de type « collection » qui l’autorise `a avoir plusieurs interfaces clientes connect´es. Lorsque ce composant re¸coit une requˆete http, il l’envoie tour-`a-tour `a chacun de ses clients, jusqu’`a ce que l’un d’entre eux soit capable d’y r´epondre (motif Chaˆıne de responsabilit´e [Gamma et al., 1994]). La configuration initiale de Comanche, repr´esent´ee sur la figure 8.4, utilise deux composants : le premier tente de lire le contenu du fichier local mentionn´e dans l’url8de la requˆete, et si il ´echoue, le second composant (qui lui n’´echoue jamais) renvoie un message

d’erreur standard (404).

Fig.8.4 – Architecture initiale du gestionnaire de requˆetes Comanche.

Ajout du cache. L’introduction d’un cache de fichiers dans cette architecture se fait tr`es simplement. Il suffit d’impl´ementer le composant correspondant et de l’introduire dans l’architecture ci-dessus sur le chemin du composant file-handler, ce qui ne n´ecessite qu’une modification tr`es simple du fichier .fractald´ecrivant l’architecture de l’application. Sans rentrer dans le d´etail des diff´erentes m´ethodes, le code m´etier de ce composant cache est le suivant :

8

public void handle(Request req) throws IOException { if (inCache(req.url)) {

returnCachedVersion(req); } else {

ByteArrayOutputStream data = new ByteArrayOutputStream(); Request proxy = createProxyRequest(req, data);

fileHandler.handle(proxy); if (cache(req.url, data)) { returnCachedVersion(req); } else { req.output.write(data.toByteArray()); } } }

Lorsqu’il re¸coit une requˆete, il v´erifie tout d’abord si le fichier d´esign´e (req.url) n’est pas en m´emoire. Si c’est le cas, il renvoie directement son contenu, sans avoir besoin d’acc´eder au disque. Sinon, il cr´ee une copie de la requˆete initiale, modifi´ee pour que la r´eponse soit stock´ee dans un tampon m´emoire plutˆot qu’envoy´ee directement `a travers la connexion r´eseau. Cette requˆete est ensuite envoy´ee au composant file-handler, qui remplit le tampon avec le contenu du fichier. Enfin, les donn´ees de ce tampon sont ajout´ees dans le cache du composant (s’il reste assez de place), et renvoy´ees comme r´esultat de la requˆete. L’architecture r´esultante est repr´esent´ee sur la figure 8.5.

Fig.8.5 – Introduction d’un cache de fichiers dans Comanche.

Informations contextuelles. Le composant cache d´ecrit ci-dessus expose deux param`etres de confi- guration accessibles par son interface attribute-controller. Le premier, currentSize, est accessible en lecture seule et indique la quantit´e de m´emoire actuellement utilis´ee par le cache (en kilo-octets). Le second, accessible en lecture et ´ecriture, se nomme maximumSize et indique la quantit´e de m´emoire maximale (toujours en kilo-octets) utilisable par le cache pour stocker le contenu des fichiers d´ej`a lus. Comme nous l’avons indiqu´e plus haut, la valeur de ce param`etre doit ˆetre assujettie `a la quantit´e de m´emoire disponible sur le syst`eme pour offrir de bonnes performances en toutes circonstances. Notre po- litique d’adaptation doit donc r´eagir aux ´evolutions de cette quantit´e de m´emoire libre. WildCAT fournit en standard une sonde observant la m´emoire (en utilisant le fichier sp´ecial /proc/meminfo sous Linux), et la quantit´e de m´emoire libre est accessible par l’attribut sys ://storage/memory@free.

Politique d’adaptation. Nous avons maintenant toutes les informations n´ecessaires pour ´ecrire la politique d’adaptation :

action disable-cache(handler) = {

dispatcher := $handler/child::request-dispatcher;

if (name($dispatcher/#handler/component::*) = ’cache’) then { unbind($dispatcher/#handler); file-handler := $handler/child::file-handler; bind($dispatcher/#handler, $file-handler/interface::request-handler); } } action enable-cache(handler) = { dispatcher := $handler/child::request-dispatcher;

if (name($dispatcher/#handler/component::*) != ’cache’) then { unbind($dispatcher/#handler); file-handler := $handler/child::file-handler; cache := $handler/child::cache; bind($dispatcher/#handler, $cache/interface::request-handler); bind($cache/#handler, $file-handler/interface::request-handler); } } policy adaptive-cache = { rule { when realized(sys://storage/memory@free < 10*1024), not(realized(sys://storage/memory@free >= 10*1024, 10)) do { to-free := 10*1024 - sys://storage/memory@free; size := $target/cache/@currentSize - $to-free; if ($size < 500) then { set-value($target/cache/@maximumSize, 0); disable-cache($target); } else { set-value($target/cache/@maximumSize, $size); } } } rule { when mem:changed(sys://storage/memory@free) if (sys://storage/memory@free >= 10*1024) do { enable-cache($target);

size := 0.8 * ($mem.new-value + $target/cache/@currentSize);

max := sys://storage/memory@used - $target/cache/@currentSize + $size; if ($max < sys://storage/memory@total - 10*1024) { set-value($target/cache/@maximumSize, $size); } } } }

Ce fichier adaptive-cache.policy commence par d´efinir deux actions FScript qui seront ensuite utilis´ees dans le corps de la politique. La premi`ere action, disable-cache d´esactive le cache en le d´econ- nectant compl`etement, afin de r´etablir la configuration initiale de l’application. Ainsi, lorsque le cache n’est pas utilis´e, le syst`eme n’a pas `a payer le coˆut des indirections suppl´ementaires. La seconde action, enable-cache, r´e-introduit le cache dans le flot d’ex´ecution des composants. Ces deux actions permettent donc de passer `a loisir entre les deux architectures d´ecrites pr´ec´edemment.

La politique d’adaptation elle-mˆeme est constitu´ee de deux r`egles. La premi`ere est d´eclench´ee si la quantit´e totale de m´emoire disponible sur le syst`eme passe en dessous de 10 Mo pendant au moins 10 secondes (ceci afin de ne pas r´egir aux conditions transitoires). Lorsque cela se produit, l’action de reconfiguration tente de lib´erer de la m´emoire en r´eduisant la taille maximale du cache, ou en le d´esactivant compl`etement en dessous d’une certaine taille minimale (ici 500 Ko). La seconde r`egle se d´eclenche lorsque la quantit´e de m´emoire libre varie9 mais est au dessus de 10 Mo. Dans ce cas, la reconfiguration ajuste

la taille allou´ee au cache `a 80% de la quantit´e de m´emoire utilisable, mais uniquement si cela laisse suffisamment de m´emoire libre globalement.

D´eploiement. Si le composant cache lui-mˆeme est d´ej`a int´egr´e dans l’application en cours d’ex´ecution, le d´eploiement de la politique se fait de la mˆeme mani`ere que les pr´ec´edentes, par exemple en utilisant la console :

Safran> !load adaptive-cache.policy

Safran> attach($handler, "adaptive-cache");

O`u $handler est le composite repr´esent´e sur les figures pr´ec´edentes. Sinon, la console peut ˆetre utilis´ee pour introduire dynamiquement le composant cache avant de d´eployer la politique elle-mˆeme10:

Safran> cache := new("cache"); Safran> add($handler, $cache); Safran> !load adaptive-cache.policy

Safran> attach($handler, "adaptive-cache"); `

A partir de cet instant, le composant cache sera activ´e, d´esactiv´e, et configur´e dynamiquement et de fa¸con automatique afin d’offrir les meilleures performances possibles en toutes circonstances. Notons que la nature dynamique de safran permet d’exp´erimenter facilement diff´erentes politiques, par exemple pour rechercher les meilleures valeurs possibles concernant la taille maximale allou´ee au cache. Il suffit pour cela de retirer la politique, ´editer son fichier source, puis la recharger et r´e-attacher au composant : Safran> detach($handler, "adaptive-cache");

[ emacs adaptive-cachepolicy ... ] Safran> !load adaptive-cache.policy

Safran> attach($handler, "adaptive-cache");