Cet article est le premier d’une série co-écrite avec David Brassely autour de MongoDB.
- MongoDB : faisons les présentations
- MongoDB : MapReduce en action
- MongoDB : un peu de Java
- MongoDB : la scalabilité avec les replicaset et le sharding
Un peu de culture générale
Les bases NoSQL (comprendre Not Only SQL) sont des bases de données issues du monde web et répondant aux problématiques de hautes disponibilités, grandes performances en lecture et/ou écriture ainsi que le traitement de grands volumes de données. Facebook, Twitter, Google, LinkedIn, Amazon sont parmi les têtes d’affiches à utiliser des bases NoSQL au sein de leur architecture afin d’adresser des problématiques différentes (avec des bases différentes).
Nous distinguons plusieurs catégories de bases NoSQL :
- les bases clefs/valeurs (ex : Redis, Voldemort)
- les bases orientées documents (ex : MongoDB, couchDB)
- Les bases orientées colonnes(ex : Cassandra, BigTable)
- les bases orientées graphes (ex : Neo4J)
MongoDB
MongoDB est une base de données orientée document sponsorisée par 10gen. Elle est utilisée notamment chez Foursquare, SourceForge et Bit.ly (liste plus complète ici).
Mongo stocke les documents au format BSON (JSON binaire supportant un peu plus de types de valeur) et fournit un shell javascript pour accéder aux données et faire les opérations d’administration. De nombreux drivers sont implémentés notamment pour Java, .Net, PHP, Javascript (tous les drivers).
La notion de base orientée document signifie que les objets stockés sont représentés sous la forme d’un document BSON (pas seulement une map de style clef/valeur) permettant facilement de se mapper sur les objets que l’on manipule dans nos programmes. On pourrait le comparer à un stockage d’une représentation XML d’une grappe d’objets.
La notion de schemaless vient quant à elle du fait qu’aucun schéma de document n’est nécessaire pour pouvoir stocker les données.
Installation
Pour tester, rien de plus simple, il suffit de créer un répertoire qui stockera votre base puis de décompresser la dernière version disponible ici et enfin de démarrer le serveur :
[shell]
$ mkdir /mon/chemin/vers/ma/base/
$ mongodb-version/bin/mongod –dbpath /mon/chemin/vers/ma/base/
Sun Aug 28 20:03:30 [initandlisten] MongoDB starting : pid=9544 port=27017 dbpath=data/dbTest/ 64-bit
Sun Aug 28 20:03:30 [initandlisten] db version v1.8.3, pdfile version 4.5
Sun Aug 28 20:03:30 [initandlisten] git version: c206d77e94bc3b65c76681df5a6b605f68a2de05
Sun Aug 28 20:03:30 [initandlisten] build sys info: Linux bs-linux64.10gen.cc 2.6.21.7-2.ec2.v1.2.fc8xen #1 SMP Fri Nov 20 17:48:28 EST 2009 x86_64 BOOST_LIB_VERSION=1_41
Sun Aug 28 20:03:30 [initandlisten] waiting for connections on port 27017
Sun Aug 28 20:03:30 [websvr] web admin interface listening on port 28017
[/shell]
Voilà, mongodb est lancé, il n’y a plus qu’à l’utiliser.
Utilisation
MongoDB fournit une interface web d’administration digne d’une page web des années 90. Vous pouvez vous y connecter sur l’url http://localhost:28017/
Un shell javascript est également disponible.
[shell]
$ ~/mongodb-version/bin/mongo
MongoDB shell version: 1.8.3
connecting to: test
>
[/shell]
Pour apprendre le shell nous vous conseillons vivement le tutoriel en ligne disponible sur le site de mongodb ainsi que celui de Karl Seguin : http://mongly.com/tutorial/ .
MongoDB est “schemaless” c’est à dire que vous pouvez tout de suite stocker des documents sans avoir à définir un quelconque schéma de données au préalable.
[shell]
> p1 = {prenom:"nicolas"}
{ "prenom" : "nicolas" }
> p2 = {nom:"geraud"}
{ "nom" : "geraud" }
> p3 = {prenom:"nicolas",nom:"geraud"}
{ "prenom" : "nicolas", "nom" : "geraud" }
> db.test.save(p1)
> db.test.save(p2)
> db.test.save(p3)
> db.test.find()
{ "_id" : ObjectId("4e5aac9a4c2a6134a339c3f1"), "prenom" : "nicolas" }
{ "_id" : ObjectId("4e5aad054c2a6134a339c3f3"), "nom" : "geraud" }
{ "_id" : ObjectId("4e5aad084c2a6134a339c3f4"), "prenom" : "nicolas", "nom" : "geraud" }
[/shell]
Nous avons donc 3 documents stockés avec des structures différentes.
Nous pouvons néanmoins requêter ces données sans problème :
[shell]
> db.test.find({nom:"geraud"})
{ "_id" : ObjectId("4e5aad054c2a6134a339c3f3"), "nom" : "geraud" }
{ "_id" : ObjectId("4e5aad084c2a6134a339c3f4"), "prenom" : "nicolas", "nom" : "geraud" }
[/shell]
ou encore enrichir les documents :
[shell]
> p = db.test.findOne({prenom:"nicolas"})
{ "_id" : ObjectId("4e5aac9a4c2a6134a339c3f1"), "prenom" : "nicolas" }
> p.jambe="2"
2
> db.test.save(p)
> db.test.find()
{ "_id" : ObjectId("4e5aad054c2a6134a339c3f3"), "nom" : "geraud" }
{ "_id" : ObjectId("4e5aad084c2a6134a339c3f4"), "prenom" : "nicolas", "nom" : "geraud" }
{ "_id" : ObjectId("4e5aac9a4c2a6134a339c3f1"), "prenom" : "nicolas", "jambe" : "2" }
[/shell]
Comme vous pouvez le constater, MongoDB permet de stocker des documents dans des collections sans nous contraindre à respecter un schéma donné. La garantie de la cohérence de ces documents au sein d’une collection n’est plus assurée par la base, mais par les programmes qui les manipulent. Il faut donc une certaine discipline pour que vos collections soient exploitables.
Sous le capot
Ce très court paragraphe sur l’utilisation de MongoDB vous aura, nous l’espérons, donné l’envie de parcourir les tutoriaux pour apprendre le shell. Voyons maintenant comment MongoDB travaille.
Une base MongoDB est composée de collections, constituées de documents, regroupant des paires clef/valeur dont la clef est une string et la valeur de type :
- basique : string, integer, float, timestamp, binary, etc.,
- un document
- une liste de valeur
MongoDB fonctionne en environnement 32 ou 64 bits, mais sachez qu’il y a une énorme différence entre les deux : la version 32bits ne permet pas de stocker plus de 2.5Gb de données. Cette version n’est donc pas indiquée pour un environnement de production.
Nos premiers documents sont stockés, allons regarder comment cela se passe sur votre file system.
Nous avions lancé mongo sur sa propre base (option –dbPath). Voici son contenu :
[shell]
$ ls -lhog /mon/chemin/vers/ma/base
total 209M
-rwxr-xr-x 1 5 28 août 20:03 mongod.lock
-rw——- 1 64M 28 août 23:11 test.0
-rw——- 1 128M 28 août 23:01 test.1
-rw——- 1 16M 28 août 23:11 test.ns
drwxr-xr-x 2 4,0K 28 août 23:01 _tmp
[/shell]
209Mo d’espace pour 3 documents (la commande >db.test.totalSize() me renvoit 10752bytes) !
L’explication vient du fait que MongoDB pré alloue de l’espace pour son stockage afin d’éviter la fragmentation.
Ainsi il initialise 2 fichiers (test.0 et test.1) dont la taille est le double du précédent (64Mo, 128, 256, …). Les fichiers vont ainsi jusqu’à une taille de 2Gb puis chaque nouveau fichier fera 2Gb. Autant dire que mongo devient vite gourmand en espace disque. Il y a une page dédiée sur les solutions de régime pour votre base : http://www.mongodb.org/display/DOCS/Excessive+Disk+Space
Le fichier test.ns quant à lui stocke les namespace de notre base. Par défaut il fait 16M (modifiable via l’option–nssize, avec pour taille maximum 2Gb) et permet de stocker environ 24000 namespace. Le nom des collections ainsi que les index occupent chacun un namespace.
Passons enfin aux différentes commandes fournies :
- mongod – le moteur de base
- mongos – le contrôleur de Sharding
- mongo – le shell javascript
- Les outils d’import/export
- mongoimport
- mongoexport
- mongodump
- mongorestore
- bsondump
- mongofiles – l’utilitaire GridFS
- mongostat – pour voir les stats d’une instance mongo
- mongosniff – le tcp dump de mongo, utile pour ceux qui développent des drivers.
ObjectId et BSON
Vous aurait certainement remarqué que lorsque vous effectuait un save sur un document, Mongo ajoute un champ (field) _id. Celui-ci est l’équivalent de la primary key pour mongodb. Cet attribut est obligatoire et peut être soit fourni, soit généré automatiquement par mongo :
[shell]
> p = {_id:"monId", nom:"geraud"}
{ "_id" : "monId", "nom" : "geraud" }
> db.test.save(p)
> db.test.find()
{ "_id" : ObjectId("4e5aad054c2a6134a339c3f3"), "nom" : "geraud" }
{ "_id" : "monId", "nom" : "geraud" }
[/shell]
Vous venez de voir une des spécificités du BSON. En effet ce dernier, en plus d’être un format binaire, autorise plus de type de données que le JSON.
Voici ceux disponibles en JSON (issus de json.org) :
A cela, mongo vient ajouter les types suivants : ObjectID, date, binaire, expression régulière, code javascript.
Le format BSON fait dorénavant l’objet d’une spécification et d’implémentations (plugin Jackson notamment pour le monde java) lui permettant de mener sa propre vie indépendamment de MongoDB.
Pour terminer voici un exemple complet de documents issus du projet YouGO représentant un utilisateur et ses demandes de congés:
[shell]
MongoDB shell version: 1.8.3
connecting to: test
> use yougo
switched to db yougo
> db.getCollectionNames()
[
"request_type",
"requests",
"requests_by_user",
"system.indexes",
"user_type",
"users"
]
> db.users.findOne()
{
"_id" : ObjectId("4e5e5c0f9d684166c66dd094"),
"login" : "kchung",
"fullname" : "Kristina CHUNG",
"active" : true,
"admin" : true,
"email" : "kristina.chung@company.com",
"password" : "password",
"type" :
{
"$ref" : "user_type",
"$id" : ObjectId("4e5e5c0f9d684166c66dd090")
}
}
> db.requests.find({login:"kchung"})
{
"_id" : ObjectId("4e5e5c0f9d684166c66dd095"),
"login" : "kchung",
"from" : ISODate("2011-01-02T23:00:00Z"),
"to" : ISODate("2011-01-02T23:00:00Z"),
"status" : "PENDING",
"ask_comment" : "Petit jour de repos",
"answer_comment" : "",
"type" :
{
"$ref" : "request_type",
"$id" : ObjectId("4e5e5c0f9d684166c66dd089")
}
}
{
"_id" : ObjectId("4e5e5c0f9d684166c66dd096"),
"login" : "kchung",
"from" : ISODate("2011-11-13T23:00:00Z"),
"to" : ISODate("2011-11-13T23:00:00Z"),
"status" : "ACCEPTED",
"ask_comment" : "Petit jour de repos",
"answer_comment" : "C’est bon",
"type" :
{
"$ref" : "request_type",
"$id" : ObjectId("4e5e5c0f9d684166c66dd08b")
}
}
[/shell]
Quelques liens supplémentaires :
- http://www.vineetgupta.com/2010/01/nosql-databases-part-1-landscape/
- http://kkovacs.eu/cassandra-vs-mongodb-vs-couchdb-vs-redis
- http://nosql-database.org/
- http://mongly.com/tutorial/
- http://bsonspec.org/
1 Comment
Comments are closed.