Follow me
RSS feed
My sources
My Viadeo

{key, value}

Greg | 19 Feb 2009

database Il existe bon nombre de systèmes de cache de données. Le plus connu d'entre aux est certainement memcached. Mais ce n'est pas le seul... Tokyo Cabinet, qui se veut plus le successeur de GDBM et QDBM, offre des possibilités similaires. mixr est aussi une solution.

Il faut faire attention à ce que l'on entend par cache dans le cas présent. En effet, si memcached porte bien son nom, à savoir qu'il ne stocke les données qu'en mémoire, ce n'est pas toujours le cas. Tokyo Cabinet est plus une base de données. Et c'est tant mieux, car c'est bien ce qu'il revendique être. Dans son cas, les données sont stockées dans des fichiers, mais la solution tout en mémoire est possible. mixr est à cheval entre ces deux solutions. En effet, les données sont stockées sur disque grâce à mnesia, mais de façon temporaire. Les données ne sont pas conservées d'un reboot à l'autre.

Examinons chacune de ces solutions à l'oeuvre.

Dans tous les cas, nous ferons une installation à partir des sources. Tous les exemples se feront en Ruby et nous considérerons donc qu'il est déjà installé.

1. Memcached

Installation

Pour installer memcached nous avons besoin de la librairie libevent. L'installation se fait classiquement avec un configure, make, make install. Pour des raisons pratiques, tout sera installé dans le répertoire /opt/local

Une fois libevent installé, vous pouvez récupérer les sources de memcached. La encore, un simple configure, make, make install suffit. N'oubliez pas d'utiliser l'option --with-libevent=/opt/local avec le configure afin qu'il sache ou trouver la libevent.

Pour la partie client, nous utiliserons memcache-client qui existe sous forme de gem.

sudo gem install memcache-client

Ce gem nécessite l'installation de RubyInline

sudo gem install RubyInline
Lancement du serveur

La première chose à faire est de lancer le serveur memcached. Pour cela nous exécutons la commande memcached sans paramètre :

/opt/local/bin/memcached

Par défaut le serveur est accessible sur le port 11211. Bien entendu vous pouvez modifier cela. Je vous laisser regarder la sortie de memcached -h pour voir les options disponibles.

Utilisation

Nous pouvons créer un premier client. L'utilisation de memcache-client est on ne peut plus simple. Il suffit d'instancier un objet MemCache en lui passant en paramètre l'adresse du serveur. Ensuite, l'ajout d'un couple [cle, valeur] se fait via la méthode set ou la méthode []=. Pour récupérer la valeur pour une clé donnée, nous utilisons get ou []

 1 require "rubygems"
 2 require "memcache"
 3 
 4 memcache = MemCache.new( 'localhost:11211' )
 5 
 6 memcache['cle'] = 'valeur'
 7 memcache['key'] = 'valeur'
 8   
 9 puts "cle = #{memcache['cle']}" 
10 # => cle = valeur
11 puts "key = #{memcache['key']}"
12 # => key = value

Pour vous convaincre que les données sont bien en cache, commentez les lignes 6 et 7 de l'exemple précédent et relancez le script. Vous devez retrouver les deux valeurs. Si par contre, entre temps, vous stoppez le serveur memcache, vous ne retrouverez rien.

Pour supprimer une valeur du cache, il suffit d'utiliser la méthode delete

memcache.delete( "cle" )
memcache.delete( "key" )

Je ne vais pas entrer dans le détail, mais il y a plusieurs choses à savoir pour profiter pleinement de memcache. Tous d'abord nous pouvons utiliser les espaces de nommages pour stocker des valeurs. En effet, pour une clé donnée, memcache ne peut stocker qu'une seule valeur. Or si plusieurs applications se partagent une même serveur memcache, il peut y avoir des risques d'écrasement de données d'une application sur l'autre. Pour éviter cela, lors de l'instanciation, vous pouvez spécifier un espace de nommage via l'option :namespace.

Voici un exemple mettant cela en exergue :

require "rubygems"
require "memcache"

memcache1 = MemCache.new( 'localhost:11211', :namespace => "ns1" )
memcache2 = MemCache.new( 'localhost:11211', :namespace => "ns2" )

memcache1['cle'] = 'valeur1'
memcache2['cle'] = 'valeur2'
  
puts "ns1 : cle = #{memcache1['cle']}" 
# => cle = valeur1
puts "ns2 : cle = #{memcache2['cle']}" 
# => cle = valeur2

Sachez également que vous pouvez utiliser plusieurs serveurs memcache. Pour cela il suffit, lors de l'instanciation, de passer plusieurs adresses :

memcache = MemCache( ["server_one:11211", "server_two:11211"], :namespace => "ns" )

Enfin, une dernière chose. Vous pouvez préciser, dès l'ajout d'une valeur dans le cache, quand est-ce qu'elle expirera (sera supprimée). En effet, la méthode set prend en troisième paramètre la durée avant expiration de la donnée, en seconde. Il existe un quatrième paramètre à set, de type booléen, qui indique si oui (true) ou non (false -- défaut) la valeur doit être sérialisée.

2. Tokyo Cabinet|Tyrant

Si vous regardez la page de Tokyo Cabinet vous remarquerez qu'il s'agit avant tout d'une librairie de gestion de fichiers de donnée, chaque fichier contenant des enregistrements de type [cle, valeur]. Au premier abord il n'est pas question de serveur. C'est sans compter sur Tokyo Tyrant, l'interface réseau de Tokyo Cabinet. C'est avec cette dernière que nous allons travailler.

Installation

Commençons par installer Tokyo Cabinet. Pour cela, téléchargez les sources depuis la page du projet sur sourceforge. Comme d'habitude, un simple configure, make, make install permet de faire l'installation.

Récupérez ensuite les sources de Tokyo Tyrant et rejouez au jeu du configure, make, make install. Si comme moi, vous installez tout cela dans /opt/local vous devez spécifier, lors du configure, le chemin vers Tokyo Cabinet avec l'option --with-tc=/opt/local

Lancement du serveur

L'exécutable du serveur est ttserver. Vous trouverez ici l'ensemble des options disponibles. Pour cette présentation nous retiendrons une seule chose. Si le serveur est lancé sans aucun paramètre, mais surtout sans aucune information sur le nom du fichier de base, alors les données seront stockées en mémoire dans une base de type hash. C'est ce comportement qui est le plus proche de ce que propose memcached et mixr. Vous pouvez cependant vouloir utiliser une base, en mémoire, de type tree. Dans ce cas, nommez simplement votre base +. L'idée première de Tokyo Cabinet étant de permettre une gestion de fichier de données. Vous pouvez bien entendu faire en sorte d'utiliser ce moyen de persistance. Dans ce cas, nommez votre base et utilisez l'extension .tch si vous voulez une base de type hash ou .tcb pour une base de type tree.

Vous pouvez également utiliser les extensions .tcf et .tct mais nous n'en parlerons pas ici.

Démarrons donc le serveur...

$ /opt/local/bin/ttserver
2009-02-18T17:42:32+01:00	--------- logging started [30215] --------
2009-02-18T17:42:32+01:00	server configuration: host=(any) port=1978
2009-02-18T17:42:32+01:00	database configuration: name=*
2009-02-18T17:42:32+01:00	service started: 30215
2009-02-18T17:42:32+01:00	timer thread 1 started
2009-02-18T17:42:32+01:00	worker thread 1 started
2009-02-18T17:42:32+01:00	worker thread 2 started
2009-02-18T17:42:32+01:00	worker thread 3 started
2009-02-18T17:42:32+01:00	worker thread 4 started
2009-02-18T17:42:32+01:00	worker thread 5 started
2009-02-18T17:42:32+01:00	worker thread 6 started
2009-02-18T17:42:32+01:00	worker thread 7 started
2009-02-18T17:42:32+01:00	worker thread 8 started
2009-02-18T17:42:32+01:00	listening started

Le serveur utilise (par défaut) le port 1978. Référez-vous à la documentation si vous souhaitez personnaliser le démarrage.

Utilisation

La solution la plus simple pour stocker des données est d'utiliser l'API REST de Tokyo Tyrant. Pour cela nous utiliserons le gem rest-client :

 1 require 'rubygems'
 2 require "rest_client"
 3 
 4 tokyotyrant = RestClient::Resource.new("http://localhost:1978")
 5 
 6 tokyotyrant["cle"].put "valeur"
 7 tokyotyrant["key"].put "value"
 8 
 9 puts tokyotyrant["cle"].get
10 # => valeur
11 puts tokyotyrant["key"].get
12 # => value

Comme pour l'exemple que nous avons fait avec memcached, vous pouvez ici aussi commenter les lignes 6 et 7 avant de relancer l'exemple pour vérifier que vous retrouvez bien vos valeurs.

La méthode delete permet de supprimer une valeur :

tokyotyrant["cle"].delete
tokyotyrant["key"].delete

Nous avons vu que memcached utilise les espaces de nommage pour éviter l'écrasement de valeur dans le cas ou l'on utilise plusieurs fois la même clé. Avec Tokyo Tyrant, si vous utilisez REST, il faudra gérer cela vous même. Mais, bonne nouvelle, Tyrant supporte le protocole memcached. Vous pouvez donc gérer cela facilement et utilisant memcache-client :

require "rubygems"
require "memcache"

memcache1 = MemCache.new( 'localhost:1978', :namespace => "ns1" )
memcache2 = MemCache.new( 'localhost:1978', :namespace => "ns2" )

memcache1['cle'] = "valeur1"
memcache2['cle'] = "valeur2"

puts "ns1: cle = #{memcache1['cle']}" 
# => ns1: cle = valeur1
puts "ns2: cle = #{memcache2['cle']}"
# => ns2: cle = valaur2

Comme vous pouvez le voir, la différence entre Tokyo Tyrant et memcached, dans notre contexte d'utilisation, est quasiment nulle. Cependant, il ne faut pas oublier que le premier permet également de gérer la persistance dans des fichiers, ce qui peut se révéler très pratique et avantageux dans certains cas.

3. mixr

Dans cette présentation, nous utiliserons la version 0.1.0 de mixr. Cependant, comme je l'ai dit, ce projet est en développement actif (lent, mais actif) et devrait évoluer dans les semaines à venir. Quoi qu'il en soit, les exemples que nous allons montrer ici resteront valables.

Installation

Dans sa version actuelle, mixr est packagé dans un gem. L'installation se fait donc simplement :

sudo gem install mixr

Ce gem contient le serveur et le client Ruby. Il faut cependant avoir installé Erlang. En effet, le serveur est écrit dans ce langage. Nous faisons l'installation d'Erlang en récupérant les sources et en exécutant le classique configure, make, make install.

Lancement du serveur

Pour démarrer le serveur, il suffit d'exécuter la commande : mixr start. Vous seriez tenté de penser que faire un mixr stop suffirait à stopper le serveur. Malheureusement ce n'est pas le cas... Défaut de jeunesse ;)

Le port utilisé par le serveur est le 9900. Il n'existe pas d'option au lancement du serveur. Mais vous pouvez modifier le port à utiliser en créant une fichier mixr.conf dans votre HOME. Par exemple :

{port, 9909}.
Utilisation

Comme je l'ai signalé, le gem mixr contient la librairie cliente. Nous n'avons donc rien de plus à installer.

La classe MixrClient met à disposition des méthodes très proches de ce que nous avons vu avec memcached :

 1 require 'rubygems'
 2 require 'mixr_client'
 3 
 4 m = MixrClient.new( "localhost", 9900 )
 5 
 6 m["cle"] = "valeur"
 7 m["key"] = "value"
 8 
 9 puts m["cle"]
10 # => valeur
11 puts m["key"]
12 # => value

Comme pour les exemples avec memcached et Tokyo Tyrant, vous pouvez commenter les lignes 6 et 7 avant de relancer l’exemple pour vérifier que vous retrouvez bien vos valeurs.

Vous pouvez également supprimer une valeur grâce à la méthode delete. Il y a cependant une petite différence avec mixr. En effet, la méthode delete supprime le couple [cle, valeur] et renvoie la valeur, ou nil si la clé n'existe pas. Mais delete peut également prendre un bloc en paramètre. Dans ce cas, si la clé n'existe pas, delete renvoie la valeur du bloc.

require 'rubygems'
require 'mixr_client'

m = MixrClient.new( "localhost", 9900 )
m["cle"] = "valeur"

puts m["cle"]
# => valeur

puts m.delete("cle") { |k|
  "#{k} not found!"
}
# => valeur

puts m.delete("cle") { |k|
  "#{k} not found!"
}
# => cle not found!

Nous avons utilisé la méthode [] pour récupérer une valeur. Sachez qu'il existe également la méthode fetch qui, comme pour la méthode delete peut prendre en paramètre, en plus ce la clé, une valeur et un bloc. Ainsi si la clé n'existe pas, fetch renvoie la valeur passée en second paramètre ou le résultat du bloc.

require 'rubygems'
require 'mixr_client'

m = MixrClient.new( "localhost", 9900 )
m["cle"] = "valeur"

puts m.fetch( "cle" )
# => valeur

m.delete( "cle" )

puts m.fetch( "cle", "pas de valeur pour la cle" )
# => pas de valeur pour la cle

puts m.fetch( "cle" ) { |k| 
  "#{k} not found!"
}
# => cle not found

mixr offre ainsi différentes méthodes bien pratiques. Je vous laisse regarder la documentation.

Pour terminer, sachez que mixr n'offre rien pour éviter l'écrasement de donnée. Dans la version actuelle, il n'y a aucune gestion des espaces de nommages.

4. So, what ?

La question maintenant c'est de savoir pourquoi l'un plutôt que l'autre ?

Ne comptez pas sur moi pour vous répondre. Tout d'abord parce que je ne peux pas être objectif (et pour cause). Mais surtout parce que comme vous avez pu le voir, ces trois projets offrent des services qui, même s'ils peuvent sembler comparables, présentent des caractéristiques bien différentes (utilisation mémoire, stockage disque, ...). Enfin, cela reviendrait à entrer dans un débat trollesque qui ne ferait pas avancer les choses.

La seule chose qui est certaine est que ces systèmes apportent une réelle solution à des problèmes de partage (et stockage) de données qui font qu'en devenant naturel dans le cadre de certains développements, nous nous demandons comment on pouvait vivre sans ;)

Copyright © 2009 - 2011 Grégoire Lejeune.
All documents licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 2.5 License, except ones with specified licence.
Powered by Jekyll.