Dans un précédent billet étaient annoncée la disponibilité d’un nouveau système de cache dans Windows Azure, en l’occurrence Windows Azure Caching, et proposée une rapide description de sa mise en œuvre dans la plateforme de données ouvertes OGDI DataLab.

Dans ce billet, nous vous proposons d’entrer plus en détail dans la mise en œuvre du cache dans OGDI DataLab afin d’obtenir une solution prête à l’emploi sur la base de la version v5 disponible sur la forge GitHub.

Mais avant de rentrer dans le vif du sujet, commençons par un bref rappel sur Windows Azure Caching et plus précisément sur la fonctionnalité de cache partagé.

Le Cache Windows Azure aussi identifié comme un cache fondé sur un ou plusieurs rôles apporte de multiples avantages. Parmi ces avantages, notons plus particulièrement un fonctionnement basé sur des ressources non utilisées des différentes instances hébergeant un service ou encore une utilisation d’un cache commun à ces différentes instances ; l’objectif étant bien entendu de fournir simplement et rapidement un ensemble de ressources permettant d’économiser des transactions couteuses (en termes d’utilisation ou de prix).

Plus que des mots, voici un schéma tiré d’un billet de Scott Guthrie sur le cache partagé :

image

L’idée consiste ici d’utiliser dans OGDI DataLab les différentes ressources « dormantes » de chaque instance afin de créer un cache rapide et puissant et ce, afin de fournir les données (ouvertes) de manière optimisée.

Bien entendu, cette utilisation du cache étant faite sur des rôles déjà provisionnés, l’utilisation d’un tel système de cache est totalement gratuite.

Cependant, il est important de prendre en compte les recommandations d’utilisation du cache partagé dans Windows Azure avant de mettre en œuvre celui-ci :

  • La taille du cache ne doit pas dépasser 1,5 Go ;
  • Un cluster de cache avec plus de 400 transactions par seconde par instance sera moins performant ;
  • Un cluster de cache avec plus de 1,2 Mo de bande passante utilisée pour des opérations de cache par seconde par instance risque d’engorger le système.

Si jamais une de ces considérations est rencontrée, il est préférable de s’orienter vers le 2ème type de cache de Windows Azure Caching, à savoir le cache distribué.

Ces informations sont issues des recommandations de l’article MSDN Windows Azure Caching sur les rôles existants.

Voyons à présent comment le cache partagé peut être mise en œuvre au sein de la plateforme OGDI DataLab et comment celui-ci fonctionne dans ce contexte.

Implémentation dans OGDI DataLab

Le cache partagé est utilisé sur les 2 services d’OGDI DataLab :

  1. Le service de données DataService.WebRole,
  2. Et le service Web DataBrowser.WebRole.

L’utilisation du cache dans le service de données permet ainsi d’optimiser les temps de réponse pour les différentes applications utilisant celui-ci et aussi d’optimiser la restitution des données dans le service web. D’autre part, l’implémentation du cache dans le service web permet d’alléger l’utilisation du service de données et ainsi de réduire considérablement le nombre de transactions effectuées tout en augmentant les temps de réponse du frontal.

Il est important de préciser que les gains sur une plateforme OGDI DataLab, avec des peu de jeux de données ou de petits jeux de données, ne sont pas significatifs. La solution étant par défaut très réactive, les temps de réponses n’en seront pas accrus. Cependant, le cache se révèle très utile pour de très grosses plateformes utilisant un nombre conséquent de données et en gros volume. C’est dans ce cas de figure particulier que le système de cache montrera tout son potentiel et permet ainsi à OGDI DataLab de tenir la charge aisément sur de très gros volumes de données.

Intéressons-nous à présent à l’implémentation du système de cache dans le service de données.

Avant de commencer l’implémentation du système de cache, assurez-vous d’avoir installé le SDK Windows Azure 1.7 (Juin) et si ce n’est déjà fait mettre à jour les projets Windows Azure dans la version du SDK Windows Azure 1.7 (Juin).

Mise en œuvre dans le service de données DataService.WebRole

Le service de données étant par défaut extrêmement réactif, même sur de gros volumes de données, il n’y a pas besoin d’implémenter un cache pour chaque requête (Pour cette partie, il sera préférable d’utiliser un système de cache côté consommateur de données).

Cependant, la requête concernant les métadonnées peut se révéler très consommatrice en ressources dans des conditions de gros volumes de donnée (nombre ET taille).

C’est donc dans la requête gérant les métadonnées (MetaDataHttpHandler.cs) que le cache sera implémenté.

Pour cela, ouvrez votre solution OGDI DataLab dans Visual Studio et localisez le projet DataService.WebRole situé dans le dossier DataService.

image

Il faut à présent ajouter au projet les références du système de cache Windows Azure Caching.

Faîtes un clic droit sur References du projet DataService.WebRole puis Add Reference…

Recherchez les références suivantes :

  • Microsoft.ApplicationServer.Caching.Core,
  • Microsoft.ApplicationServer.Caching.Client

puis ajoutez les.

image

Maintenant que les références sont ajoutées, ouvrez le fichier MetaDataHttpHandler.cs situé dans le dossier HttpHandlers afin d’ajouter le code nécessaire à l’utilisation du système de cache.

Repérez la méthode ProcessRequest. C’est cette méthode qui permet de récupérer puis de retourner les informations lors d’une requête sur les métadonnées.

Le processus de cette requête avec cache est assez simple. Il convient, dans un premier temps, de récupérer l’instance de cache par défaut puis d’essayer de récupérer les données mises en cache en fonction d’une clé selon l’approche suivante :

Si cette clé est présente et qu’il y a bien des données, alors nous retournons ces données et stoppons la requête. Sinon, nous récupérons les données depuis le compte de stockage, les écrivons dans le navigateur et, dans un même temps, dans un StringBuilder puis nous sauvegardons ce StringBuilder dans le cache.

Soit au final pour la méthode ProcessRequest :

public void ProcessRequest(HttpContext context)

{

   if (!this.IsHttpGet(context))

   {

      this.RespondForbidden(context);

   }

   else

   {

       context.Response.Headers["DataServiceVersion"] = "1.0;";

       context.Response.CacheControl = "no-cache";

       context.Response.ContentType = "application/xml;charset=utf-8";

      

       DataCacheFactory factory = new DataCacheFactory();

       DataCache cache = factory.GetDefaultCache();

       var cachedMetadata = cache.Get("metadataCache") as string;

      

       if (!string.IsNullOrEmpty(cachedMetadata))

       {

          context.Response.Write(cachedMetadata);

          return;

       }

      

       StringBuilder cacheData = new StringBuilder();

      

       var requestUrl = AppSettings.TableStorageBaseUrl + "EntityMetadata";

       WebRequest request = this.CreateTableStorageSignedRequest(context,

       AppSettings.ParseStorageAccount(

       AppSettings.EnabledStorageAccounts[OgdiAlias].storageaccountname,

       AppSettings.EnabledStorageAccounts[OgdiAlias].storageaccountkey),

                requestUrl, false);

      

       try

       {

           var response = request.GetResponse();

           var responseStream = response.GetResponseStream();

          

           var feed = XElement.Load(XmlReader.Create(responseStream));

          

           context.Response.Write(string.Format(START_DATASERVICES_TEMPLATE, this.OgdiAlias));

           cacheData.Append(string.Format(START_DATASERVICES_TEMPLATE, this.OgdiAlias));

          

           var propertiesElements = feed.Elements(_nsAtom + "entry").Elements(_nsAtom + "content")

.Elemens(_nsm + "properties");

          

           foreach (var e in propertiesElements)

           {

              // Changed to use the simple approach of representing "entitykind" as

              // "entityset" value plus the text "Item."  A decision was made to do

              // this at the service level for now so that we wouldn't have to deal

              // with changing the data import code and the existing values in the

              // EntityMetadata table.

              // New notice: Import code was changed, so entityKind = entitySet + "Item"

              // in storage for all new data

              // So return the code back:

         

              var entitySet = e.Element(_nsd + "entityset").Value;

              //var entityKind = entitySet + "Item";

              var entityKind = e.Element(_nsd + "entitykind").Value;

         

              context.Response.Write(string.Format(ENTITYSET_TEMPLATE,

                                               entitySet,

                                               this.OgdiAlias,

                                               entityKind));

              cacheData.Append(string.Format(ENTITYSET_TEMPLATE,

                                         entitySet,

                                         this.OgdiAlias,

                                         entityKind));

          }

 

          context.Response.Write(END_ENTITYCONTAINER_TEMPLATE);

          cacheData.Append(END_ENTITYCONTAINER_TEMPLATE);

         

          foreach (var e in propertiesElements)

           {

              var entityKind = e.Element(_nsd + "entitykind").Value;

             

              context.Response.Write(string.Format(START_ENTITYTYPE_TEMPLATE, entityKind));

              cacheData.Append(string.Format(START_ENTITYTYPE_TEMPLATE, entityKind));

             

              e.Elements(_nsd + "PartitionKey").Remove();

              e.Elements(_nsd + "RowKey").Remove();

              e.Elements(_nsd + "Timestamp").Remove();

              e.Elements(_nsd + "entityset").Remove();

              e.Elements(_nsd + "entitykind").Remove();

              e.Elements(_nsd + "entityid").Remove();

             

              foreach (XElement prop in e.Elements())

              {

                 context.Response.Write(string.Format(START_PROPERTY_TEMPLATE,

                                                  prop.Name.LocalName,

                                                  prop.Value.Replace("System", "Edm")));

                 cacheData.Append(string.Format(START_PROPERTY_TEMPLATE,

                                            prop.Name.LocalName,

                                            prop.Value.Replace("System", "Edm")));

              }

             

              context.Response.Write(END_ENTITYTYPE_TEMPLATE);

              cacheData.Append(END_ENTITYTYPE_TEMPLATE);

           }

          

           context.Response.Write(END_ENTITYTYPESCHEMA_TEMPLATE);

           context.Response.Write(END_DATASERVICES_TEMPLATE);

          

           cacheData.Append(END_ENTITYTYPESCHEMA_TEMPLATE);

           cacheData.Append(END_DATASERVICES_TEMPLATE);

 

           cache.Add("metadataCache", cacheData.ToString());

       }

       catch (WebException ex)

       {

          var response = ex.Response as HttpWebResponse;

          context.Response.StatusCode = (int) response.StatusCode;

          context.Response.End();

       }

    }

}

C’est tout pour le cache côté service de données. Dans la dernière étape, nous activerons le cache pour le rôle Windows Azure DataBrowser.WebRole.

Nous allons à présent implémenter le système de cache côté site Web.

Mise en œuvre dans le service Web DataBrowser.WebRole

Afin de maximiser l’utilisation de cache et de réduire grandement les nombre de requêtes sur le service de données, le cache sera implémenté dans 2 parties différentes.

La première partie concerne la modification du système de cache par défaut d’OGDI DataLab basé sur un cache de session et donc propre à chaque utilisateur.

La seconde partie s’effectue sur la récupération des différents jeux de données via le service de données.

Pour implémenter le cache dans le service Web, toujours dans Visual Studio, localisez le projet DataBrowser.WebRole situé dans le dossier DataBrowser.

image

Il faut à présent ajouter au projet les références du système de cache.

Faîtes un clic droit sur References du projet DataService.WebRole puis Add Reference…

Recherchez et ajoutez les références suivantes :

  • Microsoft.ApplicationServer.Caching.Core,
  • Microsoft.ApplicationServer.Caching.Client,
  • Microsoft.ApplicationServer.Caching.AzureCommon,
  • Microsoft.ApplicationServer.Caching.AzureClientHelper.

A présent que les références sont ajoutées, nous allons implémenter le cache dans la première partie, à savoir modifier le système de cache par défaut.

Pour cela, ouvrez le fichier Cache.cs situé dans le dossier Helpers afin d’ajouter le code nécessaire à l’utilisation du système de cache.

Repérez la méthode EntitySets. Cette méthode permet de récupérer les différentes entités en fonction d’un conteneur.

Dans cette méthode, nous allons implémenter la cinématique suivante :

Si le cache partagé contient une clé composée du nom du conteneur demandé et d’une valeur fixe permettant d’identifier le type de données mises en cache, alors nous retournons ces données. Sinon, nous requêtons le service de données, ajoutons les données au cache en respectant la règle de nommage de clé décrite précédemment puis nous retournons les données.

Soit au final :

static public IEnumerable<EntitySet> EntitySets(String container)

{

   DataCacheFactory factory = new DataCacheFactory();

   DataCache cache = factory.GetDefaultCache();

   IEnumerable<EntitySet> cachedData = cache.Get("EntitySetCache_" + container) as List<EntitySet>;

 

   if (cachedData == null)

   {

      cachedData = GetEntitySets(container);

     

      cache.Put("EntitySetCache_" + container, cachedData.ToList());

   }

 

   return cachedData;

}

Le cache par défaut étant remplacé, il convient maintenant d’intégrer le cache au système de récupération des différents jeux de données.

Pour cela, ouvrez le fichier DatasetListModel.cs situé dans le dossier Models/ViewData.

Le principe est identique à la partie précédente :

Si le cache partagé contient une clé du nom du jeu ou type de données, alors nous retournons ces données. Sinon nous requêtons le service de données, ajoutons les données au cache en respectant la règle de nommage de clé décrite précédemment puis nous retournons les données.

Pour des raisons d’accès très fréquents, le cache partagé ne sera pas déclaré dans les méthodes mais cette fois-ci au niveau de la classe. Soit :

private DataCacheFactory factory = new DataCacheFactory();

private DataCache cache;

Il ne nous reste plus qu’à implémenter le cache dans les méthodes désirées. Pour OGDI DataLab la mise en cache est effectuée sur les méthodes suivantes :

  • GetTopList

public IEnumerable<EntitySetWrapper> GetTopList(Field field)

{

   if (cache == null)

      cache = factory.GetDefaultCache();

 

   IEnumerable<EntitySetWrapper> top = cache.Get("top" + field) as List<EntitySetWrapper>;

   if (top == null)

   {

      top = GetDataSets(0, 5, new OrderByInfo { Field = field, Direction = SortDirection.Desc },

                        null, null);

      cache.Put("top" + field, top.ToList());

   }

 

   return top;

}

 

  • Categories

public IEnumerable<string> Categories

{

   get

   {

      if (cache == null)

         cache = factory.GetDefaultCache();

     

      IEnumerable<string> categories = cache.Get("categories") as List<string>;

 

      if (_categories == null)

      {

         _categories = (from category in GetEntitySets(null)

                        orderby category.CategoryValue

                        select category.CategoryValue).Distinct();

       

         cache.Put("categories", _categories.ToList());

      }

      else _categories = categories;

 

      return _categories;

   }

}

 

  • DataSetInfos

private IEnumerable<AnalyticInfo> DatasetInfos

{

   get

   {

      if (_datasetInfos == null)

      {

         if (cache == null)

            cache = factory.GetDefaultCache();

 

            _datasetInfos = cache.Get("datasetinfo") as List<AnalyticInfo>;

            if (_datasetInfos == null)

            {

               var datasetInfoDataSource = new DatasetInfoDataSource();

               _datasetInfos = datasetInfoDataSource.SelectAll();

               cache.Put("datasetinfo", _datasetInfos.ToList());

            }

      }

 

      return _datasetInfos;

   }

}

 

  • GetEntitySets

private IEnumerable<EntitySet> GetEntitySets(IEnumerable<string> containers)

{

    containers = containers ?? from container in AllContainers select container.Alias;

    foreach (var container in containers)

    {

       IEnumerable<EntitySet> sets;

       _entitySets.TryGetValue(container, out sets);

      

       if (sets == null)

       {

          if (cache == null)

             cache = factory.GetDefaultCache();

         

          sets = cache.Get(container) as List<EntitySet>;

         

          if (sets == null)

          {

             sets = EntitySetRepository.GetEntitySets(container, null);

             cache.Put(container, sets.ToList());

          }

 

          _entitySets.Add(container, sets);

       }

      

       foreach (var set in sets)

          yield return set;

    }

}

 

Maintenant que les différentes parties à mettre en cache sont dûment implémentées, la dernière étape dans le service Web consiste à configurer celui-ci pour qu’il accepte l’utilisation du cache.

Pour cela, ouvrez le fichier Web.config afin de référencer le cache et de lier le service de données au rôle Windows Azure DataBrowser.WebRole.

Pour cela, ajoutez à la fin la déclaration du système de cache pointant sur le rôle Windows Azure.

<dataCacheClients>

   <dataCacheClient name="default">

      <!--<localCache isEnabled="true" sync="TimeoutBased" objectCount="100000" ttlValue="300" />-->

      <autoDiscover isEnabled="true" identifier="DataBrowser.WebRole" />

   </dataCacheClient>

   <tracing sinkType="DiagnosticSink" traceLevel="Error" />

</dataCacheClients>

Ensuite, il vous faut référencer le cache dans la section de configuration pour que celui-ci soit disponible soit :

</sectionGroup>

   <section name="dataCacheClients"

            type="Microsoft.ApplicationServer.Caching.DataCacheClientsSection,

                 Microsoft.ApplicationServer.Caching.Core"

            allowLocation="true"

            allowDefinition="Everywhere" />

</configSections>

C’est tout pour le fichier Web.config !

Le cache est à présent implémenté côté service Web. La dernière étape consiste à activer et configurer le cache dans le rôle Windows Azure DataBrowser.WebRole.

Activation et configuration du cache

Ceci constitue la dernière étape et non des moindre puisque c’est dans celle-ci que sera activé et configuré le système de cache.

Repérez le projet DataBrowser.Cloud situé dans le dossier DataBrowser. Dans ce projet, double-cliquez sur DataBrowser.WebRole afin de configurer le rôle pour héberger le cache.

image

Une fois la nouvelle fenêtre ouverte, vous noterez l’onglet Caching. C’est dans celui-ci que ce fera l’activation et l’ensemble de la configuration.

image

La première chose à faire est de cocher la case Enable Caching.

Ensuite, sélectionnez le type de cache. Dans notre cas, nous utilisons le système de cache partagé soit Co-located Role. Lorsque vous sélectionnez cette option vous devez choisir la taille du cache en pourcentage. Ce pourcentage correspond à la taille de la mémoire qui sera utilisée sur chaque instance. Dans notre cas, nous utiliserons 30% de la mémoire vie de chaque instance soit pour une instance Small (1,75 Go de RAM) environ 525 Mo de RAM.

L’étape suivante consiste à saisir les informations d’un compte de stockage afin d’assurer l’état du cluster de cache lorsque plusieurs instances du DataBrowser.WebRole sont déployées.

Enfin, vous devez spécifier la durée de vie du cache avant renouvellement des données dans la colonne Time To Live (min). La durée est spécifiée en minute et expirera quoiqu’il arrive après ce laps de temps du fait de l’Expiration Type défini à Absolute.

En guise de conclusion

Nous avons vu au cours de ce billet les différents avantages à utiliser le système de cache partagé Windows Azure et comment le mettre en place au sein de la plateforme OGDI DataLab.

Si vous avez suivi toutes les étapes précédentes, vous devez à présent disposer d’un cache entièrement fonctionnel et donc une solution OGDI DataLab prête à gérer de très gros volumes de données de manière fluide et optimisée. Vous trouverez également joint comme à l’accoutumée les fichiers modifiés de façon à les réinjecter directement dans votre solution OGDI DataLab si besoin.

Pour plus d’informations sur Windows Azure Caching, nous vous invitons à visiter l’article Mise en cache dans Windows Azure.

Enfin, si vous désirez en apprendre plus sur les nouveautés Windows Azure, n’hésitez pas à visiter le site dédié Windows Azure et à faire le cas échéant une évaluation gratuite de 90 jours.

image