Utiliser Couchbase comme serveur de cache

Publié par Fabrice Michellonet sous le(s) label(s) , , le 11 avril 2014

Dernièrement, je vous présentais une façon simple d'utiliser le cache dans vos applications .NET
Cependant cette solution possède plusieurs inconvénients. En voici quelque uns :

  • Le cache est lié au processus système de votre application (executable / IIS etc...). Si votre programme plante ou est fermé, vous perdez votre cache
  • Si vos besoins en mémoire augmentent, cette solution n'est pas scalable
  • Rien n'est mis a disposition pour monitorer votre cache
  • Vous n'avez pas d'interface de gestion du cache (augmentation de la mémoire allouée etc...)
  • Vous ne pouvez pas invalider le cache sans arrêter l'application.
  • etc...
Bref, vous avez compris, c'est une solution pour démarrer petit.

Aujourd'hui, je voudrais partager avec vous une solution qui répond à tous les manques évoqués plus haut et plus encore.
Couchbase est une base NoSQL Key/Value; la value ayant comme particularité d'être un document json.
Couchbase est issu du mariage heureux de Membase (cache mémoire avec réplication des caches, persistance et interface d’admin Web sexy) et
CouchDB (NoSQL orienté document). Ce qui nous intéresse plus particulièrement aujourd'hui sont les fonctionnalités qui en font un bon serveur de cache.


Installation


Pour l'installer, tout commence par télécharger le setup ici

Je vous passe le scénario d'installation qui a le bon gout de se faire en "Next" > "Next" > "Next"

Passons à la configuration :

Dans la première étape, il vous faudra spécifier l'emplacement des fichiers (base et index) ainsi que la mémoire dévolue à Couchbase.

configure server

Un peu plus loin nous allons créer le l'espace qui accueillera les objets que l'on va mettre en cache (bucket).

bucket

Quelques instant plus tard vous voila avec un Couchbase flambant neuf sur votre machine.

up and running

Note : si la page ne s'ouvre pas automatiquement dans votre browser, la console d'admin de couchbase se trouve a l'adresse suivante : http://localhost:8091/


Implementation


Couchbase dispose d'un très bel écosystème qui gravite autour, et la plateforme .NET ne fait pas exception.
Je me propose d'adapter le cache manager vu dans le précédent article.
Pour rappel nous avions l'interface suivante :


public interface ICacheManager
{
    T GetOrInsert<T>(string key, Func<T> callback) where T : class;
}

Passons à l'implémentation. Il va nous falloir un driver, que l'on peut aisément installer via nuget :



son utilisation est des plus simple, avec des méthodes explicites comme Get() et Store().
Voici une implémentation de cache respectant notre interface basée sur Couchbase.


public class CouchbaseCacheManager : ICacheManager
    {

        private readonly CouchbaseClient _client;

        public CouchebaseCacheManager()
        {
            _client = new CouchbaseClient();
        }

        public T GetOrInsert<T>(string key, Func<T> callback) where T : class
        {
            T elmnt;
            try
            {
                if (_client.KeyExists(key))
                    return _client.Get<T>(key);
                elmnt = callback();
                _client.Store(StoreMode.Add, key, elmnt);
            }
            catch (Exception)
            {
                elmnt = callback();
            }
            return elmnt;
        }
    }

Et vous, quel type de cache utilisez vous dans vos projets?

Poor man's cache

Publié par Fabrice Michellonet sous le(s) label(s) , , le 4 avril 2014

Depuis la version 4 du framework .NET, la gestion d'un cache est profondement encrée dans l'adn de notre framework.
Il devient alors extrêmement facile de stocker en mémoire à peu près n'importe quoi.
Je vous livre dans ce court billet, une implémentation extrêmement simple que j'utilise souvent dans mes projets.
Commençons par définir une interface, qui nous permettra d'injecter notre cache manager un peu partout :

public interface ICacheManager
{
    T GetOrInsert<T>(string key, Func<T> callback) where T : class;
}

et finalement voici une implémentation extra simple de notre cache manager

public class CacheManager : ICacheManager
{
    public T GetOrInsert<T>(string key, Func<T> callback) where T : class
    {
        ObjectCache cache = MemoryCache.Default;
        if (cache.Contains(key))
            return cache[key] as T;
        T elmnt = callback();
        cache.Add(new CacheItem(key, elmnt), new CacheItemPolicy());
        return elmnt;
    }
}

Voici un exemple d'utilisation.

IEnumerable<Product> products = _cache.GetOrInsert("Ma_Clef_Unique_Cache", 
() => { return _productService.GetFrenchProducts(); } );

Simple non?

IISExpress : Servir des pages vers l’extérieur.

Publié par Fabrice Michellonet sous le(s) label(s) , le 28 mars 2014

Nul doute, IISExpress nous a simplifié la vie au quotidien de par sa facilité d'utilisation et son intégration à Visual Studio.
Aujourd'hui, je devais travailler à rendre un site compatible avec l'ipad. Ni une ni deux, je lance safari et pointe vers l'ip de ma machine de dev.
Et boom une belle erreur, me prévient que IISExpress ne servira de pages qu'en local.

Voici comment je l'ai configuré afin de pouvoir tester sur mon Ipad.

1 - Configurons notre site web afin qu'il soit bindé sur toutes les IP de notre machine de dev.

Pour ce faire ouvrons le fichier de config de IISExpress qui se trouve sur : %userprofile%\My Documents\IISExpress\config\applicationhost.config
Recherchons la config de notre site


<site name="MonSite" id="10">
<application path="/" applicationPool="Clr4IntegratedAppPool">
<virtualDirectory path="/" physicalPath="C:\!Project\MonSite" />
</application>
<bindings>
<binding protocol="https" bindingInformation="*:44300:localhost" />
</bindings>
</site>

remplaçons le binding afin que le site soit servi quelque soit l'ip sur laquelle arrive la requete


<site name="MonSite" id="10">
<application path="/" applicationPool="Clr4IntegratedAppPool">
<virtualDirectory path="/" physicalPath="C:\!Project\MonSite" />
</application>
<bindings>
<binding protocol="https" bindingInformation="*:44300:*" />
</bindings>
</site>

2 - Réservons l'url au niveau du kernel (HTTP.sys)

Cela se fait via la ligne de commande suivante, a executée dans une console en mode admin.


netsh http add urlacl url=https://*:44300/ user=everyone

Note : pour ceux qui travaillent sur un windows en français il faudra alors taper


netsh http add urlacl url=https://*:44300/ user="tout le monde"


3 - Autoriser le trafic au travers du firewall

De nouveau une commande à exécuter via une console en mode admin :


netsh firewall add portopening TCP 44300 IISExpressWeb enable ALL


Voila, j'espère que cette astuce pourra vous éviter de rester coincé si vous devez exposer un site hebergé sur votre machine de dev

EntityFramework.Patterns 0.8 est compatible EF 6

Publié par Fabrice Michellonet sous le(s) label(s) , , , le 17 novembre 2013

Ça faisait longtemps que je n'avais pas remis les mains dans ce projet, plus d'un an pour être exact. Le passage à Entity Framework 5 n'avait pas posé de soucis car aucun breaking changes dans l'API n'avait bloqué l'utilisation de la librairie. C'est surement ce qui explique le nombre "plutôt important" (7500) download sur nuget gallery.

Mais avec EF 6 c'est une autre histoire, tout un tas de nouvelles fonctionnalités très sympa, mais aussi une api qui a pas mal bougé... ainsi plusieurs utilisateurs sur codeplex, m'ont tout naturellement contacté pour me demander de mettre à jour EntityFramework.Patterns.

C'est chose faite, la version 0.8 est désormais disponible via nuget ou sous forme binaire et est compatible avec la dernière version d'EF

Ça signifie que mes chers utilisateurs vont pouvoir profiter des gains de performances apportées par EF 6; Quant au projet sur lequel je travaille actuellement, j'espère bien upgrader EF dès demain... Romain, si tu m'entends :p

Dans un avenir proche, je pense ajouter un bout d'API asynchrone aux repository d'EntityFramework.Patterns.

Bon upgrade de package !!

SSIS vs TPL.Dataflow

Publié par Fabrice Michellonet sous le(s) label(s) , , , le 31 octobre 2013

Il y a quelques jours je tweetais de joie, après être tombé sur la msdn concernant TPL.Dataflow
L'API propose un système de pipeline que l'on peut aisément imaginer utiliser dans un process d'ETL.

Ni une ni deux, je m'imagine délaisser SSII lors de mes prochaines alims de base de données et enfin pouvoir unit tester mon ETL.
L'accueil fait par mes collègues purement BI fut plutôt frileux... n'est ce pas David :)


Au rayon des réponses constructives, Fred Brossard, me fait revenir très vite sur terre, en demandant à voir les perfs du framework.

C'est ce que je me propose de présenter ici avec un exemple simple.
L'idée est d'importer le snapshot de septembre des posts de stackoverflow; d'abord avec notre bon vieux SSIS puis avec TPL.Dataflow.

En entrée on a donc un bon gros fichier XML de 170MO correspondant aux nouveaux posts du mois.

Commençons par SSIS, rien de sorcier, une XML source qui se déverse directement dans une SQL Destination pointant vers ma jolie table.



Ce qui nous donne env. 150K lignes intégrées en env. 20 sec



Ok, passons à la version C#. Ce que je vous livre ici est une version non optimisée et naive d'un algo d'import avec TPL.Dataflow.

Commencons par créer nos taches :

// XML datasource 
            var inputBlock = new BufferBlock<XDocument>();

            // Split xml, one for each <row> element
            var xDocumentToXmlRow = new TransformManyBlock<XDocument, XElement>(
                x => x.Root.DescendantNodes().OfType<XElement>());

            // Convert xml <row> element to a Post object.
            var xmlRowToObjectBlock = new TransformBlock<XElement, Post>(
                x => x.FromXElement<Post>(),
                new ExecutionDataflowBlockOptions {MaxDegreeOfParallelism = DataflowBlockOptions.Unbounded});

            // a buffer of 10000 elements
            var batch = new BatchBlock<Post>(10000);

            // SQL destination, using bulk insert.
            var sqlBulk = new ActionBlock<Post[]>(x =>
                {
                    using (SqlBulkCopy sbc = new SqlBulkCopy("Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=Stackoverflow;Data Source=(local)", SqlBulkCopyOptions.TableLock))
                    {
                        sbc.DestinationTableName = "Posts";

                        // Number of records to be processed in one go
                        sbc.BatchSize = 10000;

                        // commit to the server
                        sbc.WriteToServer(x.ToList().ToDataTable());
                        sbc.Close();
                    }
                },
            new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = DataflowBlockOptions.Unbounded });

Il nous faut maintenant ordonnancer ces taches, comme nous le faisons dans SSIS en reliant les taches entre elles.

using (inputBlock.LinkTo(xDocumentToXmlRow))
            using (xDocumentToXmlRow.LinkTo(xmlRowToObjectBlock))
            using (xmlRowToObjectBlock.LinkTo(batch))
            using (batch.LinkTo(sqlBulk))
            {
                inputBlock.Completion.ContinueWith(t => xDocumentToXmlRow.Complete());
                xDocumentToXmlRow.Completion.ContinueWith(t => xmlRowToObjectBlock.Complete());
                xmlRowToObjectBlock.Completion.ContinueWith(t => batch.Complete());
                batch.Completion.ContinueWith(t => sqlBulk.Complete());

                var inputfile = XDocument.Load(@"C:\!PoC\Stackoverflow\Sourcefiles\Posts.xml");
                inputBlock.Post(inputfile);

                inputBlock.Complete();
                sqlBulk.Completion.Wait();
            }


Aller, sans plus attendre, voici le résultat de l’exécution :



Je suis sur que vous êtes aussi étonnés que moi... TPL est plus rapide (de quelques 15%) par rapport à SSIS dans ce cas simple.
Pour être tout à fait franc, je ne m'y attendais pas du tout. Je pensais qu'il y aurait vraisemblablement une 20 aine de % d'écart, mais en faveur de SSIS.

Bien que cet exemple simplissime ne puisse pas permettre de généraliser, TPL.Dataflow semble apporter des performances suffisantes, si non meilleures qu'SSIS;
mais surtout, cet API va nous permettre d’écrire des tests unitaires sur nos jobs d'intégration. C'est ce que je vous présenterai dans un prochain article.


Et vous, avez vous déjà utilisé TPL.Dataflow? Pourriez-vous l'utiliser dans vos projets? A la lumière de ce post, que pensez vous de ces performances?