Publié par
Il y a 4 années · 6 minutes · Data

Voldemort, depuis Hadoop (3/3)

Précédemment, nous avons vu ensemble l’intérêt de Voldemort pour stocker vos recommandations quotidiennes (article) et comment installer Voldemort en lecture seule (article). Mais celui-ci, sans données, ne présente pas beaucoup d’intérêt. Il est désormais temps de terminer le tutoriel pas à pas, avec la génération des fichiers (données et index) depuis Hadoop, puis leur import.

Ce tutoriel nécessite l’accès à un cluster Hadoop. Son installation ne sera pas couverte dans cet article mais reste relativement facile en mode pseudo-distribué sur une seule machine. Si vous souhaitez aller plus loin, un précédent article détaille comment installer un véritable cluster Hadoop.

Les données

Nous allons partir d’un fichier source très simple contenant un seul utilisateur (42) suivi par la liste des identifiants des films à recommander (16,4,8,23,15).

echo -n "42:16,4,8,23,15" > mymovies.txt
bin/hadoop fs -put mymovies.txt /tmp/

Dans le cadre d’un projet pérenne, il est plus vraisemblable d’utiliser un conteneur (confer introduction). La démarche à suivre sera à adapter mais aura les mêmes étapes.

La transformation

Voldemort fournit l’essentiel du code pour réaliser ces fichiers (données et index) mais bien sûr ne connait pas les détails de votre stockage et il est donc nécessaire de fournir une classe transformant votre stockage en une liste de clef/valeur. Voici ce que cela donnerait pour notre exemple en texte.

package fr.xebia;

import java.util.ArrayList;
import java.util.List;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import voldemort.store.readonly.mr.AbstractHadoopStoreBuilderMapper;

public class XroMapper extends AbstractHadoopStoreBuilderMapper<LongWritable, Text> {
    @Override
    public Object makeKey(LongWritable key, Text value) {
        return extractPart(0, value);
    }
    @Override
    public Object makeValue(LongWritable key, Text value) {
        return asLongs(extractPart(1, value));
    }
    private String extractPart(int index, Text value) {
        return value.toString().split(":")[index];
    }
    private List<Long> asLongs(String rightToken) {
        String[] values = rightToken.split(",");
        List<Long> result = new ArrayList<Long>();
        for (int i = 0; i < values.length; i++) {
            result.add(Long.valueOf(values[i]));
        }
        return result;
    }
}
  

Malheureusement, Voldemort n’est pas maven friendly. Il vous faudra ajouter manuellement les jar voldemort et voldemort-contrib se trouvant dans le répertoire dist au classpath en plus de hadoop-core. Une fois le jar créé, on le déplacera dans config/xro/lib par soucis d’homogénéité.

La génération des fichiers (données et index)

La création des fichiers est un job MapReduce géré par Voldemort. Il faut lui fournir les chemins (–input, –output, –tmpdir) ainsi que le code d’adaptation (–jar, –mapper). En analysant notre configuration (–cluster, –storedefinitions, –storename), il réalisera la création des fichiers.

contrib/hadoop-store-builder/bin/hadoop-build-readonly-store.sh \
 -libjars lib/avro-1.4.0.jar \
 --input /tmp/mymovies.txt \
 --output /tmp/voldemort \
 --tmpdir tmp-build \
 --jar config/xro/lib/XroMapper.jar \
 --mapper fr.xebia.XroMapper \
 --cluster config/xro/config/cluster.xml \
 --storedefinitions config/xro/config/stores.xml \
 --storename mymovies \
 --chunksize 1073741824

À noter : les options génériques d’Hadoop sont supportées. Nous les utilisons ici pour ajouter le jar avro au job (-libjars) afin que ce jar soit disponible sur toutes les machines du cluster.

L’import

Une fois le job passé avec succès, nous pouvons alors charger ces données à la place de la version d’avant (vide).

contrib/hadoop-store-builder/bin/swap-store.sh \
 --cluster config/xro/config/cluster.xml \
 --file hftp://localhost:50070/tmp/voldemort \
 --name mymovies \
 --push-version 1

Le protocole à utiliser est bien hftp et non hdfs ou alors il faut sécuriser votre cluster avec Kerberos. La version des données doit être précisée et permet de garantir de pouvoir rejouer l’import. En effet, l’import sera annulé si la version existe déjà. Il est possible de préciser une version antérieure en ajoutant le flag –rollback. Dans ce cas la, la version courante sera suffixée de la date du jour. Un import ultérieur pourra donc être effectué sans soucis. Par exemple, si les données de la version 2 se trouvent être inadaptées, après rollback, nous aurons :

ls -l config/xro/data/read-only/mymovies/
latest -> .../voldemort-1.3.0/config/xro/data/read-only/mymovies/version-1
version-1
version-2.05-03-2013.bak

Il ne nous reste plus qu’à vérifier avec le client que les données ont effectivement bien été importées.

bin/voldemort-shell.sh mymovies tcp://localhost:6666
 Established connection to mymovies via tcp://localhost:6666
> get "42"
version() ts:1367521876687: [16482315]

Même si l’affichage est trompeur, il s’agit de ce que nous attendions : la liste des identifiants des films à recommander à l’utilisateur "42". C’est-à-dire : [16, 4, 8, 23, 15].

Et maintenant?

Ce pas à pas est désormais fini. Il s’agit d’une version adaptée, corrigée et détaillée d’un tutoriel publié en 2009 sur le blog de LinkedIn. Il s’agissait de l’état de l’art à l’époque. Depuis LinkedIn a continué à investir sur ce sujet.

L’exploitation a été considérablement simplifiée grâce à un job Azkaban. Le nombre de tâches, vues durant ce pas à pas, est réduit et leur enchainement est automatisé. En effet, si les données sont stockées au final sous forme de clef/valeur en avro, il n’est pas incohérent que les données d’origine avant toutes transformations pour Voldemort soient également stockées sous forme d’objets avro sur HDFS. En partant de ce principe, il est alors possible d’avoir une transformation générique qui prendra comme clef/valeur deux champs configurables de l’objet source. Puisque avro est défini par un schéma, cette solution fournit en plus un moyen de vérifier que le format source est bien compatible avec le format attendu par le store. Azkaban ordonnance des jobs batch et permet de visualiser leur résultat (un peu comme Jenkins/Hudson). Grâce à celui-ci, les opérations de vérification, construction des fichiers (données et index) ainsi que leur import se gère au sein d’un seul processus configurable et automatisable.

Une architecture commune a émergé pour répondre à certains besoins : Avatara, pour servir de nombreux, petits cubes OLAP. En effet, comme nous l’avons souligné à plusieurs reprises, le schéma de la valeur doit être fixé mais est libre. Si les fonctionnalités d’un cube OLAP sont intéressantes, la construction de celui-ci peut être cependant très consommatrice de ressources (temps, espace). Dans la mesure où le nombre de dimensions est réduit et que l’une d’elle peut être partitionnée, il est alors envisageable de stocker chaque petit cube résultant en tant que valeur. Un moteur intermédiaire peut alors être utilisé pour effectuer des agrégations, filtres et projections à la volée. LinkedIn utilise cette architecture pour visualiser la consultation d’une page (par exemple, votre profil ou une proposition de poste) selon de nombreux axes : temporel, géographique, par industrie… Cela concerne plus de 160 millions de cubes (un par membre pour les profils) de quelques megaoctets.

Voldemort est un projet open source, si vous souhaitez en savoir plus : le site, le code source, et le blog de LinkedIn sont les meilleures ressources.

Bertrand Dechoux
Consultant et Formateur Hadoop @BertrandDechoux

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *