TP MongoDB
Note du 08/02/2022 Ce sujet de TP est actuellement en réécriture d'une ancienne version en anglais vers une version mise à jour en français.
Bart.Lamiroy@univ-reims.fr
Original credits: S. Vialle, CentraleSupélec
Exercice 1 : Démarrer une session MongoDB et interagir avec des données
Il existe plusieurs façons d'utiliser MongoDB, les deux principales sont
- créer un compte sur www.mongodb.com et utiliser l'infrastructure cloud mise à disposition ;
- installer la version community de Mongodb sur son équipement personnel.
L'avantage de la première est qu'il n'y a aucune contrainte technique d'installation. En revanche la version gratuite est limitée à 512 Mo de stockage. La seconde offre plus de flexibilité au coût d'un peu plus de technicité lors de l'installation et des limites matérielles de votre équipement.
Dans les deux cas, il y aura besoin d'un serveur (distant dans le premier cas, local dans le second) qui héberge les données et qui fait les calculs, et un ou plusieurs clients (forcément locaux) permettant de se connecter et d'interagir avec le serveur.
Il existe plusieurs types de clients pouvant interagir avec le serveur :
- une interface web, accessible depuis www.mongodb.com elle a l'avantage de ne nécessiter aucune installation sur le poste client, tout se passe par un navigateur classique. L'inconvénient est une réactivité dépendante de la charge du serveur web côté serveur et le fait que toutes les fonctionnalités ne sont pas toujours accessibles (notamment le téléversement de données en masse)
- une application graphique, facile d'utilisation, et très similaire à ce que permet de faire l'iterface web, mais plus réactive et permettant le téléversement en masse : MongoDB-Compass. Elle nécessite l'installation de l'application en local.
- une série d'interfaces textuelles en mode console. Elles sont plus légères et réactives, permettent une plus large gamme
d'intéractions avec le serveur, mais nécessitent une maitrise des langages et leur syntaxe sous-jacente.
Dans ce TP nous utiliserons
mongoshet éventuellementmongoimportetmongod.
Dans ce qui suit nous supposons que le client fonctionne sous Windows. Les lecteurs concernés ajusteront les instructions pour Linux ou MacOS.
Nous supposerons également un fonctionnement par interface en ligne de commande. Les interfaces graphiques permettent généralement d'exprimer la plupart des instructions abordées ici de façon plus intuitive aussi.
Exercice 2 : utilisation d'une base de données préconfigurée
Informations préliminaires
Dans cette partie nous allons utiliser une base préconfigurée sur mongodb.com en y accédant par mongosh.
mongosh
Pour se connecter à la base de données préconfigurée utilisez le lien fourni par mongoDB pour se connecter à Compass. Cet URL doit ressembler à
mongodb+srv://<username>:<password>@cluster0.rv6yxw8.mongodb.net/test
Vous utiliserez les username et password fournis en séance.
Pour tester si vous êtes bien connectés à la bonne base, les commandes suivantes doivent fournir les résultats fournis ci-dessous
> show dbs
Master_SEP 780.00 KiB
sample_airbnb 52.97 MiB
sample_analytics 9.11 MiB
sample_geospatial 1.37 MiB
sample_guides 40.00 KiB
sample_mflix 48.81 MiB
sample_restaurants 8.71 MiB
sample_supplies 2.02 MiB
sample_training 53.70 MiB
sample_weatherdata 2.83 MiB
admin 336.00 KiB
local 3.77 GiB
> use Master_SEP
switched to db Master_SEP
Interrogations sur la base
La commande pour interroger une collection MongoDB est find(). Sa syntaxe et son fonctionnement sont fournis dans
le cours. Vous pourrez également vous référer à la documentation en ligne
pour une utilisation plus avancée.
La collection de données utilisée ici s'appelle est esr_stat et provient des données ouvertes publiées par le Ministère de
l'Enseignement Supérieur et de la Recherche téléchargéables ici.
Si vous travaillez sur votre propre instance, importez cette collection. Elle est déjà présente dans la version partagée en ligne.
Si ce n'est pas déjà fait, sélectionnez la base de données sur laquelle vous souhaitez travailler.
> use Master_SEP
Les requêtes prendront la forme de db.myCollection.find() où myCollection est le nom de la collection (ici esr_stat)
Question 1 : Explorer les collections et leur contenu
- Listez toutes les collections disponibles dans la base de données courante avec la commande
show collections - Visualisez le contenu d'un enregistrement quelconque (document JSON) avec la commande
findOne()appliquée à la collection. - Émettez une requête permettant de voir l'ensemble des contenus de la collection avec
db.esr_stat.find().
On observe que sans arguments, la commande find() n'opère aucune sélection et affiche l'ensemble des attributs de chaque document de l'ensemble de la collection !
Question 2 : Requêtes simples sur la collection esr_stat
En vous basant sur la documentation en ligne de MongoDB, réalisez les opérations suivantes :
- L'un des champs de chaque enregistrement est en réalité la description d'une image.
Utilisez
find()de façon appropriée pour projeter le champpublication_image_fichierde tous les enregistrements de la collectionesr_stat. - Affichez maintenant seulement les hauteurs et largeurs d'images de type PNG.
- Triez ces enregistrements selon l'ordre croissant de la hauteur des images.
-
Raffinez les enregistrements obtenus en ne retenant seulement les images PNG dont la hauteur est contenue dans l'intervalle ] 735; 800 [.
Attention : le test
(if height >735) and (if height <800)ne fonctionnera pas s'il est réalisé avec deux tests séparés et associés avec l'opérateur","comme premier argument defind({...}, {...})! Il faut utiliser la syntaxe appropriée. - Comptez le nombre d'enregistrements satisfaisant la requête précédente.
Question 3 : Requêtes et modifications de la base
ATTENTION : Cette question ne pourra être traitée correctement que si vous travaillez avec un accès (hébergé ou local) ayant des droits d'écriture/modification. Si vous utilisez l'accès générique avec le compte
etudiantsur la base hébergée partagée il ne vous sera pas possible de faire cette partie.
Nous utiliserons la fonction forEach()qui peut être appliquée aux documents résultant d'une requête find().
> db.myCollection.find().forEach(<function>)
La fonction passée en argument de la méthode forEach() peut soit être complètement
définie et décrite entre les parenthèses du forEach(), soit être une fonction définie au
préalable dans le shell de MongoDB.
Par ailleurs, la requête update, associée à l'opértateur $set, permet de changer les champs d'un ou plusieurs
enregistrements sans modifier les autres. On peut ainsi soit en ajouter, soit en modifier le contenu.
> db.myCollection.update({_id:xxxx},{$set:{theField:theValue}})
-
À partir d'une requête
find ()et la méthodeforEach(): calculez et affichez le nombre de pixels pour chaque enregistrement décrivant une image PNG.Vous écrirez la fonction appelée par
forEach()en JavaScript.e.g.:
function f(doc) {
var x = doc.a + doc.b;
print ("ab =" + x);
}
- Changez la fonction précédemment appelée dans
forEach()à ce qu'elle affiche également le champ_idde l'objet avant d'afficher le nombre de pixels. - En utilisant l'
_iddu dernier enregistrement affiché par la commande précédente, ajoutez un nouveau champnbpixà la description de l'image PNG en utilisant la requêteupdateassociée à l'opérateur$set. - Modifiez la requête
forEachde sorte à non seulement afficher le nombre de pixels pour chaque image PNG mais que la fonction évoquée insère également un champnbpixdans chaque description. - En utilisant une requête
find()et la méthodesort(), triez tous les enregistrements par ordre croissant de la taille des images PNG (exprimée en pixels).
Exercice 3 : mapReduce dans MongoDB
Évolution de la prise en charge dans les dernières versions
L'accès à et l'utilisation de mapReduce sont désormais considérés comme obsolètes dans MongoDB et l'utilisation des aggrégations leur est préférée.
Dans ce qui suit, nous continuerons à utiliser les formulations sous forme de mapReduce, sachant que des méthodes de conversion sont mises en place pour les transformer en pipeline d'aggrégation. Dans la suite vous pourrez répondre aux questions invariablement soit en fournissant une solution mapReduce, soit une aggrégation.
Notez que l'utilisation de mapReduce est bloquée sur la version gratuite hébergée et ne peut donc qu'être utilisée sur une version hébergée payante ou sur une version installée localement.
Rappel : l'implantation MongoDB mapReduce est différente de celle de Hadoop. Elle est moins générique et permet moins d'optimisations, mais la différence fondamentale concerne la phase de reduce, dans la mesure où les fonctions map et reduce doivent partager le meme format de sortie
<key, value>. Ceci permet notamment d'éviter des débordements de mémoire, mais introduit des restrictions sur les tâches qui peuvent être réalisées.
Question 1 : Import a larger collection
Pour cet exercice nous tenterons d'utiliser une collection de données plus large, partiellement pour tenter de montrer les limites de l'exécution sur une seule machine et d'illustrer les besoins d'un passage à l'échelle horizontal. Cette collection de données, stockée au format JSON prend approximativement 8 Go, et occupera environ 1.5 Go une fois transformée en BSON (Binary JSON) et stockée dans MongoDB.
Ceci signifie que cette base de données ne peut pas être hébergée sur l'offre gratuite sur mongodb.com, puisque l'offre de stockage y est limitée à 512 Mo
Les options pour faire cette partie sont alors :
- héberger les données sur une installation locale ;
- utiliser une version tronquée sur l'offre d'hébergement gratuite ;
- utiliser une solution d'hébergement payante.
En utilisant la même base de données que précédemment (Master_SEP) téléchargez les données
en-ESR-located-workforce-of-registered-students-ESR-public.json et importez-les dans la collectionenseignement
Remarques:
- Il s'agit à nouveau d'un jsonArray.
- Compte-tenu de la taille de la collection, l'importation peut prendre plusieurs dizaines de minutes.
Cette collection contient des données sur les inscriptions d'étudiants dans le système d'enseignement supérieur français.
Question 2: Compter le nombre de baccalauréats pour chaque type
Quickly visualize one of the recordings of the enseignement
collection. One of the fields called bac (near the end of
each record) encodes each type of baccalaureate by a character string
containing a single integer.
Write a mapReduce program (or alternatively a aggregate pipeline) that counts the number of occurrences of
each baccalaureate type:
- Write a
mapfunction and areducefunction.
Reminder of the syntax of a map function for mapReduce in MongoDB:
map2 = function () {
var k = ...;
var v = ...;
emit (k, v);
}
Reminder of the syntax of a reduce function for mapReduce in
MongoDB:
reduce2 = function (k, Array_of_v) {
...;
return (result); // 'result' of same type as 'v'
}
- Run a mapReduce according to the 2 possible syntaxes
> db.runCommand({"mapreduce": "enseignement", "map": map2, "reduce": reduce2, "out": "res2a"})
And
> db.enseignement.mapReduce (map2, reduce2,{"out": "res2b"})
The results (the res2a and res2b collections) must be
identical. You can view their contents with the find() command.
In both cases, the mapReduce operation returns a quick report of its executions, and indicates in particular its execution time. The execution time is usually longer for the first mapReduce that requires loading more data in RAM.
Question 3: trier des données et l'impact des index
The MongoDB mapReduce allows sorting/ordering the input data (the
collection enseignement in our case) with the option sort
(specifying the attribute and the sorting order):
e.g.
db.myCollection.mapReduce(map, reduce,{"out":"results", "sort": {"abcd": -1}, ...})
See the online MongoDB mapReduce documentation for more information on the option
sort .
- Re-run your mapReduce by sorting the input data according to the
field
bacin ascending order, then in descending order.
What "problem" appears?
You can make sort work more efficiently by creating an index on
the sorted field (bac). This accelerates the treatments
and avoids the previous problem.
- Create an index on this collection and on the
bacthe increasing order.
Use: db.myCollection.createIndex (...).
- Re-run your mapReduce by sorting the input data according to the
field
bacin ascending order, then in descending order. - Did the problem that appeared previously disappear? Both in ascending and descending order?
Question 4: limitation des données d'entrée
In addition to sorting the input data, we can limit their number with
the limit option of mapReduce. This makes it possible to test
an algorithm, a command on a subset of the data and not to have to wait
for too long.
- Use the
sortandlimitoptions together, and limit the number of entries to 1000. - Are the results identical with sorting in ascending and descending order? Why ?
Question 5: reformatage des résultats
The MongoDB mapReduce requires that the key-output pairs of the
reducer are the same as those of the map output. However, a
function finalize(k, v) makes it possible to reformat outputs of
the reduce phase.
-
Implement a function
finalize (k, v) {newVal = ...; return (newVal);}in order to generate outputs in the format{"Bin": bin code, "number": number} -
Run your mapReduce specifying to execute your
finalize()function - View the results with the
find()command
Question 6: adjointre une requête aux données d'entrée
It is possible to specify a query on the input data as to apply simple filtering before entering the map phase.
- Extend the execution of your mapReduce with a query that retains
only those records whose
dep_etab_libis"Marne". - Rerun your mapReduce and view the result
Exercice 4: identifier toutes les valeurs possibles d'un champ
The field dep_etab_lib can take multiple different
values, difficult to predict because of the accentuation of certain
characters, but in finite numbers (according to the nature of the
values). We wish to get a list of all the possible values of this field,
by presenting in the end each
value once and only once.
Question 1: code mapReduce
Write and run a mapReduce program to list once and only once each
existing value from dep_etab_lib. Which pattern will you
use?
Question 2: compter les résultats
Use the count() method to count the number of responses of the
result.
Exercice 5: opérer une jointure entre deux collections avec l'opérateur $lookup
NoSQL databases remain weak for joining. It is always assumed that the join must be done at the time of writing, meaning that we must put in a collection "all that is necessary" to be able to query it without needing other data.
There are still solutions to execute the necessary joins, but they are expensive in processing time. Hadoop proposes a Map-Reduce on two flows of entries (two distributed files). However, MongoDB limits its mapReduce to a single collection, which prevents it from joining on two collections.
MongoDB allows "joining" accross two collections with the
$lookup operator in an aggregation .
Question 1: Generation of Two Specialized Tables
From the larger collection enseignement we start by generating two
simple collections, that we than will "join" together.
-
We want to generate a collection
res51of all documents containing the fields:establishment_lib, the name of the establishmentcursus_lmd, containing "L", "M" or "D"
In fact, we will generate documents / records containing only keys,
composed of these two fields: _id: { "etablissement_lib":
leEtablissementLib, "cursus_lmd": leCursusLMD}
This will allow us to benefit from the deduplication capability of the key management mechanism, and will get as many records as there are different pairs of these two fields.
-
Write and run the small mapReduce program that will produce this collection
res51of two-field key records. -
Repeat the same operations to obtain the
res52collection of recordings of keys with the fields:establishment_libthe name of the establishmentaca_etab_libthe name of the home academy
The goal is to obtain records containing:
`_id: { "etablissement_lib": leEtablissementLib, "aca_etab_lib": laAcademie}
Question 2: effectuer une jointure avec l'opérateur $lookup dans une aggregation
We will now operate a join of the res51 and res52
collections using the shared field etablissement_lib as a join
key, and using the $lookup operator in an aggregate made
especially for this purpose.
- Implement an aggregation that realises this join and stores the
result in the collection
res5j.
db.res51.aggregate ([{"$lookup": {...}},{"$out": "res5j"}]);
Refer to the online MongoDB documentation.
- View the result in the
res5jcollection. The output format should contain all the relevant information, but sometimes redundantly and not very straightforward to use.
Question 3: transformation du résultat de la jointure
- Complete the aggregation with the
$projectand$unwindoperators to obtain a collection output whose records are similar to:
>db.res5j.findOne()
{
"_id": ObjectId ("592d91222bb10509205a4f77"),
"establishment_lib": "Université Aix-Marseille",
"cursus_lmd": "D",
"aca_etab_lib": "Aix-Marseille"
}
- View the result in the new
res5jcollection.