Saturday, August 27, 2011

HBase, versions de données: principes et usage chez Facebook

Lorsque l'on insère une donnée dans HBase, la précédente version, si elle existe, n'est pas écrasée. HBase comme son mentor BigTable peut gérer plusieurs versions du contenu d'une même cellule.
Pour ceux qui se demandent comment exploiter au mieux cette capacité de HBase voici un conseil de Bill Graham sur la mailing list HBase :

"[...] Once someone realizes that you can store multiple values for the same cell, each with a timestamp there can be a natural tendency to think "hey, I can store a one-to-many using multiple version of a cell". That's not the intent of versioned cell values.


Versioned cell values can be thought of as a way to keep a history of change for a single entity that at any given time only has one value. Like keeping track of a state change over time. For a one-to-many relationship (i.e., a user with many events), favor either multiple rows or multiple columns instead."

Pour résumer les versions multiples d'une même cellule ne sont pas faites pour faire du mapping de données de type one-to-many. Dans ce dernier cas il ne faudra pas hésiter à créer plusieurs lignes ou ajouter des colonnes car contrairement à une base de donnée classique ajouter une colonne à une table HBase ne coûte rien (il ne faut donc pas s'en priver).
Il ne faut pas oublier qu'à l'origine cette fonctionnalité de "version de cellule" a été introduite dans la base de donnée BigTable de Google pour conserver un historique des évolutions des sites qui sont indexés par Google.

Il existe pourtant des cas où cela peut être nécessaire et astucieux : le meilleur exemple est sans doute Facebook et son nouveau système de messagerie (qui repose sur HBase) dans lequel on se sert de l'identifiant du message comme numéro de version et signifier la présence d'un mot dans le contenu du message  :

Modèle de donnée du nouveau système de messagerie facebook
d'après http://www.slideshare.net/brizzzdotcom/facebook-messages-hbase
Dans l'exemple ci dessus les messages msgid1 et msgid3 de l'utilisateur userid contiennent les mots mot1 et mot2. La valeur de la cellule, la donnée, n'est que la position ('offset') du mot dans le message. Il est alors facile pour Facebook de trouver les messages d'un utilisateur qui contiennent un mot voire même une succession précise de mots en exploitant les offsets.

D'un point de vu technique le principal reproche que l'on pourrait faire à ce type d'usage est que toutes les données d'un même utilisateur vont être stockées dans le même FileStore. Comme toutes ces données ne seront liées qu'à une seule clé il ne sera pas possible de les éclater par la suite sur plusieurs nœux. Les ingénieurs de Facebook ont du calculer que si ils s'en tenaient à stocker un 'offset', c'est à dire l'endroit dans le message où le mot apparaît alors cela devait être acceptable.

Dans HBase il n'est pas conseillé de garder plus d'une centaine de versions, il existe quelques subtilités à connaitre lorsque l'on joue avec les versions, la page http://hbase.apache.org/book.html#versions  est un bon début pour approfondir le sujet en s'aidant du glossaire si nécessaire.

Thursday, August 18, 2011

Mockito ou comment simuler des objets dans les tests unitaires


Enfin un framework qui me réconcilie avec les tests unitaires, non pas que j'étais très fâché, les tests unitaires sont nécessaires mais je ne prenais pas un grand plaisir à les faire.
Je travaille en ce moment sur un projet de client/serveur basé sur les excellents framework Netty et Protobuf et écrire des test unitaires sur ce type de projet très technique peut se révéler être assez difficile.
Pourtant depuis peu je dois confesser que je prend un certain plaisir à en écrire, ceci depuis que j'ai découvert Mockito.

Mockito ça sert à quoi ?
L'un des problèmes avec les tests unitaires ce sont les objets qui sont mis en jeu. Certains objets sont en effet très difficile à instancier, il peut être même impossible de le faire si on est en phase de spécification et que seule l'interface a été écrite.
Il est encore plus difficile de contrôler leur comportement pour recréer des contextes particuliers d’exécution comme recréer un contexte d'erreur.
C'est là qu'intervient Mockito : Mockito permet de créer des classes "fantoches", des "marionnettes" à partir de n'importe quelle classe ou même à partir d'une simple interface. En anglais on appelle ça des "mock objects".
Cette classe vous allez pouvoir contrôler finement son comportement, comme les valeurs de retour de ses méthodes : c'est qu'on appelle le "stubbing".

Exemples

Je vais reprendre les exemples du site, ceux basés sur l'interface List, mais avec mes propres commentaires :

1. Création de "mock objects"

import static org.mockito.Mockito.*;
List mockedList = mock(List.class);

Dans cette exemple nous venons de créer un objet à partir de l'interface List. Cet objet implémente l'interface par contre son comportement est pour le moment assez limité, un appel à mockedList.get(0) retournera null.
Pour controler son comportement nous allons faire du "stubbing"

2. Le "stubbing"


when(mockedList.get(0)).thenReturn("toto");

Maintenant un appel à mockedList.get(0)retournera la chaine "toto".
Si l'on souhaite répondre "toto" pour n'importe qu'elle indice de la liste :

when(mockedList.get(anyInt())).thenReturn("toto");

Maintenant un appel à mockedList.get(indice) avec n'importe quel entier en indice retournera la chaine "toto".

3. Vérifier les appels
Il peut être intéressant de contrôler qu'une méthode a bien été appelée et avec les bon arguments en paramètre.

System.out.println(mockedList.get(888));
// Doit afficher "toto" sur la sortie standard
// Si l'on souhaite vérifier qu'il y a bien eu un appel à la méthode get avec comme paramètre 888
verify(mockedList).get(888);

Voilà pour la petite intro, les autres fonctionnalités sont décrites ici. Au passage Mockito est un bon tremplein vers le Behaviour Driven Developement alors pourquoi se priver ?

Thursday, August 4, 2011

Glossaire à l'usage de ceux qui découvrent HBase

Avant-propos : à l'origine ce glossaire était destiné à consigner pour mon propre usage les définitions et les fonctions des différents éléments qui constituent la base de donnée NoSQL HBase. Il peut s'agir aussi bien de simples clarifications sur la sémantique comme de précisions beaucoup plus techniques sur le rôle joué par certains éléments. Il n'est pas exhaustif et est amené à être mis à jour au fil des évolutions de HBase.

BloomFilter
Filtre permettant d'exclure d'une recherche les FileStore d'une Region. Utilisé par exemple lorsque que l'on cherche la valeur associée à une clé, et que les données de cette clé sont potentiellement éclatées sur le MemStore et/ou plusieurs FileStore. Un BloomFilter permet alors de ne pas avoir à parcourir tous les FileStore présents dans la Region.

Column
Dans HBase la notion de colonne n'a pas réellement de sens.  Le modèle de gestion des données de HBase repose sur une notion de couple {clé, valeur}. Dans HBase le nom de la colonne peut être vue comme la clé (en partie, voir la définition d'une Table). Il n'y a en interne aucune notion de colonne telle que l'on peut l'entendre dans une base de donnée relationnelle. Le terme a surement été conservé pour donner un (mauvais) point de comparaison.

Colum Family
Groupe de colonnes. Physiquement si des colonnes appartiennent à une même famille alors elles seront stockées dans une même Region. A noter : HBase gère pour le moment assez mal un trop grand nombre de famille de colonnes. Par contre une famille de colonne peut gérer un très grand nombre de colonne.

Compaction (Minor)
Concaténation d'une partie des FileStores. HBase utilise un algorithme qui va privilégier les fichiers volumineux en concaténant des petits fichiers.

Compaction(Major)
Concaténation de tous les FileStores. Généralement exécuté une fois par jour (c.f. paramètre hbase.hregion.majorcompaction), une bonne pratique consiste à la forcer "manuellement" lors de moments de faible activité (voir la commande shell major_compact 'MYTABLE')

Coprocessor
API permettant d'écrire des programmes pouvant être appliqués à une Region et exécutés directement sur un RegionServer. Exemples d'utilisation : peut être utilisé pour construire des indexes secondaires sur les données ou contrôler finement l'accès à certaines partie la base de donnée.

FileStore
Fichier au format HFile qui stocke tout ou partie des données d'une Region.
Flush
Opération qui consiste à stocker dans un FileStore le contenu d'un MemStore. Par défaut cette sérialisation est demandée :
  • si un MemStore dépasse les 64Mo (c.f. HTableDescriptor.DEFAULT_MEMSTORE_FLUSH_SIZE)
  • si l'ensemble des MemStores d'un RegionServer occupent 35% du Heap (modifiable via le paramètre hbase.regionserver.global.memstore.lowerLimit). Au delà de 40% (paramètre hbase.regionserver.global.memstore.upperLimit) la sérialisation est forcé et les mises à jour sont bloquées.
HFile
Format de fichier utilisé par HBase pour stocker les données sur le système de fichier (voir HDFS). 

HDFS
Système de fichier massivement distribué développé dans le cadre du projet Hadoop. HDFS est généralement utilisé par HBase pour stocker les données. Il existe cependant des adaptateurs pour le service Amazon S3. HBase tire ainsi profit des fonctionnalités de HDFS comme la duplication des données et la possibilité d'effectuer sur ces dernières des traitements complexes de manière distribuée, rapide et fiable.

MapReduce
Framework du projet Hadoop permettant le traitement de donnée. HBase peut en tirer nativement profit afin de traiter les données.

MemStore
Structure mémoire qui conserve les dernières modifications effectués dans une Region. Lorsqu'un MemStore atteint une certaine taille il est "flushé" dans un FileStore.

MemStoreLAB (ou MSLAB)
Zone mémoire contiguë (de fait garantie d'être non fragmentée) , d'une taille 2Mo allouée dans la JVM dans laquelle un RegionServer va stocker les données d'un MemStore. Le but est d'éviter une fragmentation mémoire lors de la sérialisation des données (flush) du MemStore vers HDFS et du passage du Garbage Collector.

MasterServer
Serveur dont le rôle est de gérer l'assignation des Regions auprès des RegionServers. Il ne peut y avoir qu'un seul MasterServer en fonction à un instant donné mais un MasterServer de secours peut être déployé pour assurer une continuité de service automatiquement en cas de défaillance du premier.

Region : Rassemble tout ou partie des données d'une Column Family pour une Table. Dans une Region les données sont ordonnées par rapport à la clé, par défaut c'est l'ordre lexicographique qui est utilisé. Une Region rassemble les données d'un couple (Table, Column Family) d'une clé de départ à une clé de fin. (voir Split)

Region Server
Serveur en charge de la gestion d'une ou plusieurs Region

Split
Action qui consiste à diviser une région. Par défaut une Region ne peut excéder 256MB (paramétrable via hbase.hregion.max.filesize)

Table
Structure de donnée qui a un triplet {Key, Column, Timestamp} associe une valeur. Dans HBase les valeurs ne sont pas typées, tout est codé en interne comme un tableau d'octets (byte[]). HBase propose une classe "utilitaire" Bytes qui permet de convertir les types les plus courants.

Timestamp
Pour une clé et une colonne données HBase conserve plusieurs versions de la donnée, le Timestamp permet de distinguer les différentes versions. Par défaut HBase conserve 3 versions, ce paramètre est défini à la création de la Column Family.

TTL (ou Time To Live)
A chaque valeur est attribué une durée de vie. Ce paramètre, tout comme le nombre de versions conservées est définie au niveau de la ColumnFamily. Les données obsolètes sont supprimées lors des MajorCompactions.

WAL (Write Ahead Log)
Journal traçant les modifications apportées à une Region. C'est seulement une fois la modification enregistrée dans celui ci qu'elle est chargée en mémoire dans le MemStore. Le WAL peut être vu comme un point de reprise en cas d'arrêt brutal du RegionServer. Tout comme les autres fichiers qui contiennent des données le WAL est un fichier stocké sur HDFS ce qui permet à un RegionServer d'être secouru par d'autres RegionServer en cas de problème.


Zookeeper
Système de synchronisation massivement distribué du projet Hadoop. Il permet aux différents membres du cluster HBase de se synchroniser (e.g. élection d'un leader)