Aller au contenu principal

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 appeler ensure_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 un sort
    • 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
  • 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"