Lorsque MongoDB a été introduit, la principale caractéristique mise en évidence était sa capacité à être "sans schéma". Qu'est-ce que ça veut dire? Cela signifie que l'on peut stocker des documents JSON, chacun avec une structure différente, dans la même collection. C'est plutôt cool. Mais le problème commence lorsque vous devez récupérer les documents. Comment savez-vous qu'un document récupéré est d'une certaine structure, ou s'il contient un champ particulier ou non ? Vous devez parcourir tous les documents et rechercher ce champ particulier. C'est pourquoi il est utile de planifier soigneusement le schéma MongoDB, en particulier pour les grandes applications.
En ce qui concerne MongoDB, il n'existe aucun moyen spécifique de concevoir le schéma. Tout dépend de votre application et de la façon dont votre application va utiliser les données. Cependant, il existe certaines pratiques courantes que vous pouvez suivre lors de la conception de votre schéma de base de données. Ici, je vais discuter de ces pratiques et de leurs avantages et inconvénients.
Modélisation un à quelques (intégration)
Cette conception est un très bon exemple d'incorporation de documents. Prenons cet exemple de collection Person pour illustrer cette modélisation.
{
name: "Amy Cooper",
hometown: "Seoul",
addresses: [
{ city: 'New York', state: 'NY', cc: 'USA' },
{ city: 'Jersey City', state: 'NJ', cc: 'USA' }
]
}
Avantages :
- Vous pouvez obtenir toutes les informations en une seule requête.
Inconvénients :
- Les données intégrées dépendent entièrement du document parent. Vous ne pouvez pas rechercher les données intégrées de manière indépendante.
- Prenez l'exemple où vous créez un système de suivi des tâches à l'aide de cette approche. Ensuite, vous intégrerez toutes les tâches spécifiques à une personne dans la collection Person. Si vous voulez lancer une requête comme :Montrez-moi toutes les tâches qui ont demain comme date limite. Cela peut être très difficile, même s'il s'agit d'une simple requête. Dans ce cas, vous devriez envisager d'autres approches.
Modélisation un-à-plusieurs (référencement)
Dans ce type de modélisation, le document parent contiendra l'ID de référence (ObjectID) des documents enfants. Vous devez utiliser des jointures au niveau de l'application (combinant deux documents après les avoir récupérés de la base de données au niveau de l'application) pour récupérer des documents, donc pas de jointures au niveau de la base de données. Par conséquent, la charge sur une base de données sera réduite. Prenons cet exemple :
// Parts collection
{
_id: ObjectID(1234),
partno: '1',
name: ‘Intel 100 Ghz CPU',
qty: 100,
cost: 1000,
price: 1050
}
// Products collection
{
name: 'Computer WQ-1020',
manufacturer: 'ABC Company',
catalog_number: 1234,
parts: [
ObjectID(‘1234’), <- Ref. for Part No: 1
ObjectID('2345'),
ObjectID('3456')
]
}
Supposons que chaque produit puisse être associé à plusieurs milliers de pièces. Pour ce type de base de données, le référencement est le type de modélisation idéal. Vous mettez les identifiants de référence de toutes les pièces associées sous le document produit. Ensuite, vous pouvez utiliser des jointures au niveau de l'application pour obtenir les pièces d'un produit particulier.
Avantages :
- Dans ce type de modélisation, chaque pièce est un document distinct afin que vous puissiez appliquer toutes les requêtes liées à la pièce sur ces documents. Pas besoin de dépendre du document parent.
- Opérations CRUD (création, lecture, mise à jour, écriture) très faciles à effectuer sur chaque document indépendamment.
Inconvénients :
- L'un des principaux inconvénients de cette méthode est que vous devez effectuer une requête supplémentaire pour obtenir les détails de la pièce. Afin que vous puissiez effectuer des jointures au niveau de l'application avec le document produit pour obtenir l'ensemble de résultats nécessaire. Cela peut donc entraîner une baisse des performances de la base de données.
Modélisation d'un à des millions (référencement parent)
Lorsque vous devez stocker des tonnes de données dans chaque document, vous ne pouvez utiliser aucune des approches ci-dessus car MongoDB a une limite de taille de 16 Mo par document. Un exemple parfait de ce type de scénario peut être un système de journalisation des événements qui collecte les journaux de différents types de machines et les stocke dans les collections Logs et Machine.
Ici, vous ne pouvez même pas penser à utiliser l'approche d'intégration qui stocke toutes les informations de journaux pour une machine particulière dans un seul document. En effet, dans quelques heures seulement, la taille du document sera supérieure à 16 Mo. Même si vous ne stockez que les identifiants de référence de tous les documents de journaux, vous épuiserez toujours la limite de 16 Mo, car certaines machines peuvent générer des millions de messages de journaux en une seule journée.
Donc, dans ce cas, nous pouvons utiliser l'approche de référencement parent. Dans cette approche, au lieu de stocker les identifiants de référence des documents enfants dans le document parent, nous stockerons l'identifiant de référence du document parent dans tous les documents enfants. Ainsi, pour notre exemple, nous allons stocker l'ObjectID de la machine dans les documents Logs. Prenons cet exemple :
// Machines collection
{
_id : ObjectID('AAA'),
name : 'mydb.example.com',
ipaddr : '127.66.0.4'
}
// Logs collection
{
time : ISODate("2015-09-02T09:10:09.032Z"),
message : 'WARNING: CPU usage is critical!',
host: ObjectID('AAA') -> references Machine document
}
Supposons que vous souhaitiez rechercher les 3 000 journaux les plus récents de la machine 127.66.0.4 :
machine = db.machines.findOne({ipaddr : '127.66.0.4'});
msgs = db.logmsg.find({machine: machine._id}).sort({time : -1}).limit(3000).toArray()
Référencement bidirectionnel
Dans cette approche, nous stockons les références des deux côtés, ce qui signifie que la référence du parent sera stockée dans le document enfant et la référence de l'enfant sera stockée dans le document parent. Cela rend la recherche relativement facile dans une à plusieurs modélisations. Par exemple, nous pouvons effectuer une recherche sur les documents parents et sur les tâches. D'un autre côté, cette approche nécessite deux requêtes distinctes pour mettre à jour un document.
// person
{
_id: ObjectID("AAAA"),
name: "Bear",
tasks [
ObjectID("AAAD"),
ObjectID("ABCD"), -> Reference of child document
ObjectID("AAAB")
]
}
// tasks
{
_id: ObjectID("ABCD"),
description: "Read a Novel",
due_date: ISODate("2015-11-01"),
owner: ObjectID("AAAA") -> Reference of parent document
}
Conclusion
En fin de compte, tout dépend des exigences de votre application. Vous pouvez concevoir le schéma MongoDB de la manière la plus bénéfique pour votre application et vous offrant des performances élevées. Voici quelques considérations résumées que vous pouvez prendre en compte lors de la conception de votre schéma.
- Concevez le schéma en fonction des modèles d'accès aux données de votre application.
- Il n'est pas nécessaire d'intégrer des documents à chaque fois. Combinez des documents uniquement si vous comptez les utiliser ensemble.
- Envisagez de dupliquer les données, car le stockage est aujourd'hui moins cher que la puissance de calcul.
- Optimiser le schéma pour les cas d'utilisation plus fréquents
- Les tableaux ne doivent pas dépasser les limites. S'il y a plus de quelques centaines de documents enfants, ne l'intégrez pas.
- Préférez les jointures au niveau de l'application aux jointures au niveau de la base de données. Avec une indexation appropriée et une utilisation appropriée des champs de projection, cela peut vous faire gagner beaucoup de temps.