Tutoriel sur un retour d'expérience d'une formation MongoDB pour les développeurs Java

| Mickaël Barroux (Arolla) | Arolla |

Cet article se propose de présenter une formation MongoDB pour les développeurs Java.

Pour réagir au contenu de cet article, un espace de dialogue vous est proposé sur le forum Commentez . ♪

- Titre : Tutoriel sur un retour d'expérience d'une formation MongoDB pour les développeurs Java

- Auteurs : Mickaël Barroux (Arolla), Arolla

- Parution : 13 octobre 2014

- Licence : Copyright ® 2014 Arolla. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- II. Partie 1 : Introduction

- III. Partie 2 : CRUD

- IV. Partie 3 : Schema design

- V. Partie 4 : Performances

- VI. Partie 5 : Aggregation pipeline

- VII. Partie 6 : Application engineering

- VII-A. Réplication (vertical scaling)

- VII-B. Sharding (horizontal scaling)

- VIII. Partie 7 : études de cas

- IX. Conclusion

- X. Remerciements

I - Introduction

Dès que j'ai eu vent d'une formation initiale à MongoDB pour les développeurs JAVA, je n'ai pas hésité et me suis inscrit. L'équipe MongoDB propose cette formation en ligne, d'une durée de 7 semaines, sous forme de vidéos qui permettent d'appréhender cette technologie NoSQL pas à pas, y compris pour les débutants. À l'issue de ces 7 semaines, une certification nous est décernée si nous atteignons un score minimum de 65 % qui se décompose ainsi : chaque semaine voit une série de devoirs à rendre (50 % de la note) et il y a un examen final à la fin de la session comptant pour l'autre moitié de la note.

Ces cours se décomposent donc en 7 grandes parties, que je vais détailler dans la suite de l'article.

https://education.10gen.com/courses/10gen/M101J/2013_Spring/about

II - Partie 1 : Introduction

Cette première partie permet de présenter la technologie MongoDB, un système de base de données orienté documents, c'est-à-dire qu'il faut oublier le système relationnel des bases de données classiques style Oracle ou MySQL. Les bases de données NoSQL contiennent des « collections » (qui peuvent être assimilées à des tables dans le monde relationnel) et chaque collection contient des « documents » (lignes dans le monde relationnel).

Ce système est plus souple en termes de structure, puisque les documents sont structurés en JSON (JavaScript Object Notation) et qu'il peut y avoir des documents structurés différemment au sein d'une même collection (champs en plus, différents types de valeurs pour un même champ…). L'utilisation de ce langage permet également d'écrire des scripts en JavaScript à exécuter sur l'instance de base de données (il faut oublier le SQL pour ce type de bases de données).

Un service (« daemon ») est exécuté sur le serveur de bases de données (« mongod ») et il existe différents clients pour exécuter des requêtes. Ce cours permet de nous intéresser à deux d'entre eux : le shell « mongo », qui permet de requêter en ligne de commande, et un driver JAVA qui permet d'utiliser cette base de données au sein d'une application JAVA.

Les commandes principales du shell pour se connecter à une base et utiliser une collection sont dévoilées dans ce premier chapitre.

III - Partie 2 : CRUD

Les opérations de base (CRUD = Create, Retrieve, Update, Delete) sont présentées lors de la 2e semaine de formation. On apprend à manipuler les différents types de requêtes, via le shell ou le driver JAVA fourni.

Il faut savoir que chaque document au sein d'une collection a un identifiant unique, par défaut un champ de type ObjectId généré par Mongo. On peut cependant très bien utiliser un ID de notre système d'information à la place (numéro de sécurité sociale pour un individu par exemple).

La commande find() permet de retrouver une liste de documents au sein d'une collection (findOne() permet de n'en prendre qu'un seul, utile pour voir la structure des documents). Il suffit ensuite de combiner cette commande avec des filtres dont la syntaxe est propre à Mongo et utilise la notation JSON.

Exemples :

db.people.find({"firstname" : "John"}) permet de lister l'ensemble des personnes se prénommant « John ».

db.people.find({"age" : {$lte : 25} }) liste les personnes qui ont plus de 25 ans.

On peut bien sûr combiner ces critères avec les opérateurs booléens courants $or, $and et d'autres opérateurs plus complexes. Pour les connaisseurs, on peut même définir ses propres opérateurs en Javascript et les intégrer à Mongo.

Dans cette partie, on appréhende également la notion de tableaux (arrays) qui sont un ensemble de documents. Un document est composé de propriétés de base (chaîne de caractères, nombre, date), de sous-documents (champ composé d'autres propriétés) et de tableaux (liste de documents).

Exemple d'objet complexe :

{
"firstname" : "Mickael",
"lastname" : "Barroux",
"birth_date" : {
"$date" : 616651200000
},
"address" : {
"street" : "5, Main Street",
"state" : "California",
"city" : "Los Angeles"
"country" : "US"
},
"phone_number" : [
{ "0102030405" },
{ "0203040506" },
]
}

Les commandes d'insertion de document (insert) et de suppression de document (remove) sont également passées en revue.

La mise à jour de document est un peu plus compliquée à appréhender, car elle utilise un critère permettant de sélectionner les documents à mettre à jour, et enfin une commande pour savoir comment les mettre à jour (modification ou ajout de propriété : $set, ajout d'une valeur à un tableau : $push ou $addToSet).

Exemple de mise à jour (modifie la ville de l'utilisateur dont le nom est « Barroux » et ajoute un nouveau numéro de téléphone) :

db.people.update(
{ "lastname" : "Barroux" },
{
$set: { "address.city" : "Irvine" },
$push: {
phone_numbers: { "0605040302" }
}
}
)

D'autres commandes de base sont présentées permettant de compter le nombre de documents (count), de trier une liste (sort), de passer certains documents (skip) et de limiter le nombre de résultats (limit). Ils peuvent être chaînés à une requête find().

IV - Partie 3 : Schema design

Cette partie permet de se poser des questions plus fonctionnelles et plus haut niveau sur l'application que l'on souhaite implémenter. En effet, il est nécessaire de réfléchir à la modélisation du schéma de notre application en amont. Cette réflexion est différente quand on implémente le schéma avec MongoDB par rapport à ce qu'on a l'habitude de faire avec des SGBD classiques.

Par exemple, dans les cas les plus courants du monde relationnel, il est recommandé d'utiliser la 3e forme normale (3FN) ou la forme normale de Boyce-Codd (FNBC) afin d'éviter la redondance des données. Toutes les propriétés sont définies une seule fois et on référence ensuite toutes ces données à l'aide d'une clé étrangère composée le plus souvent à partir d'un ID généré.

En MongoDB, on essaie de conserver ces règles également, mais on est souvent amené à adapter davantage le schéma à la façon dont il est utilisé par l'application.

Je m'explique : selon la façon et la fréquence à laquelle on va accéder en lecture à certaines données, on peut choisir de les inclure en intégralité dans une collection. On perd donc ce principe de « non-redondance » pour optimiser les requêtes de chargement, qui n'ont désormais plus besoin de faire des jointures dans tous les sens pour récupérer les données à partir des ID qui définissent les clés étrangères (foreign key).

À noter qu'il n'existe en MongoDB aucune jointure ni clé étrangère. Il n'y a même aucune contrainte sur les collections, mises à part les contraintes d'unicité, qui garantissent qu'une valeur est unique sur la collection (utilisé en interne notamment pour l'ObjectID qui est la clé primaire (primary key) des documents par défaut).

MongoDB n'est pas transactionnel et ne permet pas l'écriture ACID d'un ensemble cohérent de données. Il sacrifie cette contrainte issue du monde relationnel au profit d'une haute disponibilité des données. Pour plus d'informations à ce sujet : How ACID is MongoDB ?

Exemple :

Imaginons un blog contenant des posts, chaque post contenant des commentaires.

En base de données relationnelle, on aurait un schéma de ce type :

Pour récupérer la liste des derniers posts du blog, on doit donc récupérer le contenu de la table posts, leurs commentaires associés en faisant 2 jointures (posts_comments et comments) et si on veut leur auteur on doit faire une 3e jointure sur la table authors.

Avec MongoDB, on va créer des documents qui contiennent un tableau de commentaires (dénormalisation) comme suit :

{
"_id" : { "$oid" : "5143ddf3bcf1bf4ab37d9c6d" },
"body" : "Je suis un super post !",
"post_date" : { "$date" : 1363402227874 },
"comments" : [
{
"_id": 1,
"comment_text": "Je suis le commentaire 1",
"author": "Mickael Barroux"
},
{
"_id": 2,
"comment_text": "Je suis le commentaire 2",
"author": "Arolla",
}
]
}

Cela sera optimisé pour la récupération des données en lecture. Par contre, revers de la médaille, ce sera un peu plus compliqué dans des cas de mises à jour du nom d'un auteur par exemple (il faudra mettre à jour l'auteur de chaque commentaire écrit par la personne).

V - Partie 4 : Performances

Une autre façon d'optimiser les performances sur MongoDB, et là on rejoint une notion des bases de données relationnelles, est d'utiliser des index. Cela permet de conserver à un instant T la façon dont sont organisés les documents en base (sorte de table des matières avancée), afin de les retrouver plus rapidement par la suite. Cela rend les lectures plus rapides (si elles utilisent les indexes), mais les écritures plus coûteuses, car il faut maintenir ces index à jour.

Mongo dispose de commandes de création (ensureIndex) et de suppression d'index (removeIndex). Ces index peuvent être multiclés, c'est-à-dire que l'on peut organiser les documents selon un champ puis un autre (date et titre d'un article par exemple). Il faut faire preuve de réflexion pour définir des index pertinents sur les données, en fonction de leur utilisation dans les requêtes (si on veut les 10 derniers articles sur la page d'accueil du site, il peut être opportun de définir un index sur la date de publication dans l'ordre du plus récent au plus ancien par exemple).

La commande Mongo explain() permet de déboguer l'utilisation des index sur une requête donnée. Elle donne des détails intéressants sur les index utilisés, le nombre de documents parcourus, le temps d'exécution…

Dans une optique d'optimisation de notre application, on peut logguer les requêtes lentes (définition d'un seuil limite) afin d'ajouter les index appropriés par la suite. Du profiling peut également être nécessaire à l'aide des commandes mongotop() et mongostat().

VI - Partie 5 : Aggregation pipeline

Mongo apporte un framework d'agrégation complet pour les requêtes les plus complexes. En effet, il permet de chaîner différents opérateurs dans une même commande, afin de filtrer les résultats. Les différents opérateurs sont détaillés ci-dessous :

- $group : opérateur de groupement des données (= GROUP BY en SQL). De nouveaux opérateurs sont utilisables pour les données agrégées avec $group, à la manière de SQL : $sum (somme), $avg (moyenne), $max, $min, $addToSet / $push (ajout à une liste). => Attention à la taille de la requête et au nombre de docs générés ! (un index peut être utile dans ces cas-là) ;

- $unwind : opérateur qui permet de dénormaliser les données d'un tableau. En fait pour chaque document contenant un tableau de N valeurs, cet opérateur génère N documents avec chaque valeur contenue dans le tableau sur lequel est appliqué $unwind ;

- $project : permet de ne projeter que certaines propriétés que l'on souhaite afficher ;

- $match : permet de filtrer les données sur différents critères, comme avec la commande find() ;

- $sort : permet de trier les données par le champ spécifié (dans l'ordre ASC / 1 ou DESC / -1) ;

- $skip : permet de « passer » les N premiers documents ;

- $limit : permet de limiter le nombre de documents résultats.

Exemple d'opérateurs combinés : on cherche l'auteur du plus grand nombre de commentaires sur notre blog.

db.posts.aggregate([
{
"$unwind": "$comments" /* Cree un doc par commentaire */
},
{
"$project" : {
comments: 1    /* Ne conserve comme donnees que les commentaires */
}
},
{
"$group": {
_id: "$comments.author",
nb_comments: {
$sum: 1 /*groupe par auteur, en comptant le nb de commentaires*/
}
}
},
{
"$sort": {
"nb_comments": -1 /* trie par nb de commentaires du + grand au + petit*/
}
},
{
"$limit": 1              /* ne garde que le premier resultat */
}
])

Le système d'agrégation de Mongo est donc très puissant et permet de créer tout type de requête complexe.

VII - Partie 6 : Application engineering

Cette dernière partie d'apprentissage concerne toute la partie architecture d'une application utilisant MongoDB.

Les principaux concepts expliqués dans cette partie sont la réplication et le sharding.

VII-A - Réplication (vertical scaling)

Cette technique permet la redondance des données sur différents nœuds Mongo et donc garantit la disponibilité des données en cas de crash. Mongo permet en effet de définir un nombre de nœuds qui sont des répliques du nœud maître à l'aide de commandes de configuration au démarrage de l'application.

Les écritures de documents sont toujours effectuées sur le nœud maître, et par défaut les lectures aussi. Les nœuds répliques servent uniquement à répliquer les données du maître, grâce à des mécanismes de synchronisation. Mais l'utilisateur peut choisir d'utiliser les nœuds répliqués pour lire les données et ainsi décharger le maître et accélérer les requêtes. Attention cependant, les données peuvent être inconsistantes, car les nœuds sur lesquels on lit dans ce cas peuvent ne pas avoir récupéré les données écrites sur le maître…

Lors d'un crash du nœud maître, un algorithme d'élection d'un nouveau nœud maître parmi les nœuds restant « up » est alors lancé. Au retour éventuel du nœud crashé, il reste esclave et se synchronise pour récupérer le delta de données perdu lors de son interruption de service.

Lors de l'instanciation du client JAVA, il suffit de lui passer l'adresse d'un des nœuds du « replica set » et il découvre tout seul l'intégralité des nœuds qui le constituent, en toute transparence pour l'utilisateur.

VII-B - Sharding (horizontal scaling)

Le sharding est une technique de répartition des données sur plusieurs nœuds, afin de mieux supporter la charge. On découpe les données et on les répartit sur les différents nœuds Mongo. Chaque « shard » est donc une base de données indépendante et, ensemble, les « shards » forment une base de données logique complète, qui est ensuite utilisée de manière transparente par les différents clients.

Par exemple, une recherche peut porter sur un ou plusieurs shards, c'est MongoDB qui s'occupe de récupérer les données aux bons endroits et de les agréger pour nous retourner un résultat cohérent.

Le choix d'une « shard key » intelligente est également abordé, puisqu'elle sert de critère pour répartir les données sur chaque shard.

On peut bien sûr utiliser ces deux techniques afin de répartir les données physiques sur plusieurs « replica sets » (qui garantissent pour leur part la disponibilité de ces données).

VIII - Partie 7 : études de cas

La partie 7 présente des cas réels d'utilisation de la technologie MongoDB par des grandes entreprises telles que Foursquare et Codecademy.

Les architectes techniques expliquent ce choix, les avantages de MongoDB et ce que cette technologie apporte à leur produit.

- An Interview : How Foursquare uses MongoDB
Principaux avantages de Mongo de leur point de vue :

- les mécanismes de sharding et de replica set avec failover automatique (pour la partie montée en charge de l'application) ;

- l'indexation spatiale est très utile pour les mécanismes de géolocalisation, calcul de distance et de coordonnées pour permettre à Foursquare d'offrir un service de qualité ;

- beaucoup moins de collections Mongo que de tables relationnelles (Postgresql) permettent une gestion plus simple et plus propre du code de l'application ;

- Codecademy utilise MongoDB en raison de la souplesse et du petit nombre de collections nécessaires, contrairement aux nombreuses tables dénormalisées que leur offre le monde relationnel ;

- de plus, il est assez simple de faire du scaling horizontal et vertical avec cette technologie.

Codecademy cofounder Ryan Bubinski explains how his company uses MongoDB

IX - Conclusion

MongoDB est une technologie prometteuse qui présente donc de nombreux avantages pour certains types d'applications WEB.

Cette formation certifiante en ligne, offerte gracieusement par MongoDB Inc., est ainsi très complète et très bien structurée et permet d'appréhender cette technologie pas à pas, grâce aux nombreux exemples fournis.

La création d'un blog utilisant les drivers JAVA pour Mongo permet de bien comprendre le fonctionnement et les applications possibles de ce type de base de données NoSQL.

La possibilité de suivre cette formation en ligne, en gérant donc son emploi du temps comme on le souhaite pour regarder les vidéos et faire les exercices, et la volonté des formateurs d'améliorer continuellement le contenu et les moyens de communication (blog, forum, etc.) en font des atouts indéniables par rapport à d'autres formations sur ce type de technologies.

Quelques chiffres pour finir :

- nombre de participants : 8.863 ;

- taux de réussite de cette session : 21% ;

- temps par semaine : entre 1h et 2h de vidéos et 3h de travail personnel.

X - Remerciements

Cet article a été publié avec l'aimable autorisation de la société Arolla. L'article original (Formation MongoDB pour les développeurs JAVA) peut être vu sur le blog/site de Arolla.

Nous tenons à remercier ced pour sa relecture orthographique attentive de cet article et Régis Pouiller pour la mise au gabarit.