MongoDB
Design patterns
Référence ou intégration ?
=> est-ce qu'on doit référencer un autre objet avec son id, ou l'intégrer directement ?
- joindre des tables est l'opération la plus coûteuse qu'une bdd relationnelle puisse faire
- si on a besoin de scale la bdd sur plusieurs serveurs, on retrouve le pb de générer un
distributed join
, qui est encore plus coûteux - Intégrer =>
- Pour l'avoir localement et éviter la multi recherches à la bdd, qui aurait pu être évité
- Pour l'atomicité et l'isolation => MongoDB n'a pas de transaction (= ensemble d'opérations), donc une MAJ pourrait échouer sur une opération et réussir sur l'autre
- vs Référencer =>
- Pour la flexibilité => Si le fait d'intégrer nous retourne beaucoup plus de donnée que ce que nous aurions besoin
Schémas polymorphiques
- Schéma polymorphique = Quand tous les documents d'une collection sont similaires mais n'ont pas la même structure
- Pour aider avec les migrations et le stockage des données, on peut utiliser un MongoDB ODM (object-document mapper). Permet de migrer les documents en tâche de fond par exemple.
- Stocker des données semi-structurées
- Cela peut être compliqué de lancer des requêtes et indexer des champs que mon application ignore
- Une approche est d'avoir un tableau de paire propriété/valeur, exemple
{
_id: ObjectId(...),
title: 'Big and Fast Disk Drive',
properties: [
['Seek Time', '5ms' ],
['Rotational Speed', '15k RPM'],
['Transfer Rate', '...']
}
On peut ensuite indexer le champ properties
: db.products.ensure_index('properties')
.
Une fois indexé, nos requêtes peuvent seulement indiquer la paire que nous cherchons : db.products.find({'properties': [ 'Seek Time': '5ms' ]})
Reproduire le comportement transactionnel
- Comment maintenir la consistance sans utiliser de transactions ? Approche
compensation
pour imiter le comportement des BDD relationnelles - Une approche pourrait être d'émuler des transactions. On aurait une collection
transaction
qui contient les documents qui stocke l'état des opérations. Avec un state "new", "committed", "ou "rollback". Si l'opération prend trop de temps (avec un temps max défini à l'avance), l'opération est rollback. Si l'opération est finie ("committed"), elle sera supprimée de la collection. Exemple :
{
_id: ObjectId(...),
state: 'new',
ts: ISODateTime(...),
amt: 55.22, // ici c'était une transaction bancaire donc on garde la donnée modifiée
src: 1, // id du document source
dst: 2 // id du document de destination
}
Cas d'utilisation
De manière générale :
- Ne pas oublier d'appeler
ensure_index
pour indexer la requête- le faire avant d'appeler
find
pour la 1ere fois (ne pas appelerensure_index
à chaque find par contre) - Ne pas oublier de le faire sur toutes les propriétés appelées dans un
find
, et sur les propriétés utilisées dans unsort
- Avantage : Amélioration dans la vitesse de recherche
- Inconvénient : Plus lent pour les MAJ, mais dans le cas de données qui ne sont pas mis à jour tout le temps (ex: un catalogue de produits), c'est intéressant
- le faire avant d'appeler
- Série d'instructions =
aggregation pipeline
(appeléstage
)
Pour une recherche fulltext :
find
avec regex- Si beaucoup de recherche fulltext, plutôt passer par un moteur séparé comme Apache Solr ou ElasticSearch
Pour gérer de la catégorisation :
// Approche 1 : stocker le parent_id dans "parent". Inconvénient : on ne peut examiner qu'un seul niveau à la fois
{
_id: "modal-jazz",
name: "Modal Jazz",
parent: "bop"
}
// Approche 2 : stocker les ids de tous les parents => permet de connaître tous les parents sans requête supplémentaire, mais inconvénients => mettre à jour la hiérarchie est lourd
{
_id: "ragtime:bop:modal-jazz",
name: "Modal Jazz",
parent: "ragtime/bop"
}
// Approche 3 : stocker tout l'arbre des catégories dans un seul objet.
{
_id: ObjectId(),
slug: "modal-jazz",
name: "Modal jazz",
parent: ObjectId(),
ancestors: [
{
_id: ObjectId(),
slug: "bop",
name: "Bop",
// parent,
// ancestors,
// ...
}
]
}
Gestion des photos :
- Comme les photos peuvent être lourdes, il faut séparer le stockage de la photo et de la donnée d'un item (dans une collection séparée
assets
par exemple) - GridFS permet de stocker des gros fichiers (> 16 Mb)
Sources
- 📖 Livre "MongoDB applied design patterns"