Une courte introduction à Redis

Quand il s’agit de faire persister ses données, la seule option qui semble disponible est d’utiliser un SGBDR (une base de données relationnelle, telle que MySQL ou Oracle). Mais on se rend parfois compte qu’un seul outil ne peut résoudre tous les problèmes. Ainsi, si on ne dispose que de marteaux, on aura tendance à voir des clous partout.

Le mouvement NoSQL (comme Not Only SQL), nous propose des alternatives : bases clefs-valeurs, bases orientées document, bases orientées colonnes, bases orientées graphe ; Cassandra, MongoDB, Redis, Dynamo, Riak, Big Table, Voldemort sont souvent utilisés par les sites à gros trafic et à tendance “sociale” qui font le buzz : Google, Amazon, Facebook, Twitter, LinkedIn.

Aujourd’hui, nous allons explorer redis, ses principes, son API et ce qu’on peut en faire. Dans un prochain article, nous verrons une utilisation typique en écrivant un clone de twitter simple et sans prétention.

Redis est une base de données open source de type clefs-valeurs mono-threadée. C’est en gros une grosse HashMap, mais avec des données structurées que nous allons détailler : des chaînes de caractères, des listes, des hash, des set, des set triés. L’utilisation de redis est très simple et la vitesse de lecture et d’écriture est vertigineuse (plus de 100 000 insertions par seconde sur mon petit poste de travail). Toutes les opérations sont atomiques, vous ne risquez pas d’avoir des soucis de concurrence d’accès à vos données. Par contre, il est impossible de requêter les valeurs comme on le fait habituellement avec un WHERE en MySQL, mais avec un peu d’astuce, d’habitude et de dénormalisation de vos données, on arrive très vite à nos fins en demandant “la bonne clef”. Il faut également savoir que vous êtes limités par la taille de votre RAM car redis garde toutes ses données en mémoire (c’est aussi pour cela que c’est très rapide). Ne vous effrayez cependant pas, redis sauvegarde régulièrement (avec la possibilité d’un paramétrage poussé) ses données sur le disque et supporte également la réplication sur de multiples serveurs.

Si vous avez besoin de sauvegarder de grands volumes de données, vous pouvez stocker les données les moins lues dans un MySQL et garder les données volatiles et fréquemment lues dans redis, c’est même une configuration souvent rencontrée actuellement

Enfin, l’installation de redis est très simple : sur Ubuntu, le paquet s’appelle redis-server. Le serveur démarre immédiatement l’installation et un client en ligne de commande est  disponible : redis-cli. C’est avec cette interface que nous allons faire nos tests et découvrir redis. Vous pouvez également utiliser *redis-bench *pour constater la vitesse de redis sur votre machine. Parcourons dès à présent les différents types de données de stockage.

String

C’est le type le plus simple : à une clef, on peut y associer une valeur. Cette valeur peut-être une string de tout type (json, valeur d’une image jpeg), mais aussi un entier. La limite est de 1 Go. Les principales commandes associées sont SET, GET, INCR, DECR, et GETSET.

redis 127.0.0.1:6379> SET compteur 10
OK
redis 127.0.0.1:6379> INCR compteur
(integer) 11
redis 127.0.0.1:6379> INCR compteur
(integer) 12
redis 127.0.0.1:6379> GET compteur
"12"
redis 127.0.0.1:6379> GETSET compteur 123
"12"
redis 127.0.0.1:6379> GET compteur
"123"

redis 127.0.0.1:6379> SET clef valeur
OK
redis 127.0.0.1:6379> GET clef
"valeur"

Listes

Les listes de redis sont des listes liées (linked list). Cela veut dire que même si vous avez des millions d’enregistrement dans une liste, cela sera toujours aussi rapide d’insérer un élément en tête ou en queue de liste. La contrepartie de cette vitesse est l’accès à un élément dans la liste par son index. Les utilisateurs de ArrayList ou LinkedList en java connaissent bien cette différence. Les principales commandes commencent en général par L comme List et sont RPUSH et LPUSH (et leurs amies xPOP) qui permettent respectivement d’ajouter un élément en fin ou en début de liste, LRANGE pour obtenir une partie des éléments de la liste, LINDEX pour obtenir un seul élément de la liste, LLEN pour obtenir la taille de la liste.

Comme vous pouvez le remarquer, le résultat sort dans l’ordre dans lequel les données ont été rentrées, par besoin de ORDER BY de MySQL.

Hash

Une hash permet de stocker dans un même enregistrement plusieurs couples de clef/valeurs. Les commande de hash commencent… par un H comme Hash. On y trouve (vous avez déjà deviné je parie) HSET, HGET, HLEN, mais aussi HGETALL pour obtenir tous les couples clef-valeur, HINCRBY pour incrémenter un compteur dans la hash, HKEYS et HVALS pour obtenir toutes les clefs ou valeurs et HDEL pour faire le ménage.

redis 127.0.0.1:6379> HSET nosql redis "clef/valeur"
(integer) 1
redis 127.0.0.1:6379> HSET nosql mongodb document
(integer) 1
redis 127.0.0.1:6379> HSET nosql riak "fault tolerant"
(integer) 1
redis 127.0.0.1:6379> HGET nosql redis
"clef/valeur"
redis 127.0.0.1:6379> HGETALL nosql
1) "redis"
2) "clef/valeur"
3) "mongodb"
4) "document"
5) "riak"
6) "fault tolerant"
redis 127.0.0.1:6379> HKEYS nosql
1) "redis"
2) "mongodb"
3) "riak"
redis 127.0.0.1:6379> HVALS nosql
1) "clef/valeur"
2) "document"
3) "fault tolerant"
redis 127.0.0.1:6379> HLEN nosql
(integer) 3

Sets

Les Sets sont des collections d’objets non ordonnées. Les commandes commencent toutes avec un S comme Set, parmi celles-ci on trouve SADD pour ajouter une valeur à un set, SCARD pour obtenir la taille (cardinalité) d’un set, et surtout les commandes SINTER, SUNION, SDIFF qui permettent respectivement d’obtenir l’intersection, l’union et la différences entre 2 sets. Ces commandes existent en version “STORE” ; ainsi SINTERSTORE permet de stocker dans un nouveau set l’intersection de 2 autres. Dans l’exemple suivant, nous allons modéliser ce qui se trouve au supermarché du coin et ce que j’ai dans mon frigo (oui, certains éléments peuvent surprendre) :

redis 127.0.0.1:6379> SADD supermarche pommes
(integer) 1
redis 127.0.0.1:6379> SADD supermarche poires
(integer) 1
redis 127.0.0.1:6379> SADD supermarche scoubidous
(integer) 1
redis 127.0.0.1:6379> SADD monFrigo beurre
(integer) 1
redis 127.0.0.1:6379> SADD monFrigo pommes
(integer) 1
redis 127.0.0.1:6379> SADD monFrigo TddByExample
(integer) 1
redis 127.0.0.1:6379> SCARD monFrigo
(integer) 3
redis 127.0.0.1:6379> SINTER supermarche monFrigo
1) "pommes"
redis 127.0.0.1:6379> SUNION supermarche monFrigo
1) "scoubidous"
2) "pommes"
3) "poires"
4) "TddByExample"
5) "beurre"
redis 127.0.0.1:6379> SDIFF supermarche monFrigo
1) "scoubidous"
2) "poires"

Sets triés

Les sets triés (Sorted Set) sont similaires à des Sets mais ajoutent une notion de score aux valeurs ajoutées aux Set, ce qui permet de faire des tris. Les commandes commencent toutes par Zcomme Zorted Set. On trouve donc les Zéquivalents des commandes précédentes, à savoir ZADD, ZCARD, ZINTER, ZUNION, ZDIFF mais aussi ZRANGE, ZRANGEBYSCORE et ZRANK qui tirent parti des scores des données stockées.

redis 127.0.0.1:6379> ZADD savants 1564 "Galilee"
(integer) 1
redis 127.0.0.1:6379> ZADD savants 1643 "Isaac Newton"
(integer) 1
redis 127.0.0.1:6379> ZADD savants 1571 "Johannes Kepler"
(integer) 1
redis 127.0.0.1:6379> ZADD savants 1879 "Albert Einstein"
(integer) 1
redis 127.0.0.1:6379> ZADD savants 1858 "Max Planck"
(integer) 1
redis 127.0.0.1:6379> ZADD savants 1887 "Erwin Schrodinger"
(integer) 1
redis 127.0.0.1:6379> ZRANGE savants 0 -1
1) "Galilee"
2) "Johannes Kepler"
3) "Isaac Newton"
4) "Max Planck"
5) "Albert Einstein"
6) "Erwin Schrodinger"
redis 127.0.0.1:6379> ZREVRANGE savants 0 -1
1) "Erwin Schrodinger"
2) "Albert Einstein"
3) "Max Planck"
4) "Isaac Newton"
5) "Johannes Kepler"
6) "Galilee"
redis 127.0.0.1:6379> ZREVRANGE savants 0 -1 WITHSCORES
 1) "Erwin Schrodinger"
 2) "1887"
 3) "Albert Einstein"
 4) "1879"
 5) "Max Planck"
 6) "1858"
 7) "Isaac Newton"
8 ) "1643"
 9) "Johannes Kepler"
10) "1571"
11) "Galilee"
12) "1564"

Ce qui est intéressant, c’est de pouvoir faire des requêtes sur les scores du set :

redis 127.0.0.1:6379> ZRANGEBYSCORE savants -inf 1800
1) "Galilee"
2) "Johannes Kepler"
3) "Isaac Newton"
redis 127.0.0.1:6379> ZRANGEBYSCORE savants 1800 2010
1) "Max Planck"
2) "Albert Einstein"
3) "Erwin Schrodinger"

On peut imaginer des tas d’applications de calcul de poids de tags dans un blog par exemple (avec in ZINCRBY du nombre d’occurrences du tag dans un corpus) ou à des fins de statistiques. Toutes ces commandes de lecture/écriture sont très rapides.

Bref !

A cette courte présentation, il faudrait ajouter les possibilité suivantes que nous n’explorerons pas dans le cadre de cet article :

  • une API Pub/Sub qui permet de poster et de recevoir des messages sur des “channels” ; cela peut paraître hors sujet mais cela permet d’avoir un petit système de messaging pour pas cher (pas besoin d’installer un serveur RabbitMQ en plus de votre base Redis). Pourquoi pas ;
  • donner une durée de vie à une clef (avec les commandes EXPIRE clef secondes ou EXPIREAT clef timestamp) : permet de laisser redis purger ses données sans avoir à écrire des scripts de purge ou de l’utiliser comme cache distribué ;
  • passer une série de commandes dans une transaction (MULTI et EXEC) avec la possibilité de l’éxécuter seulement si la clef change (avec WATCH à la place de EXEC);
  • utiliser plusieurs bases de données (16 disponibles) avec SELECT n ;
  • brancher un ou plusieurs slaves (tous sur le master ou les uns à la suite des autres, en cascade) : la réplication est extrêmement rapide ;
  • scripter des commandes dans le serveur avec le langage Lua, ce qui nous permet d’apprendre un nouveau langage par la même occasion !

Si vous avez de très nombreux accès en lecture par rapport à vos écritures, ce qui est le cas pour une vaste majorité de sites tel que twitter, vous pouvez multiplier les slaves pour accéder à vos données en lecture de manière très rapide et ne garder qu’un seul master pour toutes vos écritures. Ainsi, vos temps de réponses seront très faibles et vos utilisateurs seront époustouflés par la vitesse de votre site.

Enfin, comme redis est open source, on constate rapidement que :

  • c’est bien écrit
  • le code est lisible
  • c’est court (moins de 20 000 lignes de C)
  • il y a des tests unitaires

Tout ceci rassure, donne confiance dans un produit qui est encore amené à évoluer (scripting, clustering, réplication, performances…). La blogosphère parle souvent de redis, des tutoriaux sont publiés régulièrement et de nombreux clients sont supportés.

Clients

Il existe de nombreux clients redis dans de nombreux langages, vous trouverez forcément votre bonheur :


Quelques liens :

Réactions

  • Bruno Thomas le 2011-11-22 : Juste pour préciser que ZRANGE savants 0 -1 signifie “du premier élément (0) au dernier (-1)”. -2 est l’avant dernier, etc.
  • Jean-Philippe Caruana le 2011-11-23 :

    Également, pour répondre à une question “off” :

    comment faire pour lister toutes les clefs présentes dans la base ?

    Avec la commande KEYS *. À ne pas faire en production !

    À noter qu’on peut faire une sélection plus étroite, par exemple, si on désire remonter tous les tags :

    > KEYS tag:*
    1) "tag:FP"
    2) "tag:computing"
    3) "tag:distributedcomputing"
    4) "tag:erlang"
    5) "tag:python"
    6) "tag:programming"
    7) "tag:haskell"
    

    Toutes les regex “habituelles” sont possibles : un seul caractère (?), plusieurs (*), choix entre plusieurs ([ab]).

  • Jean-Baptiste Potonnier le 2011-11-24 : Je pense qu’il s’agit plus de “glob pattern” à la Shell que de regex
  • Amaury Prévôt-Leygonie le 2014-01-14 : On peut faire joujou en ligne avec une base NoSQL pour pas un Kopek! Ni une ni deux, je cherche un tutoriel sur le sujet, et tombe sur un truc vraiement simple et accessible.Convaincu par l’ensemble, je ne peux pas m’empêcher de partager. Enjoy: barreverte.fr/une-courte-introduction-a-redis