Tout ce que le getting started mongodb ne vous dira pas
Web2Day 2016 #web2day
#MongoDB #web2day
Tout ce que le getting started
MongoDB ne vous dira pas
Bruno BONNIN - @_bruno_b_
Pierre-Alban DEWITTE - @padewitte
Getting started with MongoDB
# Installation de Mongodb
> sudo apt-get install mongodb
# Lancement du serveur
> sudo service mongod start
# Import de données
> mongoimport --db test --collection restaurants --drop --file primer-dataset.
json
# Lancement du shell
> mongo
// On s’amuse avec quelques commandes CRUD
>>> db.restaurants.insert(...)
>>> db.restaurants.find(...)
>>> db.restaurants.delete(...)
● Cette présentation est truffée de mauvaise
foi
● Ne prenez pas tout, adaptez vous à votre
contexte
● Notre but est de vous donner envie d’aller
chercher la bonne information
Des documents !
● MongoDB stocke des documents
○ Structure de données hiérarchique
○ Un champ peut être un type de base (entier, string,
booléen), une liste, un objet (= sous-document)
RDBMS MongoDB
Database Database
Table Collection
Enregistrement Document
Colonne Champ
Index Index
Jointure $lookup, sous-document, lien
Modèle relationnel et normalisation
● Data rangées dans les bonnes cases, pas de
redondance de l’information
○ C’est beau, c’est carré !
● Mais, attention aux performances lors des
recherches sur les disques de l’info dans les
différentes tables
La réponse de MongoDB
Pensez...
Dénormalisation
Documents
Usages
Personne
nom adresseId
... ...
Adresse
id code_postal ville
... ... ...
Personne
{
nom: “...”,
code_postal: “...”,
ville: “...”
}
Personne
{
nom: “...”,
adresse: {
code_postal: “...”,
ville: “...”
}
}
Document, sous-document, ...
Avantages d’un seul document :
● locality : stockage de toute l’info au même
endroit sur le disque
● consistency : comme il n’y pas de
transaction, MongoDB assure que l’update
d’un doc est atomique
Personne
{
nom: “...”,
adresse: {
code_postal: “...”,
ville: “...”
}
}
Document, sous-document, ...
Désavantages d’un seul document :
● On peut récupérer plus d’info qu’on n’en a
réellement besoin
○ on peut utiliser les projections dans les
queries (ok d’un point de vue réseau),
○ mais cela n’empêche le document d’
être entirèrement présent en mémoire
avant d’être retourné au client
Personne
{
nom: “...”,
adresse: {
code_postal: “...”,
ville: “...”
}
}
Références entre docs
Avantages à séparer dans plusieurs
docs ?
● Flexibilité
○ Changement sur une collection
sans impact sur les autres
Personne
{
nom: “...”,
adresseId: 123
}
Adresse
{
adresseId: 123,
code_postal: “...”,
ville: “...”
}
● Cardinalité forte / gros document:
○ Attention à la place mémoire (document
déserialisé en mémoire)
○ Taille max des docs: 16Mo
Références entre docs
Désavantages à séparer dans
plusieurs docs ?
● On risque d’avoir à gérer des
jointures dans l’application,
compliqué !
● Ou utilisation de $lookup : a des
contraintes fortes !
Personne
{
nom: “...”,
adresseId: 123
}
Adresse
{
adresseId: 123,
code_postal: “...”,
ville: “...”
}
Modélisation: quelques règles simples
1..1
1 seule collection Personne
- Avec un sous-document
“carte_vitale”
Collection “Personne”
{
nom: “...”,
carte_vitale: {
date_emission: “...”,
cnam: “...”
}
}
Personne Carte Vitale
Modélisation: quelques règles simples
1 seule collection Personne
- Avec une liste de sous-
documents “adresse”
1..peu
Collection “Personne”
{
nom: “Séraphin Lampion”,
adresses: [
{
rue: “rue de la Roquette”,
ville: “Paris”
},
{
rue: “chemin du chateau”,
ville: “Moulinsart”
}
]
}
Personne Adresse
Modélisation: quelques règles simples
2 collections Systeme et Log
- C’est Log qui référence le parent !
1..énormément
Collection “Système”
{
id: “...”,
host: “...”,
...
}
Système Log
Collection “Log”
{
date: “...”,
level: “...”,
id_systeme: “...”
}
Et concernant l’usage...
Ne pas oubliez d’en tenir compte !
● “J’ai besoin de l’ensemble des données à
chaque requête”
○ Mettez tout dans une seule collection
● “J’ai besoin d’en avoir seulement une partie”
○ Faites plusieurs collections et des références
○ Ex: les posts d’un blog et leurs commentaires :
■ 2 besoins : affichage liste des posts + affichage post avec
commentaires
■ Modélisation avec 2 collections (posts, comments)
$lookup
Des jointures ! c’est un début…
● Left outer join
● Uniquement avec des collections non shardées
Collection Conference
{
“title”: “Plein de mots-clefs hyper cools...”,
“description”: “Refaites votre SI avec
tout ça, sinon vous êtes mort!”,
“speaker_id”: “techno_maniac”
}
Collection Speaker
{
“_id”: “techno_maniac”,
“name”: “Jean-Marcel Le Maniac”,
“email”: “le_maniac@web2day.org”
}
Exemple $lookup
db.Conference.aggregate( [ {
“$lookup”: {
“from”: “Speaker”,
“localField”: “speaker_id”,
“foreignField”: “_id”,
“as”: “speaker_infos”
} }
] )
[ {
“title”: “Plein de mots-clefs hyper cools...”,
“description”: “Refaites votre SI avec…”,
“speaker_id”: “techno_maniac”,
“speaker_infos”: [ {
“_id”: “techno_maniac”,
“name”: “Jean-Marcel Le Maniac”,
“email”: “le_maniac@web2day.org”
} ]
}, … ]
$lookup
Collection Conference
{
“title”: “Plein de mots-clefs hyper cools...”,
“description”: “Refaites votre SI avec ça”,
“speaker_ids”: [ “miss_techno”, “techno_maniac” ]
}
Exemple $lookup
db.Conference.aggregate( [ {
$unwind: “$speaker_ids”
}, {
$lookup: {
from: “Speaker”,
localField: “speaker_ids”,
foreignField: “_id”,
as: “speakers_infos”
}
}, {
$group: {
_id: “$_id”,
title: { $first: “$title” },
speakers: { $push: “$speakers_infos” }
}
}
] )
{
“title”: “Plein de mots-clefs hyper cools...”,
“speakers”: [{
“_id”: “miss_techno”,
“name”: “Jeanne-Irene La Maniac”,
“email”: “miss_techno@web2day.org”
}, {
“_id”: “techno_maniac”,
“name”: “Jean-Marcel Le Maniac”,
“email”: “le_maniac@web2day.org”
} ]
},
{
...
},
…
Avec des listes, jouez avec
$unwind et $group
Pas de transaction, mais...
Ne pas oublier qu’il existe des méthodes
findAndXXX permettant d’exécuter des actions
de manière atomique
FindOneAndUpdateOptions options = new
FindOneAndUpdateOptions().returnDocument(ReturnDocument.AFTER);
// UpdatedConf contient les données de la conférence suite à la modif
Conference updatedConf = confCollection.findOneAndUpdate(
new Document(), // Query
new Document("$push", new Document("speakerIds", "pad")), // Update
options);
Mongo-hacker
● Le shell MongoDB vitaminé
○ Colorisation
○ Alias pour compter collections, documents et
indexes
○ Alias pour aggrégations gcount, gavg, gsum
● Toujours nécessaire notamment quand la
base n’est pas accessible directement
https://github.com/TylerBrock/mongo-hacker
M
● Equivalent pour mongodb de n pour node
● Simplification du téléchargement
● Changement en une commande de version
mongodb
https://github.com/aheckmann/m
Versionner vos documents
Il n’existe pas de solutions toutes faites
proposées par MongoDB
Prenons quelques exigences (il y en a d’
autres):
● Récupération de la dernière version lors des requêtes
● Pas d’incohérence
● System failure: il faut pouvoir revenir à un système
cohérent
Versionner vos documents
Quelles sont les possiblités ?
● Un nouveau document par nouvelle version,
avec un champ “current”
● Stockage de toutes les versions dans un
même document
{ docId: 12, v: 1, color: “red” }
{ docId: 12, v: 2, color: “red”, count: 3 }
{ docId: 12, v: 3, color: “blue”, count: 3, current: true }
{
docId: 12,
previous: [ { v: 1, color: “red” }, { v: 2, color: “red”, count: 3 } ],
Current: { v: 3, color: “blue”, count: 3, current: true }
}
Versionner vos documents
Quelles sont les possiblités (suite) ?
● 2 collections: 1 pour la dernière version et
une autre pour l’historique
● Stockage des deltas uniquement
Collection PREV:
{ docId: 12, v: 1, color: “red” }
{ docId: 12, v: 2, color: “red”, count: 3 }
Collection CURRENT:
{ docId: 12, v: 3, color: “blue”, count: 3 }
{ docId: “123”, v: 1, color:”red” }
{ docId: “123”, v: 2, count: 3 }
{ docId: “123”, v: 3, color:”blue” }
Versionner vos documents
Type Avantages Désavantages
1 nouveau doc par
version (avec champs
“current” et “version”)
Find rapide/facile (ajout du champ
“current” dans la query pour avoir la
dernière version)
Risque d’incohérence lors des
updates car mises-à-jour de
plusieurs docs
Toutes les versions dans
un même document
Find rapide/facile (ne pas oublier de
filtrer sur les anciennes versions)
Update : il faut jongler avec la partie
“current” et la partie “previous”
Taille max des docs: attention à ne
pas arriver à la taille max (update
impossible !).
2 collections (une
current, une historique)
Find rapide/facile Risque d’incohérence lors des
updates car mises-à-jour de
plusieurs docs / collections
1 nouveau doc par
update avec stockage
des deltas
Update simple: création d’un nouveau
doc avec les mises-à-jour (pas touche
à l’ancienne version)
Find complexe: reconstruction du
doc peut être compliqué (nécessite
plusieurs docs pour reconstruire le
foc final)
ORM ou plutôt ODM
Avant-propos: eh oui, on est mono-maniaque et
on ne développe qu’en Java !
● Driver Java
○ Évolue bien, mais bas niveau
● Morphia
○ Intéressant
● Spring Data MongoDB
○ C’est Spring...
● Jongo
○ Fun !
● Encore d’autres que l’on ne connait pas
Morphia
Nécessaire et suffisant
Morphia morphia = new Morphia();
morphia.map(Conference.class, Speaker.class);
Datastore datastore = morphia.createDatastore(client, "web2day2016");
Conference conf = new Conference("Have fun With MongoDB", "@padewitte", "@_bruno_b_");
datastore.save(conf);
List<Conference> confWithPAD = datastore.find(Conference.class)
.filter("speakers", "@padewitte")
.field("speakers").sizeEq(1)
.asList();
datastore.createAggregation(Conference.class)
.unwind("speakers")
.project(projection("twitterHandle", "speakers"), projection("title"))
.group("twitterHandle", grouping("conferences", addToSet("title")))
.out(Speaker.class);
Driver Java 3.0
Nouvelles classes (les anciennes sont toujours là, hélas…)
// A OUBLIER !!!!
DB db = mongoClient.getDB("web2day2016");
DBCollection dbCollection = db.getCollection("conferences");
Iterator<DBObject> confIter = dbCollection.find().iterator();
// Utilisation d’un mapper pour convertir les DBObjects en objets métier
MongoDatabase db = mongoClient.getDatabase("web2day2016");
MongoCollection<Document> dbCollection = db.getCollection("conferences");
// Ou en utilisation directe de la classe métier
MongoCollection<Conference> dbCollection = db.getCollection("conferences",
Conference.class);
MongoCursor<Conference> conferences = confCollection.find().iterator();
// Il ne faut pas oublier de déclarer un codec pour la classe Conference
Driver Java 3.0
Codec: permet de gérer finement le codage en
BSON (est-ce bien utile ? bof...)
public class ConferenceCodec implements Codec<Conference> {
@Override
public void encode(BsonWriter writer, Conference conf,
EncoderContext ctx) {
}
@Override
public Class<Conference> getEncoderClass() {
return Conference.class;
}
@Override
public Conference decode(BsonReader reader,
DecoderContext ctx) {
}
}
public class ConferenceCodecProvider implements
CodecProvider {
@Override
public <T> Codec<T> get(Class<T> clazz,
CodecRegistry registry) {
if (clazz == Conference.class) {
return (Codec<T>) new
ConferenceCodec();
}
return null;
}
}
Utilisation d’un codec générique (projet à suivre!):
➔ https://github.com/ylemoigne/mongo-jackson-codec
Driver Java 3.0
Async API (mêmes noms de classes, pas le même package)
// Async Database et Collection
//
MongoDatabase db = client.getDatabase("web2day2016");
MongoCollection<Conference> collection = db.getCollection("conferences",
Conference.class);
Conference conference = new Conference(
"Framework TrucMachinChoseJS et Dockerification de mes microservices",
"C'est trop cool !",
new Speaker("JsAndDockerAndMicroServicesManiac"));
// Insertion d’un élément => il faut fournir une callback
//
collection.insertOne(conference,
(final Void result, final Throwable t) -> {
System.out.println("Conference inserée");
});
Estimer la volumétrie
1. Prototyper et concevoir une première
version du schéma
2. Extraire les statistiques de taille de chaque
collection pour les datas et les index
a. taille moyenne des datas
b. taille moyenne des indexes
3. Pour chaque collection, estimer un nombre
de documents
4. Appliquer un coefficient multiplicateur
(un conseil, à cacher le dans une formule d’excel)
Extraire les statistiques
> show collections
> count docs
> count index
> db.macollection.count()
> db.macollection.stats(1024*1024)
Le working set
● Portion des données utilisée fréquemment
● Attention aux batchs réalisant une lecture
complète d’une collection
● Idéalement la taille du working set doit
correspondre à la taille de la RAM
Taille sur disque d’une collection
Taille moyenne
d’un document
Taille data=*
Nb de
doc.
Taille moyenne
des indexes
Taille
indexes=*
Nb de
doc.
=
Taille sur
disque
+
Taille du working-set d’une collection
Taille data=*
Nb de
doc.
Taille
indexes=*
Nb de
doc.
=
Taille
working-set
+
Proportion
data utile
*
Taille moyenne
d’un document
Taille moyenne
des indexes
Mémoire nécessaire
Somme des working-set
de chaque collections
+
100 MB pour les binaires
+
1 MB par connexion
=
Mémoire nécessaire
CPU
● 4 vCPU pour un moteur NMAP
● 8 vCPU voir plus pour un moteur wiredtiger
http://lamada.eu/blog/2016/04/26/mongodb-how-to-perform-
sizing-capacity-planning/
Quelques banalitées
● Sans réseau correct point de salut
● Toujours un nombre impair de membres
● Le journal n’est plus nécessaire
Configurer un replica-set
Rien de plus simple !
● Sur chacun de vos serveurs:
● Connexion sur un des nodes:
$ mongod --dbpath /data/mongo --replSet web2day_rs
$ mongo --host mongod-node1
MongoDB shell version: 3.2.4
connecting to: mongod-node1:27017/test
>
> rs.initiate({_id:"web2day_rs", members:[
{_id:0,host:"mongod-node1:27017"},
{_id:1,host:"mongod-node2:27017"}]})
{"ok":1}
web2day_rs:PRIMARY>
Read concern >= 3.2
PRIMARY
SECONDARYSECONDARY
T0 : Demande ecriture avec w : majority
OK
KO KO
T1 : Lecture sans read concern
T2 : Annulation écriture
T1 : Lecture sans read concern
d’une donnée incorrecte
Read concern >= 3.2
Pour se prémunire de ces cas de lecture non
intègre
Attention non appliqué aux findAndModify
readConcern: { level: <"majority"|"local"> }
https://docs.mongodb.com/manual/reference/read-concern/
Lister si possible l’ensemble des noeuds du
replica-set pour assurer la reconnexion des
clients au cluster
Attention aux param. de vos clients
mongodb://node1:27017
// Connexion au cluster impossible si node 1 down
mongodb://node1:27017,node2:27017,node3:27017
// Connexion possible malgré un noeud absent
Node 2
(Primary)
Node 3Node 1
Storage engine
MongoDB s’adapte aux différents use cases en
fournissant plusieurs moteurs de stockage
● MMAPv1: présent depuis le début
● WiredTiger : depuis la 3.0 et par défaut à partir de la 3.2
● In memory ( 3.2.6-rc0 )
● Autres moteurs (RocksDB, Tokumx)
Pour un noeud donné, on ne peut pas changer de
type, mais on peut avoir des nodes avec diff.
engines
Mettez un tigre dans votre moteur
Super performant en écriture !
➔ Lock au niveau du document (et plus au niveau de la
collection)
Gain de place:
➔ Support de la compression pour les collections, les
index, les journaux
○ Snappy: par défaut pour les docs et les journaux
■ 70% de taux de compression, peu de surcoût en CPU
○ Zlib: meilleure compression (mais surcoût de CPU)
Sécurité:
➔ Support du chiffrement au niveau du stockage
(uniquement pour MongoDB enterprise)
Stockage : dans la vraie vie...
30 millions de docs (10 ko chaque), 20 index
Il faut
tester
Et pour finir, soyons joueur !
● Plusieurs types peuvent co-exister au sein d’
un même replica set
● On peut imaginer:
○ Un node pour les lectures => MMAPv1
○ Un node pour les écritures => WiredTiger
Node 1 avec
WiredTiger
Node 2 avec
MMAPv1
Data
ingestion
Data Read
Replica set
Testez en environnement cible
● Update et remove doivent inclure la clé de
sharding
● La lecture sur les secondaires est à éviter
tant que possible (orphans)
● Limites sur l’aggrégation
○ pas de join depuis une collection shardée
○ optimisation largement dépendante des données
Si jamais vous êtes encore motivés
● Sans réseau correct point de salut
● Colocaliser les MongoS avec vos
applications
● N’oubliez pas de sauvegarder les Config
Server (pas de reconstruction possible
simplement)
● Faites vérifier votre architecture par un ami
Quand sharder ?
● Quand ce n’est pas votre premier projet
MongoDB en production
● Quand vous pouvez acheter quelques
machines de plus (à minima triplement du
nombre de machine)
● Quand il n’est pas possible de faire
autrement
○ Envisager d’abord la scalabilité verticale
○ Monitorer la taille de votre working set pour
anticiper le plus possible cette opération
Les points d’attention
● Privilegier autant que possible le sharding
hashé
● Choisissez bien vos clées de sharding
● Surveiller les plages de lancement du
balancer
● Nettoyer régulièrement les orphans
● Ne jamais lire sur un secondary si l’
exactitude de la donnée est critique
https://docs.mongodb.org/manual/reference/command/cleanupOrphaned/
https://www.kchodorow.com/blog/2011/01/04/how-to-choose-a-shard-key-the-card-
game/
La check list
● Activer l’authentification et ne configurer
que les rôles nécessaires
● Limiter la surface d’attaque réseau et
d'exposer pas votre base hors d’une zone
réseau dédiée
● Lancer mongodb avec un user system dédié
● Préférer si possible l’installation via un
paquet plûtot que la tarball
● Si besoin activer le cryptage de la base et
autres options avancées
https://docs.mongodb.com/manual/administration/security-checklist/
https://s3.amazonaws.com/info-mongodb-com/MongoD....
Le minimum à superviser
● Espace libre
● Nombre de connexions réseau
● Nombre de fichiers ouverts
● Mémoire utilisée
Dans le cas d’un replica-set :
● Statut dans le replica-set
● Présence de fichiers dans le répertoire
rollbacks
Les solutions
● Zabbix
○ Set de graph et d’alarmes préconfigurées
○ Il faudra mettre les mains dans PHP
● Nagios
○ Nombreux check à ajouter
○ Accompagné du plugin CACTI il est aisé de se
construire une métrologie
○ Il faudra mettre les mains dans Python
● Votre script dans votre langage de
prédilection
https://github.com/nightw/mikoomi-zabbix-mongodb-
monitoring
● Quelques scripts inspirés de nixCraft - http:
//www.cyberciti.biz/ pour la supervision
système
● Mongotop et Monstat alimentant un fichier
● Des scripts englobants les commandes
mongo :
○ rs.status()
○ db.stats()
○ collections.stats()
La solution à pas cher
MongoDump / MongoRestore
● Tool de Mongo permettant la sauvegarde
● Dans le cas d’un replica set utiliser l’option --
oplog à la sauvegarde et la restauration
pour backuper un maximum d’
enregistrement
● Il est nécessaire d’avoir la place disponible
et peut prendre beaucoup de temps
Copie des fichiers
● Il est possible de backuper par copie des
fichiers
● Dans le cas de MMAP il est indispensable d’
activer le journal et que celui ci soit présent
sur le même volume que les données
● Dans le cas d’un replica set préférer la
sauvegarde sur le primary ou alors s’assurer
que le secondary source de la sauvegarde n’
est pas en retard
● Si vous pouvez vous le permettre préférez
stopper l’instance
Faire son propre “Point in time …”
● Si jamais vous avez un oplog couvrant
l'intervalle de temps entre une sauvegarde
et le moment auquel vous pouvez restaurer
il est possible de ne pas perdre de données
● Commencer par isoler un membre puis
sauvegarder l’oplog
● http://www.codepimp.org/2014/08/replay-
the-oplog-in-mongodb/
● A tester absolument dans un
environnement non cible la première fois
MongoDB Cloud Manager Backup
● De loin la solution la plus simple mais
nécessite une souscription
● Fonctionne par lecture de l’oplog en
continue
● Permet le point in time recovery
● Disponible dans le cloud ou dans votre
réseau
● Si auto-herbergé la machine de backup doit
être aussi puissante avec encore plus de
stockage que vos autres machines
Solution idéale
● Un backup idéal est un backup testé
● Pour des petits volumes MongoDump est
suffisant
● Les outils de MongoDB corp sont les plus
simple à utiliser
● Un bon schéma doit répondre :
○ Aux besoins métier
○ Aux exigences de performance
● Pour bien connaitre le besoin, il faut le
développer et le tester
● Pour savoir si l’application répond aux
exigences de performance, il faut la tester
● Les tests doivent commencer avec le
développement
● Pour tester rapidement, il faut déployer
rapidement
En résumé
Les liens à connaitre
Se former :
● https://university.mongodb.com/
● https://www.mongodb.com/presentations/all
Les indispensables :
● https://docs.mongodb.
org/manual/administration/production-checklist/
● https://docs.mongodb.org/manual/faq/diagnostics/
Pour compléter :
● http://dba.stackexchange.
com/questions/121160/mongodb-mmapv1-vs-
wiredtiger-storage-engines
● https://objectrocket.com/blog/company/mongodb-
wiredtiger
● http://zanon.io/posts/mongodb-storage-engine-mmap-
or-wiredtiger
mongo-connector
● Ecrit et maintenu par MongoDB en python
● Permet de copier depuis MongoDB vers :
○ ElasticSearch
○ Solr
○ Mongodb
○ Postgresql
● Peut permettre de synchroniser une base de
préproduction avec la production simplement
● Idéal pour des duplications sans
transformation
https://github.com/mongodb-labs/mongo-connector
Apache Camel & Groovy
def ds = new OracleDataSource(... )
SimpleRegistry registry = new SimpleRegistry();
registry.put("myDataSource", ds);
def camelContext = new DefaultCamelContext(registry)
camelContext.addRoutes(new RouteBuilder() {
def void configure() {
from("direct:EXTRACT_SQL")
.setHeader("table", constant("CONFERENCES"))
.setBody().constant("select * where (CONFERENCES.DATMAJ > sysdate -
2 )")
.to("jdbc:myDataSource?
outputType=StreamList&useHeadersAsParameters=true")
.split(body()).streaming().process(confProcessor) .to("mongodb:
myDb?database=mymongo_databse&collection=conferences&operation=save")
.end()
}
})
camelContext.start()
// send a message to the route
ProducerTemplate template = camelContext.createProducerTemplate();
template.sendBody("direct:EXTRACT_SQL", "Starting migration");
camelContext.stop()