MongoDB
 sql >> Base de données >  >> NoSQL >> MongoDB

Commentaires de requête MongoDB avec informations sur l'utilisateur

Le(s) problème(s)

Comme écrit avant , il y a plusieurs problèmes lors de la sur-intégration :

Problème 1 :taille limite de BSON

Au moment d'écrire ces lignes, les documents BSON sont limités à 16 Mo . Si cette limite est atteinte, MongoDB lèvera une exception et vous ne pourrez tout simplement pas ajouter plus de commentaires et dans le pire des cas, ne même pas changer le nom (d'utilisateur) ou l'image si le changement augmente la taille du document.

Problème 2 :Limitations et performances des requêtes

Il n'est pas facile d'interroger ou de trier le tableau de commentaires sous certaines conditions. Certaines choses nécessiteraient une agrégation assez coûteuse, d'autres des déclarations plutôt compliquées.

Bien que l'on puisse affirmer qu'une fois les requêtes en place, ce n'est pas vraiment un problème, je ne suis pas d'accord. Premièrement, plus une requête est compliquée, plus elle est difficile à optimiser, à la fois pour le développeur et par la suite pour l'optimiseur de requête MongoDBs. J'ai obtenu les meilleurs résultats en simplifiant les modèles de données et les requêtes, en accélérant les réponses d'un facteur 100 dans une instance.

Lors de la mise à l'échelle, les ressources nécessaires pour les requêtes compliquées et/ou coûteuses peuvent même se résumer à des machines entières par rapport à un modèle de données plus simple et selon les requêtes.

Problème 3 :maintenabilité

Enfin, vous pourriez bien rencontrer des problèmes pour maintenir votre code. En règle générale

Dans ce contexte, "coûteux" fait à la fois référence à l'argent (pour les projets professionnels) et au temps (pour les projets de loisirs).

(Ma !) Solution

C'est assez simple :simplifiez votre modèle de données. Par conséquent, vos requêtes deviendront moins compliquées et (espérons-le) plus rapides.

Étape 1 :Identifiez vos cas d'utilisation

Cela va être une supposition folle pour moi, mais l'important ici est de vous montrer la méthode générale. Je définirais vos cas d'utilisation comme suit :

  1. Pour une publication donnée, les utilisateurs doivent pouvoir commenter
  2. Pour un article donné, afficher l'auteur et les commentaires, ainsi que le nom d'utilisateur des commentateurs et des auteurs et leur photo
  3. Pour un utilisateur donné, il devrait être facilement possible de changer le nom, le nom d'utilisateur et l'image

Étape 2 :Modélisez vos données en conséquence

Utilisateurs

Tout d'abord, nous avons un modèle utilisateur simple

{
  _id: new ObjectId(),
  name: "Joe Average",
  username: "HotGrrrl96",
  picture: "some_link"
}

Rien de nouveau ici, ajouté juste pour être complet.

Messages

{
  _id: new ObjectId()
  title: "A post",
  content: " Interesting stuff",
  picture: "some_link",
  created: new ISODate(),
  author: {
    username: "HotGrrrl96",
    picture: "some_link"
  }
}

Et c'est à peu près tout pour un post. Il y a deux choses à noter ici :premièrement, nous stockons les données de l'auteur dont nous avons immédiatement besoin lors de l'affichage d'un article, car cela nous évite une requête pour un cas d'utilisation très courant, voire omniprésent. Pourquoi ne sauvegardons-nous pas les commentaires et les données des commentateurs en conséquence ? En raison de la limite de taille de 16 Mo , nous essayons d'empêcher le stockage des références dans un seul document. Nous stockons plutôt les références dans des documents de commentaires :

Commentaires

{
  _id: new ObjectId(),
  post: someObjectId,
  created: new ISODate(),
  commenter: {
    username: "FooBar",
    picture: "some_link"
  },
  comment: "Awesome!"
}

Comme pour les publications, nous avons toutes les données nécessaires pour afficher une publication.

Les requêtes

Ce que nous avons réalisé maintenant, c'est que nous avons contourné la limite de taille BSON et nous n'avons pas besoin de nous référer aux données de l'utilisateur pour pouvoir afficher les publications et les commentaires, ce qui devrait nous éviter de nombreuses requêtes. Mais revenons aux cas d'utilisation et à quelques requêtes supplémentaires

Ajouter un commentaire

C'est tout à fait simple maintenant.

Obtenir tout ou partie des commentaires pour une publication donnée

Pour tous les commentaires

db.comments.find({post:objectIdOfPost})

Pour les 3 derniers commentaires

db.comments.find({post:objectIdOfPost}).sort({created:-1}).limit(3)

Donc, pour afficher un message et tout (ou certains) de ses commentaires, y compris les noms d'utilisateur et les images, nous en sommes à deux requêtes. Plus que ce dont vous aviez besoin auparavant, mais nous avons contourné la limite de taille et, en gros, vous pouvez avoir un nombre indéfini de commentaires pour chaque publication. Mais passons à quelque chose de réel

Obtenir les 5 derniers articles et leurs 3 derniers commentaires

Il s'agit d'un processus en deux étapes. Cependant, avec une indexation correcte (nous y reviendrons plus tard), cela devrait toujours être rapide (et donc économiser des ressources) :

var posts = db.posts.find().sort({created:-1}).limit(5)
posts.forEach(
  function(post) {
    doSomethingWith(post);
    var comments = db.comments.find({"post":post._id}).sort("created":-1).limit(3);
    doSomethingElseWith(comments);
  }
)

Obtenir tous les messages d'un utilisateur donné triés du plus récent au plus ancien et leurs commentaires

var posts = db.posts.find({"author.username": "HotGrrrl96"},{_id:1}).sort({"created":-1});
var postIds = [];
posts.forEach(
  function(post){
    postIds.push(post._id);
  }
)
var comments = db.comments.find({post: {$in: postIds}}).sort({post:1, created:-1});

Notez que nous n'avons ici que deux requêtes. Bien que vous deviez établir "manuellement" le lien entre les publications et leurs commentaires respectifs, cela devrait être assez simple.

Modifier un nom d'utilisateur

Il s'agit vraisemblablement d'un cas d'utilisation rare exécuté. Cependant, ce n'est pas très compliqué avec ledit modèle de données

Tout d'abord, nous changeons le document utilisateur

db.users.update(
  { username: "HotGrrrl96"},
  {
    $set: { username: "Joe Cool"},
    $push: {oldUsernames: "HotGrrrl96" }
  },
  {
    writeConcern: {w: "majority"}
  }
);

Nous poussons l'ancien nom d'utilisateur vers un tableau correspondant. Il s'agit d'une mesure de sécurité en cas de problème avec les opérations suivantes. De plus, nous plaçons le problème d'écriture à un niveau assez élevé afin de garantir la pérennité des données.

db.posts.update(
  { "author.username": "HotGrrrl96"},
  { $set:{ "author.username": "Joe Cool"} },
  {
    multi:true,
    writeConcern: {w:"majority"}
  }
)

Rien de spécial ici. La déclaration de mise à jour pour les commentaires est à peu près la même. Bien que ces requêtes prennent un certain temps, elles sont rarement exécutées.

Les indices

En règle générale, on peut dire que MongoDB ne peut utiliser qu'un seul index par requête. Bien que ce ne soit pas tout à fait vrai puisqu'il existe des intersections d'index, c'est facile à gérer. Une autre chose est que les champs individuels d'un index composé peuvent être utilisés indépendamment. Ainsi, une approche simple de l'optimisation des index consiste à trouver la requête avec le plus de champs utilisés dans les opérations qui utilisent des index et à en créer un index composé. Notez que l'ordre d'occurrence dans la requête est important. Alors, allons-y.

Messages

db.posts.createIndex({"author.username":1,"created":-1})

Commentaires

db.comments.createIndex({"post":1, "created":-1})

Conclusion

Certes, un document entièrement intégré par message est le moyen le plus rapide de le charger et de ses commentaires. Cependant, il ne s'adapte pas bien et en raison de la nature des requêtes éventuellement complexes nécessaires pour le traiter, cet avantage en termes de performances peut être exploité ou même éliminé.

Avec la solution ci-dessus, vous échangez une certaine vitesse (si !) contre une évolutivité pratiquement illimitée et une manière beaucoup plus simple de traiter les données.

Hth.