Valentin Traën
Projet NFE204 - 2018
Table des matières
PRÉSENTATION DU PROJET.....................................................................................................4
Riak KV.......................................................................................................................................................................4
Jeu de données.......................................................................................................................................................4
DÉCOUVERTE DE RIAK KV........................................................................................................5
Architecture de Riak KV.........................................................................................................................................5
Le modèle clé-valeur...................................................................................................................................................................................... 5
Organisation des données........................................................................................................................................................................... 5
Les Backends.................................................................................................................................................................................................... 6
Une API REST.................................................................................................................................................................................................... 7
Le moteur de recherche Riak Search....................................................................................................................................................... 7
Le modèle Dynamo d’Amazon................................................................................................................................................................... 7
Les différents utilitaires d’administration........................................................................................................7
L’utilitaire riak.................................................................................................................................................................................................... 7
L’utilitaire riak-admin...................................................................................................................................................................................... 7
L’interface web Riak Explorer..................................................................................................................................................................... 8
INSERTION ET INTERROGATION DES DONNÉES.................................................................9
Mise en place de Bucket Types et insertion....................................................................................................9
Le Bucket Type Default................................................................................................................................................................................. 9
Les Buckets avec datatype........................................................................................................................................................................ 11
Interrogation des données................................................................................................................................18
Mise en place de Riak Search................................................................................................................................................................... 19
L’ Active Anti-Entropy.................................................................................................................................................................................. 24
Les Secondary Indexes............................................................................................................................................................................... 29
RÉPLICATION ET PARTITIONNEMENT................................................................................31
Dynamo...................................................................................................................................................................31
Rappel du principe de hashage cohérent........................................................................................................................................... 31
Répartition des données............................................................................................................................................................................ 32
La réplication.................................................................................................................................................................................................. 33
Maintien de la cohérence des données............................................................................................................................................... 34
Mise en place d’un cluster Riak KV..................................................................................................................36
Construction du cluster et commandes de base.............................................................................................................................. 36
Paramétrage du cluster.............................................................................................................................................................................. 45
Expérimentations sur notre cluster.................................................................................................................46
La réplication.................................................................................................................................................................................................. 48
Les Vector Clocks......................................................................................................................................................................................... 52
Cohérence des données et DataTypes................................................................................................................................................ 56
Index des illustrations................................................................................................................................................................................. 62
Sources............................................................................................................................................................................................................. 63
Pages Internet................................................................................................................................................................................................ 63
3
PRÉSENTATION DU PROJET
Dans ce rapport, je vous ferai partager ma découverte du SGBD NoSQL Riak KV, en me focalisant sur
les données, la façon dont on peut les stocker, les interroger et les mécanismes nous assurant leur
cohérence. En début de rapport, nous nous intéresserons aux concepts autour desquels Riak KV a été
construit ainsi que sur les services qu’il propose ; la dernière partie sera quant à elle dédiée à l’architecture
distribuée de Riak KV. L’intégralité des expérimentations de ce rapports ont été réalisées sur une machine
hôte Linux et chaque instance de Riak KV a été mise en place grâce à des containers Dockers (containers
fournis par Basho ou personnalisés par mes soins en fonction du besoin).
Riak KV
Riak KV (pour Riak Key-Value) est un système de
gestion de bases de données NoSQL, distribué et développé
en versions opensource et entreprise par la société Basho
Technologies. Il s’agit d’un système distribué scalable,
résistant aux pannes basé sur la technologie Dynamo
d’Amazon. Il fonctionne sur le principe de données clé-
valeur : une donnée insérée peut à la fois être une valeur
atomique ou une valeur plus complexe - comme un document json ou xml - et est toujours associée à une
clé unique permettant de retrouver l’information. Initié en août 2009, la dernière version disponible à ce jour
a été publiée en avril 2017; le langage Erlang a été utilisé pour mettre en place ce système. Précisons que
Basho, en plus de Riak KV, développe un autre système de gestion de bases de données NoSQL, Riak TS
(pour Riak Time Series).
Jeu de données
Le jeu de données utilisé lors de ce projet est une collection de documents json regroupant plus de
200 000 questions et réponses issues du jeu télévisé américain Jeopardy !. La structure de ces documents
est visible dans l’extrait de données présenté ci-dessous.
4
Version de Riak KV utilisée
Dans ce rapport, l’ensemble des opérations
seront réalisées sur la dernière version de
Riak KV à ce jour : v. 2.2.3
{
"air_date": "2003-06-06",
"answer": "Jolly Rancher",
"category": "CANDY",
"question": "Bill Harmsen, who raised horses in Colo., happily founded this candy co. in 1949 to make money during the winter",
"round": "Final Jeopardy!",
"show_number": 4335,
"value": null
},
{
"air_date": "2004-05-10",
"answer": "George III",
"category": "HISTORIC NICKNAMES",
"question": "Because of his Hanoverian heritage, American colonists called this monarch "German Georgie" or "Geordie"",
"round": "Jeopardy!",
"show_number": 4541,
"value": "$200"
}
DÉCOUVERTE DE RIAK KV
Architecture de Riak KV
Le modèle clé-valeur
Riak KV comme son nom l’indique est basé sur le modèle clé-valeur. Ce modèle permet de stocker
une information (ici des documents structurés) et d’y associer une clé afn de traiter cette donnée. Un
avantage certain de ce type de stockage par rapport, par exemple, à celui d’un modèle relationnel est qu’il
limite la recherche et la récupération des documents à l’interrogation d’une clé, ce qui est très rapide. Cette
rapidité ne se fait cependant pas sans certains sacrifces. En effet, il est impossible avec ce système de
récupérer l’ensemble des documents comme on pourrait le faire en SQL (SELECT * FROM table) ; il est
nécessaire de connaître la clé de l’objet recherché afn de le visualiser. Toute la diffculté réside alors dans le
bon choix du nommage de la clé ; un nommage discriminant et sémantiquement précis facilitera la
recherche d’éléments au sein d’une collection de documents.
Organisation des données
• Les Buckets
Dans Riak KV, les données sont insérées dans des Buckets (seau en français). Les Buckets
permettent de séparer logiquement les données physiques insérées dans le système ; les Buckets ne sont
donc pas visibles en dehors de Riak KV sur l’OS de la machine hôte, ce ne sont pas des dossiers. Leur utilité
est double :
- organiser les données en groupes cohérents. Ainsi, dans le cas du jeu de données utilisé dans ce projet,
toutes les données ont logiquement leur place dans un Bucket « jeopardy ». SI je décide un jour de traiter
les données d’un autre jeu télévisé comme Questions pour un champion, il sera nécessaire pour respecter la
cohérence de mon système de gestion de données de les insérer dans un Bucket
« questions_pour_un_champion ». Il s’agit plus ou moins d’un système de table si l’on veut faire une
corrélation avec les SGBD relationnels, mis à part l’absence de structure de données relative au Buckets et
leur seule existence logique. Lors de la recherche des éléments présents en base, les Buckets apportent un
niveau de précision supplémentaire à la recherche d’information par clé ; celle-ci se fera en effet d’abord par
Bucket (on recherche par exemple des éléments présents dans un Bucket «meteo_limoges »), puis par
identifant de la donnée (on aura attribué un timestamp en tant qu’id des chaque données météorologiques
relatives à la ville de Limoges insérées).
- les Buckets permettent aussi une gestion des confguration relatives à la réplication des données qui y sont
présentes. Des variables de paramétrage sont attribuées à chacun des Buckets, apportant la possibilité à
l’administrateur de prioriser la réplication de données sensibles, d’en assurer une cohérence plus forte pour
certains Buckets par rapport à d’autres. Un type de données peut être aussi appliqué afn de structurer le
Bucket et de n’y accepter que des données dont le format sera exploitable pour réaliser des traitements
spécifques. (je reste vague pour le moment mais ce point sera expliqué et développé dans la suite de ce
rapport).
5
• Les Bucket Types
Un Bucket Type est une confguration applicable à un Bucket, permettant de gérer leurs différentes
variables de paramétrage évoquées précédemment. Si aucun Bucket Type n’est renseigné lors de la
création d’un Bucket, un Bucket Type default est appliqué à celui-ci, comprenant les paramétrages par
défaut choisis par les équipes de Basho. L’utilité des Bucket Types est de pouvoir conserver plusieurs
versions de paramétrages en fonction des besoins de réplication et de les appliquer facilement et
rapidement à autant de Buckets qu’on le désire.
Les Backends
Les Backends correspondent aux moteurs de stockage présents dans Riak KV ; ils sont appliqués au
niveau des Buckets et sont au nombre de trois : Bitcask, LevelDB et Memory. Je ne développerai pas en
détail chaque Backend (il ne s’agit pas du thème du cours NFE204) mais voici ce que l’on peut retenir
concernant chacun d’entre eux :
Bitcask est le moteur utilisé par défaut dans Riak KV, les données y sont persistantes comme c’est le cas pour
LevelDB mais pas pour Memory (les données restent en Ram). Les données d’un Bucket « Bitcask » sont
6
Illustration 1: Paramétrage du Bucket Type Default
chargées en Ram au démarrage du serveur et y sont maintenues par la suite ; LevelDB lit les données
directement depuis le disque. Bitcask ne permet pas d’avoir une quantité de données supérieure à la
quantité de Ram allouée au serveur, l’intégralité de ces données devant pouvoir y loger.
Une API REST
Plusieurs API sont fournies afn d’utiliser Riak KV avec différents langages de programmation (Java,
Ruby, Python, C Sharp, NodeJS, Erlang, Go, Php). Une API REST permet aussi d’effectuer l’ensemble des
actions sur la base grâce aux méthodes GET, PUT, POST, DELETE bien connues ; c’est cette interface qui
sera principalement utilisée tout au long de ce rapport.
Le moteur de recherche Riak Search
Riak KV est fourni avec son propre moteur de recherche – Riak Search 2.0 – basé sur Solr. Il s’agit en
fait d’une intégration de Solr pour l’indexation et l’interrogation des données et de Riak KV pour la gestion et
le stockage des données.
Le modèle Dynamo d’Amazon
Le modèle utilisé pour la réplication des données d’un cluster Riak KV est le modèle Dynamo
d’Amazon . Une présentation complète de ce système et de son implémentation dans Riak KV a été faite en
dernière partie de ce rapport.
Les différents utilitaires d’administration
Riak KV est fourni avec deux utilitaires permettant la gestion des serveurs : riak et riak-admin.
L’utilitaire riak
riak permet la gestion du processus associé à chaque serveur. Je ne le développerai pas dans ce
rapport tant son utilisation est commune à la plupart des programmes gérant la gestion de processus. On
retrouvera ainsi comme arguments de cette commande différents mots-clés comme start, stop, restart, ping,
console, version, get-pid… dont la résultat pour chacun est évident.
L’utilitaire riak-admin
L’utilitaire riak-admin est plus intéressant que le précédent car il permet une gestion poussée de
chaque serveur grâce à des arguments relatifs aux confgurations spécifques de Riak KV. Il permet par
exemple de confgurer un cluster, d’en visualiser le statut et la santé, de récupérer des statistiques… Cet
utilitaire sera utilisé tout le long de ce projet afn de mettre en place des confgurations, les vérifer ou bien de
tester et constater les différents aspects mis en avant par ce système.
7
L’interface web Riak Explorer
Une interface web accessible par défaut à l’url http://localhost:8098/admin permet l’administration
graphique de Riak KV. Les fonctions sont similaires à celles apportées par l’utilitaire cité précédemment mais
permettent une visualisation plus intuitive et plus claire des différentes informations du cluster. J’utiliserai
cette interface pour illustrer certains aspects des différents points développés dans ce rapport.
8
Illustration 2: Page d'accueil lors du premier lancement de Riak Explorer
INSERTION ET INTERROGATION DES DONNÉES
Maintenant que les concepts globaux ont été abordés, passons à la pratique. Dans cette partie nous
allons insérer des données dans des Buckets, interroger le serveur afn de récupérer ces données ou d’y
appliquer une transformation.
Mise en place de Bucket Types et insertion
Les Bucket Type permettent le partage de mêmes confgurations entre différents Buckets. Leur
utilisation est intéressante d’un point de vue administration (on peut avoir autant de Bucket Type que l’on
veut, chacun décrivant une confguration spécifque) et essentielle d’un point de vue performance d’un
cluster . En effet, un Bucket partagé entre différentes machines devra avoir la même confguration sur l’une
ou sur l’autre ; il est donc bien plus effcace d’assigner un Bucket Type à un Bucket (la gestion des
paramètres étant alors centralisée) plutôt que d’attribuer des paramètres spécifques directement au Bucket,
dans quel cas ce serait au cluster d’aller chercher ces variables de confguration et de les diffuser auprès de
l’ensemble des serveurs.
Le Bucket Type Default
Un seul Bucket Type est disponible lors de l’installation de Riak KV : le Bucket Type default.
Remarquez le premier argument « bucket-type » passé à notre utilitaire riak-admin ; il permet d’indiquer à
Riak KV que les informations que nous demandons ici concernent la gestion des Bucket Type. Un certain
nombre d’arguments suivent alors afn de préciser notre souhait d’information.
Nous pouvons lister les paramètres qui ont été défnis pour ce Bucket Type :
9
# riak-admin bucket-type list
default (active)
# riak-admin bucket-type status default
default is active
allow_mult: false
basic_quorum: false
big_vclock: 50
chash_keyfun: {riak_core_util,chash_std_keyfun}
dvv_enabled: false
dw: quorum
last_write_wins: false
linkfun: {modfun,riak_kv_wm_link_walker,mapreduce_linkfun}
n_val: 3
notfound_ok: true
old_vclock: 86400
postcommit: []
pr: 0
precommit: []
pw: 0
r: quorum
rw: quorum
small_vclock: 50
w: quorum
write_once: false
young_vclock: 20
De très nombreuses variables peuvent être alimentées afn de personnaliser au maximum ses Buckets. Leur
listage serait fastidieux et peu utile dans le cadre de ce projet. Je reviendrai sur les paramètres liés à la
réplication et au partitionnement dans la partie qui y est consacrée plus loin.
Pour créer un Bucket utilisant ce Bucket Type, il sufft d’insérer une donnée comme ceci :
L’API REST requiert que l’on indique quel Bucket Type doit être utilisé (après /types), quel Bucket sera
alimenté (après /buckets) et quel identifant sera associé à la donnée insérée (après /keys). Voici une autre
différence avec une table SQL : il n’est pas possible de créer un Bucket sans y insérer de donnée ; c’est à
l’insertion de chaque donnée que le Bucket et son Bucket Type devront être précisés. Un même nom de
Bucket peut être utilisé associé à plusieurs Bucket Type différents mais il ne s’agira alors pas du même
Bucket pour Riak (il ne serait de toute façon sans doute pas judicieux de créer plusieurs Buckets ayant le
même nom).
Réponse de la commande :
10
curl -v -X PUT http://localhost:8098/types/default/buckets/jeopardy_default/keys/show_1 -H "Content-Type: application/json" -d ‘{
"air_date": "2003-06-06",
"answer": "Jolly Rancher",
"category": "CANDY",
"question": "Bill Harmsen, who raised horses in Colo., happily founded this candy co. in 1949 to make money during the winter",
"round": "Final Jeopardy!",
"show_number": 4335,
"value": null
}’
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8098 (#0)
> PUT /types/default/buckets/jeopardy_default/keys/show_1 HTTP/1.1
> Host: localhost:8098
> User-Agent: curl/7.58.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 260
>
* upload completely sent off: 260 out of 260 bytes
< HTTP/1.1 204 No Content
< Vary: Accept-Encoding
< Server: MochiWeb/1.1 WebMachine/1.10.9 (cafe not found)
< Date: Sat, 05 May 2018 19:53:30 GMT
< Content-Type: application/json
< Content-Length: 0
<
* Connection #0 to host localhost left intact
Vérifcation de la présence de la donnée et donc du Bucket:
La lecture d’une donnée dans Riak KV nécessite la même architecture de lien que celle vue pour l’insertion.
Les trois « couches » Bucket Type, Bucket et clé sont nécessaires.
Les Buckets avec datatype
Penchons nous maintenant sur un paramètre intéressant des Bucket Types : le datatype.
Le datatype est un paramètre permettant de rendre Riak KV « conscient » des données qu’il contient. Là où
par défaut Riak KV stocke des documents sans que leur structure et leur sens ne l’importe, il peut aussi
« prendre conscience » des données qu’on lui fournit afn d’y appliquer des transformations. Trois datatypes
issus des CRDT ont alors été intégrés :
- les Counters
- les Sets
- les Maps
Pour maintenir la cohérence des données
voulue par l’utilisation de CRDT, ces types de
données ne peuvent être effcaces sans
l’utilisation d’un context ; c’est en effet ce
context qui indiquera au serveur quelle est la
version de l’objet qui est présent et qu’il s’apprête à modifer. Il permettra entre autres de gérer les
incohérence au niveau de l’existence des objets (si un serveur a commandé la suppression d’un objet mais
qu’un autre le met à jour, il faut le savoir). Dans cette parie, nous ne travaillerons pas dans un contexte
distribué ; nous introduirons donc seulement les différents types de données en expliquant leur rôle et leur
implémentation. Nous testerons leur résistance aux confits dans la dernière partie de ce rapport.
11
#curl -v -X GET http://localhost:8098/types/default/buckets/jeopardy_default/keys/show_1
Note: Unnecessary use of -X or --request, GET is already inferred.
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8098 (#0)
> GET /types/default/buckets/jeopardy_default/keys/show_1 HTTP/1.1
> Host: localhost:8098
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 200 OK
< X-Riak-Vclock: a85hYGBgzGDKBVI8+xkWvufumDgbIpTImMfKsLM96QpfFgA=
< Vary: Accept-Encoding
< Server: MochiWeb/1.1 WebMachine/1.10.9 (cafe not found)
< Link: </buckets/jeopardy_default>; rel="up"
< Last-Modifed: Sat, 05 May 2018 19:53:29 GMT
< ETag: "4gXEpr1wVPZsKPhI69CW25"
< Date: Sat, 05 May 2018 20:01:42 GMT
< Content-Type: application/json
< Content-Length: 260
<
* Connection #0 to host localhost left intact
{"air_date": "2003-06-06","answer": "Jolly Rancher","category": "CANDY","question": "Bill Harmsen, who raised horses in Colo., happily
founded this candy co. in 1949 to make money during the winter","round": "Final Jeopardy!","show_number": 4335,"value": null}
Les CRDT
Un CRDT (Confict-Free Replicated Data Type) est une
structure de données pouvant être répliquée sur différents
serveurs et dont les réplicas peuvent être mis à jour
indépendamment. Les incohérences résultant de ces mises
à jour concurrentes peuvent alors toujours être résolues
mathématiquement afn de retrouver une structure de
donnée cohérente.
• Bucket Type « Map »
Un Map est un tableau associatif associant à un ensemble de clés un ensemble correspondant de
valeurs. Il a une structure similaire à un document json (« clé » : »valeur ») mais sa structure connue par
l’applicatif permet de lui appliquer un certain nombre d’opérations : ajout d’une nouvelle valeur à une
nouvelle clé, modifcation ou suppression d’une valeur de clé existante. Un Map peut aussi contenir des
booléens, des compteurs (counters vus plus tard dans le rapport), des liste d’objets uniques (sets vus plus
tard dans le rapport) ou d’autres maps.
Création d’un Bucket Type « Map »
Avec la clé « props », nous indiquons à Riak KV que les éléments qui vont suivre vont être des variables de
confguration du Bucket Type. Ici, le paramètre « datatype » nous permet de personnaliser le type de
données contenues dans un Bucket dont le Bucket Type sera « bucket_type_map ».
Le Bucket Type est créé mais n’est pas encore actif :
12
#riak-admin bucket-type create bucket_type_map '{"props":{"datatype":"map"}}'
bucket_type_map created
WARNING: After activating bucket_type_map, nodes in this cluster
can no longer be downgraded to a version of Riak prior to 2.0
# riak-admin bucket-type status bucket_type_map
bucket_type_map has been created and may be activated
young_vclock: 20
w: quorum
small_vclock: 50
rw: quorum
r: quorum
pw: 0
precommit: []
pr: 0
postcommit: []
old_vclock: 86400
notfound_ok: true
n_val: 3
linkfun: {modfun,riak_kv_wm_link_walker,mapreduce_linkfun}
last_write_wins: false
dw: quorum
dvv_enabled: true
chash_keyfun: {riak_core_util,chash_std_keyfun}
big_vclock: 50
basic_quorum: false
allow_mult: true
datatype: map
active: false
claimant: 'riak@172.17.0.2'
Activons le :
Créons maintenant un objet Map en Java:
Nous ne pouvons pas créer d’objet Map à l’aide de l’API REST ; il s’agit pour Riak KV d’un objet dont la
structure complexe ne peut être décrite par l’interface http.Il s’agit en fait d’un objet au sens
« programmation » du terme, ayant besoin d’un constructeur.
L’Objet de type Map a bien été créé mais ne contient encore aucune information :
Enregistrons maintenant des données Register dans notre Map :
Une fois l’objet créé, il peut être alimenté et modifé directement avec l’API REST.« update » indique que
nous allons mettre à jour notre Map, soit avec un attribut existant dont la valeur sera alors modifée, soit avec
un nouvel attribut dont le couple clé:valeur sera alors ajouté.
L’utilisation des Registers se fait pas l’ajout du suffxe « _register » au nom des clés passées en argument ; un
Register est en fait l’appel d’une fonction appliquée à un Map permettant l’ajout d’un couple clé:valeur
dans celui-ci si cette clé n’existe pas déjà.
13
# riak-admin bucket-type activate bucket_type_map
bucket_type_map has been activated
WARNING: Nodes in this cluster can no longer be
downgraded to a version of Riak prior to 2.0
//Initialisation du client
RiakCluster cluster = setUpCluster();
RiakClient client = new RiakClient(cluster);
//Création d'un Objet Map d'id question_example_1, dans un Bucket jeopardy_question de type bucket_type_map
Location question_example_map =new Location(new Namespace("bucket_type_map", "jeopardy_question"), "question_example_1");
//On envoie l'information au serveur
FetchMap fetch = new FetchMap.Builder(question_example_map).build();
client.execute(fetch);
# curl -GET http://localhost:8098/types/bucket_type_map/buckets/jeopady_question/datatypes/question_example_1
{"type":"map","error":"notfound"}
#curl -XPOST http://localhost:8098/types/bucket_type_map/buckets/jeopady_question/datatypes/question_example_1 
-H "Content-Type: application/json" 
-d '
{
"update": {
"air_date_register": "2004-05-10",
"answer_register": "George III",
"category_register": "HISTORIC NICKNAMES",
"question_register": "Because of his Hanoverian heritage, American colonists called this monarch "German Georgie" or "Geordie"",
"round_register": "Jeopardy!",
"show_number_register": "4541",
"value_register": "$200"
}
}
'
Que contient maintenant notre objet ?
Nous constatons que l’objet a bien été alimenté par la commande précédente. Une clé « context » a aussi
été ajoutée indiquant la version de l’objet (nous verrons cela plus en détail plus tard).
Le type Flag permet de créer une clé dont la valeur sera booléenne (enable ou disable).
Voici alors le Map :
Il est aussi possible d‘y imbriquer d’autres Maps, Sets (notion vue juste après) ou Counter (notion abordée
après les Sets). Il est aussi possible de supprimer une clé et sa valeur. Par exemple, voici comment nous
supprimerions la clé « answer_register » :
Enfn, nous pouvons mettre à jour une valeur existante (ici nous modifons la date):
14
#curl -GET http://localhost:8098/types/bucket_type_map/buckets/jeopady_question/datatypes/question_example_1
{"type":"map","value":{"air_date_register":"2004-05-10","answer_register":"George III","category_register":"HISTORIC
NICKNAMES","question_register":"Because of his Hanoverian heritage, American colonists called this monarch "German Georgie"
or "Geordie"","round_register":"Jeopardy!","show_number_register":"4541","value_register":"$200"},"context":"g2wAAAABaAJtAAAA
DL8Aoe8LbXWwAAAAAWEBag=="}
#curl -XPOST http://localhost:8098/types/bucket_type_map/buckets/jeopady_question/datatypes/question_example_1 
-H "Content-Type: application/json" 
-d '
{
"update": {
"good_answer_fag": "enable"
}
}
'
#curl -GET http://localhost:8098/types/bucket_type_map/buckets/jeopady_question/datatypes/question_example_1
{"type":"map","value":{"air_date_register":"2004-05-10","answer_register":"George III","category_register":"HISTORIC
NICKNAMES","good_answer_fag":true,"question_register":"Because of his Hanoverian heritage, American colonists called this
monarch "German Georgie"
or "Geordie"","round_register":"Jeopardy!","show_number_register":"4541","value_register":"$201"},"context":"g2wAAAABaAJtAAAA
DL8Aoe8LbXWwAAAAAWEGag=="}
#curl -XPOST http://localhost:8098/types/bucket_type_map/buckets/jeopady_question/datatypes/question_example_1 
-H "Content-Type: application/json" 
-d '
{
"update": {
"air_date_register": "2018-05-25"} }'
#curl -XPOST http://localhost:8098/types/bucket_type_map/buckets/jeopady_question/datatypes/question_example_1 
-H "Content-Type: application/json" 
-d '
{
"remove": "air_date_register" }'
• Bucket Type « Set »
Un Set est une collection de valeurs uniques. Ajouter dans un Set une valeur déjà existante n’aboutira donc
à aucune modifcation de celui-ci.
Création d’un Bucket Type «Set»
Le Bucket Type est créé mais n’est pas encore actif :
Activons le :
Créons maintenant un objet Set en Java:
15
#riak-admin bucket-type create bucket_type_set '{"props":{"datatype":"set"}}'
bucket_type_set created
# riak-admin bucket-type status bucket_type_set
bucket_type_set has been created and may be activated
young_vclock: 20
w: quorum
small_vclock: 50
rw: quorum
r: quorum
pw: 0
precommit: []
pr: 0
postcommit: []
old_vclock: 86400
notfound_ok: true
n_val: 3
linkfun: {modfun,riak_kv_wm_link_walker,mapreduce_linkfun}
last_write_wins: false
dw: quorum
dvv_enabled: true
chash_keyfun: {riak_core_util,chash_std_keyfun}
big_vclock: 50
basic_quorum: false
allow_mult: true
datatype: set
active: false
claimant: 'riak@172.17.0.2'
# riak-admin bucket-type activate bucket_type_set
bucket_type_set has been activated
//Initialisation du client
RiakCluster cluster = setUpCluster();
RiakClient client = new RiakClient(cluster);
//Création d'un Objet Set d'id question_example_1, dans un Bucket jeopardy_question de type bucket_type_set
Location question_example_set =new Location(new Namespace("bucket_type_set", "jeopardy_question"), "question_example_1");
//On envoie l'information au serveur
FetchSet fetch = new FetchSet.Builder(question_example_set).build();
client.execute(fetch);
Encore une fois, l’API REST ne peut être utilisée pour l’initialisation de l’objet.
Enregistrons maintenant des données dans notre Set :
« add_all » permet l’ajout d’un ensemble de valeurs à notre Set.
Que contient maintenant notre objet ?
Les valeurs d’un Set étant uniques, cet ajout sera par exemple ignoré :
Nous pouvons supprimer une valeur du Set :
L’objet contient alors :
Dans le client Java, le type Set étant connu, il est possible de lui appliquer plusieurs fonctions classiques
(isEmplty(), contains(), size() ...)
• Bucket Type « Counter »
Comme son nom l’indique, un Counter permet de créer un objet « compteur », contenant un nombre entier
et permettant de lui appliquer des fonctions élémentaires (additions et soustraction).
Création d’un Bucket Type «Counter»
16
# curl -XPOST http://localhost:8098/types/bucket_type_set/buckets/jeopady_question/datatypes/question_example_1 
-H "Content-Type: application/json" 
-d '{"add_all":["2004-05-10", "George III","HISTORIC NICKNAMES"]}'
# curl -GET http://localhost:8098/types/bucket_type_set/buckets/jeopady_question/datatypes/question_example_1
{"type":"set","value":["2004-05-10","George III","HISTORIC
NICKNAMES"],"context":"g2wAAAABaAJtAAAADL8Aoe8LafemAAAAAWEDag=="}
# curl -XPOST http://localhost:8098/types/bucket_type_set/buckets/jeopady_question/datatypes/question_example_1 
-H "Content-Type: application/json" 
-d '{"add_all":["HISTORIC NICKNAMES"]}'
# curl -XPOST http://localhost:8098/types/bucket_type_set/buckets/jeopady_question/datatypes/question_example_1 
-H "Content-Type: application/json" 
-d '{"remove": "HISTORIC NICKNAMES"}'
# curl -GET http://localhost:8098/types/bucket_type_set/buckets/jeopady_question/datatypes/question_example_1
{"type":"set","value":["2004-05-10","George III"],"context":"g2wAAAABaAJtAAAADL8Aoe8LafemAAAAAWEDag=="}
#riak-admin bucket-type create bucket_type_counter '{"props":{"datatype":"counter"}}'
bucket_type_set created
Le Bucket Type est créé mais n’est pas encore actif :
Activons le :
Créons maintenant un objet Counter grâce à l’API REST :
Il s’agit du seul Data Type fournit par Riak KV pour lequel la création de l’objet de requiert pas un client autre
que curl.
Le compteur est initialisé :
17
# riak-admin bucket-type status bucket_type_counter
bucket_type_counter has been created and may be activated
young_vclock: 20
w: quorum
small_vclock: 50
rw: quorum
r: quorum
pw: 0
precommit: []
pr: 0
postcommit: []
old_vclock: 86400
notfound_ok: true
n_val: 3
linkfun: {modfun,riak_kv_wm_link_walker,mapreduce_linkfun}
last_write_wins: false
dw: quorum
dvv_enabled: true
chash_keyfun: {riak_core_util,chash_std_keyfun}
big_vclock: 50
basic_quorum: false
allow_mult: true
datatype: set
active: false
claimant: 'riak@172.17.0.2'
# riak-admin bucket-type activate bucket_type_counter
bucket_type_counter has been activated
# curl -XPOST http://localhost:8098/types/bucket_type_counter/buckets/jeopady_question/datatypes/question_example_1 
-H "Content-Type: application/json" 
-d '{"increment": 0}'
# curl -GET http://localhost:8098/types/bucket_type_counter/buckets/jeopady_question/datatypes/question_example_1
{"type":"counter","value":0}
Nous pouvons alors l’incrémenter :
Ou le décrémenter :
Interrogation des données
Nous avons vu dans la partie précédente comment lire des données insérées en base grâce à la clé
associée à la valeur lors de l’insertion. Cependant, ce mode de récupération - bien que très rapide - n’est pas
suffsant dans la plupart des cas d’utilisation d’une base de
données. Un mode de récupération des données en fonction
de requêtes approximatives ou exactes est possible grâce à
l’outil Riak Search, intégré dans Riak RK, qui n’est rien d’autre
qu’une implémentation de Solr et un interfaçage automatisé
de ce logiciel de recherche avec les données présentes dans
les Buckets. Ce rapport n’a pas pour but l’étude de Solr mais
omettre Riak Search n’est pas envisageable tant il fait partie
intégrante de Riak KV. Cette partie sera donc dédiée à une
présentation rapide de l’implémentation et du fonctionnement de Solr au sein du système NoSQL Riak KV.
18
# curl -XPOST http://localhost:8098/types/bucket_type_counter/buckets/jeopady_question/datatypes/question_example_1 -H
"Content-Type: application/json" -d '{"increment": 2}'
# curl -GET http://localhost:8098/types/bucket_type_counter/buckets/jeopady_question/datatypes/question_example_1
{"type":"counter","value":2}
# curl -XPOST http://localhost:8098/types/bucket_type_counter/buckets/jeopady_question/datatypes/question_example_1 -H
"Content-Type: application/json" -d '{"increment": 3}'
# curl -GET http://localhost:8098/types/bucket_type_counter/buckets/jeopady_question/datatypes/question_example_1
{"type":"counter","value":5}
# curl -XPOST http://localhost:8098/types/bucket_type_counter/buckets/jeopady_question/datatypes/question_example_1 -H
"Content-Type: application/json" -d '{"decrement": 2}'
# curl -GET http://localhost:8098/types/bucket_type_counter/buckets/jeopady_question/datatypes/question_example_1
{"type":"counter","value":3}
# curl -XPOST http://localhost:8098/types/bucket_type_counter/buckets/jeopady_question/datatypes/question_example_1 -H
"Content-Type: application/json" -d '{"decrement": 4}'
# curl -GET http://localhost:8098/types/bucket_type_counter/buckets/jeopady_question/datatypes/question_example_1
{"type":"counter","value":-1}
AAE
Riak Search utilise l’Active Anti-Entropy
pour assurer la cohérence des données
stockées et indexées pour la recherche. Un
paragraphe dédié à l’explication de ce
fonctionnement se trouve en fn de partie.
Mise en place de Riak Search
• Activation de Riak Search
Par défaut, Riak Search n’est pas activé dans Riak KV. Pour ce faire, il sufft de remplacer
« search = off » par « search = on » dans le fchier de confguration de Riak KV :
puis de redémarrer de service Riak.
• Schéma Riak Search (Solr)
Le schéma permet d’indiquer à Riak Search la nature des données à indexer afn d’y appliquer par la
suite des fonctions de recherche adéquates (recherche entre deux dates, entre deux bornes entières, etc.). Il
permet aussi d’indiquer la langue dans laquelle les données sont écrites afn d’y appliquer des fonctions de
transformations en vue de supprimer les stop words, pluriels, etc. Cette partie est détaillée dans le cours,
c’est pourquoi je n’y apporterai pas plus de précisions.
19
# sed -i -- 's/search = off/search = on/g' /etc/riak/riak.conf
Voici un exemple de schéma correspondant au jeu de données du projet :
Ce schéma doit ensuite être inséré dans Riak Search. :
Remarquez dans l’url ci-dessus que nous ne faisons bien plus appel à la base de données Riak KV (il n’est
plus question de Bucket Types ou Buckets) mais bien à un module « search » à part. Nous créons ici un
schéma Riak Search nommé « jeopardy_schema » dont le contenu est le document xml cité plus haut,
stocké dans le fchier local « jeopardy_schema.xml ».
20
<?xml version="1.0" encoding="UTF-8" ?>
<schema name="schedule" version="1.5">
<felds>
<feld name="air_date" type="date" indexed="true" stored="true" default="NOW"/>
<feld name="answer" type="string" indexed="true" stored="false" />
<feld name="category" type="string" indexed="true" stored="true" />
<feld name="question" type="string" indexed="true" stored="false" />
<feld name="round" type="string" indexed="true" stored="true" />
<feld name="show_number" type="int" indexed="true" stored="false" />
<feld name="value" type="string" indexed="true" stored="false" />
<dynamicField name="*_en" type="text_en" indexed="true" stored="true" multiValued="true" />
<!-- All of these felds are required by Riak Search -->
<feld name="_yz_id" type="_yz_str" indexed="true" stored="true" multiValued="false" required="true"/>
<feld name="_yz_ed" type="_yz_str" indexed="true" stored="false" multiValued="false"/>
<feld name="_yz_pn" type="_yz_str" indexed="true" stored="false" multiValued="false"/>
<feld name="_yz_fpn" type="_yz_str" indexed="true" stored="false" multiValued="false"/>
<feld name="_yz_vtag" type="_yz_str" indexed="true" stored="false" multiValued="false"/>
<feld name="_yz_rk" type="_yz_str" indexed="true" stored="true" multiValued="false"/>
<feld name="_yz_rt" type="_yz_str" indexed="true" stored="true" multiValued="false"/>
<feld name="_yz_rb" type="_yz_str" indexed="true" stored="true" multiValued="false"/>
<feld name="_yz_err" type="_yz_str" indexed="true" stored="false" multiValued="false"/>
</felds>
<types>
<!-- YZ String: Used for non-analyzed felds -->
<feldType name="_yz_str" class="solr.StrField" sortMissingLast="true" />
<feldType name="string" class="solr.StrField" sortMissingLast="true" />
<feldType name="int" class="solr.TrieIntField" precisionStep="0" positionIncrementGap="0"/>
<feldType name="date" class="solr.TrieDateField" omitNorms="false" precisionStep="0" positionIncrementGap="0"/>
<feldType name="text_en" class="solr.TextField" positionIncrementGap="100">
<analyzer>
<tokenizer class="solr.StandardTokenizerFactory"/>
<flter class="solr.LowerCaseFilterFactory"/>
<flter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_en.txt" format="snowball" />
<flter class="solr.EnglishMinimalStemFilterFactory"/>
</analyzer>
</feldType>
</types>
<uniqueKey>_yz_id</uniqueKey>
</schema>
# curl -XPUT http://localhost:8098/search/schema/jeopardy_schema 
-H 'Content-Type:application/xml' 
--data-binary @jeopardy_schema.xml
• Création de l’index Riak Search
Nous devons maintenant créer un index Riak Search utilisant le schéma précédemment défni. Cet
index ne sera pour le moment alimenté par aucune donnée.
L’index créé « jeopardy_index » utilise bien comme schéma « jeopardy_schema ».
• Attribution d’un index à une collection de données
L’index que nous venons de créer peut désormais être lié au choix soit à un Bucket Type, soit à un
Bucket.
Dans le cas où nous le lions à un Bucket Type, toutes les données des Buckets de ce type seront indexées
dans cet index ; il sera donc nécessaire de garder une cohérence de structure forte entre les données
insérées dans ces différents Buckets afn que toutes répondent au schéma Riak Search associé. Les futures
recherches utilisant cet index pourront alors se faire à travers les données stockées dans tous ces Buckets.
L’attribution d’un index lors de la création d’un Bucket Type se ferait alors ainsi :
Nous pouvons aussi directement lier un index à un Bucket (c’est ce que nous ferons ici) :
Toutes les actions précédentes peuvent aussi se faire et être visualisées grâce à l’interface Web :
21
Illustration 3: Liste des index créés avec leur schéma associé
# curl -XPUT http://localhost:8098/search/index/jeopardy_index 
-H 'Content-Type: application/json' 
-d '{"schema":"jeopardy_schema"}'
[info] <0.2691.0>@yz_index:core_create:284 Created index jeopardy_index with schema jeopardy_schema
# riak-admin bucket-type create mon_bucket_type '{"props":{"search_index":"leopardy_schema"}}'
# curl -XPUT http://localhost:8098/types/default/buckets/jeopardy_bucket/props 
-H 'Content-Type: application/json' 
-d '{"props":{"search_index":"jeopardy_index"}}'
• Alimentation de l’index
Insérons l’ensemble de notre collection de documents Jeopardy dans le bucket
« jeopardy_bucket »grâce à un script shell :
Vérifons que le Bucket et l’Index comportent le même nombre de données.
Nous pouvons récupérer le nombre de données dans le Bucket grâce à une fonction MapReduce simple :
22
Illustration 4: Détail de l'index "jeopardy_index"
#!/bin/bash
json_fle=$(cat jeopardy.json);
type="default"
bucket="jeopardy_bucket"
i=0;
for data in $(echo "${json_fle}" | jq -r '.[] | @base64'); do
((i++));
_jq() {
echo ${data} | base64 --decode;
}
json_item="'"$(_jq)"'";
insert_command=`echo curl -v -X PUT http://localhost:8098/types/$type/buckets/$bucket/keys/show_$i -H """Content-
Type: application/json""" -d $json_item`;
eval $insert_command;
done;
#curl -XPOST http://localhost:8098/mapred -H 'Content-Type: application/json' -d '
{"inputs":"jeopardy_bucket",
"query":[{"map":{"language":"javascript",
"keep":false,
"source":"function(jeopardykv) {return [1]; }"}},
{"reduce":{"language":"javascript",
"keep":true,
"name":"Riak.reduceSum"}}]}'
216770
Pour vérifer le nombre de données dans l’index, il sufft d’une requête renvoyant le nombre total de
documents (ici nous sélectionnant tous les documents ayant une clé category) :
• Interrogation de l’index
Voici quelques exemple d’interrogation de l’index nouvellement créé :
Cherchons les documents dont la catégorie est « ALL MY SON » :
La requête est passée après « q= » (pour query =). Riak Search nous indique que 5 documents
correspondent à notre requête et liste les liste avec leurs attributs stockés dans l’index.
23
# curl -XGET "http://localhost:8098/search/query/jeopardy_index?wt=json&q=category:*"
{"responseHeader":{"status":0,"QTime":52,"params":{"q":"category:*","shards":"172.17.0.2:8093/internal_solr/
jeopardy_index","172.17.0.2:8093":"(_yz_pn:62 AND (_yz_fpn:62)) OR _yz_pn:61 OR _yz_pn:58 OR _yz_pn:55 OR _yz_pn:52 OR
_yz_pn:49 OR _yz_pn:46 OR _yz_pn:43 OR _yz_pn:40 OR _yz_pn:37 OR _yz_pn:34 OR _yz_pn:31 OR _yz_pn:28 OR _yz_pn:25 OR
_yz_pn:22 OR _yz_pn:19 OR _yz_pn:16 OR _yz_pn:13 OR _yz_pn:10 OR _yz_pn:7 OR _yz_pn:4 OR _yz_pn:1","wt":"json"}},"response":
{"numFound":216770,"start":0,"maxScore":1.0,"docs":[{"round":"Double Jeopardy!","air_date":"2005-09-
27T00:00:00Z","category":"YOURE
A "STAR"","_yz_id":"1*default*jeopardy_bucket*show_70751*25","_yz_rk":"show_70751","_yz_rt":"default","_yz_rb":"jeopardy_buck
et"},{"round":"Double Jeopardy!","air_date":"2005-09-27T00:00:00Z","category":"LITERARY ____ OF
____","_yz_id":"1*default*jeopardy_bucket*show_70759*40","_yz_rk":"show_70759","_yz_rt":"default","_yz_rb":"jeopardy_bucket"},
{"round":"Double Jeopardy!","air_date":"2005-09-27T00:00:00Z","category":"CLARK"…...}]}}
# curl "http://localhost:8098/search/query/jeopardy_index?wt=json&q=category:"ALL MY SONS"
{"responseHeader":{"status":0,"QTime":4,"params":{"q":"category:"ALL MY
SONS"","shards":"172.17.0.2:8093/internal_solr/jeopardy_index","172.17.0.2:8093":"_yz_pn:64 OR (_yz_pn:61 AND (_yz_fpn:61)) OR
_yz_pn:60 OR _yz_pn:57 OR _yz_pn:54 OR _yz_pn:51 OR _yz_pn:48 OR _yz_pn:45 OR _yz_pn:42 OR _yz_pn:39 OR _yz_pn:36 OR
_yz_pn:33 OR _yz_pn:30 OR _yz_pn:27 OR _yz_pn:24 OR _yz_pn:21 OR _yz_pn:18 OR _yz_pn:15 OR _yz_pn:12 OR _yz_pn:9 OR
_yz_pn:6 OR _yz_pn:3","wt":"json"}},"response":{"numFound":5,"start":0,"maxScore":11.613354,"docs":
[{"round":"Jeopardy!","air_date":"2008-03-13T00:00:00Z","category":"ALL MY
SONS","_yz_id":"1*default*jeopardy_bucket*show_4698*27","_yz_rk":"show_4698","_yz_rt":"default","_yz_rb":"jeopardy_bucket"},
{"round":"Jeopardy!","air_date":"2008-03-13T00:00:00Z","category":"ALL MY
SONS","_yz_id":"1*default*jeopardy_bucket*show_4704*42","_yz_rk":"show_4704","_yz_rt":"default","_yz_rb":"jeopardy_bucket"},
{"round":"Jeopardy!","air_date":"2008-03-13T00:00:00Z","category":"ALL MY
SONS","_yz_id":"1*default*jeopardy_bucket*show_4680*64","_yz_rk":"show_4680","_yz_rt":"default","_yz_rb":"jeopardy_bucket"},
{"round":"Jeopardy!","air_date":"2008-03-13T00:00:00Z","category":"ALL MY
SONS","_yz_id":"1*default*jeopardy_bucket*show_4686*51","_yz_rk":"show_4686","_yz_rt":"default","_yz_rb":"jeopardy_bucket"},
{"round":"Jeopardy!","air_date":"2008-03-13T00:00:00Z","category":"ALL MY
SONS","_yz_id":"1*default*jeopardy_bucket*show_4692*6","_yz_rk":"show_4692","_yz_rt":"default","_yz_rb":"jeopardy_bucket"}]}}
Cherchons les documents dont la date de diffusion (air_date) est comprise entre le 01/03/2008 et le
01/12/2008 :
Il est bien sûr possible d’exploiter toutes les possibilités offertes par Solr et donc d’aller bien plus loin
(vraiment plus loin) que les deux exemples ci-dessus.
L’ Active Anti-Entropy
L’active Anti-Entropy est un processus permettant de rechercher et de corriger les incohérences et
différences entre les données stockées dans la base Riak RK et l’index Riak Search (Solr). Ces erreurs
peuvent être dues à de multiples raisons (extinction de serveur, problème matériel, problème réseau, etc.).
Le principe de l’AAE est qu’il est impossible de prévoir les pannes tant elles peuvent être diverses et parfois
diffciles à déceler ; il est alors plus effcace d’implémenter un script qui vérife en temps réel la bonne
duplication des informations voulues entre la base et l’index.
Il serait bien évidemment totalement ineffcace d’utiliser un processus qui scannerait d’un côté les données
présentes en base et de l’autre celles présentes dans l’index et de les comparer. Pour palier à ce problème,
l’AAE utilise le système des hashtrees (arbres de hashage ou arbre de Merkle).
Lors de l’insertion des données, celles-ci ont été réparties en plusieurs fragments sur le serveur (il s’agit en
fait de vnode mais nous conserverons le terme «fragment» dans cette partie pour plus de clarté. Les notions
liées à la répartition des données seront vues et expliquées dans la dernière partie de ce rapport).
24
# curl "http://localhost:8098/search/query/jeopardy_index?wt=json&q=air_date:[2008-03-01T00:00:00Z TO 2008-12-01T00:00:00Z]"
{"responseHeader":{"status":0,"QTime":8,"params":{"q":"air_date:[2008-03-01T00:00:00Z TO 2008-12-
01T00:00:00Z]","shards":"172.17.0.2:8093/internal_solr/jeopardy_index","172.17.0.2:8093":"_yz_pn:64 OR (_yz_pn:61 AND
(_yz_fpn:61)) OR _yz_pn:60 OR _yz_pn:57 OR _yz_pn:54 OR _yz_pn:51 OR _yz_pn:48 OR _yz_pn:45 OR _yz_pn:42 OR _yz_pn:39 OR
_yz_pn:36 OR _yz_pn:33 OR _yz_pn:30 OR _yz_pn:27 OR _yz_pn:24 OR _yz_pn:21 OR _yz_pn:18 OR _yz_pn:15 OR _yz_pn:12 OR
_yz_pn:9 OR _yz_pn:6 OR _yz_pn:3","wt":"json"}},"response":{"numFound":9521,"start":0,"maxScore":1.0,"docs":
[{"round":"Jeopardy!","air_date":"2008-05-
29T00:00:00Z","category":"NUMBERS","_yz_id":"1*default*jeopardy_bucket*show_70839*24","_yz_rk":"show_70839","_yz_rt":"defaul
t","_yz_rb":"jeopardy_bucket"},{"round":"Jeopardy!","air_date":"2008-05-29T00:00:00Z","category":"I
DO","_yz_id":"1*default*jeopardy_bucket*show_70848*24","_yz_rk":"show_70848","_yz_rt":"default","_yz_rb":"jeopardy_bucket"},
{"round":"Jeopardy!","air_date":"2008-05-29T00:00:00Z","category":"THE 20th
CENTURY","_yz_id":"1*default*jeopardy_bucket*show_70835*57","_yz_rk":"show_70835","_yz_rt":"default","_yz_rb":"jeopardy_buck
et"},{"round":"Jeopardy!","air_date":"2008-05-29T00:00:00Z","category":"THE 20th
CENTURY","_yz_id":"1*default*jeopardy_bucket*show_70841*64","_yz_rk":"show_70841","_yz_rt":"default","_yz_rb":"jeopardy_buck
et"},{"round":"Jeopardy!","air_date":"2008-05-
29T00:00:00Z","category":"NUMBERS","_yz_id":"1*default*jeopardy_bucket*show_70845*3","_yz_rk":"show_70845","_yz_rt":"default
","_yz_rb":"jeopardy_bucket"},{"round":"Jeopardy!","air_date":"2008-05-29T00:00:00Z","category":"FAMILIAR
PHRASES","_yz_id":"1*default*jeopardy_bucket*show_70846*12","_yz_rk":"show_70846","_yz_rt":"default","_yz_rb":"jeopardy_bucke
t"},{"round":"Jeopardy!","air_date":"2008-05-
29T00:00:00Z","category":"FABRICS","_yz_id":"1*default*jeopardy_bucket*show_70837*57","_yz_rk":"show_70837","_yz_rt":"default"
,"_yz_rb":"jeopardy_bucket"},{"round":"Jeopardy!","air_date":"2008-05-29T00:00:00Z","category":"I
DO","_yz_id":"1*default*jeopardy_bucket*show_70836*64","_yz_rk":"show_70836","_yz_rt":"default","_yz_rb":"jeopardy_bucket"},
{"round":"Jeopardy!","air_date":"2008-05-29T00:00:00Z","category":"FAMILIAR
PHRASES","_yz_id":"1*default*jeopardy_bucket*show_70852*64","_yz_rk":"show_70852","_yz_rt":"default","_yz_rb":"jeopardy_bucke
t"},{"round":"Jeopardy!","air_date":"2008-05-29T00:00:00Z","category":"LITERARY
LOCALES","_yz_id":"1*default*jeopardy_bucket*show_70838*45","_yz_rk":"show_70838","_yz_rt":"default","_yz_rb":"jeopardy_bucke
t"}]}}
Ces fragments peuvent être listés ainsi :
25
# riak-admin vnode-status
Vnode status information
-------------------------------------------
VNode: 0
Backend: riak_kv_bitcask_backend
Status:
[{key_count,10213},{status,[]}]
Status:
{vnodeid,<<191,0,161,239,224,222,129,194>>}
Status:
{counter,20213}
Status:
{counter_lease,30000}
Status:
{counter_lease_size,10000}
Status:
{counter_leasing,false}
VNode: 22835963083295358096932575511191922182123945984
Backend: riak_kv_bitcask_backend
Status:
[{key_count,10174},{status,[]}]
Status:
{vnodeid,<<191,0,161,239,224,222,130,8>>}
Status:
{counter,20174}
Status:
{counter_lease,30000}
Status:
{counter_lease_size,10000}
Status:
{counter_leasing,false}
.
.
.
VNode: 1438665674247607560106752257205091097473808596992
Backend: riak_kv_bitcask_backend
Status:
[{key_count,10227},{status,[]}]
Status:
{vnodeid,<<191,0,161,239,226,126,207,204>>}
Status:
{counter,10227}
Status:
{counter_lease,20000}
Status:
{counter_lease_size,10000}
Status:
{counter_leasing,false}
Le statut de l’AAE peut être vérifé :
Qu’apprend-on ici ? La section « Entropy Trees » nous indique quand les hashtrees de chaque partition ont
été créés pour les besoins de l’AAE. La section « Keys Repaired » nous indique le nombre d’incohérences
qui ont été détectées et réparées ; ici tout s’est bien passé, aucun problème n’a eu lieu pendant l’insertion
des données et l’alimentation de l’index en parallèle. Enfn, la section « Exchange » indique le temps depuis
la dernière opération de communication entre le fragment de base contenant les données et l’index et
depuis combien de temps plus aucun échange n’a été réalisé.
Mais alors, comment tout cela fonctionne ? Pour chaque partition, deux hashtrees sont créés : un pour la
base Riak KV, l’autre pour Riak Search (Solr). A chaque insertion ou modifcation de données d’un côté ou de
l’autre du système, l’arbre est mis à jour. L’AAE consiste alors à comparer périodiquement les deux arbres de
hashage afn de voir s’ils sont identiques. Dans un premier lieu, les hash des deux racines sont comparés ; s’il
s’agit des mêmes valeurs alors il est inutile d’aller plus loin, les arbres sont identiques. En effet, les valeurs de
hash des feuilles supérieures sont déduites par rapport aux valeurs de hash inférieures. S’il existe une
différence entre les valeurs de hash des feuilles racines, il est alors nécessaire de descendre dans l’arbre afn
de trouver la valeur qui diffère. Une fois la différence trouvée, une fonction de réparation est appelée afn de
créer la valeur manquante ou la corriger si elle est existante. Une illustration de ce fonctionnement en arbre
est présente ci-dessous.
26
# riak-admin search aae-status
================================== Exchanges ==================================
Index Last (ago) All (ago)
-------------------------------------------------------------------------------
0 1.1 hr 1.1 hr
22835963083295358096932575511191922182123945984 1.1 hr 1.1 hr
.
.
.
1438665674247607560106752257205091097473808596992 20.6 min 21.1 min
================================ Entropy Trees ================================
Index Built (ago)
-------------------------------------------------------------------------------
0 1.9 hr
22835963083295358096932575511191922182123945984 1.9 hr
.
.
.
1438665674247607560106752257205091097473808596992 1.9 hr
================================ Keys Repaired ================================
Index Last Mean Max
-------------------------------------------------------------------------------
0 0 0 0
22835963083295358096932575511191922182123945984 0 0 0
.
.
.
1438665674247607560106752257205091097473808596992 0 0 0
Ce système n’est pas cependant exempt de toute erreur. En effet, il est tout à fait possible d’imaginer qu’une
erreur puisse empêcher la mise à jour des hashtrees et que ceux-ci restent donc identiques, quelles que
soient les opérations faites sur la base de données. C’est pour cela que les hashtrees n’ont pas une durée de
vie illimitée ; Riak KV considère qu’au bout d’une semaine, il est nécessaire de détruire les deux arbres et de
les reconstruire entièrement afn de pouvoir les comparer à nouveau.
Testons maintenant la reconstruction d’une valeur présente dans l’index après une erreur de
synchronisation. Pour ce faire, nous couperons le service Riak Search, mettrons à jour un document dans le
Bucket « jeopardy_bucket »puis relancerons Riak Search.
Nous travaillerons ici avec le document d’id « show_4680 ».
Voici ce qui est stocké dans l’index pour ce document :
(vous remarquerez ici l’utilisation de _yz_rk qui est un champs ajouté automatiquement indexant la clé Riak
KV par Riak Search.)
27
Illustration 5: Schéma représentant le fonctionnement de l'AAE
Hashtree Riak Search
Hash racine
Partition 22835963083295358096932575511191922182123945984
Hash racine
Hash Document 1 Hash Document 2
...
Hash Clé 1
Hash Document 1 Hash Document 2
...
"air_date": "2003-06-06" answer": "Jolly Rancher" "category": "CANDY"
Hash Clé 2 Hash Clé 3
... ... ...
# curl -XGET "http://localhost:8098/search/queer/jeopardy_index?wt=json&q=_yz_rk:show_4680"
{"responseHeader":{"status":0,"QTime":15,"params":{"q":"_yz_rk:show_4680","shards":"172.17.0.2:8093/internal_solr/
jeopardy_index","172.17.0.2:8093":"_yz_pn:64 OR (_yz_pn:61 AND (_yz_fpn:61)) OR _yz_pn:60 OR _yz_pn:57 OR _yz_pn:54 OR
_yz_pn:51 OR _yz_pn:48 OR _yz_pn:45 OR _yz_pn:42 OR _yz_pn:39 OR _yz_pn:36 OR _yz_pn:33 OR _yz_pn:30 OR _yz_pn:27 OR
_yz_pn:24 OR _yz_pn:21 OR _yz_pn:18 OR _yz_pn:15 OR _yz_pn:12 OR _yz_pn:9 OR _yz_pn:6 OR _yz_pn:3","wt":"json"}},"response":
{"numFound":1,"start":0,"maxScore":12.999648,"docs":[{"round":"Jeopardy!","air_date":"2008-03-13T00:00:00Z","category":"ALL MY
SONS","_yz_id":"1*default*jeopardy_bucket*show_4680*64","_yz_rk":"show_4680","_yz_rt":"default","_yz_rb":"jeopardy_bucket"}]}}
Hashtree Riak KV
Coupons maintenant Riak Search :
On redémarre le serveur.
Nous n’avons plus accès à l’index. Regardons le document stocké dans Riak KV :
Mettons à jour ce document en modifant la valeur de la clé category :
Relançons maintenant Riak Search :
On redémarre le serveur.
Pour le moment l’index n’a pas encore été mis à jour. Les deux hashtrees sont donc différents.
Au bout de quelques instants, l’AAE se rend compte de la différence entre la donnée présente dans le
Bucket et celle qui est indexée :
Suite à cette réparation, l’index a bien été mis à jour :
28
#sed -i -- 's/search = on/search = off/g' /etc/riak/riak.conf
#curl -XGET "http://localhost:8098/types/default/buckets/jeopardy_bucket/keys/show_4680"
{"category":"ALL MY SONS","air_date":"2008-03-13T00:00:00Z","question":"Gutzon Borglum died in 1941 so his son, Lincoln, fnished
sculpting the 4 fgures of this memorial","value":"$200","answer":"Mount Rushmore","round":"Jeopardy!","show_number":5419}
#curl -XPOST http://localhost:8098/types/default/buckets/jeopardy_bucket/keys/show_4680 -H "Content-Type: application/json" -d
'{"category":"TEST RIAK SEARCH","air_date":"2008-03-13T00:00:00Z","question":"Gutzon Borglum died in 1941 so his son, Lincoln,
fnished sculpting the 4 fgures of this memorial","value":"$200","answer":"Mount
Rushmore","round":"Jeopardy!","show_number":5419}'
#curl -XGET "http://localhost:8098/types/default/buckets/jeopardy_bucket/keys/show_4680"
{"category":"TEST RIAK SEARCH","air_date":"2008-03-13T00:00:00Z","question":"Gutzon Borglum died in 1941 so his son, Lincoln,
fnished sculpting the 4 fgures of this memorial","value":"$200","answer":"Mount
Rushmore","round":"Jeopardy!","show_number":5419}
#sed -i -- 's/search = off/search = on/g' /etc/riak/riak.conf
#curl -XGET "http://localhost:8098/search/query/jeopardy_index?wt=json&q=_yz_rk:show_4680"
{"responseHeader":{"status":0,"QTime":37,"params":{"q":"_yz_rk:show_4680","shards":"172.17.0.2:8093/internal_solr/
jeopardy_index","172.17.0.2:8093":"_yz_pn:64 OR (_yz_pn:61 AND (_yz_fpn:61)) OR _yz_pn:60 OR _yz_pn:57 OR _yz_pn:54 OR
_yz_pn:51 OR _yz_pn:48 OR _yz_pn:45 OR _yz_pn:42 OR _yz_pn:39 OR _yz_pn:36 OR _yz_pn:33 OR _yz_pn:30 OR _yz_pn:27 OR
_yz_pn:24 OR _yz_pn:21 OR _yz_pn:18 OR _yz_pn:15 OR _yz_pn:12 OR _yz_pn:9 OR _yz_pn:6 OR _yz_pn:3","wt":"json"}},"response":
{"numFound":1,"start":0,"maxScore":12.999648,"docs":[{"round":"Jeopardy!","air_date":"2008-03-13T00:00:00Z","category":"ALL MY
SONS","_yz_id":"1*default*jeopardy_bucket*show_4680*64","_yz_rk":"show_4680","_yz_rt":"default","_yz_rb":"jeopardy_bucket"}]}}
[info] <0.19507.0>@yz_exchange_fsm:key_exchange:209 Will delete 0 keys and repair 1 keys of partition
1415829711164312202009819681693899175291684651008 for prefist
{1415829711164312202009819681693899175291684651008
#curl -XGET "http://localhost:8098/search/query/jeopardy_index?wt=json&q=_yz_rk:show_4680"
{"responseHeader":{"status":0,"QTime":14,"params":{"q":"_yz_rk:show_4680","shards":"172.17.0.2:8093/internal_solr/
jeopardy_index","172.17.0.2:8093":"(_yz_pn:62 AND (_yz_fpn:62)) OR _yz_pn:61 OR _yz_pn:58 OR _yz_pn:55 OR _yz_pn:52 OR
_yz_pn:49 OR _yz_pn:46 OR _yz_pn:43 OR _yz_pn:40 OR _yz_pn:37 OR _yz_pn:34 OR _yz_pn:31 OR _yz_pn:28 OR _yz_pn:25 OR
_yz_pn:22 OR _yz_pn:19 OR _yz_pn:16 OR _yz_pn:13 OR _yz_pn:10 OR _yz_pn:7 OR _yz_pn:4 OR _yz_pn:1","wt":"json"}},"response":
{"numFound":1,"start":0,"maxScore":12.452759,"docs":[{"round":"Jeopardy!","air_date":"2008-03-13T00:00:00Z","category":"TEST RIAK
SEARCH","_yz_id":"1*default*jeopardy_bucket*show_4680*1","_yz_rk":"show_4680","_yz_rt":"default","_yz_rb":"jeopardy_bucket"}]}}
La réparation de la valeur dans l’index est bien marquée comme ayant été repérée et appliquée :
Alors que nous n’avions ici qu’un serveur et seulement 200 000 valeurs, il est nécessaire d’indiquer que le
processus a tout de même mis du temps avant de réaliser la réparation ; plusieurs dizaines de fragments ont
été analysés avant celui qui nous intéressait ; l’interrogation de l’index n’était donc pas cohérente par
rapport à la donnée présente en base. Selon Basho, c’est le prix à payer pour obtenir une interface de
recherche évoluée : les échanges de données entre les deux systèmes apportent des incohérences qu’un
système seul met moins de temps à régler.
Les Secondary Indexes
Les Secondary Indexes (Index secondaires) – notés 2i – sont une autre sorte d’index, moins évolués
que ceux fournis par Riak Search mais directement intégrés au moteur de Riak KV. Il s’agit ici de
métadonnées liées à chaque document, créées lors de l’écriture de celui-ci et stockées dans le Bucket avec
l’objet. Ils permettent l’interrogation des données en se basant sur un autre critère que la clé du document.
Ici, seuls les éléments de type « string » ou « int » peuvent être indexés ; de plus, il n’est possible d’exécuter
que des requêtes de recherche exacte ou de recherche d’intervalle. Attention cependant, bien que faciles et
pratiques à mettre en place, Riak KV recommande cependant de privilégier Riak Search dans les cas où les
besoins de recherche sont complexes. Il est en effet, avec les 2I, impossible de composer des requêtes avec
un argument « OR » ou « AND » ; il est nécessaire d’exécuter les deux requêtes séparément et de fusionner
les résultats. Un autre possible inconvénient des 2I est qu’ils ne sont compatibles qu’avec deux backends :
LevelDB et Memory. Enfn, des problèmes de performances peuvent apparaître à cause des 2i sur de grands
Clusters. Les 2I peuvent néanmoins être utilisés comme entrée de traitement MapReduce.
Nous allons maintenant voir comment créer des index pour les documents de notre collection. L’absence de
schéma décrivant les documents comme dans Riak Search nous contraint d’entrer « à la main » la valeur
29
# riak-admin search aae-status
================================== Exchanges ==================================
Index Last (ago) All (ago)
-------------------------------------------------------------------------------
.
.
.
1415829711164312202009819681693899175291684651008 24.8 min 25.3 min
.
.
.
================================ Entropy Trees ================================
Index Built (ago)
-------------------------------------------------------------------------------
.
.
.
1415829711164312202009819681693899175291684651008 5.9 hr
.
.
.
================================ Keys Repaired ================================
Index Last Mean Max
-------------------------------------------------------------------------------
.
.
.
1415829711164312202009819681693899175291684651008 1 0 1
.
.
.
que nous souhaitons indexer. Par exemple, voici comment nous pourrions créer des 2I pour un document
de notre collection :
Nous alimentons ici deux index « category_bin » et « show_number_int » avec des valeurs que nous avons
nous même spécifées : elles n’ont pas été extraites du document. Modifons notre script d’insertion en
masse afn que chaque donnée de notre collection soit indexée ainsi.
Seule une partie de la collection suffsante pour les exemples suivants a été insérée.
Que pouvons nous faire ?
Rechercher les documents dont la category est « HISTORY » :
Remarquez que ce type d’index ne fait rien d’autre que de retourner la liste des identifants des documents
correspondants à notre requête.
Il est aussi possible de rechercher entre deux bornes, ici entre 1000 et 3000 :
(le numéro de show du document ne correspond pas au numéro d’id « show_x » entré par le script
30
#curl -v -X PUT http://localhost:8098/types/default/buckets/jeopardy_bucket_2I/keys/show_1
-H "Content-Type: application/json"
-H "x-riak-index-category_bin : CANDY"
-H "x-riak-index-show_number_int : 4335"
-d ‘{
"air_date": "2003-06-06",
"answer": "Jolly Rancher",
"category": "CANDY",
"question": "Bill Harmsen, who raised horses in Colo., happily founded this candy co. in 1949 to make money during the winter",
"round": "Final Jeopardy!",
"show_number": 4335,
"value": null
}’
#!/bin/bash
json_fle=$(cat jeopardy.json.format);
type="leveldb_backend"
bucket="jeopardy_bucket_2I"
i=0;
for data in $(echo "${json_fle}" | jq -r '.[] | @base64'); do
((i++));
_jq() {
echo ${data} | base64 --decode;
}
json_item="'"$(_jq)"'";
category=$(echo $json_item | grep -o -P '(?<="category":).*?(?=,)')
show_number=$(echo $json_item | grep -o -P '(?<="show_number":)[0-9]*')
insert_command=`echo curl -v -X PUT http://localhost:8098/types/$type/buckets/$bucket/keys/show_$i -H "’"Content-Type:
application/json"’" -H "’"x-riak-index-category_bin: $category"’" -H "’"x-riak-index-show_number_int: $show_number"’" -d $json_item`;
eval $insert_command;
done;
#curl -XGET http://localhost:32768/buckets/jeopardy_2I_bucket4/index/category_bin/HISTORY
{"keys":["show_417","show_25","show_405","show_7","show_393","show_19","show_1","show_399","show_13","show_411"]}
#curl -XGET http://localhost:32768/buckets/jeopardy_2I_bucket4/index/show_number_int/1000/3000
{"keys":
["show_2","show_393","show_9","show_417","show_399","show_542","show_6","show_522","show_744","show_125""show_630","sh
ow_25","show_405"...}
RÉPLICATION ET PARTITIONNEMENT
Nous avons vu jusqu’à présent quelques aspects de Riak KV concernant la gestion des données.
Cependant, comme tout système NoSQL, celui-ci prend tout son sens dans un contexte distribué. Riak KV se
veut être scalable, cohérent et disponible grâce à l’implémentation du système Dynamo d’Amazon. Nous
verrons dans cette partie comment mettre en place un cluster Riak KV et quelques mécanismes assurant la
cohérence des données.
Dynamo
Dynamo, introduit par Amazon avec son SGBD NoSQL Amazon DynamoDB, regroupe un ensemble
de techniques permettant à un système clé-valeur d’être facilement scalable, redondant, tolérant aux
pannes.
Aucun nœud du cluster n’a ici un rôle différent des autres ; il n’y a donc pas de nœud « master », chaque
nœud connaît l’ensemble des informations du cluster (schéma de partitionnement, localisation des
données, etc.) et est en mesure de répondre à un client souhaitant récupérer une donnée ou en écrire une.
Chaque nœud interrogé devient alors un nœud coordinateur, en charge de récupérer l’information
demandée localement ou sur un autre nœud et de la transmettre au client, ou bien de transmettre l’écriture
demandée au nœud qui en est responsable. Ce fonctionnement rend l’interaction du client avec le cluster
extrêmement simplifée ; il est inutile de connaître la façon dont les données sont réparties, de savoir à qui
s’adresser afn de pouvoir les exploiter, chaque nœud saura répondre à la requête. De plus, l’absence de
nœud « master » évite le «single point of failure » rendant inexploitable le cluster en cas de panne du
« master ».
Rappel du principe de hashage cohérent
Pour répartir les données au sein du cluster, Dynamo se base sur le hashage cohérent (consistent
hashing) auquel il ajoute la notion de virtual node (vnode).
Pour rappel, le hashage cohérent utilise une fonction de hashage (appliquée à la clé de la donnée – la
concaténation du nom de Bucket et de la clé du document ) afn de répartir les données sur un cercle
partitionné en n arcs de cercle égaux (le Hash Ring), n étant un nombre fxé suffsamment haut défni en
fonction de la fonction de hashage utilisée (par exemple, 2160
si on utilise l’algorithme de hashage SHA-1
comme Riak KV l’a choisi). Ces fragments de cercles sont alors numérotés, de 0 à n-1 (sens des aiguilles
d’une montre), et chaque donnée est alors affectée à un emplacement en fonction du mode de
correspondance choisi pour associer le hash résultant de son passage dans la fonction de hashage à un
nombre entier compris entre 0 et n-1.
31
Illustration 6: Représentation du
Hash Ring
0n - 1
Hash Clé « exemple »
Répartition des données
Dans le cas où le cluster n’est constitué que d’un seul nœud, celui-ci sera responsable de l’ensemble des
fragments du cercle. A chaque ajout d’un nœud au cluster, une valeur comprise entre 0 et n-1 est tirée au
sort ; le nœud sera alors responsable de l’ensemble des données affectées aux positions du cercle
comprises entre cette valeur et celle tirée au sort par le premier nœud rencontré sur le cercle.
Dynamo a poussé un peu plus loin le concept. A l’ajout d’un nœud dans le cluster, le mécanisme de
répartition lui attribue un ensemble d’intervalles sur le Hash Ring (nombre défni par la variable locale
ring_size (64 par défaut)). Autant que possible, le Hash Ring est partitionné en différents intervalles
équivalents (par défaut 64 espaces couvrant la même surface du cercle) et ceux-ci sont répartis
équitablement entre les différents nœuds.
32
Illustration 8: Répartition des
données du Hash Ring
Nœud 1
Nœud 3
Nœud 2
Légende
Illustration 7: Répartition de
données entre deux serveurs
Nœud 1
Nœud 2
Légende0n - 1
Ajoutdunœud2Ajoutdunœud3
Si les données sont séparées en 64 fragments et que nous avons 5 nœuds, chacun aura la charge de 12 ou
13 partitions (64 = 4 x 12 + 1 x 13). Ajoutons maintenant 3 nouveaux nœuds, chacun aura la responsabilité
de 8 partitions. Ces partitions sont gérées par des processus indépendants et ont le devoir de stockage des
données sur l’intervalle qui leur est attribué ; en cela, elle n’ont pas grand-chose de différents avec les
nœuds (nodes), c’est pourquoi elles sont appelées nœuds virtuels (vnodes pour virtual nodes).
Un nœud peut donc être vu comme un cluster à part entière gérant un ensemble de vnodes.
La réplication
Nous avons maintenant une visualisation claire de la façon suivant laquelle les données sont réparties sur
les différentes partitions d’un cluster.
Il n’est cependant pas suffsant de connaître la méthode de répartition des données sur les nœuds, il est
nécessaire que celles-ci soient toujours accessibles en cas de panne de nœud. Chaque nœud rejoignant le
cluster est en fait responsable, en plus des partitions qui lui ont été attribuées, de partitions d’autres nœuds
afn d’en conserver des réplicas. Ce système permet donc d’être tolèrent aux pannes mais aussi de pouvoir
répartir la charge des requêtes sur plusieurs nœuds au lieu d’un seul. Ainsi, un nœud physique aura en fait la
responsabilité de n/(nombre de nœuds physiques) x (nombres de réplicas confgurés) partitions.
Considérons que notre cluster a été divisé en 64 partitions, que nous avons 8 nœuds physiques et que nous
souhaitons que chaque donnée soit répliquée 3 fois, chaque nœud physique aura la responsabilité de
64/8x3 = 24 partitions.
Mais alors, comment sont répartis les réplicas sur les différents nœuds du cluster ?
Il est assez évident qu’il ne faut pas qu’une partition soit répliquée sur un même nœud physique, auquel cas
la réplication n’aurait aucun sens si ce nœud tombait en panne. Lorsqu’une donnée est attribuée après
hashage à une partition appartenant à un nœud, c’est ce nœud qui se charge de la réplication de cette
donnée sur les partitions « réplicas ». Si le paramètre de réplication n_val est confguré à 3 par exemple, les
partitions « réplicas » correspondront à des partitions appartenant aux 3 - 1 = 2 nœuds suivant le nœud
responsable. En pratique, ce sont des vnodes et non les nodes directement qui stockent les données ; il
n’est pas possible de prendre les n_val – 1 vnodes afn d’assurer la réplication des données, ces vnodes
pouvant faire partie de la même node que la première . Un parcours du cercle est donc mis en place,
excluant les vnodes appartenant à une node déjà récupérée, et acceptant la première vnode (et donc la plus
proche) issue d’une node non connue.
Cette réplication est à priori effcace, mais que se passe t’il si les n_val nœuds responsables de l’écriture
d’une donnée tombent simultanément en panne ?
La liste des nœuds responsable du stockage d’une donnée associée à une certaine clé est appelée « liste de
préférence ». Afn de prévenir tout problème de nœud, cette liste contient plus de nœuds que le paramètre
de réplication n_val ne le prévoit, et donc d’autres nœuds que les nœuds réplicas. A quoi servent donc les
33
Illustration 9: Réplication d'une
donnée avec n_val = 3
Nœud 1
Nœud 3
Nœud 2
Nœud 4
Nœud 5
Hash Clé « exemple »
serveurs listés qui n’ont, à priori, rien à voir avec cette donnée ? Les n_val premiers éléments de la liste sont
appelés « réplicas primaires » ; c’est en effet eux qui se chargent de la réplication de la donnée en temps
normal ; les autres nœuds sont appelés « réplicas secondaires ». Admettons que sur le schéma précédent,
les nœuds 1, 2 et 3 soient indisponibles et que le client demande le stockage d’une donnée ayant pour clé
« exemple ». Il serait possible de lui refuser cette écriture en vertu du non respect du quorum et surtout de
l’absence de l’intégralité des serveurs, ou de ne pas rendre la main tant qu’un nœud primaire n’est pas
disponible. Ce n’est pas le cas avec Dynamo qui utilise le mécanisme « Hinted Handoff ». Celui-ci permet au
nœud coordinateur de transmettre directement à un nœud secondaire une écriture dans le cas où aucun
nœud primaire n’est accessible. Cette donnée supplémentaire sera alors stockée à part et marquée comme
devant appartenir au nœud primaire ; une fois le nœud primaire de nouveau accessible, l’information lui sera
envoyée pour prise en charge et supprimée du nœud secondaire. On parle alors de quorum dégradé
(sloppy quorum) car l’écriture (temporaire) et la lecture de la donnée pourront se faire en dépit du respect du
quorum.
Maintien de la cohérence des données
Plusieurs méthodes sont employées afn de vérifer et maintenir la cohérence des données entre les
vnodes :
- Nous retrouvons ici aussi le mécanisme de l’AAE défni précédemment dans la section dédiée à Riak
Search ; l’AAE permet ici de vérifer et de réparer les incohérences entre un nœud virtuel et ses réplicas.
- Le mécanisme « Read Repair » permet quant à lui de réparer des incohérences détectées entre les
différents réplicas lors d’une lecture de données. En effet, il arrive que lors d’une lecture de données, les
différents réplicas renvoient bien une valeur mais celle-ci diffère entre eux. Deux cas peuvent alors se
produire : 1) un nœud renvoie une valeur « not found » et n’a donc pas encore reçu de copie de l’objet, Riak
force donc la nœud à mettre à jour ses données 2) un nœud répond avec une version de la donnée
antérieure (un vector clock antérieur ; cette notion est détaillée juste après) et il est donc nécessaire de la
mettre à jour sur ce nœud.
34
Illustration 10: Gestion d'une écriture
par un nœud secondaire
Nœud 1
Nœud 3
Nœud 2
Nœud 4
Nœud 5
Hash Clé « exemple »
Liste de préférence
- Noeud 1
- Noeud 2
- Noeud 3
- Noeud 4
- Noeud 5
Donnée clé « exemple »
Propriété : Noeud 1
Ping ?
- Enfn, une méthode intéressante et essentielle au système Dynamo est celle des « Vector Clocks ». Celle-ci
permet de gérer les mises à jours concurrentes d’une même donnée par deux clients simultanément. Un
système classique ne se souciera pas vraiment de cette mise à jour concurrente et stockera soit les deux
versions différentes en laissant le client choisir, soit considérera que le dernier document mis à jour est le
document fnal. Les « Vectors Clocks » permettent d’éviter ce genre de situation dans la mesure du possible.
Le principe est assez simple à comprendre mais plutôt complexe à expliciter, c’est pourquoi j’utiliserai un
exemple.
Voici un scénario simple :
Le schéma précédent montre les modifcation faites simultanément par deux clients sur un seul document.
Connaître le document à stocker en base est simple dans certains cas, plus complexes dans d’autres. A
chaque élément du document est associé un « vector clock », indiquant sa version dans le temps; lors d’une
mise à jour d’une valeur, cette version est mise à jour.
A l’unité de temps 1, chaque client a modifé une valeur de clé différente dans le document. On déduit alors
de façon assez intuitive que le document stocké en base devra être une fusion de ces deux modifcations :
35
0
{
"clé_un: "valeur_1_1",
"clé_deux": "valeur_2_1"
}
{
"clé_un: "valeur_1_2",
"clé_deux": "valeur_2_1"
}
Donnée en base
Modifcation client 1
Modifcation client 2
1
Modifcation client 2
2
Modifcation client 1
{
"clé_un: "valeur_1_1",
"clé_deux": "valeur_2_2"
}
{
"clé_un: "valeur_1_3",
"clé_deux": "valeur_2_3"
}
{
"clé_un: "valeur_1_4",
"clé_deux": "valeur_2_3"
}
{
"clé_un: ["valeur_1_2","valeur_1_2"],
"clé_deux": ["valeur_2_1","valeur_2_2"]
}
Axetemporel
Les deux mises à jour sont satisfaites.
A l’unité de temps 2, les deux clients ont modifé de la même façon la valeur de la deuxième clé du
document ; cette dernière doit donc prendre la nouvelle valeur :
Comme précédemment, ils ont tous deux mis à jour une même clé en même temps mais une valeur
différente. On ne peut pas choisir arbitrairement de privilégier le client 1 ou le client 2, le document stocké
en base sera alors :
On voit ici l’effcacité de cette méthode. Les documents ne sont pas simplement vus comme des objets
physiques fgés avec une version temporelle fxée, ils sont interprétés comme des objets logiques avec
plusieurs attributs pouvant être mis à jour simultanément. Cette méthode permet de résoudre de nombreux
confits mais on constate assez facilement qu’elle n’est pas parfaite. Elle permet cependant la gestion des
mises à jour concurrentes de clé modifée de façon similaire et laisse le choix entre les différentes valeurs
entrées pour une même clé. Attention, comme nous le verrons dans la phase pratique en fn de rapport, les
Vector Clocks ne sont qu’une gestion de timestamp associé aux données insérées et non une méthode de
« merge » de ces données.
Mise en place d’un cluster Riak KV
Passons maintenant à la pratique et mettons en place notre propre cluster Riak KV. Pour ce rapport, un
cluster composé de cinq nodes sera mis en place.
Construction du cluster et commandes de base
Voici l’architecture de notre cluster :
Nœud 2
36
{
…,
"clé_deux": "valeur_2_3"
}
{
"clé_un: ["valeur_1_3 ", " valeur_1_4 "],
"clé_deux": "valeur_2_3"
}
Riak-kv-5
172.17.0.6
Riak-kv-3
172.17.0.4
Riak-kv-2
172.17.0.3
Riak-kv-4
172.17.0.5
Riak-kv
172.17.0.2
Routeur
172.17.0.1
Par défaut, lors du lancement d’une node, un cluster est déjà prêt à être rejoint sur chacune d’entre elles.
Nous allons donc commencer simplement à faire en sorte que les nodes riak-kv2, riak-kv3, riak-kv4 et riak-
kv5 rejoignent le cluster de riak-kv.
37
# riak-admin cluster join riak@172.17.0.2
Success: staged join request
for 'riak@172.17.0.3' to 'riak@172.17.0.2'
riak-kv-2
# riak-admin cluster join riak@172.17.0.2
Success: staged join request
for 'riak@172.17.0.4' to 'riak@172.17.0.2'
riak-kv-3
# riak-admin cluster join riak@172.17.0.2
Success: staged join request
for 'riak@172.17.0.5' to 'riak@172.17.0.2'
riak-kv-4
# riak-admin cluster join riak@172.17.0.2
Success: staged join request
for 'riak@172.17.0.6' to 'riak@172.17.0.2'
riak-kv-5
'riak@172.17.0.3' joined cluster with status 'joining'
'riak@172.17.0.4' joined cluster with status 'joining'
'riak@172.17.0.5' joined cluster with status 'joining'
'riak@172.17.0.6' joined cluster with status 'joining'
# riak-admin cluster plan
=============================== Staged Changes ===============================
Action Details(s)
-------------------------------------------------------------------------------
join 'riak@172.17.0.3'
join 'riak@172.17.0.4'
join 'riak@172.17.0.5'
join 'riak@172.17.0.6'
-------------------------------------------------------------------------------
NOTE: Applying these changes will result in 1 cluster transition
############################################################################
After cluster transition 1/1
############################################################################
================================= Membership ================================
Status Ring Pending Node
-------------------------------------------------------------------------------
valid 100.0% 20.3% 'riak@172.17.0.2'
valid 0.0% 20.3% 'riak@172.17.0.3'
valid 0.0% 20.3% 'riak@172.17.0.4'
valid 0.0% 20.3% 'riak@172.17.0.5'
valid 0.0% 18.8% 'riak@172.17.0.6'
-------------------------------------------------------------------------------
Valid:5 / Leaving:0 / Exiting:0 / Joining:0 / Down:0
Transfers resulting from cluster changes: 51
13 transfers from 'riak@172.17.0.2' to 'riak@172.17.0.4'
13 transfers from 'riak@172.17.0.2' to 'riak@172.17.0.3'
12 transfers from 'riak@172.17.0.2' to 'riak@172.17.0.6'
13 transfers from 'riak@172.17.0.2' to 'riak@172.17.0.5'
# riak-admin cluster commit
Cluster changes committed
riak-kv
Détail des commandes :
- riak-admin cluster join [node] permet à une node de joindre le cluster de la node passée en argument. Il est
aussi possible d’indiquer qu’une node doit remplacer une node déjà existante dans le cluster grâce à la
commande riak-admin cluster replace [node_à_remplacer] [nouvelle_node].
- riak-admin cluster plan permet de visualiser les actions prévues sur le cluster et les modifcations qu’elles
engendreront. On voit ici que quatre nouveaux serveurs veulent rejoindre le cluster ; la répartition des
données et aussi détaillée. Ce « plan d’exécution » peut être remis à zéro grâce à la commande riak-admin
cluster clear.
- riak-admin cluster commit ne peut s’exécuter qu’après la commande précédente. Cette commande valide
et applique les actions prévues vues précédemment.
Dans l’interface Web d’administration de chacune des nodes, les membres du cluster sont visibles :
Visualisons le statut de notre cluster (à partir de n’importe lequel des nœuds) :
38
Illustration 11: Visualtion du Cluster depuis l'interface Web
# riak-admin cluster status
---- Cluster Status ----
Ring ready: true
+----------------------------------+----------+--------+-------+-------------+
| node | status | avail | ring |pending |
+----------------------------------+----------+--------+-------+-------------+
| (C) riak@172.17.0.2 | valid | up | 20.3| -- |
| riak@172.17.0.3 | valid | up | 20.3| -- |
| riak@172.17.0.4 | valid | up | 20.3| -- |
| riak@172.17.0.5 | valid | up | 20.3| -- |
| riak@172.17.0.6 | valid | up | 18.8| -- |
+----------------------------------+----------+-------+-------+---------------+
Key: (C) = Claimant; availability marked with '!' is unexpected
Les cinq nodes sont ici listées.
La première colonne « status » indique le statut de chaque nœud :
- valid : le nœud fait partie intégrante du cluster
- joining : le nœud est prêt à rejoindre le cluster. Le plan d’exécution doit être validé.
- leaving : le nœud quitte le cluster et redistribue les données dont il avait la charge
- exiting : le nœud a terminé l’étape « leaving » et est en train de quitter le cluster
- down : le nœud n’est pas joignable par les autres membres du cluster
La seconde colonne « avail » indique « up » si le nœud est accessible et peut prendre en charge des
requêtes, « down » s’il n’est pas accessible.
La troisième colonne, « ring », indique le pourcentage du cercle de répartition des données donc chaque
nœud est responsable.
Enfn, « pending » indique le nombre de transferts sortant ou entrant de chaque nœud.
Le paramètre (C) placé devant notre premier serveur n’est pas anodin ; il indique qu’il s’agit ici de notre
« claimant node » (nœud demandeur littéralement) qui se charge de l’ajout et de la suppression des nodes
au sein de notre cluster. Cette node peut être indisponible sans pour autant perturber le bon
fonctionnement du cluster, ci ce n’est pour les tâches d’administration précisées précédemment. SI elle
n’est plus accessible, les actions relatives à la modifcation du cluster seront placées en attente ; si elle quitte
le cluster (qu’elle est marquée « down » en réalité), une autre node sera marquée comme « claimant » et se
chargera de ces tâches.
Éteignons le serveur riak-kv-2 et ré-affchons le statut du cluster :
Éteignons maintenant le serveur « claimant » riak-kv et ré-affchons le statut du cluster :
39
# riak-admin cluster status
---- Cluster Status ----
Ring ready: true
+----------------------------------+----------+---------------+-------+-------------+
| node | status | avail | ring |pending |
+----------------------------------+----------+---------------+-------+-------------+
| (C) riak@172.17.0.2 | valid | up | 20.3| -- |
| riak@172.17.0.3 | valid | down ! | 20.3| -- |
| riak@172.17.0.4 | valid | up | 20.3| -- |
| riak@172.17.0.5 | valid | up | 20.3| -- |
| riak@172.17.0.6 | valid | up | 18.8| -- |
+----------------------------------+----------+--------------+-------+---------------+
Key: (C) = Claimant; availability marked with '!' is unexpected
# riak-admin cluster status
---- Cluster Status ----
Ring ready: true
+----------------------------------+----------+---------------+-------+-------------+
| node | status | avail | ring |pending |
+----------------------------------+----------+---------------+-------+-------------+
| (C) riak@172.17.0.2 | valid | down ! | 20.3| -- |
| riak@172.17.0.3 | valid | down ! | 20.3| -- |
| riak@172.17.0.4 | valid | up | 20.3| -- |
| riak@172.17.0.5 | valid | up | 20.3| -- |
| riak@172.17.0.6 | valid | up | 18.8| -- |
+----------------------------------+----------+--------------+-------+---------------+
Key: (C) = Claimant; availability marked with '!' is unexpected
Essayons maintenant d’ajouter un nouveau serveur riak-kv-6 au cluster :
Demandons donc à un autre membre du cluster de nous y ajouter :
Nous constatons ici que riak-kv-3 est aussi en mesure d’ajouter une node au cluster. Voici le nouveau statut
de notre cluster :
Cependant l’ajout n’est pas complet. En effet, rappelons que le statut « joining » indique seulement que
cette nouvelle node est prête à rejoindre le cluster. Riak-kv-3 peut-elle prendre en charge l’ajout de cette
node dans le cluster et la répartition des données ?
Et bien non ! C’est ici qu’on constate le rôle de la « node claimant » ; elle est la seule à pouvoir modifer le
cluster. Il s’agit donc d’une node d’administration essentielle lorsque l’on veut mettre en place un cluster
Riak KV. Son accès doit être limité à un administrateur qui sait ce qu’il fait car c’est à partir d’elle et d’elle
seule que les actions impactantes sur le cluster peuvent être entreprises.
40
# riak-admin cluster join riak@172.17.0.2
Node riak@172.17.0.2 is not reachable!
# riak-admin cluster join riak@172.17.0.4
Success: staged join request for 'riak@172.17.0.7' to 'riak@172.17.0.4'
# riak-admin cluster status
---- Cluster Status ----
Ring ready: true
+----------------------------------+----------+---------------+-------+-------------+
| node | status | avail | ring |pending |
+----------------------------------+----------+---------------+-------+-------------+
| riak@172.17.0.7 | joining | up | 0.0 | |
|(C) riak@172.17.0.2 | valid | down ! | 20.3| -- |
| riak@172.17.0.3 | valid | down ! | 20.3| -- |
| riak@172.17.0.4 | valid | up | 20.3| -- |
| riak@172.17.0.5 | valid | up | 20.3| -- |
| riak@172.17.0.6 | valid | up | 18.8| -- |
+----------------------------------+----------+--------------+-------+---------------+
Key: (C) = Claimant; availability marked with '!' is unexpected
# riak-admin cluster plan
RPC to 'riak@172.17.0.4' failed: {'EXIT',
{{nodedown,'riak@172.17.0.2'},
{gen_server,call,
[{riak_core_claimant,'riak@172.17.0.2'},
plan,infnity]}}}
Redémarrons les nodes riak-kv et riak-kv-2.
Visualisons les demandes en attente de modifcation du cluster depuis notre node d’administration et
exécutons les :
41
# riak-admin cluster status
---- Cluster Status ----
Ring ready: true
+----------------------------------+----------+---------------+-------+-------------+
| node | status | avail | ring |pending |
+----------------------------------+----------+---------------+-------+-------------+
| riak@172.17.0.7 | joining | up | 0.0 | |
|(C) riak@172.17.0.2 | valid | up | 20.3| -- |
| riak@172.17.0.3 | valid | up | 20.3| -- |
| riak@172.17.0.4 | valid | up | 20.3| -- |
| riak@172.17.0.5 | valid | up | 20.3| -- |
| riak@172.17.0.6 | valid | up | 18.8| -- |
+----------------------------------+----------+--------------+-------+---------------+
Key: (C) = Claimant; availability marked with '!' is unexpected
# riak-admin cluster plan
=============================== Staged Changes ================================
Action Details(s)
-------------------------------------------------------------------------------
join 'riak@172.17.0.7'
-------------------------------------------------------------------------------
NOTE: Applying these changes will result in 1 cluster transition
###############################################################################
After cluster transition 1/1
###############################################################################
================================= Membership ==================================
Status Ring Pending Node
-------------------------------------------------------------------------------
valid 20.3% 17.2% 'riak@172.17.0.2'
valid 20.3% 17.2% 'riak@172.17.0.3'
valid 20.3% 17.2% 'riak@172.17.0.4'
valid 20.3% 17.2% 'riak@172.17.0.5'
valid 18.8% 15.6% 'riak@172.17.0.6'
valid 0.0% 15.6% 'riak@172.17.0.7'
-------------------------------------------------------------------------------
Valid:6 / Leaving:0 / Exiting:0 / Joining:0 / Down:0
Transfers resulting from cluster changes: 10
2 transfers from 'riak@172.17.0.2' to 'riak@172.17.0.7'
2 transfers from 'riak@172.17.0.3' to 'riak@172.17.0.7'
2 transfers from 'riak@172.17.0.4' to 'riak@172.17.0.7'
2 transfers from 'riak@172.17.0.5' to 'riak@172.17.0.7'
2 transfers from 'riak@172.17.0.6' to 'riak@172.17.0.7'
# riak-admin cluster commit
Cluster changes committed
La nouvelle node a bien rejoint le cluster cette fois-ci :
Retirons la maintenant:
42
# riak-admin cluster status
---- Cluster Status ----
Ring ready: true
+----------------------------------+----------+---------------+-------+-------------+
| node | status | avail | ring |pending |
+----------------------------------+----------+---------------+-------+-------------+
| riak@172.17.0.7 | valid | up | 15,6| |
|(C) riak@172.17.0.2 | valid | up | 17,2| -- |
| riak@172.17.0.3 | valid | up | 17,2| -- |
| riak@172.17.0.4 | valid | up | 17,2| -- |
| riak@172.17.0.5 | valid | up | 15,6| -- |
| riak@172.17.0.6 | valid | up | 17,2| -- |
+----------------------------------+----------+--------------+-------+---------------+
Key: (C) = Claimant; availability marked with '!' is unexpected
# riak-admin cluster leave riak@172.17.0.7
Success: staged leave request for 'riak@172.17.0.7'
L’information a bien été transmise à la node d’administration. Appliquons ce changement.
43
# riak-admin cluster plan
=============================== Staged Changes ================================
Action Details(s)
-------------------------------------------------------------------------------
leave 'riak@172.17.0.7'
-------------------------------------------------------------------------------
NOTE: Applying these changes will result in 2 cluster transitions
###############################################################################
After cluster transition 1/2
###############################################################################
================================= Membership ==================================
Status Ring Pending Node
-------------------------------------------------------------------------------
leaving 15.6% 0.0% 'riak@172.17.0.7'
valid 17.2% 20.3% 'riak@172.17.0.2'
valid 17.2% 20.3% 'riak@172.17.0.3'
valid 17.2% 20.3% 'riak@172.17.0.4'
valid 17.2% 20.3% 'riak@172.17.0.5'
valid 15.6% 18.8% 'riak@172.17.0.6'
-------------------------------------------------------------------------------
Valid:5 / Leaving:1 / Exiting:0 / Joining:0 / Down:0
Transfers resulting from cluster changes: 10
2 transfers from 'riak@172.17.0.7' to 'riak@172.17.0.5'
2 transfers from 'riak@172.17.0.7' to 'riak@172.17.0.4'
2 transfers from 'riak@172.17.0.7' to 'riak@172.17.0.3'
2 transfers from 'riak@172.17.0.7' to 'riak@172.17.0.6'
2 transfers from 'riak@172.17.0.7' to 'riak@172.17.0.2'
###############################################################################
After cluster transition 2/2
###############################################################################
================================= Membership ==================================
Status Ring Pending Node
-------------------------------------------------------------------------------
valid 20.3% -- 'riak@172.17.0.2'
valid 20.3% -- 'riak@172.17.0.3'
valid 20.3% -- 'riak@172.17.0.4'
valid 20.3% -- 'riak@172.17.0.5'
valid 18.8% -- 'riak@172.17.0.6'
-------------------------------------------------------------------------------
Valid:5 / Leaving:0 / Exiting:0 / Joining:0 / Down:0
# riak-admin cluster commit
Cluster changes committed
La node quitte alors le cluster en redistribuant préalablement les données dont elle avait la charge :
44
# riak-admin cluster status
---- Cluster Status ----
Ring ready: true
+----------------------------------+----------+---------------+-------+-------------+
| node | status | avail | ring |pending |
+----------------------------------+----------+---------------+-------+-------------+
| riak@172.17.0.7 | valid | up | 15,6| 0,0 |
|(C) riak@172.17.0.2 | valid | up | 17,2| 20,3 |
| riak@172.17.0.3 | valid | up | 17,2| 20,3 |
| riak@172.17.0.4 | valid | up | 17,2| 20,3 |
| riak@172.17.0.5 | valid | up | 15,6| 20,3 |
| riak@172.17.0.6 | valid | up | 17,2| 18,8 |
+----------------------------------+----------+--------------+-------+---------------+
Key: (C) = Claimant; availability marked with '!' is unexpected
# riak-admin cluster status
---- Cluster Status ----
Ring ready: true
+----------------------------------+----------+---------------+-------+-------------+
| node | status | avail | ring |pending |
+----------------------------------+----------+---------------+-------+-------------+
| riak@172.17.0.7 | valid | up | 3,1| 0,0 |
|(C) riak@172.17.0.2 | valid | up | 20,3| 20,3 |
| riak@172.17.0.3 | valid | up | 18,8| 20,3 |
| riak@172.17.0.4 | valid | up | 18,8| 20,3 |
| riak@172.17.0.5 | valid | up | 20,3| 20,3 |
| riak@172.17.0.6 | valid | up | 18,8| 18,8 |
+----------------------------------+----------+--------------+-------+---------------+
Key: (C) = Claimant; availability marked with '!' is unexpected
# riak-admin cluster status
---- Cluster Status ----
Ring ready: true
+----------------------------------+----------+---------------+-------+-------------+
| node | status | avail | ring |pending |
+----------------------------------+----------+---------------+-------+-------------+
| riak@172.17.0.7 | valid | up | 0,0| 0,0 |
|(C) riak@172.17.0.2 | valid | up | 20,3| 20,3 |
| riak@172.17.0.3 | valid | up | 20,3| 20,3 |
| riak@172.17.0.4 | valid | up | 20,3| 20,3 |
| riak@172.17.0.5 | valid | up | 20,3| 20,3 |
| riak@172.17.0.6 | valid | up | 18,8| 18,8 |
+----------------------------------+----------+--------------+-------+---------------+
Key: (C) = Claimant; availability marked with '!' is unexpected
# riak-admin cluster status
---- Cluster Status ----
Ring ready: true
+----------------------------------+----------+---------------+-------+-------------+
| node | status | avail | ring |pending |
+----------------------------------+----------+---------------+-------+-------------+
|(C) riak@172.17.0.2 | valid | up | 20.3| -- |
| riak@172.17.0.3 | valid | up | 20.3| -- |
| riak@172.17.0.4 | valid | up | 20.3| -- |
| riak@172.17.0.5 | valid | up | 20.3| -- |
| riak@172.17.0.6 | valid | up | 18.8| -- |
+----------------------------------+----------+--------------+-------+---------------+
Key: (C) = Claimant; availability marked with '!' is unexpected
Enfn, nous pouvons aussi visualiser le statut de notre « ring » ainsi (les informations sont similaires à celles
vues précédemment) :
Paramétrage du cluster
Nous avons mis en place dans la partie précédente un cluster sans nous soucier des paramètres de
réplication. Comme nous l’avons vu précédemment, ces paramètres se situent au niveau des Bucket Types
ou des Buckets eux-mêmes ; il est ainsi possible, au sein d’un même cluster, de défnir plusieurs politiques
relatives à la réplication en fonction de nos collections de données. Ces paramètres peuvent aussi être
directement passés dans les requêtes (PUT, GET, POST, DELETE).
Paramètre Valeur par défaut Description
n_val 3 Il s’agit du facteur de réplication, c’est à dire le nombre de nœuds d’un
cluster qui stockeront chaque donnée insérée
r quorum Le nombre de serveurs nécessaire qui doivent répondre afn d’assurer
l’exécution d’une requête de lecture
w quorum Le nombre de serveurs nécessaire qui doivent répondre afn d’assurer
l’exécution d’ une requête d’écriture
pr 0 Le nombre de vnodes primaires nécessaire qui doivent répondre afn
d’assurer l’exécution d’ une requête de lecture
pw 0 Le nombre de vnodes primaires nécessaire qui doivent répondre afn
d’assurer l’exécution d’ une requête d’écriture
dw quorum Le nombre de serveurs devant acquitter l’écriture d’une donnée avant
de rendre la main au client
rw quorum Paramètre remplaçant les valeurs de r et w si ces variables n’ont pas été
défnies
notfound_ok true Détermine la réponse de Riak si jamais une erreur de lecture a lieu sur
un nœud.
true : si le premier nœud à répondre n’a pas de copie de la donnée, une
erreur « not found » est retournée.
false indique à Riak KV de continuer de chercher la donnée autant de
fois qu’il y a de réplicas confgurés
basic_quorum false Dans le cas où la variable précédente a été confgurée à false, passer ce
paramètre à true indiquera à Riak de ne pas continuer la recherche de
la donnée et de retourner une erreur « not found » dans le cas où un
nombre de serveurs équivalent au quorum n’ont pas pu trouver la
donnée.
45
# riak-admin ring-status
================================== Claimant ===================================
Claimant: 'riak@172.17.0.2'
Status: up
Ring Ready: true
============================== Ownership Handoff ==============================
No pending changes.
============================== Unreachable Nodes ==============================
All nodes are up and reachable
Expérimentations sur notre cluster
Nous allons dans cette partie réaliser plusieurs tests en vue de vérifer et de comprendre les
différents paramètres d’un cluster Riak KV et ce qu’ils impliquent. Le cluster sera composé des cinq mêmes
serveurs que ceux vus précédemment et l’anneau sera découpé en 8 fragments :
(commande exécutée sur chaque nœud)
La répartition des fragments est visible au niveau de la ligne « ring_ownership » :
- 2 fragments sont gérés par les nodes riak-kv, riak-kv-2 et riak-kv-3
- 1 fragment est géré par les nodes riak-kv-4 et riak-kv-5
L’intégralité des données de notre collection « jeopardy » a été chargée dans un Bucket
« jeopardy_bucket » de type « jeopardy_bucket_type ». Pour le moment, aucune réplication n’est
demandée pour les données de ce Bucket. Dans la suite de cette partie, l’ensemble des modifcations de
paramètres appliquées au Bucket Type « jeopardy_bucket_type » se fera via l’interface web afn de faciliter
la lecture de ceux-ci (et pour un soucis de facilité de mise en place !).
Voici comment ont été réparties les données entre les différentes vnodes (nombre indiqué au niveau du
key_count):
46
# sed -i -- 's/## ring_size = 64/ring_size = 8/g' /etc/riak/riak.conf
# riak-admin status | grep ring
ring_creation_size : 8
ring_members : ['riak@172.17.0.2','riak@172.17.0.3','riak@172.17.0.4','riak@172.17.0.5','riak@172.17.0.6']
ring_num_partitions : 8
ring_ownership : <<"[{'riak@172.17.0.2',2},n {'riak@172.17.0.3',2},n {'riak@172.17.0.4',2},n {'riak@172.17.0.5',1},n{'riak@172.17.0.6',1}]">>
# riak-admin vnode-status
Vnode status information
-------------------------------------------
VNode: 0
Backend: riak_kv_bitcask_backend
Status:
[{key_count,27337},{status,[]}]
VNode: 913438523331814323877303020447...
Backend: riak_kv_bitcask_backend
Status:
[{key_count,27132},{status,[]}]
riak-kv
# riak-admin vnode-status
Vnode status information
-------------------------------------------
VNode: 0
Backend: riak_kv_bitcask_backend
Status:
[{key_count,27342},{status,[]}]
Vnode: 109612622799817718865276362453...
Backend: riak_kv_bitcask_backend
Status:
[{key_count,27125},{status,[]}]
riak-kv-2
# riak-admin vnode-status
Vnode status information
-------------------------------------------
VNode: 0
Backend: riak_kv_bitcask_backend
Status:
[{key_count,27232},{status,[]}]
VNode: 127881393266454005342822422422...
Backend: riak_kv_bitcask_backend
Status:
[{key_count,27249},{status,[]}]
riak-kv-3
# riak-admin vnode-status
Vnode status information
-------------------------------------------
VNode: 548063113999088594326381812268...
Backend: riak_kv_bitcask_backend
Status:
[{key_count,26991},{status,[]}]
riak-kv-4
# riak-admin vnode-status
Vnode status information
-------------------------------------------
VNode: 730750818665451459101842416358...
Backend: riak_kv_bitcask_backend
Status:
[{key_count,26903},{status,[]}]
riak-kv-5
Ce qui, sous forme de graphe, pourrait être représenté ainsi :
On constate bien ici la puissance du Hash Ring : les données ont été également réparties entre les
différentes vnodes.
Nous pouvons observer la méthode utilisée par Riak KV pour répartir chaque donnée sus le nœud virtuel
responsable de son stockage ; pour cela, nous avons besoin du client Erlang qui est capable d’exécuter les
bibliothèques internes au produit Riak KV.
Afn d’entrer dans l’interpréteur de commande Erlang, il est nécessaire de passer cette commande shell sur
n’importe quel nœud :
Nous voilà dans l’interpréteur de commandes Erlang.
Dans la première variable « Hash », nous indiquons à Erlang de stocker la valeur de hashage résultant de la
concaténation du nom du Bucket et de la clé du document. Ensuite, en fonction de cette valeur de hashage,
on interroge le Hash Ring afn de connaître la liste de préférence des vnodes chargées de stocker cette
donnée. Aucun problème n’a eu lieu lors de l’insertion, la donnée a donc dû se retrouver stockée dans la
vnode du serveur riak-kv-3 (127.17.0.4). Nous n’avons ici qu’une seule node primaire (pas de réplication),
mais le processus d’insertion est dans tous les cas :
47
Illustration 12: Répartition des données dans
notre Cluster
riak-kv
riak-kv
riak-kv-2
riak-kv-2
riak-kv-3
riak-kv-3
riak-kv-4
riak-kv-5
# riak attach
(riak@172.17.0.2)1> Hash = riak_core_util:chash_key({<<"jeopardy_bucket">>, <<"show_1">>}).
<<211,199,82,186,199,192,224,69,33,134,234,19,67,138,222, 23,191,153,67,72>>
(riak@172.17.0.2)2> Preference_list = riak_core_ring:prefist(Hash, Ring).
[{1278813932664540053428224228626747642198940975104,
'riak@172.17.0.4'},
{0,'riak@172.17.0.2'},
{182687704666362864775460604089535377456991567872,
'riak@172.17.0.3'},
{365375409332725729550921208179070754913983135744,
'riak@172.17.0.4'},
{548063113999088594326381812268606132370974703616,
'riak@172.17.0.5'},
{730750818665451459101842416358141509827966271488,
'riak@172.17.0.6'},
{913438523331814323877303020447676887284957839360,
'riak@172.17.0.2'},
{1096126227998177188652763624537212264741949407232,
'riak@172.17.0.3'}]
- calcul du hash pour la donnée à insérer
- récupération de la liste des nodes responsables de l’insertion de cette donnée
- récupération des n_val premiers serveurs listés (les nœuds primaires)
- envoi de la donnée sur ces trois serveurs
Par ailleurs, nous pouvons vérifer que la valeur de hashage associée à la donnée est bien un entier :
La réplication
Nous allons maintenant modifer le paramètre de réplication n_val de telle sorte que chaque donnée
se trouve désormais sur trois vnodes différentes.
Attention : la modifcation de ce paramètre alors que des données ont déjà été insérées précédemment est
fortement déconseillé par les équipes de Riak KV. Il ne s’agit donc pas d’une action à reproduire dans un
environnement de production. Après avoir changé la valeur de n_val, je suis contraint de lancer une
réparation sur chaque nœud (grâce à l’interface Erlang encore une fois). Ces étapes ne sont pas
intéressantes et ne refètent pas l’utilisation qui doit être faite de Riak KV, c’est pourquoi je ne les détaillerai
pas ici.
48
(riak@172.17.0.2)3> <<Integer_value:160/integer>> = Hash.
<<211,199,82,186,199,192,224,69,33,134,234,19,67,138,222,
23,191,153,67,72>>
(riak@172.17.0.2)4> Integer_value.
1209042107703822226148045677343061650479427175240
Observons maintenant le nombre de données stocké sur chaque nœud :
A vue d’œil, on constate donc bien que chaque vnode stocke environ trois fois plus de données qu’avant,
ses propres données ainsi que les données répliquées. Où est désormais stockée notre donnée ayant pour
id « show_1 » ?
$
A vue d’œil, on constate donc bien que chaque vnode stocke environ trois fois plus de données qu’avant,
ses propres données ainsi que les données répliquées. Où est désormais stockée notre donnée ayant pour
id « show_1 » ?
Elles sont présentes sur les n_val premiers serveurs de la liste de préférence soit riak-kv-3, riak-kv et riak-kv-2.
La commande :
renvoie bien le contenu la donnée tant qu’un des trois serveurs est en accessible. Cependant, dès que les
trois serveurs sont hors lignes, un « not found » est émis. (ici on interroge le serveur riak-kv-5)
49
# riak-admin vnode-status
Vnode status information
-------------------------------------------
VNode: 0
Backend: riak_kv_bitcask_backend
Status:
[{key_count,72507},{status,[]}]
VNode: 913438523331814323877303020447...
Backend: riak_kv_bitcask_backend
Status:
[{key_count,80824},{status,[]}]
riak-kv
# riak-admin vnode-status
Vnode status information
-------------------------------------------
VNode: 0
Backend: riak_kv_bitcask_backend
Status:
[{key_count,81928},{status,[]}]
Vnode: 109612622799817718865276362453...
Backend: riak_kv_bitcask_backend
Status:
[{key_count,81085},{status,[]}]
riak-kv-2
# riak-admin vnode-status
Vnode status information
-------------------------------------------
VNode: 0
Backend: riak_kv_bitcask_backend
Status:
[{key_count,81859},{status,[]}]
VNode: 127881393266454005342822422422...
Backend: riak_kv_bitcask_backend
Status:
[{key_count,81506},{status,[]}]
riak-kv-3
# riak-admin vnode-status
Vnode status information
-------------------------------------------
VNode: 548063113999088594326381812268...
Backend: riak_kv_bitcask_backend
Status:
[{key_count,81386},{status,[]}]
riak-kv-4
# riak-admin vnode-status
Vnode status information
-------------------------------------------
VNode: 730750818665451459101842416358...
Backend: riak_kv_bitcask_backend
Status:
[{key_count,80872},{status,[]}]
riak-kv-5
(riak@172.17.0.2)1> Hash = riak_core_util:chash_key({<<"jeopardy_bucket">>, <<"show_1">>}).
<<211,199,82,186,199,192,224,69,33,134,234,19,67,138,222, 23,191,153,67,72>>
(riak@172.17.0.2)2> Preference_list = riak_core_ring:prefist(Hash, Ring).
[[{1278813932664540053428224228626747642198940975104,
'riak@172.17.0.4'},
{0,'riak@172.17.0.2'},
{182687704666362864775460604089535377456991567872,
'riak@172.17.0.3'},
{365375409332725729550921208179070754913983135744,
'riak@172.17.0.4'},
{548063113999088594326381812268606132370974703616,
'riak@172.17.0.5'},
{730750818665451459101842416358141509827966271488,
'riak@172.17.0.6'},
{913438523331814323877303020447676887284957839360,
'riak@172.17.0.2'},
{1096126227998177188652763624537212264741949407232,
'riak@172.17.0.3'}]
# curl -v -XGET http://172.17.0.6:8098/types/jeopardy_bucket_type/buckets/jeopardy_bucket/keys/show_1
Testons maintenant l’effcacité des serveurs secondaires lors de l’écriture d’une donnée pour laquelle les
serveurs cibles primaires sont inaccessibles, puis d’une lecture. Pour ce faire, nous testerons tout d’abord
avec des valeurs de PR et de PW à 3 (l’insertion et la lecture ne pourront se faire que si tous les serveurs
primaires sont accessibles), puis nous remettrons ces valeurs à 0 (l’écriture sera acceptée même si aucun
serveur primaire n’est accessible ; il en va de même pour la lecture de cette donnée). Nous allons essayer
d’insérer une donnée pour un document d’id « show_999999 ». Récupérons tout d’abord la liste des
serveurs primaires afn de les désactiver au moment de l’écriture de la donnée :
Les trois serveurs primaires responsables du stockage de cette donnée sera donc les trois premiers serveurs
de la liste : riak-kv-5, riak-kv et riak-kv-2. Éteignons les.
Actuellement, nous exigeons que les serveurs primaires soient accessibles afn d’écrire une donnée :
Essayons maintenant d’insérer une donnée depuis le serveur riak-kv-3 :
50
(riak@172.17.0.2)1> Hash = riak_core_util:chash_key({<<"jeopardy_bucket">>, <<"show_999999">>}).
<<211,199,82,186,199,192,224,69,33,134,234,19,67,138,222, 23,191,153,67,72>>
(riak@172.17.0.2)2> Preference_list = riak_core_ring:prefist(Hash, Ring).
[{730750818665451459101842416358141509827966271488,
'riak@172.17.0.6'},
{913438523331814323877303020447676887284957839360,
'riak@172.17.0.2'},
{1096126227998177188652763624537212264741949407232,
'riak@172.17.0.3'},
{1278813932664540053428224228626747642198940975104,
'riak@172.17.0.4'},
{0,'riak@172.17.0.2'},
{182687704666362864775460604089535377456991567872,
'riak@172.17.0.3'},
{365375409332725729550921208179070754913983135744,
'riak@172.17.0.4'},
{548063113999088594326381812268606132370974703616,
'riak@172.17.0.5'}]
curl -v -X PUT http://172.17.0.4:8098/types/jeoprady_bucket_type/buckets/jeopardy_bucket/keys/show_999999 -H "Content-Type:
application/json" -d ‘{
"air_date": "2003-06-06",
"answer": "Jolly Rancher",
"category": "CANDY",
"question": "Bill Harmsen, who raised horses in Colo., happily founded this candy co. in 1949 to make money during the winter",
"round": "Final Jeopardy!",
"show_number": 4335,
"value": null
}’
La réponse du cluster nous indique bien qui est dans l’impossibilité d’accéder à notre requête d’écriture, les
paramètres n’étant pas respectés :
Passons maintenant les valeurs de PR et PW à 0 :
et relançons la demande d’écriture. Celle-ci a bien fonctionné. Aucun problème non plus pour lire la
donnée. Lorsque nous relançons les serveurs primaires, nous voyons apparaître la ligne « inted » indiquant
le transfert de la donné du serveur secondaire aux serveurs primaires :
Évidemment, les deux exemples précédents sont extrêmes mais peuvent tous les deux être envisagés en
production :
- soit on considère que le cluster ne doit fonctionner qu’avec les serveurs primaires au détriment de la
disponibilité de celui-ci
- soit on considère que les serveurs secondaires sont suffsants et que les serveurs primaires seront
rapidement accessibles mais cela ne peut se faire sans jouer sur la cohérence des données
On peut aussi imaginer passer le paramètre PW à 0 et le paramètre PR à 1 par exemple ; ainsi, les écritures
seront acceptées même si aucun serveur primaire n’est accessible mais la lecture de cette insertion/mise à
jour ne pourra se faire que lorsqu’un serveur primaire sera de nouveau accessible.
51
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 32890 (#0)
> PUT /types/jeopardy_bucket_type/buckets/jeopardy_bucket/keys/show_999999 HTTP/1.1
> Host: localhost:32890
> User-Agent: curl/7.58.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 13
>
* upload completely sent off: 13 out of 13 bytes
< HTTP/1.1 503 Service Unavailable
< Vary: Accept-Encoding
< Server: MochiWeb/1.1 WebMachine/1.10.9 (cafe not found)
< Date: Wed, 23 May 2018 11:53:43 GMT
< Content-Type: application/json
< Content-Length: 26
<
PW-value unsatisfed: 0/3
#hinted transfer of riak_kv_vnode from 'riak@172.17.0.4' 182687704666362864775460604089535377456991567872 to 'riak@172.17.0.6'
182687704666362864775460604089535377456991567872 completed: sent 349.00 B bytes in 1 of 1 objects in 0.17 seconds (1.96 KB/
second)
Je n’entreprendrai pas dans ce rapport de tests avec les variables r et w, celles-ci ayant déjà été développées
et testées dans le cours (avec un autre SGBD NoSQL mais le fonctionnement est identique).Il en va de même
pour les variables dw et rw dont le fonctionnement est facile à comprendre. Pour chacune d’entre elles, un
message similaire apparaît en cas d’erreur :
Les Vector Clocks
Nous allons maintenant nous pencher sur les vector clocks. Dans cette partie, nous mettrons de côté
nos documents jeopardy afn de faciliter au maximum la lecture de ce rapport.
Dans le scénario mis en place, Client 1 et Client 2 travaillerons sur un document commun qu’ils mettront à
jour chacun de leur côté. Bien sûr des confits apparaîtront entre ces mises à jour, nous verrons alors
comment Riak KV les gère. Cet exemple reprendra le schéma explicatif introduit dans la précédente partie
expliquant le fonctionnement des Vector Clocks.
Apportons un peu de précision à l’explication qui a précédemment été faite concernant les Vector Clocks.
Lors de l’insertion d’une donnée, un Vector Clock est attribué à l’objet ; il faut le voir comme une timestamp
indiquant l’âge de la donnée. Lorsqu’une mise à jour de la donnée est faite par un des clients, celui-ci, en
plus de la mise à jour en elle-même, précise le Vector Clock de la donnée qu’il souhaite mettre à jour.
Les expérimentations suivantes seront réalisées sur un seul serveur du cluster. Nous pourrions faire les
mêmes expériences sur différents serveurs mais cela n’apporterait rien à la démonstration du processus.
Commençons par créer un document ; c’est Client 1 qui va s’en charger.
Si nous lisons cette donnée ainsi que son header, nous constatons qu’un Vector Clock lui a été assigné :
52
#curl -X PUT http://172.17.0.2:8098/types/jeopardy_bucket_type/buckets/jeopardy_bucket/keys/vclocks_values_confict-H "Content-
Type: application/json" -d "{"cle_un":"valeur_1_1","cle_deux":"valeur_2_1"}"
#curl -i GET http://172.17.0.2:8098/types/jeopardy_bucket_type/buckets/jeopardy_bucket/keys/vclocks_values_confict
HTTP/1.1 403 Forbidden
Date: Thu, 24 May 2018 11:13:26 GMT
Server: Apache
Vary: Accept-Encoding
Content-Length: 202
Content-Type: text/html; charset=iso-8859-1
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>403 Forbidden</title>
</head><body>
<h1>Forbidden</h1>
<p>You don't have permission to access /
on this server.</p>
</body></html>
HTTP/1.1 200 OK
X-Riak-Vclock: aa85hYGBgzGDKBVI8+xkWvg9ne5HOwHjleAZTImMeK4N5UPUVviwA
Vary: Accept-Encoding
Server: MochiWeb/1.1 WebMachine/1.10.9 (cafe not found)
Link: </buckets/jeopardy_bucket>; rel="up"
Last-Modifed: Thu, 24 May 2018 11:09:19 GMT
ETag: "2t0OFfJHXrw9NjuJWDGqhE"
Date: Thu, 24 May 2018 11:13:26 GMT
Content-Type: application/json
Content-Length: 41
{clé_un:valeur_1_1,clé_deux:valeur_2_1}
Error:
{insuffcient_vnodes,2,need,3}
Ce Vector Clock va nous être essentiel pour la suite. En effet, lorsqu’un client demande la mise à jour d’une
donnée, il met à jour la donnée qu’il visualise dans le contexte dans lequel il se trouve. Imaginons qu’à cet
instant, client 1 et client 2 lisent cette donnée ; comme nous, ils visualiserons tous deux ce même Vector
Clock.
Tous deux souhaitent désormais mettre à jour ce document.
Voici ce qu’exécute client 1 :
et client 2 :
Tous deux ont indiqué la Vector Clock de la donnée indiquant l’état de celle-ci qu’ils souhaitaient modifer.
Nos données ont elles été fusionnées comme voulu ? :
Visiblement non… Mais que se passe-t-il alors ? Faisons une pause dans notre expérimentation et revenons
aux concepts fondamentaux des Vector Clocks et de Riak KV. Comme nous l’avions évoqué précédemment,
les Vector Clocks ne sont « que » des estampilles temporelles dont le but est de renseigner sur la version
d’un document. De plus, Riak KV n’est pas en mesure d’effectuer un « merge » sur les documents en
comparant les valeurs : il s’agit d’un SGBD NoSQL clé:valeur ! Comment pourrait-il effectuer de telles
comparaisons alors que sa structure fondamentale fait qu’il n’est pas au courant des données stockées ? Il
se contente seulement d’indiquer que deux « siblings » - deux enfants du même parent – existent pour cet
objet et qu’il n’est pas en mesure de choisir quelle version affcher. Nous n’allons (nous ne pouvons)
cependant pas nous contenter de ça : les résultat obtenus à la lecture du document ne sont pas satisfaisants
et ne permettent pas de pouvoir lire celui-ci. Nous devons donc écrire nous même notre programme
permettant de joindre des documents lorsqu’un confit apparaît. Nous allons pour ce faire utiliser l’API java .
Nous implémentons une première classe java dont le but est de permettre au client de « comprendre »
l’architecture de notre document.
53
#curl -X PUT http://172.17.0.2:8098/types/jeopardy_bucket_type/buckets/jeopardy_bucket/keys/vclocks_values_confict -H "Content-
Type: application/json" -H "X-Riak-Vclock: a85hYGBgzGDKBVI8+xkWvg9ne5HOwHjleAZTImMeK4N5UPUVviwA" -d
'{"cle_un":"valeur_1_1","cle_deux":"valeur_2_2"}'
#curl -X PUT http://172.17.0.2:8098/types/jeopardy_bucket_type/buckets/jeopardy_bucket/keys/vclocks_values_confict -H "Content-
Type: application/json" -H "X-Riak-Vclock: a85hYGBgzGDKBVI8+xkWvg9ne5HOwHjleAZTImMeK4N5UPUVviwA" -d
'{"cle_un":"valeur_1_2","cle_deux":"valeur_2_1"}'
#curl -i GET http://172.17.0.2:8098/types/jeopardy_bucket_type/buckets/jeopardy_bucket/keys/vclocks_test_values_confict
.
.
.
Siblings:
2HaDgru1ivZhGl2BElsoj5
Y60ZyoA2v7qRpdoEOeew2
public class Vclock_example {
public String clé_un, clé_deux;
public Vclock_example() {super();}
public Vclock_example(String clé_un, String clé_deux) {
this.clé_un = clé_un;
this.clé_deux = clé_deux;}
public String getClé_un() {
return clé_un;}
public void setClé_un(String clé_un) {
this.clé_un = clé_un;}
public String getClé_deux() {
return clé_deux;}
public void setClé_deux(String clé_deux) {
this.clé_deux = clé_deux;}
}
Une deuxième classe permet de redéfnir la fonction resolve de la classe ConfictResolver fournie par l’API.
C’est elle qui se chargera d’effectuer les différents traitements détaillés ci-dessous dès qu’un confit
(plusieurs siblings ) sera détecté :
54
public static class Vclock_exampleConfictResolver implements ConfictResolver<Vclock_example> {
@Override
public Vclock_example resolve(List<Vclock_example> siblings) {
//Objet Vclock_example
Vclock_example resolved_vclock_example = new Vclock_example();
String clé_un, clé_deux;
//Liste sans doublons contenant les différentes valeurs de clé_un
Set<String> clé_un_set = new HashSet<>();
//Liste sans doublons contenant les différentes valeurs de clé_deux
Set<String> clé_deux_set = new HashSet<>();
//S'il n'y a pas de confit
if (siblings.size() == 0) {
return null;
}
//S'il n'y a qu'une seule valeur du document
else if (siblings.size() == 1) {
return siblings.get(0);
}
//Si nous avons plusieurs versions confictuelles
else {
//Pour chaque version du document
for (Vclock_example vclock_example : siblings) {
//On ajoute la valeur de clé_un à la liste
clé_un_set.add(vclock_example.getClé_un());
//On ajoute la valeur de clé_deux à la liste
clé_deux_set.add(vclock_example.getClé_deux());
}
//Si la liste contenant les différentes valeurs de clé_un ne contient qu'un élément
if (clé_un_set.size() <= 1) {
clé_un = """ + clé_un_set.toString().replaceAll("[[]]", "") + """;
//On stocke la valeur de clé_un dans notre objet
resolved_vclock_example.setClé_un(clé_un);
} else {
clé_un = "["" + clé_un_set.toString().replaceAll("[[]]", "").replace(", ", "","") + ""]";
//On stocke un tableau des valeurs de clé_un dans notre objet
resolved_vclock_example.setClé_un(clé_un);
}
//Même traitement que clé_un
if (clé_deux_set.size() <= 1) {
clé_deux = """ + clé_deux_set.toString().replaceAll("[[]]", "") + """;
resolved_vclock_example.setClé_deux(clé_deux);
} else {
clé_deux = "["" + clé_deux_set.toString().replaceAll("[[]]", "").replace(", ", "","")
+ ""]";
resolved_vclock_example.setClé_deux(clé_deux);
}
//On retourne l'objet
return resolved_vclock_example;
}
}
}
Enfn, au niveau de notre classe principale Main, nous vérifons à la lecture d’une donnée si celle-ci possède
plusieurs versions confictuelles ; si tel est le cas alors nous appellerons la méthode précédemment défnie
et nous mettrons à jour la valeur de notre document.
Après exécution de ce code, nous constatons que le document interrogé a maintenant été mis à jour et
prend en compte l’ensemble des modifcations de client 1 et client 2 :
Les Vector Clocks n’ont donc rien de « magique ». Ils sont cependant à la fois simple à comprendre et
peuvent être extrêmement complexes à exploiter en fonction des cas de fgure.
Riak KV offre la possibilité de paramétrer la gestion des siblings ; en effet, dans notre cas il était assez simple
de décider de ce que nous devions faire de nos différentes données, mais qu’aurions nous fait si nous
avions reçu des centaines de versions confictuelles du même document ? Insérer un tel tableau dans le
document fnal n’aurait aucun sens (dans cet exemple en tout cas). De plus, de quand est datée la première
version en confit ? Est-elle toujours pertinente ou une des versions postérieure l’est-elle désormais
55
// Initialisation du client
RiakCluster cluster = setUpCluster();
RiakClient client = new RiakClient(cluster);
ConfictResolverFactory crf = ConfictResolverFactory.getInstance();
crf.registerConfictResolver(Vclock_example.class, new Vclock_exampleConfictResolver());
// Lecture de la donnée
Location vclocks_test_values_confict_data = new Location(
new Namespace("jeopardy_bucket_type", "jeopardy_bucket"), "vclocks_values_confict");
FetchValue fetch = new FetchValue.Builder(vclocks_test_values_confict_data).build();
FetchValue.Response res = client.execute(fetch);
//Récupération du Vector Clock
VClock current_vclock = res.getVectorClock();
System.out.println(current_vclock.toString());
RiakObject obj;
try {
obj = res.getValue(RiakObject.class);
}
// Plusieurs versions du document sont en confit
catch (com.basho.riak.client.api.cap.UnresolvedConfictException exception) {
//On applique la résolution de problème défnie dans la fonction resolve
Vclock_example vlock_fnal_value = res.getValue(Vclock_example.class);
// On transmet à Riak KV la nouvelle version du document
String vlock_fnal_value_string = "{"cle_un":" + vlock_fnal_value.getClé_un() + ","cle_deux":"
+ vlock_fnal_value.getClé_deux() + "}";
StoreValue sv = new StoreValue.Builder(vlock_fnal_value_string)
.withLocation(vclocks_test_values_confict_data).withVectorClock(current_vclock).build();
client.execute(sv);
}
client.shutdown();
System.exit(0);
#curl -i GET http://localhost:32904/types/jeopardy_bucket_type/buckets/jeopardy_bucket/keys/vclocks_values_confict
.
.
.
X-Riak-Vclock: a85hYGBgymDKBVI8+xkWvg9ne5HOwHjleAZTInMeK4NPcPUVPqj0CfaJlSm3/sYzMK7dDpRmBEqHgaSzAA==
.
.
.
{"clé_un":["valeur_1_1","valeur_1_2"],"clé_deux":["valeur_2_2","valeur_2_1"]}
Mises à jour épurées
davantage ? Voici les différents paramètres qui peuvent s’appliquer aux Bucket Types ou aux Buckets afn
de répondre à ces questions :
Paramètre Valeur par défaut Description
small_vclock 10 Taille jusqu’à laquelle la liste des siblings n’a pas besoin d’être épurée
big_vclock 50 Taille à partir de laquelle la liste des siblings est épurée en partant du
début de celle-ci
young_vclock 20 Aucune épuration ne sera faite sur un élément ayant moins de
young_vclock ms.
old_vclock 86400 Tout élément ayant plus de old_vclock ms devra être épuré
Ainsi, par défaut, aucune version d’un document datant de plus d’un jour et ayant été suivie par plus de 50
mises à jour concurrente ne sera conservée.
Ce tableau peut être résumé ainsi :
Il est cependant possible de se passer des Vector Clocks et d’indiquer à Riak KV de ne conserver que la
dernière version d’un document, quel que soit le client l’ayant « poussée » vers le serveur. N’oublions pas
non plus les types de données présentés en début de rapport qui ont été créés de telle sorte à ce qu’ils
gèrent nativement ces types de confits. Riak KV préconise d’ailleurs l’utilisation de ces DataTypes dès qu’on
le peut, faisant de l’utilisation de documents json ou xml des exceptions.
Cohérence des données et DataTypes
• Counter
Reprenons notre document counter créé précédemment. Nous avons paramétré le Bucket Type de
telle sorte que chaque donnée soit répliquée sur deux serveurs mais qu’un seul suffse pour la lecture et
l’écriture d’une donnée. Dans ce scénario, nous n’utiliserons que les serveurs riak-kv-2 et riak-kv-4, tous deux
56
Mises à jour conservées
Axe temporel
Nombrede
siblings
young_vclock old_vclock
small_vclock
big_vclock
responsables du stockage de notre counter « question_example_1 ». Nous modiferons la valeur sur un
serveur, puis sur l’autre (comme si ceux-ci étaient tous deux accessibles mais ne communiquaient pas entre
eux) et enfn nous démarrerons les deux serveurs en même temps afn de visualiser la réconciliation des
données opérée par Riak KV.
A ce stade, tous les serveurs du cluster sont éteints. On allume d’abord le serveur riak-kv-2.
Nous éteignons riak-kv-2 et allumons riak-kv-4, de telle sorte qu’aucun échange de données ne puisse se
faire pour le moment.
Laissons riak-kv-4 en ligne et rallumons riak-kv-2. Désormais, si nous interrogeons un des serveurs, voici ce
que nous obtenons :
Riak KV comprend la nature d’une donnée « counter » ; il est de ce fait très simple pour lui d’appliquer une
règle de calcul dite de « Pairwise Maximum Wins » . Cette méthode consiste, lors de la résolution de confits,
de conserver dans un premier temps la valeur la plus haute du counter ;ici il s’agit de la mise à jour de riak-
kv-4 : 15. Toutes les opérations réalisées sur les autres serveurs (ici riak-kv-2) seront alors appliquées à cette
valeur. On décrémente donc 10 de 15 afn d’obtenir 5.
• Map
Reprenons notre document map créé précédemment. Nous avons paramétré le Bucket Type de
telle sorte que chaque donnée soit répliquée sur deux serveurs mais qu’un seul suffse pour la lecture et
l’écriture d’une donnée. Dans ce scénario, nous n’utiliserons que les serveurs riak-kv et riak-kv-4 , tous deux
responsables du stockage de notre map « question_example_1 ». Nous modiferons la valeur sur un serveur,
puis sur l’autre (comme si ceux-ci étaient tous deux accessibles mais ne communiquaient pas entre eux) et
enfn nous démarrerons les deux serveurs en même temps afn de visualiser la réconciliation des données
opérée par Riak KV.
57
#curl -GET http://172.17.0.3:8098/types/bucket_type_counter/buckets/jeopady_question/datatypes/question_example_1
{"type":"counter","value":0}
#curl -XPOST http://172.17.0.3:8098/types/bucket_type_counter/buckets/jeopady_question/datatypes/question_example_1 -H
"Content-Type: application/json" -d '{"decrement": 10}'
#curl -GET http://172.17.0.3:8098/types/bucket_type_counter/buckets/jeopady_question/datatypes/question_example_1
{"type":"counter","value":-10}
#curl -GET http://172.17.0.5:8098/types/bucket_type_counter/buckets/jeopady_question/datatypes/question_example_1
{"type":"counter","value":0}
#curl -XPOST http://172.17.0.5:8098/types/bucket_type_counter/buckets/jeopady_question/datatypes/question_example_1 -H
"Content-Type: application/json" -d '{"increment": 15}'
#curl -GET http://172.17.0.5:8098/types/bucket_type_counter/buckets/jeopady_question/datatypes/question_example_1
{"type":"counter","value":15}
#curl -GET http://172.17.0.5:8098/types/bucket_type_counter/buckets/jeopady_question/datatypes/question_example_1
{"type":"counter","value": 5}
A ce stade, tous les serveurs du cluster sont éteints. On allume d’abord le serveur riak-kv :
Nous avons ici ajouté un champs « ajout_champs_register » et avons supprimé « air_date_register » ainsi
que « answer_register » (les trois commandes ont été faites individuellement pour plus de lisibilité).
Remarquez l’utilisation du « context » afn d’indiquer quelle version du document nous modifons, à la
façon d’un Vector Clock.
58
#curl -XGET http://172.17.0.2:8098/types/bucket_type_map/buckets/jeopady_question/datatypes/question_example_1
{"type":"map","value":{"air_date_register":"2004-05-10","answer_register":"George III","category_register":"HISTORIC NICKNAMES","cl
u00e9_deux_register":"valeur_2_1","clu00e9_un_register":"valeur_1_1","good_answer_fag":true,"question_register":"Because of his
Hanoverian heritage, American colonists called this monarch "German Georgie"
or "Geordie"","round_register":"Jeopardy!","show_number_register":"4541","value_register":"$200"},"context":"g2wAAAACaAJtAAAA
DL8Aoe9XBu7EAAHUwWEBaAJtAAAADL8Aoe9eM1MAAAAnEWECag=="}
#curl -XPOST http://172.17.0.2:8098/types/bucket_type_map/buckets/jeopady_question/datatypes/question_example_1 
-H "Content-Type: application/json" 
-d '
{
"update": {
"ajout_champs_register": "TEST"}
, "context" :
"g2wAAAADaAJtAAAADL8Aoe9XBu7EAAHUwWEBaAJtAAAADL8Aoe9eM1MAAAAnEWECaAJtAAAADL8Aoe9eM1MBAAAnEWECag
==" }'
#curl -XPOST http://172.17.0.2:8098/types/bucket_type_map/buckets/jeopady_question/datatypes/question_example_1 
-H "Content-Type: application/json" 
-d '
{
"remove": "air_date_register" , "context" :
"g2wAAAADaAJtAAAADL8Aoe9XBu7EAAHUwWEBaAJtAAAADL8Aoe9eM1MAAAAnEWECaAJtAAAADL8Aoe9eM1MBAAAnEWECag
==" }'
#curl -XPOST http://172.17.0.2:8098/types/bucket_type_map/buckets/jeopady_question/datatypes/question_example_1 
-H "Content-Type: application/json" 
-d '
{
"remove": "answer_register", "context" :
"g2wAAAADaAJtAAAADL8Aoe9XBu7EAAHUwWEBaAJtAAAADL8Aoe9eM1MAAAAnEWECaAJtAAAADL8Aoe9eM1MBAAAnEWECag
==" }'
#curl -XGET http://172.17.0.2:8098/types/bucket_type_map/buckets/jeopady_question/datatypes/question_example_1
{"type":"map","value":{"ajout_champs_register":"TEST","category_register":"HISTORIC NICKNAMES","cl
u00e9_deux_register":"valeur_2_1","clu00e9_un_register":"valeur_1_1","good_answer_fag":true,"question_register":"Because of his
Hanoverian heritage, American colonists called this monarch "German Georgie"
or "Geordie"","round_register":"Jeopardy!","show_number_register":"4541","value_register":"$200"},"context":"g2wAAAADaAJtAAAA
DFZjBNprLWdaAAB1MWECaAJtAAAADL8Aoe9XBu7EAAHUwWEBaAJtAAAADL8Aoe9eM1MAAAAnEWECag=="}
Nous éteignons riak-kv et allumons riak-kv-4, de telle sorte qu’aucun échange de données ne puisse se faire
pour le moment.
Nous avons ici seulement mis à jour le champs « air_date_register ».
Laissons riak-kv-4 en ligne et rallumons riak-kv. Désormais, si nous interrogeons un des serveurs, voici ce
que nous obtenons :
Ici Riak KV applique la règle « Add/Update Wins Over Remove » (les ajouts et mises à jour l’emportent sur les
suppressions), c’est pourquoi « air_date_register » est toujours présent et mis à jour . Les champs ajoutés et
supprimés (mais non modifés) sont bien ou non présents.
• Set
Reprenons notre document set créé précédemment. Nous avons paramétré le Bucket Type de telle
sorte que chaque donnée soit répliquée sur deux serveurs mais qu’un seul suffse pour la lecture et l’écriture
d’une donnée. Dans ce scénario, nous n’utiliserons que les serveurs riak-kv-2 et riak-kv-4 , tous deux
responsables du stockage de notre map « question_example_1 ». Nous modiferons la valeur sur un serveur,
puis sur l’autre (comme si ceux-ci étaient tous deux accessibles mais ne communiquaient pas entre eux) et
enfn nous démarrerons les deux serveurs en même temps afn de visualiser la réconciliation des données
opérée par Riak KV.
59
#curl -XGET http://172.17.0.5:8098/types/bucket_type_map/buckets/jeopady_question/datatypes/question_example_1
{"type":"map","value":{"air_date_register":"2004-05-10","answer_register":"George III","category_register":"HISTORIC NICKNAMES","cl
u00e9_deux_register":"valeur_2_1","clu00e9_un_register":"valeur_1_1","good_answer_fag":true,"question_register":"Because of his
Hanoverian heritage, American colonists called this monarch "German Georgie"
or "Geordie"","round_register":"Jeopardy!","show_number_register":"4541","value_register":"$200"},"context":g2wAAAACaAJtAAAAD
L8Aoe9XBu7EAAHUwWEBaAJtAAAADL8Aoe9eM1MAAAAnEWECag=="}
#curl -XPOST http://172.17.0.5:8098/types/bucket_type_map/buckets/jeopady_question/datatypes/question_example_1 
-H "Content-Type: application/json" 
-d '
{
"update": {
"air_date_register": "2018-05-25"}, "context" :
"g2wAAAADaAJtAAAADL8Aoe9XBu7EAAHUwWEBaAJtAAAADL8Aoe9eM1MAAAAnEWECaAJtAAAADL8Aoe9eM1MBAAAnEWECag
==" }'
#curl -XGET http://172.17.0.5:8098/types/bucket_type_map/buckets/jeopady_question/datatypes/question_example_1
{"type":"map","value":{"air_date_register":"2018-05-25","answer_register":"George III","category_register":"HISTORIC NICKNAMES","cl
u00e9_deux_register":"valeur_2_1","clu00e9_un_register":"valeur_1_1","good_answer_fag":true,"question_register":"Because of his
Hanoverian heritage, American colonists called this monarch "German Georgie"
or "Geordie"","round_register":"Jeopardy!","show_number_register":"4541","value_register":"$200"},"context":"g2wAAAADaAJtAAAA
DL8Aoe9XBu7EAAHUwWEBaAJtAAAADL8Aoe9eM1MAAAAnEWECaAJtAAAADL8Aoe9eM1MBAAAnEWEBag=="}
#curl -XGET http://172.17.0.2:8098/types/bucket_type_map/buckets/jeopady_question/datatypes/question_example_1
{"type":"map","value":{"air_date_register":"2018-05-25","ajout_champs_register":"TEST","category_register":"HISTORIC
NICKNAMES","clu00e9_deux_register":"valeur_2_1","cl
u00e9_un_register":"valeur_1_1","good_answer_fag":true,"question_register":"Because of his Hanoverian heritage, American
colonists called this monarch "German Georgie"
or "Geordie"","round_register":"Jeopardy!","show_number_register":"4541","value_register":"$200"},"context":"g2wAAAAFaAJtAAAAD
FZjBNprLWdaAAB1MWECaAJtAAAADL8Aoe9XBu7EAAHUwWEBaAJtAAAADL8Aoe9eM1MAAAAnEWECaAJtAAAADL8Aoe9eM1MB
AAAnEWEDaAJtAAAADL8Aoe/MCvzPAAC28GEDag=="}
A ce stade, tous les serveurs du cluster sont éteints. On allume d’abord le serveur riak-kv-4:
Nous avons ajouté deux données à notre Set : « Donnee_1 » et « Donnee_2 » et avons supprimé la donnée
« George III ».
Nous éteignons riak-kv-4 et allumons riak-kv-2, de telle sorte qu’aucun échange de données ne puisse se
faire pour le moment.
Nous avons ici ajouté une donnée « George III » (aucun changement n’a été appliqué puisque cette donnée
existait déjà dans le Set et avons supprimé « 2004-05-10 ».
Laissons riak-kv-4 en ligne et rallumons riak-kv-2. Désormais, si nous interrogeons un des serveurs, voici ce
que nous obtenons :
Ici Riak KV applique la règle « Add Wins Over Remove » (les ajouts l’emportent sur les suppressions), c’est
pourquoi « George III » est toujours présent. Nous constatons de plus bien les autres suppressions et ajouts.
60
#curl -XGET http://172.17.0.5:8098/types/bucket_type_set/buckets/jeopady_question/datatypes/question_example₁
{"type":"set","value":["2004-05-10","George
III"],"context":"g2wAAAACaAJtAAAADL8Aoe9eM1MAAAB1MWEDaAJtAAAADL8Aoe9eM1MBAACcQmEGag=="}
#curl -XPOST http://172.17.0.5:8098/types/bucket_type_set/buckets/jeopady_question/datatypes/question_example_1 
-H "Content-Type: application/json" 
-d '{"add_all":["Donnee_1", "Donnee_2"], "context" :
"g2wAAAACaAJtAAAADL8Aoe9eM1MAAAB1MWEDaAJtAAAADL8Aoe9eM1MBAACcQmEGag==" }'
#curl -XPOST http://172.17.0.5:8098/types/bucket_type_set/buckets/jeopady_question/datatypes/question_example_1 
-H "Content-Type: application/json" 
-d '{"remove":"George III", "context" :
"g2wAAAACaAJtAAAADL8Aoe9eM1MAAAB1MWEDaAJtAAAADL8Aoe9eM1MBAACcQmEGag==" }'
#curl -XGET http://172.17.0.5:8098/types/bucket_type_set/buckets/jeopady_question/datatypes/question_example₁
{"type":"set","value":["2004-05-10","Donnee_1","Donnee_2"],
"context":"g2wAAAACaAJtAAAADL8Aoe9eM1MAAAB1MWEDaAJtAAAADL8Aoe9eM1MBAACcQmEGag=="}
#curl -XGET http://172.17.0.3:8098/types/bucket_type_set/buckets/jeopady_question/datatypes/question_example₁
{"type":"set","value":["2004-05-10","George
III"],"context":"g2wAAAACaAJtAAAADL8Aoe9eM1MAAAB1MWEDaAJtAAAADL8Aoe9eM1MBAACcQmEGag=="}
#curl -XPOST http://172.17.0.3:8098/types/bucket_type_set/buckets/jeopady_question/datatypes/question_example_1 
-H "Content-Type: application/json" 
-d '{"add":"George III", "context" : "g2wAAAACaAJtAAAADL8Aoe9eM1MAAAB1MWEDaAJtAAAADL8Aoe9eM1MBAACcQmEGag==" }'
#curl -XPOST http://172.17.0.3:8098/types/bucket_type_set/buckets/jeopady_question/datatypes/question_example_1 
-H "Content-Type: application/json" 
-d '{"remove":"2004-05-10", "context" :
"g2wAAAACaAJtAAAADL8Aoe9eM1MAAAB1MWEDaAJtAAAADL8Aoe9eM1MBAACcQmEGag==" }'
#curl -XGET http://172.17.0.3:8098/types/bucket_type_set/buckets/jeopady_question/datatypes/question_example₁
{"type":"set","value":["George
III"],"context":"g2wAAAADaAJtAAAADL8Aoe9dU9CcAADeAGECaAJtAAAADL8Aoe9eM1MAAAB1MWEDaAJtAAAADL8Aoe9eM1MBA
ACcQmEGag=="}
#curl -XGET http://172.17.0.2:8098/types/bucket_type_set/buckets/jeopady_question/datatypes/question_example₁
{"type":"set","value":["Donnee_1","Donnee_2","George
III"],"context":"g2wAAAAEaAJtAAAADL8Aoe9dU9CcAADeAGECaAJtAAAADL8Aoe9eM1MAAAB1MWEDaAJtAAAADL8Aoe9eM1MBA
ACcQmEGaAJtAAAADL8Aoe9eM1MCAAAAAmECag=="}
Après de nombreuses heures d’étude et d’utilisation de Riak KV mes sentiments sont partagés. D’un
côté j’ai été confronté à un système NoSQL intuitif, avec une vraie volonté d’offrir un utilitaire
d’administration (riak-admin) clair et à l’utilisation naturelle. L’organisation des données en Cluster et
l’application des Cluster Type m’a paru être un moyen assez effcace de gérer une collection de données
dans un système clé:valeur. La mise en place d’un cluster est aussi d’une facilité déconcertante et Riak KV
semble pouvoir être hautement scalable comme Basho s’en vante par l’utilisation du système Dynamo.
Cependant, en tant que DBA, je ne peux me résoudre à considérer qu’il s’agit d’un système effcient à mettre
en production, si ce n’est dans des cas très précis. En effet, la documentation, parfois très claire, peut aussi
relever du casse-tête à certains moments : organisation complexe, informations présentes ou non en
fonction de la version de la documentation. De plus, un très gros point noir pour moi a été la quasi
inexistence de guides concernant les fonctions fournies par les API qui sont pourtant indispensables pour
un tel système. Au delà de ça, la dernière version de Riak KV ayant un an et l’inaccessibilité du site de Basho
durant plusieurs semaines courant mai n’inspirent pas confance quant à l’utilisation de Riak KV pour le long
terme. Enfn, le manque de structure imposant le schéma de données insérées me gêne encore (sûrement
parce que j’ai l’habitude de traiter les données avec du SQL ?). Il s’agissait cependant d’un sujet d’étude très
intéressant qui m’a permis de découvrir et d’expérimenter certain concepts communs à d’autres systèmes
NoSQL et de découvrir pour la première fois un SGBD NoSQL seul. J’ai pu constater à quel point le maintien
de la cohérence des données dans un cluster peut être compliqué, tant les pannes peuvent être diverses et
tant leur résolution dépend de paramètres défnis par l’administrateur. Je conserve d’ailleurs le cluster mis
en place afn d’approfondir certains points non abordés dans ce rapport durant l’été, une fois que l’UE sera
terminée.
61
Conclusion
Index des illustrations
Illustration 1: Paramétrage du Bucket Type Default.................................................................................................................6
Illustration 2: Page d'accueil lors du premier lancement de Riak Explorer......................................................................8
Illustration 3: Liste des index créés avec leur schéma associé...........................................................................................21
Illustration 4: Détail de l'index "jeopardy_index"......................................................................................................................21
Illustration 5: Schéma représentant le fonctionnement de l'AAE......................................................................................26
Illustration 6: Représentation du Hash Ring...............................................................................................................................31
Illustration 7: Répartition de données entre deux serveurs.................................................................................................32
Illustration 8: Répartition des données du Hash Ring...........................................................................................................32
Illustration 9: Réplication d'une donnée avec n_val = 3........................................................................................................33
Illustration 10: Gestion d'une écriture par un nœud secondaire........................................................................................34
Illustration 11: Visualtion du Cluster depuis l'interface Web...............................................................................................38
Illustration 12: Répartition des données dans notre Cluster...............................................................................................47
62
Sources
Pages Internet
https://docs.basho.com
Livres
Riak Hanbook – Mathias Meyer
63

Descriptif du SGBD No SQL Riak KV

  • 1.
  • 3.
    Table des matières PRÉSENTATIONDU PROJET.....................................................................................................4 Riak KV.......................................................................................................................................................................4 Jeu de données.......................................................................................................................................................4 DÉCOUVERTE DE RIAK KV........................................................................................................5 Architecture de Riak KV.........................................................................................................................................5 Le modèle clé-valeur...................................................................................................................................................................................... 5 Organisation des données........................................................................................................................................................................... 5 Les Backends.................................................................................................................................................................................................... 6 Une API REST.................................................................................................................................................................................................... 7 Le moteur de recherche Riak Search....................................................................................................................................................... 7 Le modèle Dynamo d’Amazon................................................................................................................................................................... 7 Les différents utilitaires d’administration........................................................................................................7 L’utilitaire riak.................................................................................................................................................................................................... 7 L’utilitaire riak-admin...................................................................................................................................................................................... 7 L’interface web Riak Explorer..................................................................................................................................................................... 8 INSERTION ET INTERROGATION DES DONNÉES.................................................................9 Mise en place de Bucket Types et insertion....................................................................................................9 Le Bucket Type Default................................................................................................................................................................................. 9 Les Buckets avec datatype........................................................................................................................................................................ 11 Interrogation des données................................................................................................................................18 Mise en place de Riak Search................................................................................................................................................................... 19 L’ Active Anti-Entropy.................................................................................................................................................................................. 24 Les Secondary Indexes............................................................................................................................................................................... 29 RÉPLICATION ET PARTITIONNEMENT................................................................................31 Dynamo...................................................................................................................................................................31 Rappel du principe de hashage cohérent........................................................................................................................................... 31 Répartition des données............................................................................................................................................................................ 32 La réplication.................................................................................................................................................................................................. 33 Maintien de la cohérence des données............................................................................................................................................... 34 Mise en place d’un cluster Riak KV..................................................................................................................36 Construction du cluster et commandes de base.............................................................................................................................. 36 Paramétrage du cluster.............................................................................................................................................................................. 45 Expérimentations sur notre cluster.................................................................................................................46 La réplication.................................................................................................................................................................................................. 48 Les Vector Clocks......................................................................................................................................................................................... 52 Cohérence des données et DataTypes................................................................................................................................................ 56 Index des illustrations................................................................................................................................................................................. 62 Sources............................................................................................................................................................................................................. 63 Pages Internet................................................................................................................................................................................................ 63 3
  • 4.
    PRÉSENTATION DU PROJET Dansce rapport, je vous ferai partager ma découverte du SGBD NoSQL Riak KV, en me focalisant sur les données, la façon dont on peut les stocker, les interroger et les mécanismes nous assurant leur cohérence. En début de rapport, nous nous intéresserons aux concepts autour desquels Riak KV a été construit ainsi que sur les services qu’il propose ; la dernière partie sera quant à elle dédiée à l’architecture distribuée de Riak KV. L’intégralité des expérimentations de ce rapports ont été réalisées sur une machine hôte Linux et chaque instance de Riak KV a été mise en place grâce à des containers Dockers (containers fournis par Basho ou personnalisés par mes soins en fonction du besoin). Riak KV Riak KV (pour Riak Key-Value) est un système de gestion de bases de données NoSQL, distribué et développé en versions opensource et entreprise par la société Basho Technologies. Il s’agit d’un système distribué scalable, résistant aux pannes basé sur la technologie Dynamo d’Amazon. Il fonctionne sur le principe de données clé- valeur : une donnée insérée peut à la fois être une valeur atomique ou une valeur plus complexe - comme un document json ou xml - et est toujours associée à une clé unique permettant de retrouver l’information. Initié en août 2009, la dernière version disponible à ce jour a été publiée en avril 2017; le langage Erlang a été utilisé pour mettre en place ce système. Précisons que Basho, en plus de Riak KV, développe un autre système de gestion de bases de données NoSQL, Riak TS (pour Riak Time Series). Jeu de données Le jeu de données utilisé lors de ce projet est une collection de documents json regroupant plus de 200 000 questions et réponses issues du jeu télévisé américain Jeopardy !. La structure de ces documents est visible dans l’extrait de données présenté ci-dessous. 4 Version de Riak KV utilisée Dans ce rapport, l’ensemble des opérations seront réalisées sur la dernière version de Riak KV à ce jour : v. 2.2.3 { "air_date": "2003-06-06", "answer": "Jolly Rancher", "category": "CANDY", "question": "Bill Harmsen, who raised horses in Colo., happily founded this candy co. in 1949 to make money during the winter", "round": "Final Jeopardy!", "show_number": 4335, "value": null }, { "air_date": "2004-05-10", "answer": "George III", "category": "HISTORIC NICKNAMES", "question": "Because of his Hanoverian heritage, American colonists called this monarch "German Georgie" or "Geordie"", "round": "Jeopardy!", "show_number": 4541, "value": "$200" }
  • 5.
    DÉCOUVERTE DE RIAKKV Architecture de Riak KV Le modèle clé-valeur Riak KV comme son nom l’indique est basé sur le modèle clé-valeur. Ce modèle permet de stocker une information (ici des documents structurés) et d’y associer une clé afn de traiter cette donnée. Un avantage certain de ce type de stockage par rapport, par exemple, à celui d’un modèle relationnel est qu’il limite la recherche et la récupération des documents à l’interrogation d’une clé, ce qui est très rapide. Cette rapidité ne se fait cependant pas sans certains sacrifces. En effet, il est impossible avec ce système de récupérer l’ensemble des documents comme on pourrait le faire en SQL (SELECT * FROM table) ; il est nécessaire de connaître la clé de l’objet recherché afn de le visualiser. Toute la diffculté réside alors dans le bon choix du nommage de la clé ; un nommage discriminant et sémantiquement précis facilitera la recherche d’éléments au sein d’une collection de documents. Organisation des données • Les Buckets Dans Riak KV, les données sont insérées dans des Buckets (seau en français). Les Buckets permettent de séparer logiquement les données physiques insérées dans le système ; les Buckets ne sont donc pas visibles en dehors de Riak KV sur l’OS de la machine hôte, ce ne sont pas des dossiers. Leur utilité est double : - organiser les données en groupes cohérents. Ainsi, dans le cas du jeu de données utilisé dans ce projet, toutes les données ont logiquement leur place dans un Bucket « jeopardy ». SI je décide un jour de traiter les données d’un autre jeu télévisé comme Questions pour un champion, il sera nécessaire pour respecter la cohérence de mon système de gestion de données de les insérer dans un Bucket « questions_pour_un_champion ». Il s’agit plus ou moins d’un système de table si l’on veut faire une corrélation avec les SGBD relationnels, mis à part l’absence de structure de données relative au Buckets et leur seule existence logique. Lors de la recherche des éléments présents en base, les Buckets apportent un niveau de précision supplémentaire à la recherche d’information par clé ; celle-ci se fera en effet d’abord par Bucket (on recherche par exemple des éléments présents dans un Bucket «meteo_limoges »), puis par identifant de la donnée (on aura attribué un timestamp en tant qu’id des chaque données météorologiques relatives à la ville de Limoges insérées). - les Buckets permettent aussi une gestion des confguration relatives à la réplication des données qui y sont présentes. Des variables de paramétrage sont attribuées à chacun des Buckets, apportant la possibilité à l’administrateur de prioriser la réplication de données sensibles, d’en assurer une cohérence plus forte pour certains Buckets par rapport à d’autres. Un type de données peut être aussi appliqué afn de structurer le Bucket et de n’y accepter que des données dont le format sera exploitable pour réaliser des traitements spécifques. (je reste vague pour le moment mais ce point sera expliqué et développé dans la suite de ce rapport). 5
  • 6.
    • Les BucketTypes Un Bucket Type est une confguration applicable à un Bucket, permettant de gérer leurs différentes variables de paramétrage évoquées précédemment. Si aucun Bucket Type n’est renseigné lors de la création d’un Bucket, un Bucket Type default est appliqué à celui-ci, comprenant les paramétrages par défaut choisis par les équipes de Basho. L’utilité des Bucket Types est de pouvoir conserver plusieurs versions de paramétrages en fonction des besoins de réplication et de les appliquer facilement et rapidement à autant de Buckets qu’on le désire. Les Backends Les Backends correspondent aux moteurs de stockage présents dans Riak KV ; ils sont appliqués au niveau des Buckets et sont au nombre de trois : Bitcask, LevelDB et Memory. Je ne développerai pas en détail chaque Backend (il ne s’agit pas du thème du cours NFE204) mais voici ce que l’on peut retenir concernant chacun d’entre eux : Bitcask est le moteur utilisé par défaut dans Riak KV, les données y sont persistantes comme c’est le cas pour LevelDB mais pas pour Memory (les données restent en Ram). Les données d’un Bucket « Bitcask » sont 6 Illustration 1: Paramétrage du Bucket Type Default
  • 7.
    chargées en Ramau démarrage du serveur et y sont maintenues par la suite ; LevelDB lit les données directement depuis le disque. Bitcask ne permet pas d’avoir une quantité de données supérieure à la quantité de Ram allouée au serveur, l’intégralité de ces données devant pouvoir y loger. Une API REST Plusieurs API sont fournies afn d’utiliser Riak KV avec différents langages de programmation (Java, Ruby, Python, C Sharp, NodeJS, Erlang, Go, Php). Une API REST permet aussi d’effectuer l’ensemble des actions sur la base grâce aux méthodes GET, PUT, POST, DELETE bien connues ; c’est cette interface qui sera principalement utilisée tout au long de ce rapport. Le moteur de recherche Riak Search Riak KV est fourni avec son propre moteur de recherche – Riak Search 2.0 – basé sur Solr. Il s’agit en fait d’une intégration de Solr pour l’indexation et l’interrogation des données et de Riak KV pour la gestion et le stockage des données. Le modèle Dynamo d’Amazon Le modèle utilisé pour la réplication des données d’un cluster Riak KV est le modèle Dynamo d’Amazon . Une présentation complète de ce système et de son implémentation dans Riak KV a été faite en dernière partie de ce rapport. Les différents utilitaires d’administration Riak KV est fourni avec deux utilitaires permettant la gestion des serveurs : riak et riak-admin. L’utilitaire riak riak permet la gestion du processus associé à chaque serveur. Je ne le développerai pas dans ce rapport tant son utilisation est commune à la plupart des programmes gérant la gestion de processus. On retrouvera ainsi comme arguments de cette commande différents mots-clés comme start, stop, restart, ping, console, version, get-pid… dont la résultat pour chacun est évident. L’utilitaire riak-admin L’utilitaire riak-admin est plus intéressant que le précédent car il permet une gestion poussée de chaque serveur grâce à des arguments relatifs aux confgurations spécifques de Riak KV. Il permet par exemple de confgurer un cluster, d’en visualiser le statut et la santé, de récupérer des statistiques… Cet utilitaire sera utilisé tout le long de ce projet afn de mettre en place des confgurations, les vérifer ou bien de tester et constater les différents aspects mis en avant par ce système. 7
  • 8.
    L’interface web RiakExplorer Une interface web accessible par défaut à l’url http://localhost:8098/admin permet l’administration graphique de Riak KV. Les fonctions sont similaires à celles apportées par l’utilitaire cité précédemment mais permettent une visualisation plus intuitive et plus claire des différentes informations du cluster. J’utiliserai cette interface pour illustrer certains aspects des différents points développés dans ce rapport. 8 Illustration 2: Page d'accueil lors du premier lancement de Riak Explorer
  • 9.
    INSERTION ET INTERROGATIONDES DONNÉES Maintenant que les concepts globaux ont été abordés, passons à la pratique. Dans cette partie nous allons insérer des données dans des Buckets, interroger le serveur afn de récupérer ces données ou d’y appliquer une transformation. Mise en place de Bucket Types et insertion Les Bucket Type permettent le partage de mêmes confgurations entre différents Buckets. Leur utilisation est intéressante d’un point de vue administration (on peut avoir autant de Bucket Type que l’on veut, chacun décrivant une confguration spécifque) et essentielle d’un point de vue performance d’un cluster . En effet, un Bucket partagé entre différentes machines devra avoir la même confguration sur l’une ou sur l’autre ; il est donc bien plus effcace d’assigner un Bucket Type à un Bucket (la gestion des paramètres étant alors centralisée) plutôt que d’attribuer des paramètres spécifques directement au Bucket, dans quel cas ce serait au cluster d’aller chercher ces variables de confguration et de les diffuser auprès de l’ensemble des serveurs. Le Bucket Type Default Un seul Bucket Type est disponible lors de l’installation de Riak KV : le Bucket Type default. Remarquez le premier argument « bucket-type » passé à notre utilitaire riak-admin ; il permet d’indiquer à Riak KV que les informations que nous demandons ici concernent la gestion des Bucket Type. Un certain nombre d’arguments suivent alors afn de préciser notre souhait d’information. Nous pouvons lister les paramètres qui ont été défnis pour ce Bucket Type : 9 # riak-admin bucket-type list default (active) # riak-admin bucket-type status default default is active allow_mult: false basic_quorum: false big_vclock: 50 chash_keyfun: {riak_core_util,chash_std_keyfun} dvv_enabled: false dw: quorum last_write_wins: false linkfun: {modfun,riak_kv_wm_link_walker,mapreduce_linkfun} n_val: 3 notfound_ok: true old_vclock: 86400 postcommit: [] pr: 0 precommit: [] pw: 0 r: quorum rw: quorum small_vclock: 50 w: quorum write_once: false young_vclock: 20
  • 10.
    De très nombreusesvariables peuvent être alimentées afn de personnaliser au maximum ses Buckets. Leur listage serait fastidieux et peu utile dans le cadre de ce projet. Je reviendrai sur les paramètres liés à la réplication et au partitionnement dans la partie qui y est consacrée plus loin. Pour créer un Bucket utilisant ce Bucket Type, il sufft d’insérer une donnée comme ceci : L’API REST requiert que l’on indique quel Bucket Type doit être utilisé (après /types), quel Bucket sera alimenté (après /buckets) et quel identifant sera associé à la donnée insérée (après /keys). Voici une autre différence avec une table SQL : il n’est pas possible de créer un Bucket sans y insérer de donnée ; c’est à l’insertion de chaque donnée que le Bucket et son Bucket Type devront être précisés. Un même nom de Bucket peut être utilisé associé à plusieurs Bucket Type différents mais il ne s’agira alors pas du même Bucket pour Riak (il ne serait de toute façon sans doute pas judicieux de créer plusieurs Buckets ayant le même nom). Réponse de la commande : 10 curl -v -X PUT http://localhost:8098/types/default/buckets/jeopardy_default/keys/show_1 -H "Content-Type: application/json" -d ‘{ "air_date": "2003-06-06", "answer": "Jolly Rancher", "category": "CANDY", "question": "Bill Harmsen, who raised horses in Colo., happily founded this candy co. in 1949 to make money during the winter", "round": "Final Jeopardy!", "show_number": 4335, "value": null }’ * Trying 127.0.0.1... * TCP_NODELAY set * Connected to localhost (127.0.0.1) port 8098 (#0) > PUT /types/default/buckets/jeopardy_default/keys/show_1 HTTP/1.1 > Host: localhost:8098 > User-Agent: curl/7.58.0 > Accept: */* > Content-Type: application/json > Content-Length: 260 > * upload completely sent off: 260 out of 260 bytes < HTTP/1.1 204 No Content < Vary: Accept-Encoding < Server: MochiWeb/1.1 WebMachine/1.10.9 (cafe not found) < Date: Sat, 05 May 2018 19:53:30 GMT < Content-Type: application/json < Content-Length: 0 < * Connection #0 to host localhost left intact
  • 11.
    Vérifcation de laprésence de la donnée et donc du Bucket: La lecture d’une donnée dans Riak KV nécessite la même architecture de lien que celle vue pour l’insertion. Les trois « couches » Bucket Type, Bucket et clé sont nécessaires. Les Buckets avec datatype Penchons nous maintenant sur un paramètre intéressant des Bucket Types : le datatype. Le datatype est un paramètre permettant de rendre Riak KV « conscient » des données qu’il contient. Là où par défaut Riak KV stocke des documents sans que leur structure et leur sens ne l’importe, il peut aussi « prendre conscience » des données qu’on lui fournit afn d’y appliquer des transformations. Trois datatypes issus des CRDT ont alors été intégrés : - les Counters - les Sets - les Maps Pour maintenir la cohérence des données voulue par l’utilisation de CRDT, ces types de données ne peuvent être effcaces sans l’utilisation d’un context ; c’est en effet ce context qui indiquera au serveur quelle est la version de l’objet qui est présent et qu’il s’apprête à modifer. Il permettra entre autres de gérer les incohérence au niveau de l’existence des objets (si un serveur a commandé la suppression d’un objet mais qu’un autre le met à jour, il faut le savoir). Dans cette parie, nous ne travaillerons pas dans un contexte distribué ; nous introduirons donc seulement les différents types de données en expliquant leur rôle et leur implémentation. Nous testerons leur résistance aux confits dans la dernière partie de ce rapport. 11 #curl -v -X GET http://localhost:8098/types/default/buckets/jeopardy_default/keys/show_1 Note: Unnecessary use of -X or --request, GET is already inferred. * Trying 127.0.0.1... * TCP_NODELAY set * Connected to localhost (127.0.0.1) port 8098 (#0) > GET /types/default/buckets/jeopardy_default/keys/show_1 HTTP/1.1 > Host: localhost:8098 > User-Agent: curl/7.58.0 > Accept: */* > < HTTP/1.1 200 OK < X-Riak-Vclock: a85hYGBgzGDKBVI8+xkWvufumDgbIpTImMfKsLM96QpfFgA= < Vary: Accept-Encoding < Server: MochiWeb/1.1 WebMachine/1.10.9 (cafe not found) < Link: </buckets/jeopardy_default>; rel="up" < Last-Modifed: Sat, 05 May 2018 19:53:29 GMT < ETag: "4gXEpr1wVPZsKPhI69CW25" < Date: Sat, 05 May 2018 20:01:42 GMT < Content-Type: application/json < Content-Length: 260 < * Connection #0 to host localhost left intact {"air_date": "2003-06-06","answer": "Jolly Rancher","category": "CANDY","question": "Bill Harmsen, who raised horses in Colo., happily founded this candy co. in 1949 to make money during the winter","round": "Final Jeopardy!","show_number": 4335,"value": null} Les CRDT Un CRDT (Confict-Free Replicated Data Type) est une structure de données pouvant être répliquée sur différents serveurs et dont les réplicas peuvent être mis à jour indépendamment. Les incohérences résultant de ces mises à jour concurrentes peuvent alors toujours être résolues mathématiquement afn de retrouver une structure de donnée cohérente.
  • 12.
    • Bucket Type« Map » Un Map est un tableau associatif associant à un ensemble de clés un ensemble correspondant de valeurs. Il a une structure similaire à un document json (« clé » : »valeur ») mais sa structure connue par l’applicatif permet de lui appliquer un certain nombre d’opérations : ajout d’une nouvelle valeur à une nouvelle clé, modifcation ou suppression d’une valeur de clé existante. Un Map peut aussi contenir des booléens, des compteurs (counters vus plus tard dans le rapport), des liste d’objets uniques (sets vus plus tard dans le rapport) ou d’autres maps. Création d’un Bucket Type « Map » Avec la clé « props », nous indiquons à Riak KV que les éléments qui vont suivre vont être des variables de confguration du Bucket Type. Ici, le paramètre « datatype » nous permet de personnaliser le type de données contenues dans un Bucket dont le Bucket Type sera « bucket_type_map ». Le Bucket Type est créé mais n’est pas encore actif : 12 #riak-admin bucket-type create bucket_type_map '{"props":{"datatype":"map"}}' bucket_type_map created WARNING: After activating bucket_type_map, nodes in this cluster can no longer be downgraded to a version of Riak prior to 2.0 # riak-admin bucket-type status bucket_type_map bucket_type_map has been created and may be activated young_vclock: 20 w: quorum small_vclock: 50 rw: quorum r: quorum pw: 0 precommit: [] pr: 0 postcommit: [] old_vclock: 86400 notfound_ok: true n_val: 3 linkfun: {modfun,riak_kv_wm_link_walker,mapreduce_linkfun} last_write_wins: false dw: quorum dvv_enabled: true chash_keyfun: {riak_core_util,chash_std_keyfun} big_vclock: 50 basic_quorum: false allow_mult: true datatype: map active: false claimant: 'riak@172.17.0.2'
  • 13.
    Activons le : Créonsmaintenant un objet Map en Java: Nous ne pouvons pas créer d’objet Map à l’aide de l’API REST ; il s’agit pour Riak KV d’un objet dont la structure complexe ne peut être décrite par l’interface http.Il s’agit en fait d’un objet au sens « programmation » du terme, ayant besoin d’un constructeur. L’Objet de type Map a bien été créé mais ne contient encore aucune information : Enregistrons maintenant des données Register dans notre Map : Une fois l’objet créé, il peut être alimenté et modifé directement avec l’API REST.« update » indique que nous allons mettre à jour notre Map, soit avec un attribut existant dont la valeur sera alors modifée, soit avec un nouvel attribut dont le couple clé:valeur sera alors ajouté. L’utilisation des Registers se fait pas l’ajout du suffxe « _register » au nom des clés passées en argument ; un Register est en fait l’appel d’une fonction appliquée à un Map permettant l’ajout d’un couple clé:valeur dans celui-ci si cette clé n’existe pas déjà. 13 # riak-admin bucket-type activate bucket_type_map bucket_type_map has been activated WARNING: Nodes in this cluster can no longer be downgraded to a version of Riak prior to 2.0 //Initialisation du client RiakCluster cluster = setUpCluster(); RiakClient client = new RiakClient(cluster); //Création d'un Objet Map d'id question_example_1, dans un Bucket jeopardy_question de type bucket_type_map Location question_example_map =new Location(new Namespace("bucket_type_map", "jeopardy_question"), "question_example_1"); //On envoie l'information au serveur FetchMap fetch = new FetchMap.Builder(question_example_map).build(); client.execute(fetch); # curl -GET http://localhost:8098/types/bucket_type_map/buckets/jeopady_question/datatypes/question_example_1 {"type":"map","error":"notfound"} #curl -XPOST http://localhost:8098/types/bucket_type_map/buckets/jeopady_question/datatypes/question_example_1 -H "Content-Type: application/json" -d ' { "update": { "air_date_register": "2004-05-10", "answer_register": "George III", "category_register": "HISTORIC NICKNAMES", "question_register": "Because of his Hanoverian heritage, American colonists called this monarch "German Georgie" or "Geordie"", "round_register": "Jeopardy!", "show_number_register": "4541", "value_register": "$200" } } '
  • 14.
    Que contient maintenantnotre objet ? Nous constatons que l’objet a bien été alimenté par la commande précédente. Une clé « context » a aussi été ajoutée indiquant la version de l’objet (nous verrons cela plus en détail plus tard). Le type Flag permet de créer une clé dont la valeur sera booléenne (enable ou disable). Voici alors le Map : Il est aussi possible d‘y imbriquer d’autres Maps, Sets (notion vue juste après) ou Counter (notion abordée après les Sets). Il est aussi possible de supprimer une clé et sa valeur. Par exemple, voici comment nous supprimerions la clé « answer_register » : Enfn, nous pouvons mettre à jour une valeur existante (ici nous modifons la date): 14 #curl -GET http://localhost:8098/types/bucket_type_map/buckets/jeopady_question/datatypes/question_example_1 {"type":"map","value":{"air_date_register":"2004-05-10","answer_register":"George III","category_register":"HISTORIC NICKNAMES","question_register":"Because of his Hanoverian heritage, American colonists called this monarch "German Georgie" or "Geordie"","round_register":"Jeopardy!","show_number_register":"4541","value_register":"$200"},"context":"g2wAAAABaAJtAAAA DL8Aoe8LbXWwAAAAAWEBag=="} #curl -XPOST http://localhost:8098/types/bucket_type_map/buckets/jeopady_question/datatypes/question_example_1 -H "Content-Type: application/json" -d ' { "update": { "good_answer_fag": "enable" } } ' #curl -GET http://localhost:8098/types/bucket_type_map/buckets/jeopady_question/datatypes/question_example_1 {"type":"map","value":{"air_date_register":"2004-05-10","answer_register":"George III","category_register":"HISTORIC NICKNAMES","good_answer_fag":true,"question_register":"Because of his Hanoverian heritage, American colonists called this monarch "German Georgie" or "Geordie"","round_register":"Jeopardy!","show_number_register":"4541","value_register":"$201"},"context":"g2wAAAABaAJtAAAA DL8Aoe8LbXWwAAAAAWEGag=="} #curl -XPOST http://localhost:8098/types/bucket_type_map/buckets/jeopady_question/datatypes/question_example_1 -H "Content-Type: application/json" -d ' { "update": { "air_date_register": "2018-05-25"} }' #curl -XPOST http://localhost:8098/types/bucket_type_map/buckets/jeopady_question/datatypes/question_example_1 -H "Content-Type: application/json" -d ' { "remove": "air_date_register" }'
  • 15.
    • Bucket Type« Set » Un Set est une collection de valeurs uniques. Ajouter dans un Set une valeur déjà existante n’aboutira donc à aucune modifcation de celui-ci. Création d’un Bucket Type «Set» Le Bucket Type est créé mais n’est pas encore actif : Activons le : Créons maintenant un objet Set en Java: 15 #riak-admin bucket-type create bucket_type_set '{"props":{"datatype":"set"}}' bucket_type_set created # riak-admin bucket-type status bucket_type_set bucket_type_set has been created and may be activated young_vclock: 20 w: quorum small_vclock: 50 rw: quorum r: quorum pw: 0 precommit: [] pr: 0 postcommit: [] old_vclock: 86400 notfound_ok: true n_val: 3 linkfun: {modfun,riak_kv_wm_link_walker,mapreduce_linkfun} last_write_wins: false dw: quorum dvv_enabled: true chash_keyfun: {riak_core_util,chash_std_keyfun} big_vclock: 50 basic_quorum: false allow_mult: true datatype: set active: false claimant: 'riak@172.17.0.2' # riak-admin bucket-type activate bucket_type_set bucket_type_set has been activated //Initialisation du client RiakCluster cluster = setUpCluster(); RiakClient client = new RiakClient(cluster); //Création d'un Objet Set d'id question_example_1, dans un Bucket jeopardy_question de type bucket_type_set Location question_example_set =new Location(new Namespace("bucket_type_set", "jeopardy_question"), "question_example_1"); //On envoie l'information au serveur FetchSet fetch = new FetchSet.Builder(question_example_set).build(); client.execute(fetch);
  • 16.
    Encore une fois,l’API REST ne peut être utilisée pour l’initialisation de l’objet. Enregistrons maintenant des données dans notre Set : « add_all » permet l’ajout d’un ensemble de valeurs à notre Set. Que contient maintenant notre objet ? Les valeurs d’un Set étant uniques, cet ajout sera par exemple ignoré : Nous pouvons supprimer une valeur du Set : L’objet contient alors : Dans le client Java, le type Set étant connu, il est possible de lui appliquer plusieurs fonctions classiques (isEmplty(), contains(), size() ...) • Bucket Type « Counter » Comme son nom l’indique, un Counter permet de créer un objet « compteur », contenant un nombre entier et permettant de lui appliquer des fonctions élémentaires (additions et soustraction). Création d’un Bucket Type «Counter» 16 # curl -XPOST http://localhost:8098/types/bucket_type_set/buckets/jeopady_question/datatypes/question_example_1 -H "Content-Type: application/json" -d '{"add_all":["2004-05-10", "George III","HISTORIC NICKNAMES"]}' # curl -GET http://localhost:8098/types/bucket_type_set/buckets/jeopady_question/datatypes/question_example_1 {"type":"set","value":["2004-05-10","George III","HISTORIC NICKNAMES"],"context":"g2wAAAABaAJtAAAADL8Aoe8LafemAAAAAWEDag=="} # curl -XPOST http://localhost:8098/types/bucket_type_set/buckets/jeopady_question/datatypes/question_example_1 -H "Content-Type: application/json" -d '{"add_all":["HISTORIC NICKNAMES"]}' # curl -XPOST http://localhost:8098/types/bucket_type_set/buckets/jeopady_question/datatypes/question_example_1 -H "Content-Type: application/json" -d '{"remove": "HISTORIC NICKNAMES"}' # curl -GET http://localhost:8098/types/bucket_type_set/buckets/jeopady_question/datatypes/question_example_1 {"type":"set","value":["2004-05-10","George III"],"context":"g2wAAAABaAJtAAAADL8Aoe8LafemAAAAAWEDag=="} #riak-admin bucket-type create bucket_type_counter '{"props":{"datatype":"counter"}}' bucket_type_set created
  • 17.
    Le Bucket Typeest créé mais n’est pas encore actif : Activons le : Créons maintenant un objet Counter grâce à l’API REST : Il s’agit du seul Data Type fournit par Riak KV pour lequel la création de l’objet de requiert pas un client autre que curl. Le compteur est initialisé : 17 # riak-admin bucket-type status bucket_type_counter bucket_type_counter has been created and may be activated young_vclock: 20 w: quorum small_vclock: 50 rw: quorum r: quorum pw: 0 precommit: [] pr: 0 postcommit: [] old_vclock: 86400 notfound_ok: true n_val: 3 linkfun: {modfun,riak_kv_wm_link_walker,mapreduce_linkfun} last_write_wins: false dw: quorum dvv_enabled: true chash_keyfun: {riak_core_util,chash_std_keyfun} big_vclock: 50 basic_quorum: false allow_mult: true datatype: set active: false claimant: 'riak@172.17.0.2' # riak-admin bucket-type activate bucket_type_counter bucket_type_counter has been activated # curl -XPOST http://localhost:8098/types/bucket_type_counter/buckets/jeopady_question/datatypes/question_example_1 -H "Content-Type: application/json" -d '{"increment": 0}' # curl -GET http://localhost:8098/types/bucket_type_counter/buckets/jeopady_question/datatypes/question_example_1 {"type":"counter","value":0}
  • 18.
    Nous pouvons alorsl’incrémenter : Ou le décrémenter : Interrogation des données Nous avons vu dans la partie précédente comment lire des données insérées en base grâce à la clé associée à la valeur lors de l’insertion. Cependant, ce mode de récupération - bien que très rapide - n’est pas suffsant dans la plupart des cas d’utilisation d’une base de données. Un mode de récupération des données en fonction de requêtes approximatives ou exactes est possible grâce à l’outil Riak Search, intégré dans Riak RK, qui n’est rien d’autre qu’une implémentation de Solr et un interfaçage automatisé de ce logiciel de recherche avec les données présentes dans les Buckets. Ce rapport n’a pas pour but l’étude de Solr mais omettre Riak Search n’est pas envisageable tant il fait partie intégrante de Riak KV. Cette partie sera donc dédiée à une présentation rapide de l’implémentation et du fonctionnement de Solr au sein du système NoSQL Riak KV. 18 # curl -XPOST http://localhost:8098/types/bucket_type_counter/buckets/jeopady_question/datatypes/question_example_1 -H "Content-Type: application/json" -d '{"increment": 2}' # curl -GET http://localhost:8098/types/bucket_type_counter/buckets/jeopady_question/datatypes/question_example_1 {"type":"counter","value":2} # curl -XPOST http://localhost:8098/types/bucket_type_counter/buckets/jeopady_question/datatypes/question_example_1 -H "Content-Type: application/json" -d '{"increment": 3}' # curl -GET http://localhost:8098/types/bucket_type_counter/buckets/jeopady_question/datatypes/question_example_1 {"type":"counter","value":5} # curl -XPOST http://localhost:8098/types/bucket_type_counter/buckets/jeopady_question/datatypes/question_example_1 -H "Content-Type: application/json" -d '{"decrement": 2}' # curl -GET http://localhost:8098/types/bucket_type_counter/buckets/jeopady_question/datatypes/question_example_1 {"type":"counter","value":3} # curl -XPOST http://localhost:8098/types/bucket_type_counter/buckets/jeopady_question/datatypes/question_example_1 -H "Content-Type: application/json" -d '{"decrement": 4}' # curl -GET http://localhost:8098/types/bucket_type_counter/buckets/jeopady_question/datatypes/question_example_1 {"type":"counter","value":-1} AAE Riak Search utilise l’Active Anti-Entropy pour assurer la cohérence des données stockées et indexées pour la recherche. Un paragraphe dédié à l’explication de ce fonctionnement se trouve en fn de partie.
  • 19.
    Mise en placede Riak Search • Activation de Riak Search Par défaut, Riak Search n’est pas activé dans Riak KV. Pour ce faire, il sufft de remplacer « search = off » par « search = on » dans le fchier de confguration de Riak KV : puis de redémarrer de service Riak. • Schéma Riak Search (Solr) Le schéma permet d’indiquer à Riak Search la nature des données à indexer afn d’y appliquer par la suite des fonctions de recherche adéquates (recherche entre deux dates, entre deux bornes entières, etc.). Il permet aussi d’indiquer la langue dans laquelle les données sont écrites afn d’y appliquer des fonctions de transformations en vue de supprimer les stop words, pluriels, etc. Cette partie est détaillée dans le cours, c’est pourquoi je n’y apporterai pas plus de précisions. 19 # sed -i -- 's/search = off/search = on/g' /etc/riak/riak.conf
  • 20.
    Voici un exemplede schéma correspondant au jeu de données du projet : Ce schéma doit ensuite être inséré dans Riak Search. : Remarquez dans l’url ci-dessus que nous ne faisons bien plus appel à la base de données Riak KV (il n’est plus question de Bucket Types ou Buckets) mais bien à un module « search » à part. Nous créons ici un schéma Riak Search nommé « jeopardy_schema » dont le contenu est le document xml cité plus haut, stocké dans le fchier local « jeopardy_schema.xml ». 20 <?xml version="1.0" encoding="UTF-8" ?> <schema name="schedule" version="1.5"> <felds> <feld name="air_date" type="date" indexed="true" stored="true" default="NOW"/> <feld name="answer" type="string" indexed="true" stored="false" /> <feld name="category" type="string" indexed="true" stored="true" /> <feld name="question" type="string" indexed="true" stored="false" /> <feld name="round" type="string" indexed="true" stored="true" /> <feld name="show_number" type="int" indexed="true" stored="false" /> <feld name="value" type="string" indexed="true" stored="false" /> <dynamicField name="*_en" type="text_en" indexed="true" stored="true" multiValued="true" /> <!-- All of these felds are required by Riak Search --> <feld name="_yz_id" type="_yz_str" indexed="true" stored="true" multiValued="false" required="true"/> <feld name="_yz_ed" type="_yz_str" indexed="true" stored="false" multiValued="false"/> <feld name="_yz_pn" type="_yz_str" indexed="true" stored="false" multiValued="false"/> <feld name="_yz_fpn" type="_yz_str" indexed="true" stored="false" multiValued="false"/> <feld name="_yz_vtag" type="_yz_str" indexed="true" stored="false" multiValued="false"/> <feld name="_yz_rk" type="_yz_str" indexed="true" stored="true" multiValued="false"/> <feld name="_yz_rt" type="_yz_str" indexed="true" stored="true" multiValued="false"/> <feld name="_yz_rb" type="_yz_str" indexed="true" stored="true" multiValued="false"/> <feld name="_yz_err" type="_yz_str" indexed="true" stored="false" multiValued="false"/> </felds> <types> <!-- YZ String: Used for non-analyzed felds --> <feldType name="_yz_str" class="solr.StrField" sortMissingLast="true" /> <feldType name="string" class="solr.StrField" sortMissingLast="true" /> <feldType name="int" class="solr.TrieIntField" precisionStep="0" positionIncrementGap="0"/> <feldType name="date" class="solr.TrieDateField" omitNorms="false" precisionStep="0" positionIncrementGap="0"/> <feldType name="text_en" class="solr.TextField" positionIncrementGap="100"> <analyzer> <tokenizer class="solr.StandardTokenizerFactory"/> <flter class="solr.LowerCaseFilterFactory"/> <flter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_en.txt" format="snowball" /> <flter class="solr.EnglishMinimalStemFilterFactory"/> </analyzer> </feldType> </types> <uniqueKey>_yz_id</uniqueKey> </schema> # curl -XPUT http://localhost:8098/search/schema/jeopardy_schema -H 'Content-Type:application/xml' --data-binary @jeopardy_schema.xml
  • 21.
    • Création del’index Riak Search Nous devons maintenant créer un index Riak Search utilisant le schéma précédemment défni. Cet index ne sera pour le moment alimenté par aucune donnée. L’index créé « jeopardy_index » utilise bien comme schéma « jeopardy_schema ». • Attribution d’un index à une collection de données L’index que nous venons de créer peut désormais être lié au choix soit à un Bucket Type, soit à un Bucket. Dans le cas où nous le lions à un Bucket Type, toutes les données des Buckets de ce type seront indexées dans cet index ; il sera donc nécessaire de garder une cohérence de structure forte entre les données insérées dans ces différents Buckets afn que toutes répondent au schéma Riak Search associé. Les futures recherches utilisant cet index pourront alors se faire à travers les données stockées dans tous ces Buckets. L’attribution d’un index lors de la création d’un Bucket Type se ferait alors ainsi : Nous pouvons aussi directement lier un index à un Bucket (c’est ce que nous ferons ici) : Toutes les actions précédentes peuvent aussi se faire et être visualisées grâce à l’interface Web : 21 Illustration 3: Liste des index créés avec leur schéma associé # curl -XPUT http://localhost:8098/search/index/jeopardy_index -H 'Content-Type: application/json' -d '{"schema":"jeopardy_schema"}' [info] <0.2691.0>@yz_index:core_create:284 Created index jeopardy_index with schema jeopardy_schema # riak-admin bucket-type create mon_bucket_type '{"props":{"search_index":"leopardy_schema"}}' # curl -XPUT http://localhost:8098/types/default/buckets/jeopardy_bucket/props -H 'Content-Type: application/json' -d '{"props":{"search_index":"jeopardy_index"}}'
  • 22.
    • Alimentation del’index Insérons l’ensemble de notre collection de documents Jeopardy dans le bucket « jeopardy_bucket »grâce à un script shell : Vérifons que le Bucket et l’Index comportent le même nombre de données. Nous pouvons récupérer le nombre de données dans le Bucket grâce à une fonction MapReduce simple : 22 Illustration 4: Détail de l'index "jeopardy_index" #!/bin/bash json_fle=$(cat jeopardy.json); type="default" bucket="jeopardy_bucket" i=0; for data in $(echo "${json_fle}" | jq -r '.[] | @base64'); do ((i++)); _jq() { echo ${data} | base64 --decode; } json_item="'"$(_jq)"'"; insert_command=`echo curl -v -X PUT http://localhost:8098/types/$type/buckets/$bucket/keys/show_$i -H """Content- Type: application/json""" -d $json_item`; eval $insert_command; done; #curl -XPOST http://localhost:8098/mapred -H 'Content-Type: application/json' -d ' {"inputs":"jeopardy_bucket", "query":[{"map":{"language":"javascript", "keep":false, "source":"function(jeopardykv) {return [1]; }"}}, {"reduce":{"language":"javascript", "keep":true, "name":"Riak.reduceSum"}}]}' 216770
  • 23.
    Pour vérifer lenombre de données dans l’index, il sufft d’une requête renvoyant le nombre total de documents (ici nous sélectionnant tous les documents ayant une clé category) : • Interrogation de l’index Voici quelques exemple d’interrogation de l’index nouvellement créé : Cherchons les documents dont la catégorie est « ALL MY SON » : La requête est passée après « q= » (pour query =). Riak Search nous indique que 5 documents correspondent à notre requête et liste les liste avec leurs attributs stockés dans l’index. 23 # curl -XGET "http://localhost:8098/search/query/jeopardy_index?wt=json&q=category:*" {"responseHeader":{"status":0,"QTime":52,"params":{"q":"category:*","shards":"172.17.0.2:8093/internal_solr/ jeopardy_index","172.17.0.2:8093":"(_yz_pn:62 AND (_yz_fpn:62)) OR _yz_pn:61 OR _yz_pn:58 OR _yz_pn:55 OR _yz_pn:52 OR _yz_pn:49 OR _yz_pn:46 OR _yz_pn:43 OR _yz_pn:40 OR _yz_pn:37 OR _yz_pn:34 OR _yz_pn:31 OR _yz_pn:28 OR _yz_pn:25 OR _yz_pn:22 OR _yz_pn:19 OR _yz_pn:16 OR _yz_pn:13 OR _yz_pn:10 OR _yz_pn:7 OR _yz_pn:4 OR _yz_pn:1","wt":"json"}},"response": {"numFound":216770,"start":0,"maxScore":1.0,"docs":[{"round":"Double Jeopardy!","air_date":"2005-09- 27T00:00:00Z","category":"YOURE A "STAR"","_yz_id":"1*default*jeopardy_bucket*show_70751*25","_yz_rk":"show_70751","_yz_rt":"default","_yz_rb":"jeopardy_buck et"},{"round":"Double Jeopardy!","air_date":"2005-09-27T00:00:00Z","category":"LITERARY ____ OF ____","_yz_id":"1*default*jeopardy_bucket*show_70759*40","_yz_rk":"show_70759","_yz_rt":"default","_yz_rb":"jeopardy_bucket"}, {"round":"Double Jeopardy!","air_date":"2005-09-27T00:00:00Z","category":"CLARK"…...}]}} # curl "http://localhost:8098/search/query/jeopardy_index?wt=json&q=category:"ALL MY SONS" {"responseHeader":{"status":0,"QTime":4,"params":{"q":"category:"ALL MY SONS"","shards":"172.17.0.2:8093/internal_solr/jeopardy_index","172.17.0.2:8093":"_yz_pn:64 OR (_yz_pn:61 AND (_yz_fpn:61)) OR _yz_pn:60 OR _yz_pn:57 OR _yz_pn:54 OR _yz_pn:51 OR _yz_pn:48 OR _yz_pn:45 OR _yz_pn:42 OR _yz_pn:39 OR _yz_pn:36 OR _yz_pn:33 OR _yz_pn:30 OR _yz_pn:27 OR _yz_pn:24 OR _yz_pn:21 OR _yz_pn:18 OR _yz_pn:15 OR _yz_pn:12 OR _yz_pn:9 OR _yz_pn:6 OR _yz_pn:3","wt":"json"}},"response":{"numFound":5,"start":0,"maxScore":11.613354,"docs": [{"round":"Jeopardy!","air_date":"2008-03-13T00:00:00Z","category":"ALL MY SONS","_yz_id":"1*default*jeopardy_bucket*show_4698*27","_yz_rk":"show_4698","_yz_rt":"default","_yz_rb":"jeopardy_bucket"}, {"round":"Jeopardy!","air_date":"2008-03-13T00:00:00Z","category":"ALL MY SONS","_yz_id":"1*default*jeopardy_bucket*show_4704*42","_yz_rk":"show_4704","_yz_rt":"default","_yz_rb":"jeopardy_bucket"}, {"round":"Jeopardy!","air_date":"2008-03-13T00:00:00Z","category":"ALL MY SONS","_yz_id":"1*default*jeopardy_bucket*show_4680*64","_yz_rk":"show_4680","_yz_rt":"default","_yz_rb":"jeopardy_bucket"}, {"round":"Jeopardy!","air_date":"2008-03-13T00:00:00Z","category":"ALL MY SONS","_yz_id":"1*default*jeopardy_bucket*show_4686*51","_yz_rk":"show_4686","_yz_rt":"default","_yz_rb":"jeopardy_bucket"}, {"round":"Jeopardy!","air_date":"2008-03-13T00:00:00Z","category":"ALL MY SONS","_yz_id":"1*default*jeopardy_bucket*show_4692*6","_yz_rk":"show_4692","_yz_rt":"default","_yz_rb":"jeopardy_bucket"}]}}
  • 24.
    Cherchons les documentsdont la date de diffusion (air_date) est comprise entre le 01/03/2008 et le 01/12/2008 : Il est bien sûr possible d’exploiter toutes les possibilités offertes par Solr et donc d’aller bien plus loin (vraiment plus loin) que les deux exemples ci-dessus. L’ Active Anti-Entropy L’active Anti-Entropy est un processus permettant de rechercher et de corriger les incohérences et différences entre les données stockées dans la base Riak RK et l’index Riak Search (Solr). Ces erreurs peuvent être dues à de multiples raisons (extinction de serveur, problème matériel, problème réseau, etc.). Le principe de l’AAE est qu’il est impossible de prévoir les pannes tant elles peuvent être diverses et parfois diffciles à déceler ; il est alors plus effcace d’implémenter un script qui vérife en temps réel la bonne duplication des informations voulues entre la base et l’index. Il serait bien évidemment totalement ineffcace d’utiliser un processus qui scannerait d’un côté les données présentes en base et de l’autre celles présentes dans l’index et de les comparer. Pour palier à ce problème, l’AAE utilise le système des hashtrees (arbres de hashage ou arbre de Merkle). Lors de l’insertion des données, celles-ci ont été réparties en plusieurs fragments sur le serveur (il s’agit en fait de vnode mais nous conserverons le terme «fragment» dans cette partie pour plus de clarté. Les notions liées à la répartition des données seront vues et expliquées dans la dernière partie de ce rapport). 24 # curl "http://localhost:8098/search/query/jeopardy_index?wt=json&q=air_date:[2008-03-01T00:00:00Z TO 2008-12-01T00:00:00Z]" {"responseHeader":{"status":0,"QTime":8,"params":{"q":"air_date:[2008-03-01T00:00:00Z TO 2008-12- 01T00:00:00Z]","shards":"172.17.0.2:8093/internal_solr/jeopardy_index","172.17.0.2:8093":"_yz_pn:64 OR (_yz_pn:61 AND (_yz_fpn:61)) OR _yz_pn:60 OR _yz_pn:57 OR _yz_pn:54 OR _yz_pn:51 OR _yz_pn:48 OR _yz_pn:45 OR _yz_pn:42 OR _yz_pn:39 OR _yz_pn:36 OR _yz_pn:33 OR _yz_pn:30 OR _yz_pn:27 OR _yz_pn:24 OR _yz_pn:21 OR _yz_pn:18 OR _yz_pn:15 OR _yz_pn:12 OR _yz_pn:9 OR _yz_pn:6 OR _yz_pn:3","wt":"json"}},"response":{"numFound":9521,"start":0,"maxScore":1.0,"docs": [{"round":"Jeopardy!","air_date":"2008-05- 29T00:00:00Z","category":"NUMBERS","_yz_id":"1*default*jeopardy_bucket*show_70839*24","_yz_rk":"show_70839","_yz_rt":"defaul t","_yz_rb":"jeopardy_bucket"},{"round":"Jeopardy!","air_date":"2008-05-29T00:00:00Z","category":"I DO","_yz_id":"1*default*jeopardy_bucket*show_70848*24","_yz_rk":"show_70848","_yz_rt":"default","_yz_rb":"jeopardy_bucket"}, {"round":"Jeopardy!","air_date":"2008-05-29T00:00:00Z","category":"THE 20th CENTURY","_yz_id":"1*default*jeopardy_bucket*show_70835*57","_yz_rk":"show_70835","_yz_rt":"default","_yz_rb":"jeopardy_buck et"},{"round":"Jeopardy!","air_date":"2008-05-29T00:00:00Z","category":"THE 20th CENTURY","_yz_id":"1*default*jeopardy_bucket*show_70841*64","_yz_rk":"show_70841","_yz_rt":"default","_yz_rb":"jeopardy_buck et"},{"round":"Jeopardy!","air_date":"2008-05- 29T00:00:00Z","category":"NUMBERS","_yz_id":"1*default*jeopardy_bucket*show_70845*3","_yz_rk":"show_70845","_yz_rt":"default ","_yz_rb":"jeopardy_bucket"},{"round":"Jeopardy!","air_date":"2008-05-29T00:00:00Z","category":"FAMILIAR PHRASES","_yz_id":"1*default*jeopardy_bucket*show_70846*12","_yz_rk":"show_70846","_yz_rt":"default","_yz_rb":"jeopardy_bucke t"},{"round":"Jeopardy!","air_date":"2008-05- 29T00:00:00Z","category":"FABRICS","_yz_id":"1*default*jeopardy_bucket*show_70837*57","_yz_rk":"show_70837","_yz_rt":"default" ,"_yz_rb":"jeopardy_bucket"},{"round":"Jeopardy!","air_date":"2008-05-29T00:00:00Z","category":"I DO","_yz_id":"1*default*jeopardy_bucket*show_70836*64","_yz_rk":"show_70836","_yz_rt":"default","_yz_rb":"jeopardy_bucket"}, {"round":"Jeopardy!","air_date":"2008-05-29T00:00:00Z","category":"FAMILIAR PHRASES","_yz_id":"1*default*jeopardy_bucket*show_70852*64","_yz_rk":"show_70852","_yz_rt":"default","_yz_rb":"jeopardy_bucke t"},{"round":"Jeopardy!","air_date":"2008-05-29T00:00:00Z","category":"LITERARY LOCALES","_yz_id":"1*default*jeopardy_bucket*show_70838*45","_yz_rk":"show_70838","_yz_rt":"default","_yz_rb":"jeopardy_bucke t"}]}}
  • 25.
    Ces fragments peuventêtre listés ainsi : 25 # riak-admin vnode-status Vnode status information ------------------------------------------- VNode: 0 Backend: riak_kv_bitcask_backend Status: [{key_count,10213},{status,[]}] Status: {vnodeid,<<191,0,161,239,224,222,129,194>>} Status: {counter,20213} Status: {counter_lease,30000} Status: {counter_lease_size,10000} Status: {counter_leasing,false} VNode: 22835963083295358096932575511191922182123945984 Backend: riak_kv_bitcask_backend Status: [{key_count,10174},{status,[]}] Status: {vnodeid,<<191,0,161,239,224,222,130,8>>} Status: {counter,20174} Status: {counter_lease,30000} Status: {counter_lease_size,10000} Status: {counter_leasing,false} . . . VNode: 1438665674247607560106752257205091097473808596992 Backend: riak_kv_bitcask_backend Status: [{key_count,10227},{status,[]}] Status: {vnodeid,<<191,0,161,239,226,126,207,204>>} Status: {counter,10227} Status: {counter_lease,20000} Status: {counter_lease_size,10000} Status: {counter_leasing,false}
  • 26.
    Le statut del’AAE peut être vérifé : Qu’apprend-on ici ? La section « Entropy Trees » nous indique quand les hashtrees de chaque partition ont été créés pour les besoins de l’AAE. La section « Keys Repaired » nous indique le nombre d’incohérences qui ont été détectées et réparées ; ici tout s’est bien passé, aucun problème n’a eu lieu pendant l’insertion des données et l’alimentation de l’index en parallèle. Enfn, la section « Exchange » indique le temps depuis la dernière opération de communication entre le fragment de base contenant les données et l’index et depuis combien de temps plus aucun échange n’a été réalisé. Mais alors, comment tout cela fonctionne ? Pour chaque partition, deux hashtrees sont créés : un pour la base Riak KV, l’autre pour Riak Search (Solr). A chaque insertion ou modifcation de données d’un côté ou de l’autre du système, l’arbre est mis à jour. L’AAE consiste alors à comparer périodiquement les deux arbres de hashage afn de voir s’ils sont identiques. Dans un premier lieu, les hash des deux racines sont comparés ; s’il s’agit des mêmes valeurs alors il est inutile d’aller plus loin, les arbres sont identiques. En effet, les valeurs de hash des feuilles supérieures sont déduites par rapport aux valeurs de hash inférieures. S’il existe une différence entre les valeurs de hash des feuilles racines, il est alors nécessaire de descendre dans l’arbre afn de trouver la valeur qui diffère. Une fois la différence trouvée, une fonction de réparation est appelée afn de créer la valeur manquante ou la corriger si elle est existante. Une illustration de ce fonctionnement en arbre est présente ci-dessous. 26 # riak-admin search aae-status ================================== Exchanges ================================== Index Last (ago) All (ago) ------------------------------------------------------------------------------- 0 1.1 hr 1.1 hr 22835963083295358096932575511191922182123945984 1.1 hr 1.1 hr . . . 1438665674247607560106752257205091097473808596992 20.6 min 21.1 min ================================ Entropy Trees ================================ Index Built (ago) ------------------------------------------------------------------------------- 0 1.9 hr 22835963083295358096932575511191922182123945984 1.9 hr . . . 1438665674247607560106752257205091097473808596992 1.9 hr ================================ Keys Repaired ================================ Index Last Mean Max ------------------------------------------------------------------------------- 0 0 0 0 22835963083295358096932575511191922182123945984 0 0 0 . . . 1438665674247607560106752257205091097473808596992 0 0 0
  • 27.
    Ce système n’estpas cependant exempt de toute erreur. En effet, il est tout à fait possible d’imaginer qu’une erreur puisse empêcher la mise à jour des hashtrees et que ceux-ci restent donc identiques, quelles que soient les opérations faites sur la base de données. C’est pour cela que les hashtrees n’ont pas une durée de vie illimitée ; Riak KV considère qu’au bout d’une semaine, il est nécessaire de détruire les deux arbres et de les reconstruire entièrement afn de pouvoir les comparer à nouveau. Testons maintenant la reconstruction d’une valeur présente dans l’index après une erreur de synchronisation. Pour ce faire, nous couperons le service Riak Search, mettrons à jour un document dans le Bucket « jeopardy_bucket »puis relancerons Riak Search. Nous travaillerons ici avec le document d’id « show_4680 ». Voici ce qui est stocké dans l’index pour ce document : (vous remarquerez ici l’utilisation de _yz_rk qui est un champs ajouté automatiquement indexant la clé Riak KV par Riak Search.) 27 Illustration 5: Schéma représentant le fonctionnement de l'AAE Hashtree Riak Search Hash racine Partition 22835963083295358096932575511191922182123945984 Hash racine Hash Document 1 Hash Document 2 ... Hash Clé 1 Hash Document 1 Hash Document 2 ... "air_date": "2003-06-06" answer": "Jolly Rancher" "category": "CANDY" Hash Clé 2 Hash Clé 3 ... ... ... # curl -XGET "http://localhost:8098/search/queer/jeopardy_index?wt=json&q=_yz_rk:show_4680" {"responseHeader":{"status":0,"QTime":15,"params":{"q":"_yz_rk:show_4680","shards":"172.17.0.2:8093/internal_solr/ jeopardy_index","172.17.0.2:8093":"_yz_pn:64 OR (_yz_pn:61 AND (_yz_fpn:61)) OR _yz_pn:60 OR _yz_pn:57 OR _yz_pn:54 OR _yz_pn:51 OR _yz_pn:48 OR _yz_pn:45 OR _yz_pn:42 OR _yz_pn:39 OR _yz_pn:36 OR _yz_pn:33 OR _yz_pn:30 OR _yz_pn:27 OR _yz_pn:24 OR _yz_pn:21 OR _yz_pn:18 OR _yz_pn:15 OR _yz_pn:12 OR _yz_pn:9 OR _yz_pn:6 OR _yz_pn:3","wt":"json"}},"response": {"numFound":1,"start":0,"maxScore":12.999648,"docs":[{"round":"Jeopardy!","air_date":"2008-03-13T00:00:00Z","category":"ALL MY SONS","_yz_id":"1*default*jeopardy_bucket*show_4680*64","_yz_rk":"show_4680","_yz_rt":"default","_yz_rb":"jeopardy_bucket"}]}} Hashtree Riak KV
  • 28.
    Coupons maintenant RiakSearch : On redémarre le serveur. Nous n’avons plus accès à l’index. Regardons le document stocké dans Riak KV : Mettons à jour ce document en modifant la valeur de la clé category : Relançons maintenant Riak Search : On redémarre le serveur. Pour le moment l’index n’a pas encore été mis à jour. Les deux hashtrees sont donc différents. Au bout de quelques instants, l’AAE se rend compte de la différence entre la donnée présente dans le Bucket et celle qui est indexée : Suite à cette réparation, l’index a bien été mis à jour : 28 #sed -i -- 's/search = on/search = off/g' /etc/riak/riak.conf #curl -XGET "http://localhost:8098/types/default/buckets/jeopardy_bucket/keys/show_4680" {"category":"ALL MY SONS","air_date":"2008-03-13T00:00:00Z","question":"Gutzon Borglum died in 1941 so his son, Lincoln, fnished sculpting the 4 fgures of this memorial","value":"$200","answer":"Mount Rushmore","round":"Jeopardy!","show_number":5419} #curl -XPOST http://localhost:8098/types/default/buckets/jeopardy_bucket/keys/show_4680 -H "Content-Type: application/json" -d '{"category":"TEST RIAK SEARCH","air_date":"2008-03-13T00:00:00Z","question":"Gutzon Borglum died in 1941 so his son, Lincoln, fnished sculpting the 4 fgures of this memorial","value":"$200","answer":"Mount Rushmore","round":"Jeopardy!","show_number":5419}' #curl -XGET "http://localhost:8098/types/default/buckets/jeopardy_bucket/keys/show_4680" {"category":"TEST RIAK SEARCH","air_date":"2008-03-13T00:00:00Z","question":"Gutzon Borglum died in 1941 so his son, Lincoln, fnished sculpting the 4 fgures of this memorial","value":"$200","answer":"Mount Rushmore","round":"Jeopardy!","show_number":5419} #sed -i -- 's/search = off/search = on/g' /etc/riak/riak.conf #curl -XGET "http://localhost:8098/search/query/jeopardy_index?wt=json&q=_yz_rk:show_4680" {"responseHeader":{"status":0,"QTime":37,"params":{"q":"_yz_rk:show_4680","shards":"172.17.0.2:8093/internal_solr/ jeopardy_index","172.17.0.2:8093":"_yz_pn:64 OR (_yz_pn:61 AND (_yz_fpn:61)) OR _yz_pn:60 OR _yz_pn:57 OR _yz_pn:54 OR _yz_pn:51 OR _yz_pn:48 OR _yz_pn:45 OR _yz_pn:42 OR _yz_pn:39 OR _yz_pn:36 OR _yz_pn:33 OR _yz_pn:30 OR _yz_pn:27 OR _yz_pn:24 OR _yz_pn:21 OR _yz_pn:18 OR _yz_pn:15 OR _yz_pn:12 OR _yz_pn:9 OR _yz_pn:6 OR _yz_pn:3","wt":"json"}},"response": {"numFound":1,"start":0,"maxScore":12.999648,"docs":[{"round":"Jeopardy!","air_date":"2008-03-13T00:00:00Z","category":"ALL MY SONS","_yz_id":"1*default*jeopardy_bucket*show_4680*64","_yz_rk":"show_4680","_yz_rt":"default","_yz_rb":"jeopardy_bucket"}]}} [info] <0.19507.0>@yz_exchange_fsm:key_exchange:209 Will delete 0 keys and repair 1 keys of partition 1415829711164312202009819681693899175291684651008 for prefist {1415829711164312202009819681693899175291684651008 #curl -XGET "http://localhost:8098/search/query/jeopardy_index?wt=json&q=_yz_rk:show_4680" {"responseHeader":{"status":0,"QTime":14,"params":{"q":"_yz_rk:show_4680","shards":"172.17.0.2:8093/internal_solr/ jeopardy_index","172.17.0.2:8093":"(_yz_pn:62 AND (_yz_fpn:62)) OR _yz_pn:61 OR _yz_pn:58 OR _yz_pn:55 OR _yz_pn:52 OR _yz_pn:49 OR _yz_pn:46 OR _yz_pn:43 OR _yz_pn:40 OR _yz_pn:37 OR _yz_pn:34 OR _yz_pn:31 OR _yz_pn:28 OR _yz_pn:25 OR _yz_pn:22 OR _yz_pn:19 OR _yz_pn:16 OR _yz_pn:13 OR _yz_pn:10 OR _yz_pn:7 OR _yz_pn:4 OR _yz_pn:1","wt":"json"}},"response": {"numFound":1,"start":0,"maxScore":12.452759,"docs":[{"round":"Jeopardy!","air_date":"2008-03-13T00:00:00Z","category":"TEST RIAK SEARCH","_yz_id":"1*default*jeopardy_bucket*show_4680*1","_yz_rk":"show_4680","_yz_rt":"default","_yz_rb":"jeopardy_bucket"}]}}
  • 29.
    La réparation dela valeur dans l’index est bien marquée comme ayant été repérée et appliquée : Alors que nous n’avions ici qu’un serveur et seulement 200 000 valeurs, il est nécessaire d’indiquer que le processus a tout de même mis du temps avant de réaliser la réparation ; plusieurs dizaines de fragments ont été analysés avant celui qui nous intéressait ; l’interrogation de l’index n’était donc pas cohérente par rapport à la donnée présente en base. Selon Basho, c’est le prix à payer pour obtenir une interface de recherche évoluée : les échanges de données entre les deux systèmes apportent des incohérences qu’un système seul met moins de temps à régler. Les Secondary Indexes Les Secondary Indexes (Index secondaires) – notés 2i – sont une autre sorte d’index, moins évolués que ceux fournis par Riak Search mais directement intégrés au moteur de Riak KV. Il s’agit ici de métadonnées liées à chaque document, créées lors de l’écriture de celui-ci et stockées dans le Bucket avec l’objet. Ils permettent l’interrogation des données en se basant sur un autre critère que la clé du document. Ici, seuls les éléments de type « string » ou « int » peuvent être indexés ; de plus, il n’est possible d’exécuter que des requêtes de recherche exacte ou de recherche d’intervalle. Attention cependant, bien que faciles et pratiques à mettre en place, Riak KV recommande cependant de privilégier Riak Search dans les cas où les besoins de recherche sont complexes. Il est en effet, avec les 2I, impossible de composer des requêtes avec un argument « OR » ou « AND » ; il est nécessaire d’exécuter les deux requêtes séparément et de fusionner les résultats. Un autre possible inconvénient des 2I est qu’ils ne sont compatibles qu’avec deux backends : LevelDB et Memory. Enfn, des problèmes de performances peuvent apparaître à cause des 2i sur de grands Clusters. Les 2I peuvent néanmoins être utilisés comme entrée de traitement MapReduce. Nous allons maintenant voir comment créer des index pour les documents de notre collection. L’absence de schéma décrivant les documents comme dans Riak Search nous contraint d’entrer « à la main » la valeur 29 # riak-admin search aae-status ================================== Exchanges ================================== Index Last (ago) All (ago) ------------------------------------------------------------------------------- . . . 1415829711164312202009819681693899175291684651008 24.8 min 25.3 min . . . ================================ Entropy Trees ================================ Index Built (ago) ------------------------------------------------------------------------------- . . . 1415829711164312202009819681693899175291684651008 5.9 hr . . . ================================ Keys Repaired ================================ Index Last Mean Max ------------------------------------------------------------------------------- . . . 1415829711164312202009819681693899175291684651008 1 0 1 . . .
  • 30.
    que nous souhaitonsindexer. Par exemple, voici comment nous pourrions créer des 2I pour un document de notre collection : Nous alimentons ici deux index « category_bin » et « show_number_int » avec des valeurs que nous avons nous même spécifées : elles n’ont pas été extraites du document. Modifons notre script d’insertion en masse afn que chaque donnée de notre collection soit indexée ainsi. Seule une partie de la collection suffsante pour les exemples suivants a été insérée. Que pouvons nous faire ? Rechercher les documents dont la category est « HISTORY » : Remarquez que ce type d’index ne fait rien d’autre que de retourner la liste des identifants des documents correspondants à notre requête. Il est aussi possible de rechercher entre deux bornes, ici entre 1000 et 3000 : (le numéro de show du document ne correspond pas au numéro d’id « show_x » entré par le script 30 #curl -v -X PUT http://localhost:8098/types/default/buckets/jeopardy_bucket_2I/keys/show_1 -H "Content-Type: application/json" -H "x-riak-index-category_bin : CANDY" -H "x-riak-index-show_number_int : 4335" -d ‘{ "air_date": "2003-06-06", "answer": "Jolly Rancher", "category": "CANDY", "question": "Bill Harmsen, who raised horses in Colo., happily founded this candy co. in 1949 to make money during the winter", "round": "Final Jeopardy!", "show_number": 4335, "value": null }’ #!/bin/bash json_fle=$(cat jeopardy.json.format); type="leveldb_backend" bucket="jeopardy_bucket_2I" i=0; for data in $(echo "${json_fle}" | jq -r '.[] | @base64'); do ((i++)); _jq() { echo ${data} | base64 --decode; } json_item="'"$(_jq)"'"; category=$(echo $json_item | grep -o -P '(?<="category":).*?(?=,)') show_number=$(echo $json_item | grep -o -P '(?<="show_number":)[0-9]*') insert_command=`echo curl -v -X PUT http://localhost:8098/types/$type/buckets/$bucket/keys/show_$i -H "’"Content-Type: application/json"’" -H "’"x-riak-index-category_bin: $category"’" -H "’"x-riak-index-show_number_int: $show_number"’" -d $json_item`; eval $insert_command; done; #curl -XGET http://localhost:32768/buckets/jeopardy_2I_bucket4/index/category_bin/HISTORY {"keys":["show_417","show_25","show_405","show_7","show_393","show_19","show_1","show_399","show_13","show_411"]} #curl -XGET http://localhost:32768/buckets/jeopardy_2I_bucket4/index/show_number_int/1000/3000 {"keys": ["show_2","show_393","show_9","show_417","show_399","show_542","show_6","show_522","show_744","show_125""show_630","sh ow_25","show_405"...}
  • 31.
    RÉPLICATION ET PARTITIONNEMENT Nousavons vu jusqu’à présent quelques aspects de Riak KV concernant la gestion des données. Cependant, comme tout système NoSQL, celui-ci prend tout son sens dans un contexte distribué. Riak KV se veut être scalable, cohérent et disponible grâce à l’implémentation du système Dynamo d’Amazon. Nous verrons dans cette partie comment mettre en place un cluster Riak KV et quelques mécanismes assurant la cohérence des données. Dynamo Dynamo, introduit par Amazon avec son SGBD NoSQL Amazon DynamoDB, regroupe un ensemble de techniques permettant à un système clé-valeur d’être facilement scalable, redondant, tolérant aux pannes. Aucun nœud du cluster n’a ici un rôle différent des autres ; il n’y a donc pas de nœud « master », chaque nœud connaît l’ensemble des informations du cluster (schéma de partitionnement, localisation des données, etc.) et est en mesure de répondre à un client souhaitant récupérer une donnée ou en écrire une. Chaque nœud interrogé devient alors un nœud coordinateur, en charge de récupérer l’information demandée localement ou sur un autre nœud et de la transmettre au client, ou bien de transmettre l’écriture demandée au nœud qui en est responsable. Ce fonctionnement rend l’interaction du client avec le cluster extrêmement simplifée ; il est inutile de connaître la façon dont les données sont réparties, de savoir à qui s’adresser afn de pouvoir les exploiter, chaque nœud saura répondre à la requête. De plus, l’absence de nœud « master » évite le «single point of failure » rendant inexploitable le cluster en cas de panne du « master ». Rappel du principe de hashage cohérent Pour répartir les données au sein du cluster, Dynamo se base sur le hashage cohérent (consistent hashing) auquel il ajoute la notion de virtual node (vnode). Pour rappel, le hashage cohérent utilise une fonction de hashage (appliquée à la clé de la donnée – la concaténation du nom de Bucket et de la clé du document ) afn de répartir les données sur un cercle partitionné en n arcs de cercle égaux (le Hash Ring), n étant un nombre fxé suffsamment haut défni en fonction de la fonction de hashage utilisée (par exemple, 2160 si on utilise l’algorithme de hashage SHA-1 comme Riak KV l’a choisi). Ces fragments de cercles sont alors numérotés, de 0 à n-1 (sens des aiguilles d’une montre), et chaque donnée est alors affectée à un emplacement en fonction du mode de correspondance choisi pour associer le hash résultant de son passage dans la fonction de hashage à un nombre entier compris entre 0 et n-1. 31 Illustration 6: Représentation du Hash Ring 0n - 1 Hash Clé « exemple »
  • 32.
    Répartition des données Dansle cas où le cluster n’est constitué que d’un seul nœud, celui-ci sera responsable de l’ensemble des fragments du cercle. A chaque ajout d’un nœud au cluster, une valeur comprise entre 0 et n-1 est tirée au sort ; le nœud sera alors responsable de l’ensemble des données affectées aux positions du cercle comprises entre cette valeur et celle tirée au sort par le premier nœud rencontré sur le cercle. Dynamo a poussé un peu plus loin le concept. A l’ajout d’un nœud dans le cluster, le mécanisme de répartition lui attribue un ensemble d’intervalles sur le Hash Ring (nombre défni par la variable locale ring_size (64 par défaut)). Autant que possible, le Hash Ring est partitionné en différents intervalles équivalents (par défaut 64 espaces couvrant la même surface du cercle) et ceux-ci sont répartis équitablement entre les différents nœuds. 32 Illustration 8: Répartition des données du Hash Ring Nœud 1 Nœud 3 Nœud 2 Légende Illustration 7: Répartition de données entre deux serveurs Nœud 1 Nœud 2 Légende0n - 1 Ajoutdunœud2Ajoutdunœud3
  • 33.
    Si les donnéessont séparées en 64 fragments et que nous avons 5 nœuds, chacun aura la charge de 12 ou 13 partitions (64 = 4 x 12 + 1 x 13). Ajoutons maintenant 3 nouveaux nœuds, chacun aura la responsabilité de 8 partitions. Ces partitions sont gérées par des processus indépendants et ont le devoir de stockage des données sur l’intervalle qui leur est attribué ; en cela, elle n’ont pas grand-chose de différents avec les nœuds (nodes), c’est pourquoi elles sont appelées nœuds virtuels (vnodes pour virtual nodes). Un nœud peut donc être vu comme un cluster à part entière gérant un ensemble de vnodes. La réplication Nous avons maintenant une visualisation claire de la façon suivant laquelle les données sont réparties sur les différentes partitions d’un cluster. Il n’est cependant pas suffsant de connaître la méthode de répartition des données sur les nœuds, il est nécessaire que celles-ci soient toujours accessibles en cas de panne de nœud. Chaque nœud rejoignant le cluster est en fait responsable, en plus des partitions qui lui ont été attribuées, de partitions d’autres nœuds afn d’en conserver des réplicas. Ce système permet donc d’être tolèrent aux pannes mais aussi de pouvoir répartir la charge des requêtes sur plusieurs nœuds au lieu d’un seul. Ainsi, un nœud physique aura en fait la responsabilité de n/(nombre de nœuds physiques) x (nombres de réplicas confgurés) partitions. Considérons que notre cluster a été divisé en 64 partitions, que nous avons 8 nœuds physiques et que nous souhaitons que chaque donnée soit répliquée 3 fois, chaque nœud physique aura la responsabilité de 64/8x3 = 24 partitions. Mais alors, comment sont répartis les réplicas sur les différents nœuds du cluster ? Il est assez évident qu’il ne faut pas qu’une partition soit répliquée sur un même nœud physique, auquel cas la réplication n’aurait aucun sens si ce nœud tombait en panne. Lorsqu’une donnée est attribuée après hashage à une partition appartenant à un nœud, c’est ce nœud qui se charge de la réplication de cette donnée sur les partitions « réplicas ». Si le paramètre de réplication n_val est confguré à 3 par exemple, les partitions « réplicas » correspondront à des partitions appartenant aux 3 - 1 = 2 nœuds suivant le nœud responsable. En pratique, ce sont des vnodes et non les nodes directement qui stockent les données ; il n’est pas possible de prendre les n_val – 1 vnodes afn d’assurer la réplication des données, ces vnodes pouvant faire partie de la même node que la première . Un parcours du cercle est donc mis en place, excluant les vnodes appartenant à une node déjà récupérée, et acceptant la première vnode (et donc la plus proche) issue d’une node non connue. Cette réplication est à priori effcace, mais que se passe t’il si les n_val nœuds responsables de l’écriture d’une donnée tombent simultanément en panne ? La liste des nœuds responsable du stockage d’une donnée associée à une certaine clé est appelée « liste de préférence ». Afn de prévenir tout problème de nœud, cette liste contient plus de nœuds que le paramètre de réplication n_val ne le prévoit, et donc d’autres nœuds que les nœuds réplicas. A quoi servent donc les 33 Illustration 9: Réplication d'une donnée avec n_val = 3 Nœud 1 Nœud 3 Nœud 2 Nœud 4 Nœud 5 Hash Clé « exemple »
  • 34.
    serveurs listés quin’ont, à priori, rien à voir avec cette donnée ? Les n_val premiers éléments de la liste sont appelés « réplicas primaires » ; c’est en effet eux qui se chargent de la réplication de la donnée en temps normal ; les autres nœuds sont appelés « réplicas secondaires ». Admettons que sur le schéma précédent, les nœuds 1, 2 et 3 soient indisponibles et que le client demande le stockage d’une donnée ayant pour clé « exemple ». Il serait possible de lui refuser cette écriture en vertu du non respect du quorum et surtout de l’absence de l’intégralité des serveurs, ou de ne pas rendre la main tant qu’un nœud primaire n’est pas disponible. Ce n’est pas le cas avec Dynamo qui utilise le mécanisme « Hinted Handoff ». Celui-ci permet au nœud coordinateur de transmettre directement à un nœud secondaire une écriture dans le cas où aucun nœud primaire n’est accessible. Cette donnée supplémentaire sera alors stockée à part et marquée comme devant appartenir au nœud primaire ; une fois le nœud primaire de nouveau accessible, l’information lui sera envoyée pour prise en charge et supprimée du nœud secondaire. On parle alors de quorum dégradé (sloppy quorum) car l’écriture (temporaire) et la lecture de la donnée pourront se faire en dépit du respect du quorum. Maintien de la cohérence des données Plusieurs méthodes sont employées afn de vérifer et maintenir la cohérence des données entre les vnodes : - Nous retrouvons ici aussi le mécanisme de l’AAE défni précédemment dans la section dédiée à Riak Search ; l’AAE permet ici de vérifer et de réparer les incohérences entre un nœud virtuel et ses réplicas. - Le mécanisme « Read Repair » permet quant à lui de réparer des incohérences détectées entre les différents réplicas lors d’une lecture de données. En effet, il arrive que lors d’une lecture de données, les différents réplicas renvoient bien une valeur mais celle-ci diffère entre eux. Deux cas peuvent alors se produire : 1) un nœud renvoie une valeur « not found » et n’a donc pas encore reçu de copie de l’objet, Riak force donc la nœud à mettre à jour ses données 2) un nœud répond avec une version de la donnée antérieure (un vector clock antérieur ; cette notion est détaillée juste après) et il est donc nécessaire de la mettre à jour sur ce nœud. 34 Illustration 10: Gestion d'une écriture par un nœud secondaire Nœud 1 Nœud 3 Nœud 2 Nœud 4 Nœud 5 Hash Clé « exemple » Liste de préférence - Noeud 1 - Noeud 2 - Noeud 3 - Noeud 4 - Noeud 5 Donnée clé « exemple » Propriété : Noeud 1 Ping ?
  • 35.
    - Enfn, uneméthode intéressante et essentielle au système Dynamo est celle des « Vector Clocks ». Celle-ci permet de gérer les mises à jours concurrentes d’une même donnée par deux clients simultanément. Un système classique ne se souciera pas vraiment de cette mise à jour concurrente et stockera soit les deux versions différentes en laissant le client choisir, soit considérera que le dernier document mis à jour est le document fnal. Les « Vectors Clocks » permettent d’éviter ce genre de situation dans la mesure du possible. Le principe est assez simple à comprendre mais plutôt complexe à expliciter, c’est pourquoi j’utiliserai un exemple. Voici un scénario simple : Le schéma précédent montre les modifcation faites simultanément par deux clients sur un seul document. Connaître le document à stocker en base est simple dans certains cas, plus complexes dans d’autres. A chaque élément du document est associé un « vector clock », indiquant sa version dans le temps; lors d’une mise à jour d’une valeur, cette version est mise à jour. A l’unité de temps 1, chaque client a modifé une valeur de clé différente dans le document. On déduit alors de façon assez intuitive que le document stocké en base devra être une fusion de ces deux modifcations : 35 0 { "clé_un: "valeur_1_1", "clé_deux": "valeur_2_1" } { "clé_un: "valeur_1_2", "clé_deux": "valeur_2_1" } Donnée en base Modifcation client 1 Modifcation client 2 1 Modifcation client 2 2 Modifcation client 1 { "clé_un: "valeur_1_1", "clé_deux": "valeur_2_2" } { "clé_un: "valeur_1_3", "clé_deux": "valeur_2_3" } { "clé_un: "valeur_1_4", "clé_deux": "valeur_2_3" } { "clé_un: ["valeur_1_2","valeur_1_2"], "clé_deux": ["valeur_2_1","valeur_2_2"] } Axetemporel
  • 36.
    Les deux misesà jour sont satisfaites. A l’unité de temps 2, les deux clients ont modifé de la même façon la valeur de la deuxième clé du document ; cette dernière doit donc prendre la nouvelle valeur : Comme précédemment, ils ont tous deux mis à jour une même clé en même temps mais une valeur différente. On ne peut pas choisir arbitrairement de privilégier le client 1 ou le client 2, le document stocké en base sera alors : On voit ici l’effcacité de cette méthode. Les documents ne sont pas simplement vus comme des objets physiques fgés avec une version temporelle fxée, ils sont interprétés comme des objets logiques avec plusieurs attributs pouvant être mis à jour simultanément. Cette méthode permet de résoudre de nombreux confits mais on constate assez facilement qu’elle n’est pas parfaite. Elle permet cependant la gestion des mises à jour concurrentes de clé modifée de façon similaire et laisse le choix entre les différentes valeurs entrées pour une même clé. Attention, comme nous le verrons dans la phase pratique en fn de rapport, les Vector Clocks ne sont qu’une gestion de timestamp associé aux données insérées et non une méthode de « merge » de ces données. Mise en place d’un cluster Riak KV Passons maintenant à la pratique et mettons en place notre propre cluster Riak KV. Pour ce rapport, un cluster composé de cinq nodes sera mis en place. Construction du cluster et commandes de base Voici l’architecture de notre cluster : Nœud 2 36 { …, "clé_deux": "valeur_2_3" } { "clé_un: ["valeur_1_3 ", " valeur_1_4 "], "clé_deux": "valeur_2_3" } Riak-kv-5 172.17.0.6 Riak-kv-3 172.17.0.4 Riak-kv-2 172.17.0.3 Riak-kv-4 172.17.0.5 Riak-kv 172.17.0.2 Routeur 172.17.0.1
  • 37.
    Par défaut, lorsdu lancement d’une node, un cluster est déjà prêt à être rejoint sur chacune d’entre elles. Nous allons donc commencer simplement à faire en sorte que les nodes riak-kv2, riak-kv3, riak-kv4 et riak- kv5 rejoignent le cluster de riak-kv. 37 # riak-admin cluster join riak@172.17.0.2 Success: staged join request for 'riak@172.17.0.3' to 'riak@172.17.0.2' riak-kv-2 # riak-admin cluster join riak@172.17.0.2 Success: staged join request for 'riak@172.17.0.4' to 'riak@172.17.0.2' riak-kv-3 # riak-admin cluster join riak@172.17.0.2 Success: staged join request for 'riak@172.17.0.5' to 'riak@172.17.0.2' riak-kv-4 # riak-admin cluster join riak@172.17.0.2 Success: staged join request for 'riak@172.17.0.6' to 'riak@172.17.0.2' riak-kv-5 'riak@172.17.0.3' joined cluster with status 'joining' 'riak@172.17.0.4' joined cluster with status 'joining' 'riak@172.17.0.5' joined cluster with status 'joining' 'riak@172.17.0.6' joined cluster with status 'joining' # riak-admin cluster plan =============================== Staged Changes =============================== Action Details(s) ------------------------------------------------------------------------------- join 'riak@172.17.0.3' join 'riak@172.17.0.4' join 'riak@172.17.0.5' join 'riak@172.17.0.6' ------------------------------------------------------------------------------- NOTE: Applying these changes will result in 1 cluster transition ############################################################################ After cluster transition 1/1 ############################################################################ ================================= Membership ================================ Status Ring Pending Node ------------------------------------------------------------------------------- valid 100.0% 20.3% 'riak@172.17.0.2' valid 0.0% 20.3% 'riak@172.17.0.3' valid 0.0% 20.3% 'riak@172.17.0.4' valid 0.0% 20.3% 'riak@172.17.0.5' valid 0.0% 18.8% 'riak@172.17.0.6' ------------------------------------------------------------------------------- Valid:5 / Leaving:0 / Exiting:0 / Joining:0 / Down:0 Transfers resulting from cluster changes: 51 13 transfers from 'riak@172.17.0.2' to 'riak@172.17.0.4' 13 transfers from 'riak@172.17.0.2' to 'riak@172.17.0.3' 12 transfers from 'riak@172.17.0.2' to 'riak@172.17.0.6' 13 transfers from 'riak@172.17.0.2' to 'riak@172.17.0.5' # riak-admin cluster commit Cluster changes committed riak-kv
  • 38.
    Détail des commandes: - riak-admin cluster join [node] permet à une node de joindre le cluster de la node passée en argument. Il est aussi possible d’indiquer qu’une node doit remplacer une node déjà existante dans le cluster grâce à la commande riak-admin cluster replace [node_à_remplacer] [nouvelle_node]. - riak-admin cluster plan permet de visualiser les actions prévues sur le cluster et les modifcations qu’elles engendreront. On voit ici que quatre nouveaux serveurs veulent rejoindre le cluster ; la répartition des données et aussi détaillée. Ce « plan d’exécution » peut être remis à zéro grâce à la commande riak-admin cluster clear. - riak-admin cluster commit ne peut s’exécuter qu’après la commande précédente. Cette commande valide et applique les actions prévues vues précédemment. Dans l’interface Web d’administration de chacune des nodes, les membres du cluster sont visibles : Visualisons le statut de notre cluster (à partir de n’importe lequel des nœuds) : 38 Illustration 11: Visualtion du Cluster depuis l'interface Web # riak-admin cluster status ---- Cluster Status ---- Ring ready: true +----------------------------------+----------+--------+-------+-------------+ | node | status | avail | ring |pending | +----------------------------------+----------+--------+-------+-------------+ | (C) riak@172.17.0.2 | valid | up | 20.3| -- | | riak@172.17.0.3 | valid | up | 20.3| -- | | riak@172.17.0.4 | valid | up | 20.3| -- | | riak@172.17.0.5 | valid | up | 20.3| -- | | riak@172.17.0.6 | valid | up | 18.8| -- | +----------------------------------+----------+-------+-------+---------------+ Key: (C) = Claimant; availability marked with '!' is unexpected
  • 39.
    Les cinq nodessont ici listées. La première colonne « status » indique le statut de chaque nœud : - valid : le nœud fait partie intégrante du cluster - joining : le nœud est prêt à rejoindre le cluster. Le plan d’exécution doit être validé. - leaving : le nœud quitte le cluster et redistribue les données dont il avait la charge - exiting : le nœud a terminé l’étape « leaving » et est en train de quitter le cluster - down : le nœud n’est pas joignable par les autres membres du cluster La seconde colonne « avail » indique « up » si le nœud est accessible et peut prendre en charge des requêtes, « down » s’il n’est pas accessible. La troisième colonne, « ring », indique le pourcentage du cercle de répartition des données donc chaque nœud est responsable. Enfn, « pending » indique le nombre de transferts sortant ou entrant de chaque nœud. Le paramètre (C) placé devant notre premier serveur n’est pas anodin ; il indique qu’il s’agit ici de notre « claimant node » (nœud demandeur littéralement) qui se charge de l’ajout et de la suppression des nodes au sein de notre cluster. Cette node peut être indisponible sans pour autant perturber le bon fonctionnement du cluster, ci ce n’est pour les tâches d’administration précisées précédemment. SI elle n’est plus accessible, les actions relatives à la modifcation du cluster seront placées en attente ; si elle quitte le cluster (qu’elle est marquée « down » en réalité), une autre node sera marquée comme « claimant » et se chargera de ces tâches. Éteignons le serveur riak-kv-2 et ré-affchons le statut du cluster : Éteignons maintenant le serveur « claimant » riak-kv et ré-affchons le statut du cluster : 39 # riak-admin cluster status ---- Cluster Status ---- Ring ready: true +----------------------------------+----------+---------------+-------+-------------+ | node | status | avail | ring |pending | +----------------------------------+----------+---------------+-------+-------------+ | (C) riak@172.17.0.2 | valid | up | 20.3| -- | | riak@172.17.0.3 | valid | down ! | 20.3| -- | | riak@172.17.0.4 | valid | up | 20.3| -- | | riak@172.17.0.5 | valid | up | 20.3| -- | | riak@172.17.0.6 | valid | up | 18.8| -- | +----------------------------------+----------+--------------+-------+---------------+ Key: (C) = Claimant; availability marked with '!' is unexpected # riak-admin cluster status ---- Cluster Status ---- Ring ready: true +----------------------------------+----------+---------------+-------+-------------+ | node | status | avail | ring |pending | +----------------------------------+----------+---------------+-------+-------------+ | (C) riak@172.17.0.2 | valid | down ! | 20.3| -- | | riak@172.17.0.3 | valid | down ! | 20.3| -- | | riak@172.17.0.4 | valid | up | 20.3| -- | | riak@172.17.0.5 | valid | up | 20.3| -- | | riak@172.17.0.6 | valid | up | 18.8| -- | +----------------------------------+----------+--------------+-------+---------------+ Key: (C) = Claimant; availability marked with '!' is unexpected
  • 40.
    Essayons maintenant d’ajouterun nouveau serveur riak-kv-6 au cluster : Demandons donc à un autre membre du cluster de nous y ajouter : Nous constatons ici que riak-kv-3 est aussi en mesure d’ajouter une node au cluster. Voici le nouveau statut de notre cluster : Cependant l’ajout n’est pas complet. En effet, rappelons que le statut « joining » indique seulement que cette nouvelle node est prête à rejoindre le cluster. Riak-kv-3 peut-elle prendre en charge l’ajout de cette node dans le cluster et la répartition des données ? Et bien non ! C’est ici qu’on constate le rôle de la « node claimant » ; elle est la seule à pouvoir modifer le cluster. Il s’agit donc d’une node d’administration essentielle lorsque l’on veut mettre en place un cluster Riak KV. Son accès doit être limité à un administrateur qui sait ce qu’il fait car c’est à partir d’elle et d’elle seule que les actions impactantes sur le cluster peuvent être entreprises. 40 # riak-admin cluster join riak@172.17.0.2 Node riak@172.17.0.2 is not reachable! # riak-admin cluster join riak@172.17.0.4 Success: staged join request for 'riak@172.17.0.7' to 'riak@172.17.0.4' # riak-admin cluster status ---- Cluster Status ---- Ring ready: true +----------------------------------+----------+---------------+-------+-------------+ | node | status | avail | ring |pending | +----------------------------------+----------+---------------+-------+-------------+ | riak@172.17.0.7 | joining | up | 0.0 | | |(C) riak@172.17.0.2 | valid | down ! | 20.3| -- | | riak@172.17.0.3 | valid | down ! | 20.3| -- | | riak@172.17.0.4 | valid | up | 20.3| -- | | riak@172.17.0.5 | valid | up | 20.3| -- | | riak@172.17.0.6 | valid | up | 18.8| -- | +----------------------------------+----------+--------------+-------+---------------+ Key: (C) = Claimant; availability marked with '!' is unexpected # riak-admin cluster plan RPC to 'riak@172.17.0.4' failed: {'EXIT', {{nodedown,'riak@172.17.0.2'}, {gen_server,call, [{riak_core_claimant,'riak@172.17.0.2'}, plan,infnity]}}}
  • 41.
    Redémarrons les nodesriak-kv et riak-kv-2. Visualisons les demandes en attente de modifcation du cluster depuis notre node d’administration et exécutons les : 41 # riak-admin cluster status ---- Cluster Status ---- Ring ready: true +----------------------------------+----------+---------------+-------+-------------+ | node | status | avail | ring |pending | +----------------------------------+----------+---------------+-------+-------------+ | riak@172.17.0.7 | joining | up | 0.0 | | |(C) riak@172.17.0.2 | valid | up | 20.3| -- | | riak@172.17.0.3 | valid | up | 20.3| -- | | riak@172.17.0.4 | valid | up | 20.3| -- | | riak@172.17.0.5 | valid | up | 20.3| -- | | riak@172.17.0.6 | valid | up | 18.8| -- | +----------------------------------+----------+--------------+-------+---------------+ Key: (C) = Claimant; availability marked with '!' is unexpected # riak-admin cluster plan =============================== Staged Changes ================================ Action Details(s) ------------------------------------------------------------------------------- join 'riak@172.17.0.7' ------------------------------------------------------------------------------- NOTE: Applying these changes will result in 1 cluster transition ############################################################################### After cluster transition 1/1 ############################################################################### ================================= Membership ================================== Status Ring Pending Node ------------------------------------------------------------------------------- valid 20.3% 17.2% 'riak@172.17.0.2' valid 20.3% 17.2% 'riak@172.17.0.3' valid 20.3% 17.2% 'riak@172.17.0.4' valid 20.3% 17.2% 'riak@172.17.0.5' valid 18.8% 15.6% 'riak@172.17.0.6' valid 0.0% 15.6% 'riak@172.17.0.7' ------------------------------------------------------------------------------- Valid:6 / Leaving:0 / Exiting:0 / Joining:0 / Down:0 Transfers resulting from cluster changes: 10 2 transfers from 'riak@172.17.0.2' to 'riak@172.17.0.7' 2 transfers from 'riak@172.17.0.3' to 'riak@172.17.0.7' 2 transfers from 'riak@172.17.0.4' to 'riak@172.17.0.7' 2 transfers from 'riak@172.17.0.5' to 'riak@172.17.0.7' 2 transfers from 'riak@172.17.0.6' to 'riak@172.17.0.7' # riak-admin cluster commit Cluster changes committed
  • 42.
    La nouvelle nodea bien rejoint le cluster cette fois-ci : Retirons la maintenant: 42 # riak-admin cluster status ---- Cluster Status ---- Ring ready: true +----------------------------------+----------+---------------+-------+-------------+ | node | status | avail | ring |pending | +----------------------------------+----------+---------------+-------+-------------+ | riak@172.17.0.7 | valid | up | 15,6| | |(C) riak@172.17.0.2 | valid | up | 17,2| -- | | riak@172.17.0.3 | valid | up | 17,2| -- | | riak@172.17.0.4 | valid | up | 17,2| -- | | riak@172.17.0.5 | valid | up | 15,6| -- | | riak@172.17.0.6 | valid | up | 17,2| -- | +----------------------------------+----------+--------------+-------+---------------+ Key: (C) = Claimant; availability marked with '!' is unexpected # riak-admin cluster leave riak@172.17.0.7 Success: staged leave request for 'riak@172.17.0.7'
  • 43.
    L’information a bienété transmise à la node d’administration. Appliquons ce changement. 43 # riak-admin cluster plan =============================== Staged Changes ================================ Action Details(s) ------------------------------------------------------------------------------- leave 'riak@172.17.0.7' ------------------------------------------------------------------------------- NOTE: Applying these changes will result in 2 cluster transitions ############################################################################### After cluster transition 1/2 ############################################################################### ================================= Membership ================================== Status Ring Pending Node ------------------------------------------------------------------------------- leaving 15.6% 0.0% 'riak@172.17.0.7' valid 17.2% 20.3% 'riak@172.17.0.2' valid 17.2% 20.3% 'riak@172.17.0.3' valid 17.2% 20.3% 'riak@172.17.0.4' valid 17.2% 20.3% 'riak@172.17.0.5' valid 15.6% 18.8% 'riak@172.17.0.6' ------------------------------------------------------------------------------- Valid:5 / Leaving:1 / Exiting:0 / Joining:0 / Down:0 Transfers resulting from cluster changes: 10 2 transfers from 'riak@172.17.0.7' to 'riak@172.17.0.5' 2 transfers from 'riak@172.17.0.7' to 'riak@172.17.0.4' 2 transfers from 'riak@172.17.0.7' to 'riak@172.17.0.3' 2 transfers from 'riak@172.17.0.7' to 'riak@172.17.0.6' 2 transfers from 'riak@172.17.0.7' to 'riak@172.17.0.2' ############################################################################### After cluster transition 2/2 ############################################################################### ================================= Membership ================================== Status Ring Pending Node ------------------------------------------------------------------------------- valid 20.3% -- 'riak@172.17.0.2' valid 20.3% -- 'riak@172.17.0.3' valid 20.3% -- 'riak@172.17.0.4' valid 20.3% -- 'riak@172.17.0.5' valid 18.8% -- 'riak@172.17.0.6' ------------------------------------------------------------------------------- Valid:5 / Leaving:0 / Exiting:0 / Joining:0 / Down:0 # riak-admin cluster commit Cluster changes committed
  • 44.
    La node quittealors le cluster en redistribuant préalablement les données dont elle avait la charge : 44 # riak-admin cluster status ---- Cluster Status ---- Ring ready: true +----------------------------------+----------+---------------+-------+-------------+ | node | status | avail | ring |pending | +----------------------------------+----------+---------------+-------+-------------+ | riak@172.17.0.7 | valid | up | 15,6| 0,0 | |(C) riak@172.17.0.2 | valid | up | 17,2| 20,3 | | riak@172.17.0.3 | valid | up | 17,2| 20,3 | | riak@172.17.0.4 | valid | up | 17,2| 20,3 | | riak@172.17.0.5 | valid | up | 15,6| 20,3 | | riak@172.17.0.6 | valid | up | 17,2| 18,8 | +----------------------------------+----------+--------------+-------+---------------+ Key: (C) = Claimant; availability marked with '!' is unexpected # riak-admin cluster status ---- Cluster Status ---- Ring ready: true +----------------------------------+----------+---------------+-------+-------------+ | node | status | avail | ring |pending | +----------------------------------+----------+---------------+-------+-------------+ | riak@172.17.0.7 | valid | up | 3,1| 0,0 | |(C) riak@172.17.0.2 | valid | up | 20,3| 20,3 | | riak@172.17.0.3 | valid | up | 18,8| 20,3 | | riak@172.17.0.4 | valid | up | 18,8| 20,3 | | riak@172.17.0.5 | valid | up | 20,3| 20,3 | | riak@172.17.0.6 | valid | up | 18,8| 18,8 | +----------------------------------+----------+--------------+-------+---------------+ Key: (C) = Claimant; availability marked with '!' is unexpected # riak-admin cluster status ---- Cluster Status ---- Ring ready: true +----------------------------------+----------+---------------+-------+-------------+ | node | status | avail | ring |pending | +----------------------------------+----------+---------------+-------+-------------+ | riak@172.17.0.7 | valid | up | 0,0| 0,0 | |(C) riak@172.17.0.2 | valid | up | 20,3| 20,3 | | riak@172.17.0.3 | valid | up | 20,3| 20,3 | | riak@172.17.0.4 | valid | up | 20,3| 20,3 | | riak@172.17.0.5 | valid | up | 20,3| 20,3 | | riak@172.17.0.6 | valid | up | 18,8| 18,8 | +----------------------------------+----------+--------------+-------+---------------+ Key: (C) = Claimant; availability marked with '!' is unexpected # riak-admin cluster status ---- Cluster Status ---- Ring ready: true +----------------------------------+----------+---------------+-------+-------------+ | node | status | avail | ring |pending | +----------------------------------+----------+---------------+-------+-------------+ |(C) riak@172.17.0.2 | valid | up | 20.3| -- | | riak@172.17.0.3 | valid | up | 20.3| -- | | riak@172.17.0.4 | valid | up | 20.3| -- | | riak@172.17.0.5 | valid | up | 20.3| -- | | riak@172.17.0.6 | valid | up | 18.8| -- | +----------------------------------+----------+--------------+-------+---------------+ Key: (C) = Claimant; availability marked with '!' is unexpected
  • 45.
    Enfn, nous pouvonsaussi visualiser le statut de notre « ring » ainsi (les informations sont similaires à celles vues précédemment) : Paramétrage du cluster Nous avons mis en place dans la partie précédente un cluster sans nous soucier des paramètres de réplication. Comme nous l’avons vu précédemment, ces paramètres se situent au niveau des Bucket Types ou des Buckets eux-mêmes ; il est ainsi possible, au sein d’un même cluster, de défnir plusieurs politiques relatives à la réplication en fonction de nos collections de données. Ces paramètres peuvent aussi être directement passés dans les requêtes (PUT, GET, POST, DELETE). Paramètre Valeur par défaut Description n_val 3 Il s’agit du facteur de réplication, c’est à dire le nombre de nœuds d’un cluster qui stockeront chaque donnée insérée r quorum Le nombre de serveurs nécessaire qui doivent répondre afn d’assurer l’exécution d’une requête de lecture w quorum Le nombre de serveurs nécessaire qui doivent répondre afn d’assurer l’exécution d’ une requête d’écriture pr 0 Le nombre de vnodes primaires nécessaire qui doivent répondre afn d’assurer l’exécution d’ une requête de lecture pw 0 Le nombre de vnodes primaires nécessaire qui doivent répondre afn d’assurer l’exécution d’ une requête d’écriture dw quorum Le nombre de serveurs devant acquitter l’écriture d’une donnée avant de rendre la main au client rw quorum Paramètre remplaçant les valeurs de r et w si ces variables n’ont pas été défnies notfound_ok true Détermine la réponse de Riak si jamais une erreur de lecture a lieu sur un nœud. true : si le premier nœud à répondre n’a pas de copie de la donnée, une erreur « not found » est retournée. false indique à Riak KV de continuer de chercher la donnée autant de fois qu’il y a de réplicas confgurés basic_quorum false Dans le cas où la variable précédente a été confgurée à false, passer ce paramètre à true indiquera à Riak de ne pas continuer la recherche de la donnée et de retourner une erreur « not found » dans le cas où un nombre de serveurs équivalent au quorum n’ont pas pu trouver la donnée. 45 # riak-admin ring-status ================================== Claimant =================================== Claimant: 'riak@172.17.0.2' Status: up Ring Ready: true ============================== Ownership Handoff ============================== No pending changes. ============================== Unreachable Nodes ============================== All nodes are up and reachable
  • 46.
    Expérimentations sur notrecluster Nous allons dans cette partie réaliser plusieurs tests en vue de vérifer et de comprendre les différents paramètres d’un cluster Riak KV et ce qu’ils impliquent. Le cluster sera composé des cinq mêmes serveurs que ceux vus précédemment et l’anneau sera découpé en 8 fragments : (commande exécutée sur chaque nœud) La répartition des fragments est visible au niveau de la ligne « ring_ownership » : - 2 fragments sont gérés par les nodes riak-kv, riak-kv-2 et riak-kv-3 - 1 fragment est géré par les nodes riak-kv-4 et riak-kv-5 L’intégralité des données de notre collection « jeopardy » a été chargée dans un Bucket « jeopardy_bucket » de type « jeopardy_bucket_type ». Pour le moment, aucune réplication n’est demandée pour les données de ce Bucket. Dans la suite de cette partie, l’ensemble des modifcations de paramètres appliquées au Bucket Type « jeopardy_bucket_type » se fera via l’interface web afn de faciliter la lecture de ceux-ci (et pour un soucis de facilité de mise en place !). Voici comment ont été réparties les données entre les différentes vnodes (nombre indiqué au niveau du key_count): 46 # sed -i -- 's/## ring_size = 64/ring_size = 8/g' /etc/riak/riak.conf # riak-admin status | grep ring ring_creation_size : 8 ring_members : ['riak@172.17.0.2','riak@172.17.0.3','riak@172.17.0.4','riak@172.17.0.5','riak@172.17.0.6'] ring_num_partitions : 8 ring_ownership : <<"[{'riak@172.17.0.2',2},n {'riak@172.17.0.3',2},n {'riak@172.17.0.4',2},n {'riak@172.17.0.5',1},n{'riak@172.17.0.6',1}]">> # riak-admin vnode-status Vnode status information ------------------------------------------- VNode: 0 Backend: riak_kv_bitcask_backend Status: [{key_count,27337},{status,[]}] VNode: 913438523331814323877303020447... Backend: riak_kv_bitcask_backend Status: [{key_count,27132},{status,[]}] riak-kv # riak-admin vnode-status Vnode status information ------------------------------------------- VNode: 0 Backend: riak_kv_bitcask_backend Status: [{key_count,27342},{status,[]}] Vnode: 109612622799817718865276362453... Backend: riak_kv_bitcask_backend Status: [{key_count,27125},{status,[]}] riak-kv-2 # riak-admin vnode-status Vnode status information ------------------------------------------- VNode: 0 Backend: riak_kv_bitcask_backend Status: [{key_count,27232},{status,[]}] VNode: 127881393266454005342822422422... Backend: riak_kv_bitcask_backend Status: [{key_count,27249},{status,[]}] riak-kv-3 # riak-admin vnode-status Vnode status information ------------------------------------------- VNode: 548063113999088594326381812268... Backend: riak_kv_bitcask_backend Status: [{key_count,26991},{status,[]}] riak-kv-4 # riak-admin vnode-status Vnode status information ------------------------------------------- VNode: 730750818665451459101842416358... Backend: riak_kv_bitcask_backend Status: [{key_count,26903},{status,[]}] riak-kv-5
  • 47.
    Ce qui, sousforme de graphe, pourrait être représenté ainsi : On constate bien ici la puissance du Hash Ring : les données ont été également réparties entre les différentes vnodes. Nous pouvons observer la méthode utilisée par Riak KV pour répartir chaque donnée sus le nœud virtuel responsable de son stockage ; pour cela, nous avons besoin du client Erlang qui est capable d’exécuter les bibliothèques internes au produit Riak KV. Afn d’entrer dans l’interpréteur de commande Erlang, il est nécessaire de passer cette commande shell sur n’importe quel nœud : Nous voilà dans l’interpréteur de commandes Erlang. Dans la première variable « Hash », nous indiquons à Erlang de stocker la valeur de hashage résultant de la concaténation du nom du Bucket et de la clé du document. Ensuite, en fonction de cette valeur de hashage, on interroge le Hash Ring afn de connaître la liste de préférence des vnodes chargées de stocker cette donnée. Aucun problème n’a eu lieu lors de l’insertion, la donnée a donc dû se retrouver stockée dans la vnode du serveur riak-kv-3 (127.17.0.4). Nous n’avons ici qu’une seule node primaire (pas de réplication), mais le processus d’insertion est dans tous les cas : 47 Illustration 12: Répartition des données dans notre Cluster riak-kv riak-kv riak-kv-2 riak-kv-2 riak-kv-3 riak-kv-3 riak-kv-4 riak-kv-5 # riak attach (riak@172.17.0.2)1> Hash = riak_core_util:chash_key({<<"jeopardy_bucket">>, <<"show_1">>}). <<211,199,82,186,199,192,224,69,33,134,234,19,67,138,222, 23,191,153,67,72>> (riak@172.17.0.2)2> Preference_list = riak_core_ring:prefist(Hash, Ring). [{1278813932664540053428224228626747642198940975104, 'riak@172.17.0.4'}, {0,'riak@172.17.0.2'}, {182687704666362864775460604089535377456991567872, 'riak@172.17.0.3'}, {365375409332725729550921208179070754913983135744, 'riak@172.17.0.4'}, {548063113999088594326381812268606132370974703616, 'riak@172.17.0.5'}, {730750818665451459101842416358141509827966271488, 'riak@172.17.0.6'}, {913438523331814323877303020447676887284957839360, 'riak@172.17.0.2'}, {1096126227998177188652763624537212264741949407232, 'riak@172.17.0.3'}]
  • 48.
    - calcul duhash pour la donnée à insérer - récupération de la liste des nodes responsables de l’insertion de cette donnée - récupération des n_val premiers serveurs listés (les nœuds primaires) - envoi de la donnée sur ces trois serveurs Par ailleurs, nous pouvons vérifer que la valeur de hashage associée à la donnée est bien un entier : La réplication Nous allons maintenant modifer le paramètre de réplication n_val de telle sorte que chaque donnée se trouve désormais sur trois vnodes différentes. Attention : la modifcation de ce paramètre alors que des données ont déjà été insérées précédemment est fortement déconseillé par les équipes de Riak KV. Il ne s’agit donc pas d’une action à reproduire dans un environnement de production. Après avoir changé la valeur de n_val, je suis contraint de lancer une réparation sur chaque nœud (grâce à l’interface Erlang encore une fois). Ces étapes ne sont pas intéressantes et ne refètent pas l’utilisation qui doit être faite de Riak KV, c’est pourquoi je ne les détaillerai pas ici. 48 (riak@172.17.0.2)3> <<Integer_value:160/integer>> = Hash. <<211,199,82,186,199,192,224,69,33,134,234,19,67,138,222, 23,191,153,67,72>> (riak@172.17.0.2)4> Integer_value. 1209042107703822226148045677343061650479427175240
  • 49.
    Observons maintenant lenombre de données stocké sur chaque nœud : A vue d’œil, on constate donc bien que chaque vnode stocke environ trois fois plus de données qu’avant, ses propres données ainsi que les données répliquées. Où est désormais stockée notre donnée ayant pour id « show_1 » ? $ A vue d’œil, on constate donc bien que chaque vnode stocke environ trois fois plus de données qu’avant, ses propres données ainsi que les données répliquées. Où est désormais stockée notre donnée ayant pour id « show_1 » ? Elles sont présentes sur les n_val premiers serveurs de la liste de préférence soit riak-kv-3, riak-kv et riak-kv-2. La commande : renvoie bien le contenu la donnée tant qu’un des trois serveurs est en accessible. Cependant, dès que les trois serveurs sont hors lignes, un « not found » est émis. (ici on interroge le serveur riak-kv-5) 49 # riak-admin vnode-status Vnode status information ------------------------------------------- VNode: 0 Backend: riak_kv_bitcask_backend Status: [{key_count,72507},{status,[]}] VNode: 913438523331814323877303020447... Backend: riak_kv_bitcask_backend Status: [{key_count,80824},{status,[]}] riak-kv # riak-admin vnode-status Vnode status information ------------------------------------------- VNode: 0 Backend: riak_kv_bitcask_backend Status: [{key_count,81928},{status,[]}] Vnode: 109612622799817718865276362453... Backend: riak_kv_bitcask_backend Status: [{key_count,81085},{status,[]}] riak-kv-2 # riak-admin vnode-status Vnode status information ------------------------------------------- VNode: 0 Backend: riak_kv_bitcask_backend Status: [{key_count,81859},{status,[]}] VNode: 127881393266454005342822422422... Backend: riak_kv_bitcask_backend Status: [{key_count,81506},{status,[]}] riak-kv-3 # riak-admin vnode-status Vnode status information ------------------------------------------- VNode: 548063113999088594326381812268... Backend: riak_kv_bitcask_backend Status: [{key_count,81386},{status,[]}] riak-kv-4 # riak-admin vnode-status Vnode status information ------------------------------------------- VNode: 730750818665451459101842416358... Backend: riak_kv_bitcask_backend Status: [{key_count,80872},{status,[]}] riak-kv-5 (riak@172.17.0.2)1> Hash = riak_core_util:chash_key({<<"jeopardy_bucket">>, <<"show_1">>}). <<211,199,82,186,199,192,224,69,33,134,234,19,67,138,222, 23,191,153,67,72>> (riak@172.17.0.2)2> Preference_list = riak_core_ring:prefist(Hash, Ring). [[{1278813932664540053428224228626747642198940975104, 'riak@172.17.0.4'}, {0,'riak@172.17.0.2'}, {182687704666362864775460604089535377456991567872, 'riak@172.17.0.3'}, {365375409332725729550921208179070754913983135744, 'riak@172.17.0.4'}, {548063113999088594326381812268606132370974703616, 'riak@172.17.0.5'}, {730750818665451459101842416358141509827966271488, 'riak@172.17.0.6'}, {913438523331814323877303020447676887284957839360, 'riak@172.17.0.2'}, {1096126227998177188652763624537212264741949407232, 'riak@172.17.0.3'}] # curl -v -XGET http://172.17.0.6:8098/types/jeopardy_bucket_type/buckets/jeopardy_bucket/keys/show_1
  • 50.
    Testons maintenant l’effcacitédes serveurs secondaires lors de l’écriture d’une donnée pour laquelle les serveurs cibles primaires sont inaccessibles, puis d’une lecture. Pour ce faire, nous testerons tout d’abord avec des valeurs de PR et de PW à 3 (l’insertion et la lecture ne pourront se faire que si tous les serveurs primaires sont accessibles), puis nous remettrons ces valeurs à 0 (l’écriture sera acceptée même si aucun serveur primaire n’est accessible ; il en va de même pour la lecture de cette donnée). Nous allons essayer d’insérer une donnée pour un document d’id « show_999999 ». Récupérons tout d’abord la liste des serveurs primaires afn de les désactiver au moment de l’écriture de la donnée : Les trois serveurs primaires responsables du stockage de cette donnée sera donc les trois premiers serveurs de la liste : riak-kv-5, riak-kv et riak-kv-2. Éteignons les. Actuellement, nous exigeons que les serveurs primaires soient accessibles afn d’écrire une donnée : Essayons maintenant d’insérer une donnée depuis le serveur riak-kv-3 : 50 (riak@172.17.0.2)1> Hash = riak_core_util:chash_key({<<"jeopardy_bucket">>, <<"show_999999">>}). <<211,199,82,186,199,192,224,69,33,134,234,19,67,138,222, 23,191,153,67,72>> (riak@172.17.0.2)2> Preference_list = riak_core_ring:prefist(Hash, Ring). [{730750818665451459101842416358141509827966271488, 'riak@172.17.0.6'}, {913438523331814323877303020447676887284957839360, 'riak@172.17.0.2'}, {1096126227998177188652763624537212264741949407232, 'riak@172.17.0.3'}, {1278813932664540053428224228626747642198940975104, 'riak@172.17.0.4'}, {0,'riak@172.17.0.2'}, {182687704666362864775460604089535377456991567872, 'riak@172.17.0.3'}, {365375409332725729550921208179070754913983135744, 'riak@172.17.0.4'}, {548063113999088594326381812268606132370974703616, 'riak@172.17.0.5'}] curl -v -X PUT http://172.17.0.4:8098/types/jeoprady_bucket_type/buckets/jeopardy_bucket/keys/show_999999 -H "Content-Type: application/json" -d ‘{ "air_date": "2003-06-06", "answer": "Jolly Rancher", "category": "CANDY", "question": "Bill Harmsen, who raised horses in Colo., happily founded this candy co. in 1949 to make money during the winter", "round": "Final Jeopardy!", "show_number": 4335, "value": null }’
  • 51.
    La réponse ducluster nous indique bien qui est dans l’impossibilité d’accéder à notre requête d’écriture, les paramètres n’étant pas respectés : Passons maintenant les valeurs de PR et PW à 0 : et relançons la demande d’écriture. Celle-ci a bien fonctionné. Aucun problème non plus pour lire la donnée. Lorsque nous relançons les serveurs primaires, nous voyons apparaître la ligne « inted » indiquant le transfert de la donné du serveur secondaire aux serveurs primaires : Évidemment, les deux exemples précédents sont extrêmes mais peuvent tous les deux être envisagés en production : - soit on considère que le cluster ne doit fonctionner qu’avec les serveurs primaires au détriment de la disponibilité de celui-ci - soit on considère que les serveurs secondaires sont suffsants et que les serveurs primaires seront rapidement accessibles mais cela ne peut se faire sans jouer sur la cohérence des données On peut aussi imaginer passer le paramètre PW à 0 et le paramètre PR à 1 par exemple ; ainsi, les écritures seront acceptées même si aucun serveur primaire n’est accessible mais la lecture de cette insertion/mise à jour ne pourra se faire que lorsqu’un serveur primaire sera de nouveau accessible. 51 * Trying 127.0.0.1... * TCP_NODELAY set * Connected to localhost (127.0.0.1) port 32890 (#0) > PUT /types/jeopardy_bucket_type/buckets/jeopardy_bucket/keys/show_999999 HTTP/1.1 > Host: localhost:32890 > User-Agent: curl/7.58.0 > Accept: */* > Content-Type: application/json > Content-Length: 13 > * upload completely sent off: 13 out of 13 bytes < HTTP/1.1 503 Service Unavailable < Vary: Accept-Encoding < Server: MochiWeb/1.1 WebMachine/1.10.9 (cafe not found) < Date: Wed, 23 May 2018 11:53:43 GMT < Content-Type: application/json < Content-Length: 26 < PW-value unsatisfed: 0/3 #hinted transfer of riak_kv_vnode from 'riak@172.17.0.4' 182687704666362864775460604089535377456991567872 to 'riak@172.17.0.6' 182687704666362864775460604089535377456991567872 completed: sent 349.00 B bytes in 1 of 1 objects in 0.17 seconds (1.96 KB/ second)
  • 52.
    Je n’entreprendrai pasdans ce rapport de tests avec les variables r et w, celles-ci ayant déjà été développées et testées dans le cours (avec un autre SGBD NoSQL mais le fonctionnement est identique).Il en va de même pour les variables dw et rw dont le fonctionnement est facile à comprendre. Pour chacune d’entre elles, un message similaire apparaît en cas d’erreur : Les Vector Clocks Nous allons maintenant nous pencher sur les vector clocks. Dans cette partie, nous mettrons de côté nos documents jeopardy afn de faciliter au maximum la lecture de ce rapport. Dans le scénario mis en place, Client 1 et Client 2 travaillerons sur un document commun qu’ils mettront à jour chacun de leur côté. Bien sûr des confits apparaîtront entre ces mises à jour, nous verrons alors comment Riak KV les gère. Cet exemple reprendra le schéma explicatif introduit dans la précédente partie expliquant le fonctionnement des Vector Clocks. Apportons un peu de précision à l’explication qui a précédemment été faite concernant les Vector Clocks. Lors de l’insertion d’une donnée, un Vector Clock est attribué à l’objet ; il faut le voir comme une timestamp indiquant l’âge de la donnée. Lorsqu’une mise à jour de la donnée est faite par un des clients, celui-ci, en plus de la mise à jour en elle-même, précise le Vector Clock de la donnée qu’il souhaite mettre à jour. Les expérimentations suivantes seront réalisées sur un seul serveur du cluster. Nous pourrions faire les mêmes expériences sur différents serveurs mais cela n’apporterait rien à la démonstration du processus. Commençons par créer un document ; c’est Client 1 qui va s’en charger. Si nous lisons cette donnée ainsi que son header, nous constatons qu’un Vector Clock lui a été assigné : 52 #curl -X PUT http://172.17.0.2:8098/types/jeopardy_bucket_type/buckets/jeopardy_bucket/keys/vclocks_values_confict-H "Content- Type: application/json" -d "{"cle_un":"valeur_1_1","cle_deux":"valeur_2_1"}" #curl -i GET http://172.17.0.2:8098/types/jeopardy_bucket_type/buckets/jeopardy_bucket/keys/vclocks_values_confict HTTP/1.1 403 Forbidden Date: Thu, 24 May 2018 11:13:26 GMT Server: Apache Vary: Accept-Encoding Content-Length: 202 Content-Type: text/html; charset=iso-8859-1 <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"> <html><head> <title>403 Forbidden</title> </head><body> <h1>Forbidden</h1> <p>You don't have permission to access / on this server.</p> </body></html> HTTP/1.1 200 OK X-Riak-Vclock: aa85hYGBgzGDKBVI8+xkWvg9ne5HOwHjleAZTImMeK4N5UPUVviwA Vary: Accept-Encoding Server: MochiWeb/1.1 WebMachine/1.10.9 (cafe not found) Link: </buckets/jeopardy_bucket>; rel="up" Last-Modifed: Thu, 24 May 2018 11:09:19 GMT ETag: "2t0OFfJHXrw9NjuJWDGqhE" Date: Thu, 24 May 2018 11:13:26 GMT Content-Type: application/json Content-Length: 41 {clé_un:valeur_1_1,clé_deux:valeur_2_1} Error: {insuffcient_vnodes,2,need,3}
  • 53.
    Ce Vector Clockva nous être essentiel pour la suite. En effet, lorsqu’un client demande la mise à jour d’une donnée, il met à jour la donnée qu’il visualise dans le contexte dans lequel il se trouve. Imaginons qu’à cet instant, client 1 et client 2 lisent cette donnée ; comme nous, ils visualiserons tous deux ce même Vector Clock. Tous deux souhaitent désormais mettre à jour ce document. Voici ce qu’exécute client 1 : et client 2 : Tous deux ont indiqué la Vector Clock de la donnée indiquant l’état de celle-ci qu’ils souhaitaient modifer. Nos données ont elles été fusionnées comme voulu ? : Visiblement non… Mais que se passe-t-il alors ? Faisons une pause dans notre expérimentation et revenons aux concepts fondamentaux des Vector Clocks et de Riak KV. Comme nous l’avions évoqué précédemment, les Vector Clocks ne sont « que » des estampilles temporelles dont le but est de renseigner sur la version d’un document. De plus, Riak KV n’est pas en mesure d’effectuer un « merge » sur les documents en comparant les valeurs : il s’agit d’un SGBD NoSQL clé:valeur ! Comment pourrait-il effectuer de telles comparaisons alors que sa structure fondamentale fait qu’il n’est pas au courant des données stockées ? Il se contente seulement d’indiquer que deux « siblings » - deux enfants du même parent – existent pour cet objet et qu’il n’est pas en mesure de choisir quelle version affcher. Nous n’allons (nous ne pouvons) cependant pas nous contenter de ça : les résultat obtenus à la lecture du document ne sont pas satisfaisants et ne permettent pas de pouvoir lire celui-ci. Nous devons donc écrire nous même notre programme permettant de joindre des documents lorsqu’un confit apparaît. Nous allons pour ce faire utiliser l’API java . Nous implémentons une première classe java dont le but est de permettre au client de « comprendre » l’architecture de notre document. 53 #curl -X PUT http://172.17.0.2:8098/types/jeopardy_bucket_type/buckets/jeopardy_bucket/keys/vclocks_values_confict -H "Content- Type: application/json" -H "X-Riak-Vclock: a85hYGBgzGDKBVI8+xkWvg9ne5HOwHjleAZTImMeK4N5UPUVviwA" -d '{"cle_un":"valeur_1_1","cle_deux":"valeur_2_2"}' #curl -X PUT http://172.17.0.2:8098/types/jeopardy_bucket_type/buckets/jeopardy_bucket/keys/vclocks_values_confict -H "Content- Type: application/json" -H "X-Riak-Vclock: a85hYGBgzGDKBVI8+xkWvg9ne5HOwHjleAZTImMeK4N5UPUVviwA" -d '{"cle_un":"valeur_1_2","cle_deux":"valeur_2_1"}' #curl -i GET http://172.17.0.2:8098/types/jeopardy_bucket_type/buckets/jeopardy_bucket/keys/vclocks_test_values_confict . . . Siblings: 2HaDgru1ivZhGl2BElsoj5 Y60ZyoA2v7qRpdoEOeew2 public class Vclock_example { public String clé_un, clé_deux; public Vclock_example() {super();} public Vclock_example(String clé_un, String clé_deux) { this.clé_un = clé_un; this.clé_deux = clé_deux;} public String getClé_un() { return clé_un;} public void setClé_un(String clé_un) { this.clé_un = clé_un;} public String getClé_deux() { return clé_deux;} public void setClé_deux(String clé_deux) { this.clé_deux = clé_deux;} }
  • 54.
    Une deuxième classepermet de redéfnir la fonction resolve de la classe ConfictResolver fournie par l’API. C’est elle qui se chargera d’effectuer les différents traitements détaillés ci-dessous dès qu’un confit (plusieurs siblings ) sera détecté : 54 public static class Vclock_exampleConfictResolver implements ConfictResolver<Vclock_example> { @Override public Vclock_example resolve(List<Vclock_example> siblings) { //Objet Vclock_example Vclock_example resolved_vclock_example = new Vclock_example(); String clé_un, clé_deux; //Liste sans doublons contenant les différentes valeurs de clé_un Set<String> clé_un_set = new HashSet<>(); //Liste sans doublons contenant les différentes valeurs de clé_deux Set<String> clé_deux_set = new HashSet<>(); //S'il n'y a pas de confit if (siblings.size() == 0) { return null; } //S'il n'y a qu'une seule valeur du document else if (siblings.size() == 1) { return siblings.get(0); } //Si nous avons plusieurs versions confictuelles else { //Pour chaque version du document for (Vclock_example vclock_example : siblings) { //On ajoute la valeur de clé_un à la liste clé_un_set.add(vclock_example.getClé_un()); //On ajoute la valeur de clé_deux à la liste clé_deux_set.add(vclock_example.getClé_deux()); } //Si la liste contenant les différentes valeurs de clé_un ne contient qu'un élément if (clé_un_set.size() <= 1) { clé_un = """ + clé_un_set.toString().replaceAll("[[]]", "") + """; //On stocke la valeur de clé_un dans notre objet resolved_vclock_example.setClé_un(clé_un); } else { clé_un = "["" + clé_un_set.toString().replaceAll("[[]]", "").replace(", ", "","") + ""]"; //On stocke un tableau des valeurs de clé_un dans notre objet resolved_vclock_example.setClé_un(clé_un); } //Même traitement que clé_un if (clé_deux_set.size() <= 1) { clé_deux = """ + clé_deux_set.toString().replaceAll("[[]]", "") + """; resolved_vclock_example.setClé_deux(clé_deux); } else { clé_deux = "["" + clé_deux_set.toString().replaceAll("[[]]", "").replace(", ", "","") + ""]"; resolved_vclock_example.setClé_deux(clé_deux); } //On retourne l'objet return resolved_vclock_example; } } }
  • 55.
    Enfn, au niveaude notre classe principale Main, nous vérifons à la lecture d’une donnée si celle-ci possède plusieurs versions confictuelles ; si tel est le cas alors nous appellerons la méthode précédemment défnie et nous mettrons à jour la valeur de notre document. Après exécution de ce code, nous constatons que le document interrogé a maintenant été mis à jour et prend en compte l’ensemble des modifcations de client 1 et client 2 : Les Vector Clocks n’ont donc rien de « magique ». Ils sont cependant à la fois simple à comprendre et peuvent être extrêmement complexes à exploiter en fonction des cas de fgure. Riak KV offre la possibilité de paramétrer la gestion des siblings ; en effet, dans notre cas il était assez simple de décider de ce que nous devions faire de nos différentes données, mais qu’aurions nous fait si nous avions reçu des centaines de versions confictuelles du même document ? Insérer un tel tableau dans le document fnal n’aurait aucun sens (dans cet exemple en tout cas). De plus, de quand est datée la première version en confit ? Est-elle toujours pertinente ou une des versions postérieure l’est-elle désormais 55 // Initialisation du client RiakCluster cluster = setUpCluster(); RiakClient client = new RiakClient(cluster); ConfictResolverFactory crf = ConfictResolverFactory.getInstance(); crf.registerConfictResolver(Vclock_example.class, new Vclock_exampleConfictResolver()); // Lecture de la donnée Location vclocks_test_values_confict_data = new Location( new Namespace("jeopardy_bucket_type", "jeopardy_bucket"), "vclocks_values_confict"); FetchValue fetch = new FetchValue.Builder(vclocks_test_values_confict_data).build(); FetchValue.Response res = client.execute(fetch); //Récupération du Vector Clock VClock current_vclock = res.getVectorClock(); System.out.println(current_vclock.toString()); RiakObject obj; try { obj = res.getValue(RiakObject.class); } // Plusieurs versions du document sont en confit catch (com.basho.riak.client.api.cap.UnresolvedConfictException exception) { //On applique la résolution de problème défnie dans la fonction resolve Vclock_example vlock_fnal_value = res.getValue(Vclock_example.class); // On transmet à Riak KV la nouvelle version du document String vlock_fnal_value_string = "{"cle_un":" + vlock_fnal_value.getClé_un() + ","cle_deux":" + vlock_fnal_value.getClé_deux() + "}"; StoreValue sv = new StoreValue.Builder(vlock_fnal_value_string) .withLocation(vclocks_test_values_confict_data).withVectorClock(current_vclock).build(); client.execute(sv); } client.shutdown(); System.exit(0); #curl -i GET http://localhost:32904/types/jeopardy_bucket_type/buckets/jeopardy_bucket/keys/vclocks_values_confict . . . X-Riak-Vclock: a85hYGBgymDKBVI8+xkWvg9ne5HOwHjleAZTInMeK4NPcPUVPqj0CfaJlSm3/sYzMK7dDpRmBEqHgaSzAA== . . . {"clé_un":["valeur_1_1","valeur_1_2"],"clé_deux":["valeur_2_2","valeur_2_1"]}
  • 56.
    Mises à jourépurées davantage ? Voici les différents paramètres qui peuvent s’appliquer aux Bucket Types ou aux Buckets afn de répondre à ces questions : Paramètre Valeur par défaut Description small_vclock 10 Taille jusqu’à laquelle la liste des siblings n’a pas besoin d’être épurée big_vclock 50 Taille à partir de laquelle la liste des siblings est épurée en partant du début de celle-ci young_vclock 20 Aucune épuration ne sera faite sur un élément ayant moins de young_vclock ms. old_vclock 86400 Tout élément ayant plus de old_vclock ms devra être épuré Ainsi, par défaut, aucune version d’un document datant de plus d’un jour et ayant été suivie par plus de 50 mises à jour concurrente ne sera conservée. Ce tableau peut être résumé ainsi : Il est cependant possible de se passer des Vector Clocks et d’indiquer à Riak KV de ne conserver que la dernière version d’un document, quel que soit le client l’ayant « poussée » vers le serveur. N’oublions pas non plus les types de données présentés en début de rapport qui ont été créés de telle sorte à ce qu’ils gèrent nativement ces types de confits. Riak KV préconise d’ailleurs l’utilisation de ces DataTypes dès qu’on le peut, faisant de l’utilisation de documents json ou xml des exceptions. Cohérence des données et DataTypes • Counter Reprenons notre document counter créé précédemment. Nous avons paramétré le Bucket Type de telle sorte que chaque donnée soit répliquée sur deux serveurs mais qu’un seul suffse pour la lecture et l’écriture d’une donnée. Dans ce scénario, nous n’utiliserons que les serveurs riak-kv-2 et riak-kv-4, tous deux 56 Mises à jour conservées Axe temporel Nombrede siblings young_vclock old_vclock small_vclock big_vclock
  • 57.
    responsables du stockagede notre counter « question_example_1 ». Nous modiferons la valeur sur un serveur, puis sur l’autre (comme si ceux-ci étaient tous deux accessibles mais ne communiquaient pas entre eux) et enfn nous démarrerons les deux serveurs en même temps afn de visualiser la réconciliation des données opérée par Riak KV. A ce stade, tous les serveurs du cluster sont éteints. On allume d’abord le serveur riak-kv-2. Nous éteignons riak-kv-2 et allumons riak-kv-4, de telle sorte qu’aucun échange de données ne puisse se faire pour le moment. Laissons riak-kv-4 en ligne et rallumons riak-kv-2. Désormais, si nous interrogeons un des serveurs, voici ce que nous obtenons : Riak KV comprend la nature d’une donnée « counter » ; il est de ce fait très simple pour lui d’appliquer une règle de calcul dite de « Pairwise Maximum Wins » . Cette méthode consiste, lors de la résolution de confits, de conserver dans un premier temps la valeur la plus haute du counter ;ici il s’agit de la mise à jour de riak- kv-4 : 15. Toutes les opérations réalisées sur les autres serveurs (ici riak-kv-2) seront alors appliquées à cette valeur. On décrémente donc 10 de 15 afn d’obtenir 5. • Map Reprenons notre document map créé précédemment. Nous avons paramétré le Bucket Type de telle sorte que chaque donnée soit répliquée sur deux serveurs mais qu’un seul suffse pour la lecture et l’écriture d’une donnée. Dans ce scénario, nous n’utiliserons que les serveurs riak-kv et riak-kv-4 , tous deux responsables du stockage de notre map « question_example_1 ». Nous modiferons la valeur sur un serveur, puis sur l’autre (comme si ceux-ci étaient tous deux accessibles mais ne communiquaient pas entre eux) et enfn nous démarrerons les deux serveurs en même temps afn de visualiser la réconciliation des données opérée par Riak KV. 57 #curl -GET http://172.17.0.3:8098/types/bucket_type_counter/buckets/jeopady_question/datatypes/question_example_1 {"type":"counter","value":0} #curl -XPOST http://172.17.0.3:8098/types/bucket_type_counter/buckets/jeopady_question/datatypes/question_example_1 -H "Content-Type: application/json" -d '{"decrement": 10}' #curl -GET http://172.17.0.3:8098/types/bucket_type_counter/buckets/jeopady_question/datatypes/question_example_1 {"type":"counter","value":-10} #curl -GET http://172.17.0.5:8098/types/bucket_type_counter/buckets/jeopady_question/datatypes/question_example_1 {"type":"counter","value":0} #curl -XPOST http://172.17.0.5:8098/types/bucket_type_counter/buckets/jeopady_question/datatypes/question_example_1 -H "Content-Type: application/json" -d '{"increment": 15}' #curl -GET http://172.17.0.5:8098/types/bucket_type_counter/buckets/jeopady_question/datatypes/question_example_1 {"type":"counter","value":15} #curl -GET http://172.17.0.5:8098/types/bucket_type_counter/buckets/jeopady_question/datatypes/question_example_1 {"type":"counter","value": 5}
  • 58.
    A ce stade,tous les serveurs du cluster sont éteints. On allume d’abord le serveur riak-kv : Nous avons ici ajouté un champs « ajout_champs_register » et avons supprimé « air_date_register » ainsi que « answer_register » (les trois commandes ont été faites individuellement pour plus de lisibilité). Remarquez l’utilisation du « context » afn d’indiquer quelle version du document nous modifons, à la façon d’un Vector Clock. 58 #curl -XGET http://172.17.0.2:8098/types/bucket_type_map/buckets/jeopady_question/datatypes/question_example_1 {"type":"map","value":{"air_date_register":"2004-05-10","answer_register":"George III","category_register":"HISTORIC NICKNAMES","cl u00e9_deux_register":"valeur_2_1","clu00e9_un_register":"valeur_1_1","good_answer_fag":true,"question_register":"Because of his Hanoverian heritage, American colonists called this monarch "German Georgie" or "Geordie"","round_register":"Jeopardy!","show_number_register":"4541","value_register":"$200"},"context":"g2wAAAACaAJtAAAA DL8Aoe9XBu7EAAHUwWEBaAJtAAAADL8Aoe9eM1MAAAAnEWECag=="} #curl -XPOST http://172.17.0.2:8098/types/bucket_type_map/buckets/jeopady_question/datatypes/question_example_1 -H "Content-Type: application/json" -d ' { "update": { "ajout_champs_register": "TEST"} , "context" : "g2wAAAADaAJtAAAADL8Aoe9XBu7EAAHUwWEBaAJtAAAADL8Aoe9eM1MAAAAnEWECaAJtAAAADL8Aoe9eM1MBAAAnEWECag ==" }' #curl -XPOST http://172.17.0.2:8098/types/bucket_type_map/buckets/jeopady_question/datatypes/question_example_1 -H "Content-Type: application/json" -d ' { "remove": "air_date_register" , "context" : "g2wAAAADaAJtAAAADL8Aoe9XBu7EAAHUwWEBaAJtAAAADL8Aoe9eM1MAAAAnEWECaAJtAAAADL8Aoe9eM1MBAAAnEWECag ==" }' #curl -XPOST http://172.17.0.2:8098/types/bucket_type_map/buckets/jeopady_question/datatypes/question_example_1 -H "Content-Type: application/json" -d ' { "remove": "answer_register", "context" : "g2wAAAADaAJtAAAADL8Aoe9XBu7EAAHUwWEBaAJtAAAADL8Aoe9eM1MAAAAnEWECaAJtAAAADL8Aoe9eM1MBAAAnEWECag ==" }' #curl -XGET http://172.17.0.2:8098/types/bucket_type_map/buckets/jeopady_question/datatypes/question_example_1 {"type":"map","value":{"ajout_champs_register":"TEST","category_register":"HISTORIC NICKNAMES","cl u00e9_deux_register":"valeur_2_1","clu00e9_un_register":"valeur_1_1","good_answer_fag":true,"question_register":"Because of his Hanoverian heritage, American colonists called this monarch "German Georgie" or "Geordie"","round_register":"Jeopardy!","show_number_register":"4541","value_register":"$200"},"context":"g2wAAAADaAJtAAAA DFZjBNprLWdaAAB1MWECaAJtAAAADL8Aoe9XBu7EAAHUwWEBaAJtAAAADL8Aoe9eM1MAAAAnEWECag=="}
  • 59.
    Nous éteignons riak-kvet allumons riak-kv-4, de telle sorte qu’aucun échange de données ne puisse se faire pour le moment. Nous avons ici seulement mis à jour le champs « air_date_register ». Laissons riak-kv-4 en ligne et rallumons riak-kv. Désormais, si nous interrogeons un des serveurs, voici ce que nous obtenons : Ici Riak KV applique la règle « Add/Update Wins Over Remove » (les ajouts et mises à jour l’emportent sur les suppressions), c’est pourquoi « air_date_register » est toujours présent et mis à jour . Les champs ajoutés et supprimés (mais non modifés) sont bien ou non présents. • Set Reprenons notre document set créé précédemment. Nous avons paramétré le Bucket Type de telle sorte que chaque donnée soit répliquée sur deux serveurs mais qu’un seul suffse pour la lecture et l’écriture d’une donnée. Dans ce scénario, nous n’utiliserons que les serveurs riak-kv-2 et riak-kv-4 , tous deux responsables du stockage de notre map « question_example_1 ». Nous modiferons la valeur sur un serveur, puis sur l’autre (comme si ceux-ci étaient tous deux accessibles mais ne communiquaient pas entre eux) et enfn nous démarrerons les deux serveurs en même temps afn de visualiser la réconciliation des données opérée par Riak KV. 59 #curl -XGET http://172.17.0.5:8098/types/bucket_type_map/buckets/jeopady_question/datatypes/question_example_1 {"type":"map","value":{"air_date_register":"2004-05-10","answer_register":"George III","category_register":"HISTORIC NICKNAMES","cl u00e9_deux_register":"valeur_2_1","clu00e9_un_register":"valeur_1_1","good_answer_fag":true,"question_register":"Because of his Hanoverian heritage, American colonists called this monarch "German Georgie" or "Geordie"","round_register":"Jeopardy!","show_number_register":"4541","value_register":"$200"},"context":g2wAAAACaAJtAAAAD L8Aoe9XBu7EAAHUwWEBaAJtAAAADL8Aoe9eM1MAAAAnEWECag=="} #curl -XPOST http://172.17.0.5:8098/types/bucket_type_map/buckets/jeopady_question/datatypes/question_example_1 -H "Content-Type: application/json" -d ' { "update": { "air_date_register": "2018-05-25"}, "context" : "g2wAAAADaAJtAAAADL8Aoe9XBu7EAAHUwWEBaAJtAAAADL8Aoe9eM1MAAAAnEWECaAJtAAAADL8Aoe9eM1MBAAAnEWECag ==" }' #curl -XGET http://172.17.0.5:8098/types/bucket_type_map/buckets/jeopady_question/datatypes/question_example_1 {"type":"map","value":{"air_date_register":"2018-05-25","answer_register":"George III","category_register":"HISTORIC NICKNAMES","cl u00e9_deux_register":"valeur_2_1","clu00e9_un_register":"valeur_1_1","good_answer_fag":true,"question_register":"Because of his Hanoverian heritage, American colonists called this monarch "German Georgie" or "Geordie"","round_register":"Jeopardy!","show_number_register":"4541","value_register":"$200"},"context":"g2wAAAADaAJtAAAA DL8Aoe9XBu7EAAHUwWEBaAJtAAAADL8Aoe9eM1MAAAAnEWECaAJtAAAADL8Aoe9eM1MBAAAnEWEBag=="} #curl -XGET http://172.17.0.2:8098/types/bucket_type_map/buckets/jeopady_question/datatypes/question_example_1 {"type":"map","value":{"air_date_register":"2018-05-25","ajout_champs_register":"TEST","category_register":"HISTORIC NICKNAMES","clu00e9_deux_register":"valeur_2_1","cl u00e9_un_register":"valeur_1_1","good_answer_fag":true,"question_register":"Because of his Hanoverian heritage, American colonists called this monarch "German Georgie" or "Geordie"","round_register":"Jeopardy!","show_number_register":"4541","value_register":"$200"},"context":"g2wAAAAFaAJtAAAAD FZjBNprLWdaAAB1MWECaAJtAAAADL8Aoe9XBu7EAAHUwWEBaAJtAAAADL8Aoe9eM1MAAAAnEWECaAJtAAAADL8Aoe9eM1MB AAAnEWEDaAJtAAAADL8Aoe/MCvzPAAC28GEDag=="}
  • 60.
    A ce stade,tous les serveurs du cluster sont éteints. On allume d’abord le serveur riak-kv-4: Nous avons ajouté deux données à notre Set : « Donnee_1 » et « Donnee_2 » et avons supprimé la donnée « George III ». Nous éteignons riak-kv-4 et allumons riak-kv-2, de telle sorte qu’aucun échange de données ne puisse se faire pour le moment. Nous avons ici ajouté une donnée « George III » (aucun changement n’a été appliqué puisque cette donnée existait déjà dans le Set et avons supprimé « 2004-05-10 ». Laissons riak-kv-4 en ligne et rallumons riak-kv-2. Désormais, si nous interrogeons un des serveurs, voici ce que nous obtenons : Ici Riak KV applique la règle « Add Wins Over Remove » (les ajouts l’emportent sur les suppressions), c’est pourquoi « George III » est toujours présent. Nous constatons de plus bien les autres suppressions et ajouts. 60 #curl -XGET http://172.17.0.5:8098/types/bucket_type_set/buckets/jeopady_question/datatypes/question_example₁ {"type":"set","value":["2004-05-10","George III"],"context":"g2wAAAACaAJtAAAADL8Aoe9eM1MAAAB1MWEDaAJtAAAADL8Aoe9eM1MBAACcQmEGag=="} #curl -XPOST http://172.17.0.5:8098/types/bucket_type_set/buckets/jeopady_question/datatypes/question_example_1 -H "Content-Type: application/json" -d '{"add_all":["Donnee_1", "Donnee_2"], "context" : "g2wAAAACaAJtAAAADL8Aoe9eM1MAAAB1MWEDaAJtAAAADL8Aoe9eM1MBAACcQmEGag==" }' #curl -XPOST http://172.17.0.5:8098/types/bucket_type_set/buckets/jeopady_question/datatypes/question_example_1 -H "Content-Type: application/json" -d '{"remove":"George III", "context" : "g2wAAAACaAJtAAAADL8Aoe9eM1MAAAB1MWEDaAJtAAAADL8Aoe9eM1MBAACcQmEGag==" }' #curl -XGET http://172.17.0.5:8098/types/bucket_type_set/buckets/jeopady_question/datatypes/question_example₁ {"type":"set","value":["2004-05-10","Donnee_1","Donnee_2"], "context":"g2wAAAACaAJtAAAADL8Aoe9eM1MAAAB1MWEDaAJtAAAADL8Aoe9eM1MBAACcQmEGag=="} #curl -XGET http://172.17.0.3:8098/types/bucket_type_set/buckets/jeopady_question/datatypes/question_example₁ {"type":"set","value":["2004-05-10","George III"],"context":"g2wAAAACaAJtAAAADL8Aoe9eM1MAAAB1MWEDaAJtAAAADL8Aoe9eM1MBAACcQmEGag=="} #curl -XPOST http://172.17.0.3:8098/types/bucket_type_set/buckets/jeopady_question/datatypes/question_example_1 -H "Content-Type: application/json" -d '{"add":"George III", "context" : "g2wAAAACaAJtAAAADL8Aoe9eM1MAAAB1MWEDaAJtAAAADL8Aoe9eM1MBAACcQmEGag==" }' #curl -XPOST http://172.17.0.3:8098/types/bucket_type_set/buckets/jeopady_question/datatypes/question_example_1 -H "Content-Type: application/json" -d '{"remove":"2004-05-10", "context" : "g2wAAAACaAJtAAAADL8Aoe9eM1MAAAB1MWEDaAJtAAAADL8Aoe9eM1MBAACcQmEGag==" }' #curl -XGET http://172.17.0.3:8098/types/bucket_type_set/buckets/jeopady_question/datatypes/question_example₁ {"type":"set","value":["George III"],"context":"g2wAAAADaAJtAAAADL8Aoe9dU9CcAADeAGECaAJtAAAADL8Aoe9eM1MAAAB1MWEDaAJtAAAADL8Aoe9eM1MBA ACcQmEGag=="} #curl -XGET http://172.17.0.2:8098/types/bucket_type_set/buckets/jeopady_question/datatypes/question_example₁ {"type":"set","value":["Donnee_1","Donnee_2","George III"],"context":"g2wAAAAEaAJtAAAADL8Aoe9dU9CcAADeAGECaAJtAAAADL8Aoe9eM1MAAAB1MWEDaAJtAAAADL8Aoe9eM1MBA ACcQmEGaAJtAAAADL8Aoe9eM1MCAAAAAmECag=="}
  • 61.
    Après de nombreusesheures d’étude et d’utilisation de Riak KV mes sentiments sont partagés. D’un côté j’ai été confronté à un système NoSQL intuitif, avec une vraie volonté d’offrir un utilitaire d’administration (riak-admin) clair et à l’utilisation naturelle. L’organisation des données en Cluster et l’application des Cluster Type m’a paru être un moyen assez effcace de gérer une collection de données dans un système clé:valeur. La mise en place d’un cluster est aussi d’une facilité déconcertante et Riak KV semble pouvoir être hautement scalable comme Basho s’en vante par l’utilisation du système Dynamo. Cependant, en tant que DBA, je ne peux me résoudre à considérer qu’il s’agit d’un système effcient à mettre en production, si ce n’est dans des cas très précis. En effet, la documentation, parfois très claire, peut aussi relever du casse-tête à certains moments : organisation complexe, informations présentes ou non en fonction de la version de la documentation. De plus, un très gros point noir pour moi a été la quasi inexistence de guides concernant les fonctions fournies par les API qui sont pourtant indispensables pour un tel système. Au delà de ça, la dernière version de Riak KV ayant un an et l’inaccessibilité du site de Basho durant plusieurs semaines courant mai n’inspirent pas confance quant à l’utilisation de Riak KV pour le long terme. Enfn, le manque de structure imposant le schéma de données insérées me gêne encore (sûrement parce que j’ai l’habitude de traiter les données avec du SQL ?). Il s’agissait cependant d’un sujet d’étude très intéressant qui m’a permis de découvrir et d’expérimenter certain concepts communs à d’autres systèmes NoSQL et de découvrir pour la première fois un SGBD NoSQL seul. J’ai pu constater à quel point le maintien de la cohérence des données dans un cluster peut être compliqué, tant les pannes peuvent être diverses et tant leur résolution dépend de paramètres défnis par l’administrateur. Je conserve d’ailleurs le cluster mis en place afn d’approfondir certains points non abordés dans ce rapport durant l’été, une fois que l’UE sera terminée. 61 Conclusion
  • 62.
    Index des illustrations Illustration1: Paramétrage du Bucket Type Default.................................................................................................................6 Illustration 2: Page d'accueil lors du premier lancement de Riak Explorer......................................................................8 Illustration 3: Liste des index créés avec leur schéma associé...........................................................................................21 Illustration 4: Détail de l'index "jeopardy_index"......................................................................................................................21 Illustration 5: Schéma représentant le fonctionnement de l'AAE......................................................................................26 Illustration 6: Représentation du Hash Ring...............................................................................................................................31 Illustration 7: Répartition de données entre deux serveurs.................................................................................................32 Illustration 8: Répartition des données du Hash Ring...........................................................................................................32 Illustration 9: Réplication d'une donnée avec n_val = 3........................................................................................................33 Illustration 10: Gestion d'une écriture par un nœud secondaire........................................................................................34 Illustration 11: Visualtion du Cluster depuis l'interface Web...............................................................................................38 Illustration 12: Répartition des données dans notre Cluster...............................................................................................47 62
  • 63.