MongoDB en pratique

MongoDB Xebia
Nous avons beaucoup entendu parler au cours de cette année 2010 de la technologie émergente NoSQL. Nous avons appris que les grands acteurs d’Internet – Google, Amazon, Facebook, Twitter ou Linkedin – sont derrière ce mouvement et qu’ils se sont investis dans les développements des diverses solutions Open Source : Cassandra, HBase, Voldemort etc. Il est pourtant courant de se demander si celles-ci sont adaptées, applicables et répondent aux besoins des applications d’entreprise que nous pouvons rencontrer.
Ce billet a pour objectif de vous donner un retour d’expérience réel et pratique, du point de vue du développeur Java, sur la base de donnés NoSQL MongoDB que nous avons eu l’occasion de mettre en production chez l’un de nos clients il y a deux semaines.

L’expression de besoin

Notre équipe a été confrontée au défi suivant : d’une part récupérer et stocker, via différentes sources d’information, tous les points d’intérêt de plusieurs villes en France (Paris, Marseille… ) et toute l’information possible liée à chacun de ces points (points = restaurants, plombiers, mairies, cinémas etc. contenus = avis, à l’affiche, carte, histoire etc. ). D’autre part exposer des services Web pour rechercher ces points d’intérêt par mot clé (recherche full-text) et par localisation (recherche géo-spatiale).
En résumé, le système à développer devait répondre aux caractéristiques suivantes :

  • un modèle de données :
    • très hétérogène (les données associées aux cinémas n’ont rien à voir avec les données d’une mairie ou d’un supermarché !)
    • entités très autonomes les unes des autres et sans relations complexes (relations 1:1)
    • qui évolue très rapidement étant soumis à des changements très fréquents (intégration hebdomadaire des nouveautés fournies par le marketing)
  • transactions
    • par entité unique
    • absence de transactions complexes inter-objet
  • recherche par identifiant, mot-clé et localisation
  • très haute disponibilité et performance en temps réel
  • scalabilité horizontale (ajouter des machines plutôt que d’augmenter la capacité d’une seule = distribution de données)

Nous pressentions que dans l’écosystème NoSQL nous trouverions une solution adaptée au système de stockage et de recherche que nous devions développer. Ayant observé les différentes solutions du marché, nous avons reconnu dans ce besoin le terrain idéal d’une base de données orientée document telle que MongoDB.

Introduction à MongoDB

MongoDB est une base de données NoSQL de type document. Open Source et écrite en C++, elle s’adapte bien aux applications Web :

  • cherche à répondre aux besoins de performance en temps réel
  • garantit la scalabilité horizontale (réplication et sharding)
  • offre des nombreuses fonctionnalités que l’on trouve dans le monde relationnel (count, groupBy, etc.) mais aussi le support de la recherche full-text, la recherche géo-spatiale ou MapReduce
  • supporte l’indexation pour l’optimisation des recherches

Elle a déclenché une forte adoption par la communauté (90.000 téléchargements / mois) et elle est déjà en production sur de nombreux sites tels que Foursquare, Bit.ly, SourceForge ou GitHub, offrant ainsi des garanties suffisantes pour rassurer un client.

Parmi les caractéristiques la démarquant des bases de données relationnelles, on retiendra :

  • Le concept de « schéma » n’existe pas. Chaque document est libre de suivre sa propre structure.
  • Jointures : les données sont généralement embarquées dans le même « document ». Cette forme de stockage peut ainsi être vue comme des jointures déjà exécutées (même si la possibilité de linkage entre documents existe surtout pour modéliser des relations N-M)
  • L’atomicité des transactions n’est garantie que sur un seul document
  • La modification concurrente doit être gérée au niveau de l’application
  • La flexibilité, la performance et la scalabilité se font au détriment de la capacité de gérer des transactions complexes
  • Terminologie : Table = Collection, Ligne = Document, Index = Index, Jointure = Données embarquées

Documents BSON

Les documents stockés par MongoDB sont de type BSON. Les objets BSON sont la représentation binaire des objets JSON en leur ajoutant des améliorations et des capacités supplémentaires telles que les expressions régulières, Bytes Arrays, Date et TimeStamps et le stockage de blocs de code ou de fonctions JavaScript.

Exemple :

{ "name" : "karesti",
  "duchess" : true,
  "hobbies" : {
                "danse" : "classique, moderne",
                "théâtre" : "improvisation, moderne",
                "autres" : "tapisserie niveau 5"
              }
}

Courbe d’apprentissage

Personne dans l’équipe n’avait utilisé MongoDB auparavant ; un risque apparaissait alors quant aux délais de livraison. La documentation officielle met en avant la préoccupation des concepteurs de créer une base de données utilisable rapidement pour les développeurs et les équipes d’infrastructure. Après cette expérience, je peux vous assurer que la courbe d’apprentissage et la prise en main de l’outil nous permet d’être productifs et autonomes très rapidement. La documentation est riche et de bonne qualité.

Voyons cela en pratique !

Installation et démarrage

L’installation de la base de développent est très simple. La dernière version stable peut être téléchargée depuis ce lien. Les étapes à suivre sont :

  • Décompresser le binaire téléchargé.
  • Créer le répertoire où les données seront stockées. Par défaut MongoDB cherche les données dans le répertoire /data/db, qui doit être créé par l’utilisateur.

Sous le répertoire d’installation nous trouverons le répertoire bin qui contient divers exécutables :

  • Pour démarrer la base de données il suffit de lancer la commande : ./mongod (mongod.exe sous windows)

La base de données est ainsi installée et démarrée.
Facile, n’est-ce-pas ?

Shell

La console JavaScript est notre meilleure amie pour travailler avec MongoDB. Le changement de paradigme est assez extraordinaire, étant donné que les requêtes SQL (create, update, delete, select) ne servent plus, et sont remplacées par du Javascript et un langage de requêtes propre à la base.

Vous pouvez ouvrir la console en exécutant le script sous le répertoire bin/ : ./mongo (mongo.exe sous windows)


MongoDB shell version: 1.6.4
connecting to: test

Nous sommes connectés à la base « test« . Si nous souhaitons nous connecter à une autre base – xebia – nous utiliserons :

> use xebia
switched to db xebia

Mongo créera la base ‘xebia’ automatiquement si celle-ci n’existe pas. Nous avons aussi l’aide disponible :

db.help()
db.xebia.help()

Tip : Partez de JavaScript
La meilleure approche pour apprendre le langage de requêtes est d’explorer le shell. Lors des développements, il est préférable d’abord de tester les requêtes dans la console pour ensuite les transcrire en code s’interfaçant avec le driver.

Créer la connexion

MongoDB offre un bon support et une intégration avec de nombreux langages. Le driver Java propose une API – un peu verbeuse dans certain cas – pour réaliser la transformation des objets en documents, la connexion à la base, les CRUD, les requêtes, etc.

Le driver se trouve dans le dépôt central de Maven. Il suffit de placer la dépendance dans le pom.xml :


    org.mongodb
    mongo-java-driver
    2.1

La classe qui s’occupe de la connexion est com.mongodb.Mongo, on notera qu’elle est thread safe. Dans notre projet nous avons utilisé MongoDB dans un environnement Web et batch. Dans le cas du Web, nous avons créé une instance unique qui est partagée pour chacune des requêtes : Mongo s’occupe de gérer un pool de connexions en interne.

Création de la connexion et récupération de la base « xebia »

Mongo mongo = new Mongo();
DB xebiaDB = mongo.getDB("xebia");

A noter :

  • Ceci est le cas le plus simple, l’API offre également des possibilités pour s’authentifier
  • Sur le disque, sous le répertoire /data/db, on trouvera des fichiers organisés par nom de base de données ({{test}}, xebia)

Transformation Objet – BSON

Nous allons maintenant peupler notre base de données. La classe suivante représente l’ensemble de données que nous souhaitons manipuler :

public class XebiaWorker {

    private String id;

    private String name;

    private String address;

    private boolean duchess;

    private Map hobbies;

}

Afin de pouvoir insérer dans la base, la transformation Objet-BSON doit être faite. Nous avons plusieurs possibilités.

Réaliser la transformation à la main

Avantage : Nous avons la main sur le modèle de données pour décider de l’organisation des documents par rapport à nos beans.
Désavantage : Il s’agit d’une API bas niveau par essence verbeuse et très répétitive.

Driver

public DBObject toBasicDBObject() {
    BasicDBObject dbObject = new BasicDBObject();
    if (this.id != null && ObjectId.isValid(this.id)) {
        dbObject.put("_id", new ObjectId(this.id));
    }
    dbObject.put("name", this.name);
    ...
    dbObject.put("hobbies", new BasicDBObject(this.hobbies));

    return dbObject;
}

Info : ObjectId
La classe ObjectId fournie par le driver sert d’identifiant unique pour chaque document. Elle est sauvegardée sous la propriété réservée « _id« . Nous reviendrons sur ce point plus tard dans l’article.

Utiliser une libraire

Avantage : très propre
Désavantage : la proximité avec l’API JPA peut amener une certaine confusion en laissant croire à des similitudes entre les BDD relationnelles et MongoDB qui ne sont pas réelles.

Depuis la documentation officielle plusieurs librairies sont référencées. Elles permettent de réaliser le mapping par annotations, semblable à ce que nous permet de réaliser JPA : Morphia (la plus connue), mungbean, daybreak. La librairie la plus recommandée est Morphia.

Morphia

@Entity
public class XebiaWorker {

    @Id
    ObjectId id;

    private String name;

    // pour un objet encapsulé
    @Embedded
    private Address address;
}

@Embedded
public class Address {...}

Notre choix

Morphia est une librairie qui a évolué très rapidement et qui est maintenant recommandée. Nous l’avons intégré récemment pour réaliser la transformation Object-BSON mais elle offre aussi une sur-couche à l’API de requêtes que nous sommes en train d’étudier.

CRUD

Les opérations CRUD sont très faciles à implémenter. J’ai déjà mentionné que MongoDB ne réalise que des opérations atomiques pour chaque document. La base est capable d’identifier les documents grâce au champ spécial réservé « _id ». Ainsi :

  • Chaque document est identifié de manière unique.
  • L’unicité s’étend à toutes les instances de Mongo (la clé par incrémentation n’est pas envisageable dans le cadre des environnements distribués).
  • L’identifiant est automatiquement généré, mais nous pouvons aussi le gérer nous-mêmes.

La classe suivante illustre un exemple des services CRUD :

Driver

public class XebiansRepository {

    private DB xebiaDB;

    public XebiansRepository(Mongo mongo) {
        DB xebiaDB = mongo.getDB("xebia");
    }

    DBCollection getCollection() {
        return xebiaDB.getCollection("xebians");
    }

   //les méthodes ici
}

Morphia

public class XebiansRepository {

    private Datastore ds;

    public XebiansRepository(Mongo mongo) {
        // connexion base local
        ds = new Morphia().createDatastore(mongo, "xebia")
    }

   //les méthodes ici
}

Insertion

L’identifiant sera automatiquement généré sous la forme d’un ObjectId() et sera stocké dans la propriété « _id ».

> db.xebians.insert({name:"katia", "address" : "12 rue Paul Ochon, 75012 Paris"})
> db.xebians.find()
{ "_id" : ObjectId("4d0407660766b236450b45a3"),
  "name" : "katia", "address" : "12 rue Paul Ochon, 75012 Paris" }

Driver

public void insert(XebiaWorker worker) {
    getCollection().insert(worker.toBasicDBObject());
}

Sauvegarde

Permet d’insérer ou de modifier un document. Si l’identifiant n’est pas fourni ou n’existe pas, un nouveau document sera crée; dans le cas contraire la mise à jour est réalisée.

> db.xebians.save({ "_id" : ObjectId("4d0407660766b236450b45a3"),
                    "name" : "Katia Aresti",
                    "address" : "12 rue Jacques Ouzi, 75012 Paris" }
)
> db.xebians.find()
{ "_id" : ObjectId("4d0407660766b236450b45a3"),
  "name" : "Katia Aresti",
  "addresse" : "12 rue Jacques Ouzi, 75012 Paris" }
public void save(XebiaWorker worker) {
    getCollection().save(worker.toBasicDBObject());
}

Morphia

public void save(XebiaWorker worker) {
    ds.save(worker);
}

Lecture unique

La méthode findOne permet de passer un objet BSON et récupère le premier document qui porte les valeurs de cet objet. Afin de réaliser la lecture par clé unique, nous devons passer un objet BSON qui contient uniquement la clé du document :

> db.xebians.findOne({_id: ObjectId("4d0407660766b236450b45a3")})
{
	"_id" : ObjectId("4d0407660766b236450b45a3"),
	"name" : "Katia Aresti",
	"address" : "12 rue Jacques Ouzi, 75012 Paris"
}

Driver

public XebiaWorker findById(String id) {
    BasicDBObject dbObject = (BasicDBObject) getCollection().findOne(new ObjectId(id));
    return new XebiaWorker(dbObject);
}

Morphia

public XebiaWorker findById(String id) {
    return ds.get(XebiaWorker.class, new ObjectId(id));
}

Suppression

La méthode remove permet de passer un objet BSON et efface tous les documents qui portent les valeurs de cet objet. Elle permet d’effacer plus d’un élément, voire même tous les éléments de la collection si on ne lui passe aucun paramètre (remove()).

> db.xebians.remove({ "_id" : ObjectId("4d0407660766b236450b45a3")})
> db.xebians.count()
0

Driver

public void remove(String id) {
   DBObject obj = BasicDBObjectBuilder.start("_id", new ObjectId(id)).get();
   getCollection().remove(obj);
}

Morphia

public void remove(String id) {
   ds.delete(XebiaWorker.class, new ObjectId(id));
}

A noter :

  • Nous n’avons pas créé explicitement la collection « xebians ». La base créera la collection lors de la première insertion.
  • Le schéma est très flexible.
  • L’identifiant unique est créé automatiquement.
  • Les méthodes telles que « findOne » ou « remove » réalisent une recherche avec un argument.
  • Le driver java contient des classes Builder pour faciliter les développements.
  • Il existe d’autres méthodes de modification, comme par exemple ‘update » qui permet de réaliser des modifications de plusieurs objets dans une même requête.
  • Morphia nous oblige à garder chaque objet dans sa propre collection.

Tip : Ne pas mélanger les torchons et les serviettes
Normalement, nous avons le droit de créer des objets très hétérogènes dans la même collection. Dans la pratique, le plus raisonnable à tout niveau est de stocker dans sa propre collection les objets de la même famille. De plus la nécessité d’indexation pour optimiser les requêtes nous forcera à organiser nos collections « proprement ».

Note : Attention avec le shell
Les commandes comme remove() effacent tout le contenu d’une collection (l’équivalent à une table) à une telle vitesse que vous penserez que rien ne s’est passé ! Attention à vos doigts ! Dans la suite de l’article, je vous montrerai quelques commandes pour réaliser des copies de la base afin d’éviter les mauvaises surprises.

Recherche

L’un des enjeux de la recherche est de comprendre que les requêtes sont également des objets BSON. Aussi, nous devons maîtriser les opérateurs. Nous rencontrons des opérateurs tels que $ne: not equals, $gt: greater than, $size, $within, $or ou $box. Je n’entrerai pas dans le détail de chacun de ces opérateurs (guide ici), mais voici quelques exemples de requêtes simples pour commencer :

  • Recherche stricte par nom :
> db.xebians.find({name : "Michaël"});

Note : /value/ à la place de « value » pour récupérer le document si le texte contient la valeur

Driver

public List findByName(String name) {
    DBCursor cursor = getCollection().find(new BasicDBObject("name", name));
    return buildResult(cursor);
}

List buildResult( DBCursor cursor){
    List workers = new ArrayList();
    while (cursor.hasNext()) {
        DBObject dbObject = cursor.next();
        workers.add(new XebiaWorker(dbObject));
    }
}

Morphia

public List findByName(String name) {
    return ds.createQuery(XebiaWorker.class).field("name").equal(name).asList();
}
  • Tous les xebians qui ont des hobbies :


> db.xebians.find( { hobbies : { $ne : null } } )

public List findAllWithHobbies() {
    DBObject query = QueryBuilder.start("hobbies").notEquals(null).get();
    return buildList(getCollection().find(query));
}

Morphia

public List findAllWithHobbies() {
    return ds.createQuery(XebiaWorker.class).field("hobbies").notEqual(null).asList();
}
  • Récupérer 10 xebians au hasard
> db.things.find().limit(10)

Driver

public List findXebians(int max) {
    DBCursor cursor = getCollection().find().limit(max);
    return buildResult(cursor);
}

Morphia

public List findXebians(int max) {
    return ds.find(XebiaWorker.class).limit(max).asList();
}

A noter :

  • Chaque partie entre accolades est l’équivalent d’un objet BSON (DBObject, document et/ou queries)
  • Le driver propose des Builders pour faciliter l’écriture de requêtes

Scripts de mise à jour

Imaginons que notre schéma de données évolue et que nous devions remplacer le champ String « address » par un objet. Si nous changeons uniquement le code source, la correspondance avec la base ne nous permettra plus de lire les données car elles contiennent un String à la place d’un Objet. En résumé, nous devons exécuter un script JavaScript de mise à jour de la collection.

Exemple :

db.xebians.find( { address: {$ne : null}}).forEach(

    function updt(xebian) {
        var address = xebian.address;
        xebian.address = new Object();
        xebian.address.way = address;
        xebian.address.pCode = "";
        xebian.address.phone = "";
        db.xebians.save(xebian);
    }
)

Au passage du script, nous constatons que le champ adresse est maintenant un objet BSON :

> db.xebians.findOne()
{
	"_id" : ObjectId("4d04f3abfcc39ac490669281"),
	"name" : "karesti0",
	"duchess" : true,
	"hobbies" : {
		"autres" : "tapisserie niveau 5",
		"théâtre" : "improvisation, moderne"
	},
	"address" : {
		"way" : "12 rue Alain Terieur",
		"pCode" : "",
		"phone" : ""
	}
}

Quelques commandes pratiques

Sous le répertoire bin se trouvent plusieurs exécutables. Les deux que j’ai le plus utilisé sont mongodump et mongorestore.

mongodump

Permet de faire un dump d’une base de données :

> ./mongodump --db xebia --out "/mydata/dumpmongo"
connected to: 127.0.0.1
DATABASE: xebia	 to 	/mydata/dumpmongo/xebia
	xebia.xebians to /mydata/dumpmongo/xebia/xebians.bson
		 1 objects
	xebia.system.indexes to /mydata/dumpmongo/xebia/system.indexes.bson
		 1 objects

A Noter : Sous le répertoire /mydata/dumpmongo nous trouverons un nouveau dossier « xebia » qui contient deux fichiers : system.indexes.bson et xebians.bson

mongorestore

Permet d’installer une base de données à partir d’un dump dans un répertoire local ou distant en utilisant le paramètre –host « l’ip remote »

>./mongorestore --db xebia --drop "/mydata/dumpmongo/xebia"
connected to: 127.0.0.1
Sun Dec 12 14:55:45 /mydata/dumpmongo/xebia/system.indexes.bson
Sun Dec 12 14:55:45 	 going into namespace xebia.system.indexes
Sun Dec 12 14:55:45 	 dropping
Sun Dec 12 14:55:45 	 1 objects found
Sun Dec 12 14:55:45 /mydata/dumpmongo/xebia/xebians.bson
Sun Dec 12 14:55:45 	 going into namespace xebia.xebians
Sun Dec 12 14:55:45 	 dropping
Sun Dec 12 14:55:45 	 1 objects found

Conclusion

La base de données me semble très puissante et la prise en main de l’outil a été une très belle expérience. Cette introduction nous a permis de réaliser l’installation de la base de données, les opérations CRUD, recherche et plusieurs commandes pratiques. Il s’agit vraiment d’un produit qui fonctionne très bien et qui répond très bien à notre besoin.

Par rapport aux solutions NoSQL, et MongoDB en particulier, l’effet buzz me semble risqué. Ce produit est un très mauvais choix dans nombreux cas d’application – cette affirmation étant lisible dans la documentation et répétée par les représentants de 10gen à Devoxx 2010. En effet, MongoDB n’est pas adapté aux environnements transactionnels critiques tels que nous pouvons les retrouver dans les banques. Si votre schéma de données peut facilement se modéliser de façon relationnelle, qu’il n’est pas soumis à des changements fréquents et que vous avez besoin de transactions atomiques inter-objets, MongoDB ne sera pas le meilleur choix. De surcroît, si vous n’avez pas de problématiques liées ni à la distribution de données, ni à la performance, ni à la scalabilité, il est peu probable qu’une solution NoSQL soit la réponse adaptée à votre besoin.

Soyez prudents, étudiez vos problématiques techniques et vos besoins objectivement, en tenant compte aussi de l’existence de l’écosystème NoSQL, et prenez votre temps pour décider sagement des outils qui s’adaptent le mieux à vos besoins.

Je n’ai pas abordé les fonctionnalités avancées comme l’indexation, la configuration pour la mise en production ou les requêtes complexes. Je laisse ceci pour un prochain billet lorsque nous aurons plus de retours « terrain » et lors de l’ouverture du site au grand public.

18 commentaires

  • Bonjour

    Très bon article.

    Une remarque importante toutefois: dans notre contexte et nos tests, le driver java pour mongodb ne supporte pas vraiment la montée en charge.

    En gros, voici un exemple de perf sur un quadcore avec un pool de connexion de 10:
    ========================================================================================
    # of Threads: 1
    # of Loops: 400
    # of values: 400
    Average Returned Values: 100
    Average Time: 52ms
    total Runtime: 21063ms
    ========================================================================================
    # of Threads: 4
    # of Loops: 100
    # of values: 400
    Average Returned Values: 100
    Average Time: 168ms
    total Runtime: 16871ms
    ========================================================================================
    # of Threads: 10
    # of Loops: 40
    # of values: 400
    Average Returned Values: 100
    Average Time: 544ms
    total Runtime: 22081ms

    En gros, la même requête, prend 52ms avec un thread et… 168ms avec 4 threads! Bref, une catastrophe, à moins d’avoir des temps initiaux vraiment faibles. Lorsque des tests de montée de charge ont révélé la chose, on a été très désagréablement surpris.

    Lors de l’utilisation, les 4 cores ne sont jamais tous utilisés, et cela est la même chose avec la base de données en local ou via un réseau bien rapide (pas un goulet d’étranglement en somme).

    En premier lieu, sur le ticket http://jira.mongodb.org/browse/JAVA-207, le maintainer a dit que cela était du à un problème d’accès à la couche TCP/IP inhérent à une JVM et donc que tou était normal. Perso cette explication ne me dit rien, et personne jusqu’ici n’a pu m’apporter des éléments dans ce sens. D’autant qu’à ma connaissance d’autres drivers (d’autres base de données) supportent bien mieux la montée en charge.

    Par la suite, un collègue, uwe schäfer (cf http://www.codesmell.org/blog) a trouvé une utilisation malhabile des finalizers. Lors de ses tests, supprimer les finalizers en question a permis d’utiliser les 4 cores, soit un sacré gain.

    Cela a ensuite débouché sur ce ticket http://jira.mongodb.org/browse/JAVA-221. Nous sommes en train de tester les modifications apportées, et à priori elles ne changent pas dramatiquement la donne.

    De façon plus générale, le code du driver est assez obtus, visiblement réalisé par des personnes venant du C/C++. Il y a un usage excessif de synchronisations diverses, mais un profilage n’a pas révélé de souci de ce côté là.

    Au final, nous doutons fortement de la capacité à monter en charge du driver java de mongodb sans en connaitre les raisons ni des pistes d’amélioration.

    Si jamais quelqu’un a des éléments sur ce sujet, nous serions plus que preneurs…

    A noter que cela semble vraiment lié au driver java, quatre instances distinctes de JVM attaquant une même base ne semblant pas souffrir de problème particulier.

    Tout retour bienvenu, vraiment, car nous aimerions beaucoup enlever ce clou de notre pied!

    Pour morphia, je pourrai également en parler plus abondamment, mais je me contenterai de faire court, le temps manquant: le projet n’est pas encore stable, et les différentes versions apportent régulièrement leur lot de régression, voir par exemple celle ci qui nous a beaucoup pénalisé et est susceptible de le faire encore http://code.google.com/p/morphia/issues/detail?id=176&colspec=ID%20Type%20Stars%20Status%20Priority%20Milestone%20Owner%20Summary.

    Ajoutez à cela que les bugs sont fixés dans les dernières releases, et vous avez vite une situation pas forcément reluisante. Cela a amené pas mal de difficultés, avec des compromis et des works arounds de ci de là.

    J’espère qu’une version 1.0 pourrait résoudre en partie ces problèmes.

    ++
    joseph

  • Intéressant, nos tests de montée en charge n’ont pas montré ce problème, et on tient la charge maximale avec un trés petit nombre d’instances ec2. Je vais allé vérifier avec nos données réelles de tests pour voir. Merci de ton retour.

    En ce qui concerne le driver java, je ne peux qu’abonder dans ton sens. C’est codé « bizarrement » par rapport aux bibliothèques tierces habituelles. On a contribué quelques lignes de ci de là grace au github du projet et y’a encore pas mal de possibilités d’améliorations, clairement.

    En ce qui concerne morphia, on a démarré sans, trop compliqué, trop récent pour être utilisable et avec un arrière gout d’hibernate qu’on avait pas envie de voir.
    Mais force est de constater que le code qui permet l’association entre la base et nos objets est trés verbeux, pénible à écrire, à tester et peut avantageusement être substitué par les annotations proposés par Morphia.
    Moins de code, performance équivalente: feu vert ! Nous n’utilisons pas le reste de l’API Morphia, notamment leur support aux DAOs.

  • @Joseph
    Merci pour le retour! En ce moment nous sommes en train d’aborder les tests de montée en charge afin de préparer l’ouverture au grand public d’ici à quelques mois. Votre retour nous est très utile. Je vous contacterai via mail si cela vous convient pour discuter de ces problématiques.

    Concernant Morphia, nous étudions la librairie. Pour ce qui est du « mapper », il marche bien et nous l’utilisons. Nous sommes par contre aussi conscients que la version n’est pas encore stable. Cela dit, elle est maintenant recommandée par 10Gen, les retours d’utilisation et corrections arriveront à grande vitesse; elle est donc une librairie à tester et surveiller.
    Merci encore !

  • Impressionnant d’exhaustivité.

    L’effort fait par Morphia est vraiment remarquable pour un utilisateur de JPA comme moi …

    Bravo l’auteur. J’aime beaucoup.
    Merci d’avoir partagé.

  • Est-ce que la recherche de MangoDB n’est pas un peu trop limitée ? Pourquoi ne pas avoir choisi SolR par exemple (surtout qu’il s’agit d’exposer des services de recherche au final)

  • @Waddle
    Non, je ne crois pas qu’elle soit limitée, bien au contraire. Notre besoin de recherche est d’abord géo-spatiale, nativement supporté, par id et aussi full-text sur certains champs. Le système de recherche de Mongo couvre complétement notre besoin.

  • @Waddle: Il y a ici à la fois un besoin de stockage et de recherche. Effectivement les solutions basées sur Lucene telles que Solr apportent un plus large éventail de possibilités de recherche, mais elles ne remplacent que très partiellement une solution de stockage dédiée, pour les raisons que j’avais évoquées dans un précédent commentaire.

    Les besoins en recherches de l’application sont actuellement pleinement couverts par MongoDB. Si les besoins devaient évoluer dans le futur vers une recherche plus puissante, une indexation Lucene pourrait venir en complément du stockage MongoDB existant. Cette approche pragmatique serait ainsi en pleine adéquation avec la logique de stockage polyglotte prônant le « right tool for the right job » :).

    Cordialement,
    Michaël Figuière (Xebia)

  • @Katia et Michaël
    La recherche géo-spatiale existe aussi dans SolR. En fait, je suis plutôt étonné que vous n’ayez pas eu de besoin nécessitant SolR ou Lucene sur la recherche full-text. C’est le genre de fonctionnalité où les clients demandent toujours qu’elle fasse le café (Java inside bien sûr :-p)

    Mais si MangoDB suffit au besoin, j’applaudis des deux mains le fait de ne pas ajouter un outil (pour rien) :-)

  • A l’heure actuelle, seule la recherche par rayon est disponible. D’ailleurs la Javadoc à ce sujet de Lucene indique clairement qu’il y a un gros rework en cours à ce sujet.
    La recherche géo spatiale complète ne sera disponible que pour la prochaine release de Lucene\Solr , en V4.
    MongoDB ne permet pas (pour l’instant) de faire de recherche avec classement de pertinence comme on peut l’attendre d’un moteur de recherche, si c’est ce que vous entendez par « full text ».

  • Sujet très intéressant permettant de voir en parallèle la gestion à la main et par une API … il ne me reste qu’à avoir un projet où utiliser cette technologie pour la tester in vivo.

  • @Arnaud

    En effet, il faut attendre la release de la version 4 pour une implémentation « built-in » de la recherche spatiale, mais il existe déjà des solutions sous forme de plugins. Mais peut-être qu’elles ne correspondaient pas non plus à votre besoin :-)
    Pour la recherche full-text, je pensais à des boosts, des facettes, du highlithging, du lemming, etc.

  • Re

    Merci pour vos réponses et désolé du délai de réponse (l’absence de commentaire pendant un temps m’ayant amené à faire d’autres choses entre temps). Ne pas hésiter à me contacter par mail si besoin (comme l’évoque karesti).

    concernant le driver java de mongodb, une nouvelle version vient d’être releasée (cf http://jira.mongodb.org/browse/JAVA-221). On va la tester prochainement. Espérons que cela résolve le soucis.

    Ceci dit, Jean Laurent, je serai curieux de savoir si les tests disponibles là http://jira.mongodb.org/browse/JAVA-207 reproduisent le problème avec la version 2.3 du driver dans votre environnement. Si cela n’était pas le cas, je serai preneur de plus de détails…

    Concernant morphia, perso je réserve mon avis pour l’instant, avec en quelques points les réserves suivantes:
    – code confus, redondant et avec du copié/collé,
    – non nettoyage de concepts périmés (@Property et @Embedded qui sont la même chose en fait, la distinction devant être réalisée par le framework et non le développeur),
    – l’annotation @Reference est dangereuse: elle convient pour un affichage unique d’une entité, mais dès que des listes sont impliquées cela donne vite un nombre trop important de requêtes (et la déclarer lazy ne change que peu => il y a tout de même à chaque fois une requête pour vérifier l’existence de la cible). En somme à utiliser avec parcimonie.
    – manque de visibilité sur les évolutions: Scott n’en discute pas sur la ML (ou ailleurs à ma connaissance), or parfois elles introduisent des régressions voir des concepts discutables (cf http://code.google.com/p/morphia/issues/detail?id=176 où il y a eu au final un retour arrière partiel).

    Au final, après une première période très productive (bien plus qu’en JPA), on a pas mal buté sur des bugs et/ou des régressions, jetant un doute sur l’ensemble. On utilise d’ailleurs désormais notre propre version patchée…

    Il y a enfin un point sur la façon d’utiliser morphia que je n’ai pas encore éclairci: quelle est la meilleure méthode pour ne capturer que des sous ensembles d’entités. Je m’explique: lors de requêtes aboutissant à des listes d’entités, le volume de données devient conséquent. Il faut donc alors réduire les données transmise. Cela peut se faire de 2 façons:
    – filtre sur l’entité concernée,
    – sélection via le driver des champs concernés et construction d’un objet ad hoc.

    La première méthode a l’avantage de la simplicité (au risque d’une certaine confusion dans la suite du code, vu qu’on s’attend à une entité pleinement lue là où seuls quelques champs sont présents), la deuxième permet de faire des choses plus spécifiques (avec des éléments pré calculés, par exemple « nombre de référence ») au prix d’une plus grande complexité.

    dans les deux cas la « maintenabilité » de la chose me laisse un peu inquiet… Des bonnes pratiques à partager peut être ?

    D’ailleurs, dans le même ordre d’idée, les restrictions de mongodb (en termes de OR dans les requêtes) font également que parfois nous avons arbitré entre éléments de sélection fait dans la requête ou coté java. C’est certes bien rapide au final mais la complexité est sacrément décuplée (on récupère plus de données que l’on n’utilise par la suite, avec différents filtres intermédiaires => dur de s’y retrouver dans 6 mois je crains).

    Là encore, je suis preneur de vos éclaircissements ;)

    ++
    joseph

  • juste un petit message pour dire que nous avons ouvert en beta notre service http://mongood.com

    MongoOd vous permet d héberger vos bases mongodb sur un replica set

    Gratuit pour les bases < 100 Mo

    Merci pour vos retours

  • Bonjour
    merci pour ce tuto
    je pense qu’il y a une petite erreur sur la partie lecture unique
    Morphia
    public XebiaWorker findById(String id) {
    return ds.get(XebiaWorker.class, new ObjectId(id)); // il faut corrigé par return ds.get(XebiaWorker.class, id);
    }

  • Merci pour cette page!
    Ce fut pour moi mon premier contact avec mongoDB.

    Je viens du monde de l’électronique, du c et du soft embarqué, et comme maintenant tout ce petit monde communique avec le cloud (ou le Claude), j’ai dû prendre dans mon périmètre un peu de dev serveur…

    Mongo me va très bien pour sa souplesse et ses perfs. Mais je n’ai pas fait encore de montée en charge.

    Plusieurs tests de perfs sur le Web ont attiré mon attention comme http://kkovacs.eu/cassandra-vs-mongodb-vs-couchdb-vs-redis. On dirait que le système cassandra est plus performant niveau des latences et des durées d’opération non ?

    Pour mon besoin de perf d’écriture et de lecture, des index, des fichiers et pas (ou peu) de relationel, mongo est-il un bon choix?

    Qu’en pensez-vous ?

  • Bonne intro pour débuter !

    Pour plus de contenu, il y a aussi http://www.MongoTuto.com, la première référence web 100% consacrée à MongoDB.

    Ca vient d’ouvrir et tout est gratuit, profitez-en :)

Laisser un commentaire