Blog ENI : Toute la veille numérique !
🎃 Jusqu'à -30% sur les livres en ligne, vidéos et e-formations.
Code : GHOST30.
Cliquez ici !
Accès illimité 24h/24 à tous nos livres & vidéos ! 
Découvrez la Bibliothèque Numérique ENI. Cliquez ici
  1. Livres et vidéos
  2. MongoDB
  3. Introduction
Extrait - MongoDB Comprendre et optimiser l'exploitation de vos données (avec exercices et corrigés) (2e édition)
Extraits du livre
MongoDB Comprendre et optimiser l'exploitation de vos données (avec exercices et corrigés) (2e édition) Revenir à la page d'achat du livre

Introduction

Le Big Data et NoSQL

Voilà quelques années que le terme Big Data a fait son apparition dans le vocabulaire courant tant les données se sont imposées au cœur de nos vies ; qu’elles concernent notre dossier médical, notre compte en banque, nos achats en ligne ou bien notre activité quotidienne sur les différents réseaux sociaux, il en transite quotidiennement des volumes gigantesques à travers la planète.

Le Big Data doit répondre au défi de l’analyse et de la valorisation de ces gros volumes de données afin de les rendre utiles et, soyons honnêtes, souvent monétisables : votre parcours client sur un site marchand, les publicités ciblées pouvant déclencher un achat de votre part, les dernières vidéos que vous avez regardées afin de vous en proposer de nouvelles susceptibles de vous plaire, les mots-clés et les sujets tendance (trending topics) sur votre réseau social préféré...

Pour donner un sens à toute cette information, il faut des bases de données capables de stocker et de traiter de manière très rapide la matière première que constituent ces données brutes dont la nature est pour le moins hétéroclite (flux vidéo ou audio, texte brut, données de géolocalisation, images, etc.)....

MongoDB

Lancée sur le marché voilà maintenant quinze ans et actuellement disponible en version 7.0, MongoDB est une base de données dite « orientée document ». Cela signifie que l’unité n’est plus le tuple (ou la ligne) comme c’est le cas dans le modèle relationnel, mais le document. Contrairement à l’enregistrement contenu dans une table d’une base de données relationnelle, le document n’a pas une structure figée et prédictible. Deux documents peuvent très bien contenir les mêmes champs, mais dans un ordre totalement différent, ce qui est inconcevable dans le paradigme relationnel où les tables constituent des sortes de moules dans lesquels sont « coulés » les enregistrements. Dans une table contenant des informations relatives à des personnes, tous les champs doivent contenir une valeur d’un type préalablement défini (ou bien le marqueur NULL, qui indique l’absence de toute valeur) et apparaître dans le même ordre. Dans un document, un champ sans valeur peut tout simplement en être absent, là où dans une table il lui sera obligatoirement affecté le marqueur NULL. Voilà comment on oppose souvent la structure matricielle d’une table dans le paradigme relationnel à l’absence même de structure dans une base de données NoSQL.

Dans une base de données relationnelle, le schéma (ou intention) décrit la structure que vont devoir respecter l’ensemble des tuples (ou lignes) présents dans les tables qui le composent. Dans une base de données orientée document, il peut y avoir autant de formats de données qu’il y a de documents. On dit de ces bases de données qu’elles sont schema-less (sans schéma).

Il existe de nombreuses différences entre les univers NoSQL et relationnel....

Les composants de MongoDB

Vous pouvez déployer MongoDB sur une ou plusieurs machines en entreprise, l’installer sur votre ordinateur personnel en version standalone ou bien vous en servir sans rien installer, en utilisant des solutions déployées dans le Cloud, via les datacenters d’Amazon Web Services, de Microsoft Azure ou via Atlas, la solution DBaaS (database as a service) créée par MongoDB.

Les principaux composants de MongoDB sont :

  • mongod, le programme principal. Il gère l’accès aux données et effectue en tâche de fond des opérations de gestion de données.

  • mongos, le routeur qui gère l’acheminement des requêtes en environnement distribué. Lorsque les données sont distribuées sur des shards, c’est ce processus qui gère l’accès à celles-ci.

  • mongosh, le programme d’interaction en ligne de commande avec la base de données que nous désignerons dans ces pages sous le nom de shell. L’interaction avec la base de données se fait au moyen du langage JavaScript, dont il vous faut connaître les rudiments.

À ceux-ci s’ajoutent les composants d’importation et d’exportation du BSON, format binaire dont nous parlerons bientôt :

  • mongodump va créer des fichiers contenant le BSON présent dans une base de données.

  • mongorestore va faire rigoureusement l’inverse, pour restaurer l’information.

  • bsondump vous sera utile pour transformer le BSON en JSON, format de données bien plus facile à lire !

Des utilitaires d’importation et d’exportation de données sont également disponibles :

  • mongoexport exporte les données au format JSON ou CSV.

  • mongoimport sert à importer dans MongoDB des données au format JSON, CSV ou bien générées par mongoexport.

Enfin, des outils de diagnostic sont également disponibles :

  • mongostat et mongotop pour le monitoring des processus liés aux bases de données.

  • mongoreplay pour rejouer des commandes d’une machine...

Architecture générale de MongoDB

1. La mise à l’échelle

Lorsque l’on évoque l’architecture des bases de données, on en vient inévitablement à parler de déploiement, de taux de disponibilité, de réplication, de tolérance aux pannes, de reprise sur incident... et de mise à l’échelle (scaling). Une base de données devant servir de très nombreuses requêtes devra tôt ou tard faire face à la problématique de la mise à l’échelle ; le serveur sur lequel elle réside est très sollicité et nous allons être amenés à devoir augmenter sa capacité de stockage en mémoire RAM, nous allons ajouter des disques durs, des processeurs… Cette augmentation des performances d’un serveur par ajout de composants matériels porte le nom de mise à l’échelle verticale (vertical scaling) ; il faut se la représenter comme une pile sur laquelle on va rajouter des choses (disques, CPU...) pour illustrer cette idée de verticalité. Par opposition à cette verticalité qui atteint vite ses limites (jusqu’où peut-on aller pour gonfler les capacités d’un seul serveur ?), le concept de mise à l’échelle horizontale (horizontal scaling) s’est développé, consistant à distribuer d’énormes volumes de données sur plusieurs instances d’un gestionnaire de bases de données, hébergées chacune sur une machine dédiée. Il n’est plus question d’un serveur monolithique qui enfle jusqu’à atteindre ses limites, mais de plusieurs, dont le nombre devient virtuellement illimité, sur lesquels on répartit les données.

Ce partitionnement des données sur plusieurs instances permettant une mise à l’échelle horizontale porte le nom de sharding dans l’univers MongoDB.

2. Principe du sharding

Le sharding s’effectue au niveau d’une collection. C’est cette collection qui sera distribuée sur plusieurs shards en se basant sur un sous-ensemble des valeurs d’un champ présent dans tous les documents de cette collection et qui servira de clé (shard key). Certaines restrictions s’appliquent à cette clé qui servira de pivot : il ne peut exister qu’une seule shard key par collection et sa taille ne doit pas excéder 512 octets.

Elle ne pourra pas être changée une fois la collection déployée sur les shards, il faudra en extraire les documents pour les sauvegarder, supprimer la collection puis la recréer pour y réinjecter les documents sauvegardés précédemment.

Lorsqu’une collection doit être « shardée » alors qu’elle contient déjà des documents, la clé choisie doit faire l’objet d’un index simple ou bien figurer au tout début d’un index composé.

La clé de sharding idéale permet de distribuer de manière uniforme les documents, sous forme de paquets appelés chunks, sur l’ensemble des shards. Cette distribution peut se faire suivant deux stratégies : la première consiste à hacher les valeurs des clés de sharding et la seconde à diviser les données par intervalles de valeur. C’est cette dernière que nous évoquerons.

Imaginons une collection nommée eleves dans une base de données nommée personnes ; nous allons prendre le champ age contenu dans chaque document de notre collection eleves et nous en servir comme shard key, de sorte que les élèves de moins de 10 ans seront stockés sur le shard S1, ceux dont l’âge est compris entre 10 et 13 ans sur le shard S2 et les autres sur le shard S3.

Pour diriger les requêtes faites sur l’âge des élèves vers le bon shard, MongoDB s’appuie sur le routeur mongos : c’est lui qui sait sur quel shard se trouvent les élèves dont l’âge est demandé par la requête.

MongoDB répartit les tâches d’écriture et de lecture parmi les différents shards constituant une grappe (cluster). Une grappe peut également faire l’objet d’une mise à l’échelle horizontale : l’ajout de nouveaux shards permettra de diminuer la charge de travail en lecture et en écriture de chacun des shards constitutifs de la grappe et d’étendre de manière significative les capacités de stockage de celle-ci.

Lorsqu’un des shards constituant une grappe est indisponible, il ne remet pas en cause la disponibilité des autres membres du cluster : si les données demandées sont disponibles sur un autre shard, elles seront servies !

Si une requête contient la clé de sharding ou bien le début d’une clé de sharding composée, elle sera routée vers le bon shard par mongos. Dans le cas contraire, elle sera émise en direction de l’ensemble des shards, ce qui pénalisera le temps de réponse.

Une base de données peut comporter des collections shardées et non shardées, les deux ne sont pas mutuellement exclusives ! Tandis que les premières seront distribuées sur plusieurs shards, les autres ne résideront que sur un seul.

3. Les replica sets

Les replica sets constituent une composante fondamentale de l’architecture de MongoDB et garantissent la haute disponibilité des données ainsi que leur redondance (c’est-à-dire le fait qu’il existe plusieurs copies des mêmes données réparties sur différents serveurs). Bien que cet ouvrage n’ait pas pour but de faire de vous des administrateurs de bases de données MongoDB, il est important que vous connaissiez le fonctionnement général de cette architecture de réplication des données, car nous l’évoquerons notamment lorsque nous aborderons les transactions.

Un replica set est constitué d’un nœud maître (dit primaire) auquel sont subordonnés plusieurs nœuds esclaves (dits secondaires) et potentiellement un nœud arbitre. Ces nœuds...

La notation JSON

Cette notation en langage JavaScript permet de représenter de façon simple et facilement lisible par un être humain des structures de données parfois assez complexes. L’interface en ligne de commande de MongoDB fait usage de cette notation ainsi que du langage JavaScript, il vous faut donc en maîtriser les bases.

MongoDB stocke une représentation binaire du format JSON dans un format spécialement créé par ses concepteurs : le BSON (pour binary JSON). Lorsque vous interagissez avec la base de données, MongoDB transforme le code JSON que vous lui fournissez en BSON, comme il transformera le BSON qu’il a stocké en JSON avant de vous le présenter. La taille maximale d’un document au format BSON est fixée à 16 Mo, il est cependant rare que la taille d’un document excède cette limite (à titre de comparaison, la version en texte brut du roman Guerre et Paix de Tolstoï pèse un tout petit peu plus de 3 mégaoctets... et le livre fait plus de 1 200 pages !).

Voici comment est représenté un objet vide en JSON :

{} 

Les propriétés d’un objet JSON sont matérialisées par une ou plusieurs paires clé : valeur, séparées par une virgule :

{"clé": "valeur", "autre_clé": "autre_valeur"} 

Une valeur peut à son tour être un objet :

{"clé": {"autre_clé": "valeur"}} 

Créons un objet qui représente une personne et donnons-lui des attributs de base :

{"nom": "Eric", "prenom": "Dupont"} 

Nous avons ici créé un objet avec deux propriétés (ou attributs) formées d’une clé (« nom » par exemple) que l’on associe à une valeur (« Eric »). Cette paire clé/valeur identifie un attribut de l’objet....

Les types de données

Les types de données pris en charge de façon native par JSON sont au nombre de six :

  • booléen

  • numérique

  • chaîne de caractères

  • tableau

  • objet

  • null (le marqueur d’absence de valeur)

À ces types prédéfinis dans JSON, MongoDB vient ajouter les siens :

  • Le type Date : stocké sous la forme d’un entier signé de 8 octets représentant le nombre de secondes écoulées depuis l’époque Unix (01/01/1970 à minuit). 

  • Le type ObjectId : stocké sur 12 octets, ce type est utilisé en interne pour garantir l’unicité des identifiants générés par la base de données, son importance est donc capitale !

  • Les types entiers Long et Int32 : ces types servent à représenter des nombres entiers signés dont la représentation interne se fait respectivement sur 8 et 4 octets.

  • Le type flottant Decimal128 : codé sur 16 octets, ce type décimal d’une grande précision est en général privilégié pour des applications effectuant des calculs mathématiques requérant une très grande exactitude.

  • Le type BinData : pour stocker des chaînes de caractères ne pouvant pas être représentées en codage UTF-8 ou n’importe quel contenu binaire...

MongoDB en ligne de commande

1. Démarrer et arrêter MongoDB

Pour démarrer MongoDB depuis un terminal, il suffit d’exécuter mongod (ou mongod.exe si vous utilisez Microsoft Windows). Cet exécutable a de nombreuses options et nous ne verrons que les plus importantes. Sachez toutefois que vous pouvez très bien l’utiliser sans aucune option, il vous suffira d’exécuter cette commande :

mongod 

Quelques avertissements s’affichent à l’écran avec de nombreuses informations qu’il n’est pas utile d’évoquer à ce stade. Voyons quelques-unes des options de cet exécutable :

  • port sert à désigner le numéro du port sur lequel le serveur écoutera. Par défaut, la valeur de ce numéro de port est 27017. La commande que vous avez exécutée plus haut équivaut donc à :

mongod --port 27017 
  • dpath est l’option qui sert à cibler le répertoire contenant les données. De la même manière qu’une instance donnée doit cibler un numéro de port unique (si vous exécutez deux mongod, il vous faudra utiliser deux numéros de ports distincts, par exemple 27017 et 27018), elle doit aussi pointer vers un répertoire de données unique. Sous GNU/Linux, ce répertoire a pour valeur par défaut /data/db. Ce qui signifie que la toute première commande, sans aucune option, équivaut en réalité à :

mongod --port 27017 --dbpath /data/db 
  • logpath est utilisé pour rediriger toute sortie en provenance de mongod vers un fichier journal situé dans un répertoire de votre choix (prenez soin de vérifier que vous possédez les droits d’écriture dans le répertoire que vous mentionnerez). Comme ce fichier journal est écrasé à chaque démarrage de mongod, nous signifierons, en ajoutant l’option logappend, que nous voulons ajouter nos nouveaux logs aux logs existants et non les remplacer.

    Voici la commande qui permet de réaliser ceci :

mongod --port 27017 --dbpath /data/db --logpath /tmp/mongodb.log 
--logappend 

Pour arrêter MongoDB sous GNU/Linux, plusieurs possibilités s’offrent à vous : la simple combinaison des touches Crtl+C, l’arrêt du service avec la commande sudo service mongodb stop, si vous avez installé MongoDB via un gestionnaire de paquetages, ou bien l’exécution de la commande suivante dans une nouvelle fenêtre de terminal :

mongosh --eval "db.getSiblingDB('admin').shutdownServer()" 

Même si toutes se valent, les deux dernières sont les meilleures façons d’arrêter proprement le serveur et doivent être préférées à la première façon de faire, qu’on peut toutefois utiliser lorsqu’on développe sur sa propre machine.

2. Se connecter à la base de données via le shell

MongoDB propose un outil d’interaction en ligne de commande avec la base de données : le shell. Il contient un interpréteur JavaScript complet, ce qui vous permettra notamment d’écrire de petits scripts pour manipuler les données. Une fois que mongod s’exécute en tâche de fond sur votre machine, il vous suffit de vous placer dans le répertoire stockant les fichiers exécutables et d’exécuter la commande suivante pour vous connecter à votre base de données :

mongosh 

Par défaut, vous serez connecté sur le port 27017 de la machine hôte. Pour attribuer une valeur différente au port, il faut simplement ajouter l’option port suivie du numéro de port ciblé, exactement comme vous l’avez déjà fait avec l’exécutable mongod. Si votre mongod écoute sur le port 27777, votre shell s’y connectera avec la commande :

mongosh --port 27777 

Vous pouvez également vous connecter au port de votre choix sur une machine hôte distante en utilisant l’option host :

mongosh --host mongo.monsite.fr --port 27777 

Lors de la connexion, vous voyez s’afficher trois informations importantes :

  • le numéro de version du shell ;

  • la machine hôte sur laquelle vous êtes connecté ;

  • un identifiant de connexion du shell MongoDB ;

  • le numéro de version du serveur de bases de données MongoDB.

Maintenant que vous êtes connecté, vous pouvez afficher le nom des bases de données existantes grâce à ces deux commandes, qui sont équivalentes :

show databases 
 
show dbs 

Si vous avez déjà utilisé le système de gestion de bases de données relationnelles MySQL, show databases va sûrement vous rappeler des souvenirs !

Lorsque vous venez d’installer MongoDB, vous avez trois bases de données à votre disposition :

admin 
config 
local 

Ces bases de données servent à gérer les rôles et autorisations, la gestion interne de MongoDB ou des informations sur le démarrage de vos instances. À de très rares exceptions près, vous n’aurez pas l’occasion d’y faire quelque modification que ce soit, ce sont généralement les administrateurs qui y effectuent des opérations.

Pour savoir sur quelle base de données vous êtes connecté, il vous faut taper :

db 

Par défaut, vous êtes connecté sur la base de données nommée test.

Pour lister l’ensemble des collections et des vues présentes dans une base de données, la commande est identique à celle qui affiche le nom des bases de données :

show collections 

Enfin, pour changer de base de données, vous disposez de la commande use, elle aussi utilisée dans MySQL. Si l’une de vos bases de données se nomme madb, vous vous y connecterez en exécutant la commande :

use madb 

Contrairement à MySQL, vous pouvez très bien cibler avec use une base de données qui n’existe pas, aucune erreur ne se produira, elle attendra sagement que vous y créiez des collections !

Se connecter à MongoDB en utilisant le contrôle d’accès

Si vous avez eu la curiosité de regarder la sortie générée par l’exécution de mongod sans aucune option, ou bien d’aller dans le fichier journal /tmp/mongodb.log si vous avez choisi d’utiliser logpath, vous avez sans doute aperçu cet avertissement :

{"t":{"$date":"2024-02-21T10:33:13.920+01:00"},"s":"W", "c":"CONTROL", 
"id":22120, "ctx":"initandlisten","msg":"Access control is not enabled for 
the database. Read and write access to data and configuration is 
unrestricted","tags":["startupWarnings"]} 

Ceci signifie que vous n’avez actuellement aucun contrôle d’accès à votre base de données et que n’importe quel utilisateur peut s’y connecter... Ce n’est pas très rassurant, n’est-ce pas ? Qu’à cela ne tienne, nous allons y remédier de suite !

Tout d’abord, nous allons nous connecter à la base de données admin dont nous avons parlé un peu plus haut et nous allons ensuite créer un utilisateur nommé mongosensei auquel nous attribuerons deux rôles : userAdminAnyDatabase appliqué à la base de données admin et readWriteAnyDatabase qui lui permettra de lire et d’écrire dans toute base de données qui n’est ni local ni config.

Voilà la commande de création de cet utilisateur, qui nous servira dans d’autres exemples à venir, il faut d’abord se connecter...

Gestion des collections

1. Les collations

Les collations servent à définir des règles pour la comparaison des chaînes de caractères, notamment en ce qui concerne l’accentuation des caractères ou leur casse dans un langage donné. Les collations peuvent être utilisées avec les collections, les vues ou encore les index.

Voici la forme d’un document contenant les informations liées à une collation :

{ 
  locale: < chaîne de caractères >, 
  caseLevel: < booléen >, 
  caseFirst: < chaîne de caractères >, 
  strength: < entier >, 
  numericOrdering: < booléen >, 
  alternate: < chaîne de caractères >, 
  maxVariable: < chaîne de caractères >, 
  backwards: < booléen > 
} 

Dans ce document, seul le champ locale est obligatoire ; il contient le langage utilisé, noté au format défini par l’ICU (International Components for Unicode). En ce qui concerne la langue de Molière, nous avons à notre disposition deux valeurs pour locale : fr évidemment et fr_CA pour le français utilisé par nos cousins de la belle Province.

Voyons les autres champs du document décrivant les informations de collation :

  • strength représente le niveau de comparaison qui sera effectué entre les chaînes de caractères. Ce niveau est tel que défini par l’ICU : lorsqu’il vaut 1, le niveau de comparaison est dit primaire, c’est-à-dire que la comparaison se fait uniquement en se basant sur les caractères, sans tenir compte de leur accentuation éventuelle ou de la casse (majuscule ou minuscule). Lorsque la valeur du champ strength est 2, le niveau de comparaison est dit secondaire, c’est-à-dire qu’en plus de la comparaison primaire, les accents sont désormais pris en compte. Le niveau de comparaison tertiaire est celui par défaut et, en plus des critères du niveau secondaire, il prendra en compte la casse. Il existe deux autres niveaux, mais les trois premiers restent les plus utilisés.

  • caseLevel sert lorsque le niveau de comparaison exigé dans strength est primaire ou secondaire. En effet, ces deux niveaux sont les seuls qui ne prennent pas la casse en considération. Lorsque caseLevel vaut true (ce qui n’est pas le cas par défaut), alors la casse sera prise en compte.

  • caseFirst sert uniquement si le niveau de comparaison est tertiaire et détermine l’ordre de tri : lorsqu’il vaut upper les majuscules apparaissent avant les minuscules dans le tri, lorsqu’il vaut lower l’inverse se produit. La dernière valeur, qui est celle par défaut, est off. À quelques subtilités près, elle fait la même chose que lower.

  • numericOrdering indique si les chaînes de caractères contenant des nombres doivent être traitées comme des chaînes de caractères (quand il vaut faux, ce qui est le cas par défaut) ou bien comme des numériques (quand il vaut vrai).

  • alternate dit si les caractères de ponctuation ou les espaces doivent être pris en compte lors de la comparaison de chaînes de caractères. Si tel est le cas, la valeur de ce champ sera non-ignorable (la valeur par défaut), sinon elle vaudra shifted.

  • maxVariable n’a de raison d’être que lorsque le champ alternate vaut shifted : lorsque maxVariable prend la valeur punct, les espaces et la ponctuation ne sont pas considérés comme des caractères de base. Lors qu’il vaut space, cela vaut uniquement pour les espaces.

  • backwards détermine la direction de la comparaison : s’il vaut faux (comme c’est le cas par défaut), alors la direction de la comparaison sera du début vers la fin, normalization: <booléen> précise si le texte nécessite une normalisation qui sera faite le cas échéant. Par défaut, aucune normalisation n’est effectuée.

2. Créer une collection

Vous savez maintenant que les collections se créent automatiquement lorsqu’un premier document y est inséré ou qu’un index y est créé. Il existe toutefois une méthode nommée createCollection dédiée à la création d’une collection depuis la base de données dans laquelle vous souhaitez qu’elle réside. L’utilisation de cette méthode est conseillée lorsque des options particulières doivent être utilisées à la création, comme par exemple signaler que la collection devra être de type plafonné (capped), que sa collation devra avoir une valeur précise ou bien que les documents qu’elle s’apprête à contenir devront être soumis à une validation préalable avant insertion.

Nous verrons certaines de ces options plus tard, mais pour le moment, nous nous contenterons de créer une collection sans aucune option. Pour créer une collection nommée macollection dans la base de données à laquelle vous êtes actuellement connecté, il vous suffira d’écrire :

db.createCollection("macollection") 

Si vous souhaitez faire usage d’une collation (comme fr) lors de la création de cette collection, vous pourrez le préciser comme suit :

db.createCollection("macollection", {"collation": { "locale": "fr"}}) 

Si nous nous basons sur les paramètres de collation énumérés plus haut, ceci revient à écrire :

db.createCollection("macollection", ...

Gestion des documents

1. Insérer un document

Nous avons déjà eu un aperçu de l’insertion d’un document lors de la création d’une base de données. Nous n’avions inséré qu’un seul document avec insertOne, mais il est évidemment possible d’en insérer plusieurs simultanément en les plaçant dans un tableau avec insertMany. La syntaxe simplifiée d’insertOne est la suivante :

db.collection.insertOne(< document>) 

Celle d’insertMany est :

db.collection.insertMany(< tableau de document>) 

Reprenons le tableau de documents que nous avions constitué, lorsque nous avions abordé la structure d’un document JSON :

[ 
 {"nom": "durand", "prenom": "robert"}, 
 {"nom": "dupont", "prenom": "france"} 
] 

Il s’agit bien d’un tableau d’objets, car les documents sont avant tout des objets ! Dès lors, nous pouvons nous connecter sur test et y exécuter la commande suivante :

use test 
 
db.personnes.insertMany([ 
 {"nom": "durand", "prenom": "robert"}, 
 {"nom": "dupont", "prenom": "france"} 
]) 
 
{ 
  acknowledged: true, 
  insertedIds: { 
    '0': ObjectId("65d5cd6e5a1b646dba13e33c"), 
    '1': ObjectId("65d5cd6e5a1b646dba13e33d") 
  } 
} 

Que se passe-t-il exactement ici ? Nous insérons deux documents dans une collection nommée personnes qui n’existe pas ; comme l’insertion de nos documents a lieu sans erreur, personnes est créée à l’issue de l’exécution de la commande d’insertion.

La méthode insertMany gère les insertions non ordonnées : ce type d’insertion garantit que si une erreur se produit durant l’insertion de plusieurs documents, l’opération n’est pas interrompue et les documents restants sont insérés. L’insertion non ordonnée est réalisée en passant en paramètre un second document contenant le booléen false à la clé ordered, comme suit :

db.personnes.insertMany([ 
 {"nom": "durand", "prenom": "robert"}, 
 {"nom": "dupont", "prenom": "france"} 
], {"ordered": false}) 

Par défaut, l’insertion de plusieurs documents se fait avec ordered valant true. Il est conseillé de laisser ce paramètre à true pour garantir la cohérence des données et ne pas risquer l’insertion d’une partie seulement des documents souhaités.

Voyons à présent une insertion multiple qui échouera parce que deux identifiants (de type chaîne de caractères cette fois, pour changer !) ont la même valeur. Nous utilisons les options par défaut, c’est-à-dire que l’insertion se fait de manière ordonnée : le premier document est inséré, le deuxième ne le sera pas, car son identifiant est identique à celui du premier, et le troisième ne sera pas inséré car le deuxième document a provoqué l’interruption de l’opération d’insertion.

db.personnes.insertMany([{ 
     "_id": "pers1", 
     "nom": "durand", 
     "prenom": "robert" 
}, { 
     "_id": "pers1", 
     "nom": "dupont", 
     "prenom": "france" 
}, { 
     "_id": "pers2", 
     "nom": "dupont", 
     "prenom": "eric" 
}]) 

Voilà la réponse du shell, qui confirme une erreur liée à un doublon d’identifiant lors de la tentative d’insertion du deuxième document. Le document retourné vous donne la valeur du champ ayant provoqué l’interruption de l’insertion ainsi que le document « fautif » à la clé op et nInserted, le nombre total d’éléments insérés, qui vaut bien évidemment 1.

Result: BulkWriteResult { 
  result: { 
    ok: 1, 
    writeErrors: [ 
      WriteError { 
        err: { 
          index: 1, 
          code: 11000,  
          errmsg: 'E11000 duplicate key error collection:  
test.personnes index: _id_ dup key: { _id: "pers1" }', 
          errInfo: undefined, 
          op: { _id: 'pers1', nom: 'dupont', prenom: 'france' } 
        } 
      } 
    ], 
    writeConcernErrors: [], 
    insertedIds: [ 
      { index: 0, _id: 'pers1' }, 
      { index: 1, _id: 'pers1' }, 
      { index: 2, _id: 'pers2' } 
    ], 
    nInserted: 1, 
    nUpserted: 0, 
    nMatched: 0, 
    nModified: 0, 
    nRemoved: 0, 
    upserted: [] 
  } 
} 

Supprimons notre collection et tentons à présent une insertion avec ordered à false de manière à exiger une insertion non ordonnée. Cette fois-ci, le deuxième document provoque toujours une erreur, car la valeur de son identifiant reste inchangée, mais le troisième sera inséré !

db.personnes.drop() 
 
db.personnes.insertMany([{ 
     "_id": "pers1", 
     "nom": "durand", 
     "prenom": "robert" 
}, { 
     "_id": "pers1", 
     "nom": "dupont", 
     "prenom": "france" 
}, { 
     "_id": "pers2", 
     "nom": "dupont", 
     "prenom": "eric" 
}], {"ordered": false}) 

L’exécution de cette insertion provoquera le même message d’erreur que celui vu plus haut, mais nInserted vaudra cette fois 2, preuve que le dernier document a bien été inséré !

Pour n’insérer qu’un seul document, nous avons aussi à notre disposition l’instruction insertOne. Sa syntaxe est similaire à celle d’insertMany, mais simplifiée puisqu’elle ne prend qu’un document en paramètre :

db.collection.insertOne(< document >) 

Les informations qu’elle affiche diffèrent légèrement de celles que nous obtenions jusqu’à présent :

db.personnes.insertOne({"nom": "durand", "prenom":...

Les capped collections

Les capped collections, qu’on pourrait traduire en français par « collections plafonnées », constituent un type particulier de collection en ce qu’elles sont de taille fixe. Leur comportement est celui d’un tampon circulaire : une fois que la capacité maximale de la collection est atteinte, chaque document inséré entraînera automatiquement la suppression du plus ancien document résidant dans celle-ci.

Les capped collections préservent l’ordre d’insertion ; les requêtes ciblant des documents dans ce type particulier de collection n’ont donc pas à s’appuyer sur un index pour tenir compte de cet ordre, ce qui aura pour effet de les rendre plus rapides.

On les utilise notamment pour la gestion des logs ou bien du cache. MongoDB lui-même fait usage de ce type de collection pour son fonctionnement ; l’oplog - ou journal des opérations (operation log) - est une capped collection qui sert à assurer le bon déroulement des opérations de réplication d’une machine primaire vers des machines secondaires. Depuis la version 4 de MongoDB, l’oplog a la possibilité de croître au-delà de sa capacité maximale, ce qui en fait un cas très particulier de capped collection.

1. Création d’une collection plafonnée

La commande pour créer une capped collection est celle d’une collection normale à ceci près qu’il faudra préciser dans les options de création qu’elle est plafonnée et lui attribuer une taille en octets. Si la taille passée en paramètre est inférieure ou égale à 4 Ko, alors elle est automatiquement plafonnée à cette valeur. Par ailleurs, toute taille qui ne serait pas un multiple de 256 est arrondie à un multiple de ce nombre. Créons à présent une capped collection nommée coll_plafonnee en spécifiant une taille de 5 000 octets :

db.createCollection("coll_plafonnee", {"capped": true, "size": 5000}) 

La méthode isCapped, applicable à une collection, indique à l’aide d’un booléen si la collection ciblée est plafonnée ou pas. Ici, c’est bien le cas et elle renverra true :

db.coll_plafonnee.isCapped() 

En visualisant une partie des propriétés de cette collection à l’aide de la méthode db.coll_plafonnee.stats(), nous voyons que la taille a effectivement été arrondie au prochain multiple de 256, à savoir 5120 :

{ 
  ns: 'test.coll_plafonnee', 
  size: 0, 
  count: 0, 
  numOrphanDocs: 0, 
  storageSize: 4096, 
  freeStorageSize: 0, 
  capped: true, 
  max: 0, 
  maxSize: 5000, 
} 

Il est également possible de définir un nombre maximal de documents admissibles dans une capped collection à l’aide de l’option max. Attention cependant, cette option ne dispense pas d’avoir à spécifier la taille de la collection, qui reste malgré tout obligatoire. Avant même le nombre de documents qu’elle contient, c’est le fait que la taille maximale de la collection a été atteinte qui va décider de la suppression des documents les plus anciens.

Détruisons notre ancienne collection plafonnée et recréons-la en requérant explicitement un nombre maximum de 1 000 documents :

db.coll_plafonnee.drop() 
 
db.createCollection( 
   "coll_plafonnee",  
   {"capped": true, "size": 5000, "max": 1000} 
) 

Si nous affichons à nouveau les statistiques de notre collection, nous constatons que le nombre maximum de documents que nous avons requis a bien été pris en compte :

{ 
  ns: 'test.coll_plafonnee', 
  size: 0, 
  count: 0, 
  numOrphanDocs: 0, 
  storageSize: 4096, 
  freeStorageSize: 0, 
  capped: true, 
  max: 1000, 
  maxSize: 5000, 
} 

2. Particularités des capped collections

Constituant un type de collection particulier, les capped collections possèdent elles aussi un champ identifiant _id indexé par défaut.

Ce type de collection ne peut pas faire l’objet de sharding.

Une collection standard peut être convertie en capped à l’aide de la commande convertToCapped :

db.macollectionstandard.insert({"cle": "valeur"}) 
{ 
  acknowledged: true, 
  insertedIds: { '0': ObjectId("65d5e3eaf784f3fca7ee210a") } 
} 
db.runCommand({"convertToCapped": "macollectionstandard", size: 5000})  
{ ok : 1 } 

Suppression d’un document dans une capped collection

Depuis la version 5 de MongoDB, il est possible de supprimer un document contenu dans une capped collection, ce qui n’était pas le cas avant. Pour y parvenir, vous pourrez tout simplement utiliser deleteOne !

Mise à jour d’un document dans une capped collection

La mise à jour d’un document se fait de la manière la plus naturelle qui soit, avec updateOne :

db.coll_plafonnee.updateOne({"cle": "valeur"}, {$set: {"cle": 
"nouvellevaleur"}}) 

Le shell nous répond bien en disant qu’un document a été ciblé ET modifié par la requête :

{ 
  acknowledged: true, 
  insertedId: null, 
  matchedCount: 1, 
  modifiedCount: 1, 
  upsertedCount: 0 
} 

Pour que les mises à jour soient efficaces, il est recommandé d’utiliser des index sur ce type de collection afin d’éviter d’avoir à parcourir l’intégralité de la collection.

Modifier la taille d’une capped collection

Depuis la version 6 de MongoDB, il est possible de changer la taille d’une collection plafonnée au moyen de l’option cappedSize de la commande collMod. Cette taille s’exprime en octets et doit être supérieure à 0 et inférieure ou égale à...