Yann Moisan

Scala, Web, Linux…

Retour d'expérience sur MongoDB

MongoDB est une base de données NoSQL, très facile à prendre en main.

Comme toujours, il existe quelques subtilités que l'on rencontre avec les vrais projets de la vraie vie. Le but de ce billet est de vous en présenter quelques unes :

Les index pour les requêtes de mise à jour

Généralement, nous pensons naturellement aux index pour les requêtes en lecture. Les requêtes de mise à jour ont la syntaxe suivante :

db.collection.update(
  <query>,
  <update>,
  {
    upsert: <Boolean>,
    multi: <Boolean>,
  }
)

Et bien, ces requêtes peuvent aussi tirer parti d'un index, qui sera utilisé pour la requête query.

Recherche sur un sous-document

MongoDB supporte les documents imbriqués. Considérons le document suivant {parent: {child1: 1, child2: 2}}
Cette requête renvoie bien le document ci-dessus :

db.test.find({"parent.child1": 1})

Par contre, cette requête ne renvoie pas le document ci-dessus :

db.test.find({"parent": {"child1": 1}})

Seule la notation avec le point permet de spécifier une correspondance partielle sur un sous-document. Dans le deuxième cas, MongoDB cherche une correspondance sur l'intégralité du sous-document.

Aggregation avec des $match successifs

Nous utilisons un index sur a et b : {"a": 1, "b": 1} Avec le framework d'aggregation, si nous utilisons 2 $match dans le pipeline, la requête est lente.

{$match:{"a": xxx}}, {$match:{"b": xxx}}
Si nous utilisons un seul match, la requête est rapide :
{$match:{"a": xxx, "b": xxx}}

Ces deux requêtes sont équivalentes mais dans le premier cas, l'index ne sert pas à filtrer sur b.

Aggregation et performance

MongoDB offre une méthode explain sur un curseur pour obtenir des informations sur l'exécution de la requête : temps d'exécution, index utilisé, … Malheureusement, cette méthode n'existe pas pour le framework d'aggregation. A en croire cette issue, cela sera disponible en 2.6.

Ne pas utiliser use dans un script

Dans mon premier script, j'ai utilisé l'instruction use, comme j'ai l'habitude de le faire dans la console. Et là, c'est le drame : le client renvoie une erreur peu explicite :

Thu Oct 17 10:29:21.451 SyntaxError: Unexpected identifier at test.js

J'ai mis un peu de temps à comprendre que use est un helper fourni par la console, que ce n'est pas une instruction JavaScript valide, et donc que ça ne fonctionne pas dans un script. La liste exhaustive des helpers avec leurs l'équivalents js est décrite sur le site (cf source).

Ecrire un script bloquant

Par défaut, les opérations d'écriture sont asynchrones dans un script (ce qui est le comportement opposé à celui des drivers). Le script va donc rendre la main alors que le traitement tourne encore en base. Pour attendre la fin, il suffit d'ajout db.getLastError() à la fin du script.

Passer des paramètres à un script

Nous pouvons exécuter un fichier JavaScript en utilisant mongo shell sur le serveur. Cela nous permet notamment de scripter la création des index. Nous englobons cela dans un script shell et il est parfois utile de passer des paramètres du script shell au JavaScript. Une petite astuce, non présente dans la documentation, est nécessaire. Voici le script shell : monscript.sh

mongo --eval "var mavar='$1'" monscript.js

Et voici le script.js

printjson(mavar);

L'idée est d'utiliser l'option --eval, qui permet de passer au shell un bout de JavaScript, pour déclarer une variable globale qui sera accessible dans le script.

Source: