Das/die Problem(e)
Wie zuvor geschrieben , treten beim Übereinbetten mehrere Probleme auf:
Problem 1:BSON-Größenbeschränkung
Zum Zeitpunkt der Erstellung dieses Artikels sind BSON-Dokumente auf 16 MB begrenzt . Wenn dieses Limit erreicht ist, würde MongoDB eine Ausnahme auslösen und Sie könnten einfach keine weiteren Kommentare hinzufügen und im schlimmsten Fall nicht einmal den (Benutzer-)Namen oder das Bild ändern, wenn die Änderung die Größe des Dokuments erhöhen würde.
Problem 2:Einschränkungen und Leistung der Abfrage
Unter bestimmten Bedingungen ist es nicht ohne weiteres möglich, das Kommentar-Array abzufragen oder zu sortieren. Einige Dinge würden eine ziemlich kostspielige Aggregation erfordern, andere ziemlich komplizierte Aussagen.
Während man argumentieren könnte, dass dies kein großes Problem darstellt, sobald die Abfragen vorhanden sind, bin ich anderer Meinung. Erstens:Je komplizierter eine Abfrage ist, desto schwieriger ist sie zu optimieren, sowohl für den Entwickler als auch für den anschließenden Abfrageoptimierer von MongoDB. Ich habe die besten Ergebnisse mit der Vereinfachung von Datenmodellen und Abfragen erzielt, wobei die Antworten in einem Fall um den Faktor 100 beschleunigt wurden.
Bei der Skalierung können sich die für komplizierte und/oder kostspielige Abfragen benötigten Ressourcen im Vergleich zu einem einfacheren Datenmodell und entsprechenden Abfragen sogar auf ganze Maschinen summieren.
Problem 3:Wartbarkeit
Zu guter Letzt könnten Sie durchaus auf Probleme bei der Pflege Ihres Codes stoßen. Als einfache Faustregel
In diesem Zusammenhang bezieht sich „teuer“ sowohl auf Geld (bei professionellen Projekten) als auch auf Zeit (bei Hobbyprojekten).
(Meine!) Lösung
Es ist ziemlich einfach:Vereinfachen Sie Ihr Datenmodell. Folglich werden Ihre Abfragen weniger kompliziert und (hoffentlich) schneller.
Schritt 1:Identifizieren Sie Ihre Anwendungsfälle
Das wird eine wilde Vermutung für mich sein, aber das Wichtigste hier ist, Ihnen die allgemeine Methode zu zeigen. Ich würde Ihre Anwendungsfälle wie folgt definieren:
- Für einen bestimmten Beitrag sollten Benutzer Kommentare abgeben können
- Zeigen Sie für einen bestimmten Beitrag den Autor und die Kommentare zusammen mit dem Benutzernamen und dem Bild des Kommentators und des Autors
- Für einen bestimmten Benutzer sollte es einfach möglich sein, den Namen, den Benutzernamen und das Bild zu ändern
Schritt 2:Modellieren Sie Ihre Daten entsprechend
Benutzer
Zunächst einmal haben wir ein unkompliziertes Benutzermodell
{
_id: new ObjectId(),
name: "Joe Average",
username: "HotGrrrl96",
picture: "some_link"
}
Nichts Neues hier, nur der Vollständigkeit halber hinzugefügt.
Beiträge
{
_id: new ObjectId()
title: "A post",
content: " Interesting stuff",
picture: "some_link",
created: new ISODate(),
author: {
username: "HotGrrrl96",
picture: "some_link"
}
}
Und das war's für einen Beitrag. Dabei ist zweierlei zu beachten:Erstens speichern wir die Autorendaten, die wir unmittelbar beim Anzeigen eines Beitrags benötigen, da uns dies eine Abfrage für einen sehr häufigen, wenn nicht allgegenwärtigen Anwendungsfall erspart. Warum speichern wir die Daten der Kommentare und Kommentatoren nicht entsprechend? Wegen der Größenbeschränkung von 16 MB , versuchen wir zu verhindern, dass Referenzen in einem einzigen Dokument gespeichert werden. Stattdessen speichern wir die Referenzen in Kommentardokumenten:
Kommentare
{
_id: new ObjectId(),
post: someObjectId,
created: new ISODate(),
commenter: {
username: "FooBar",
picture: "some_link"
},
comment: "Awesome!"
}
Wie bei Beiträgen haben wir alle notwendigen Daten, um einen Beitrag anzuzeigen.
Die Abfragen
Was wir jetzt erreicht haben, ist, dass wir die BSON-Größenbeschränkung umgangen haben und nicht auf die Benutzerdaten zurückgreifen müssen, um Beiträge und Kommentare anzeigen zu können, was uns viele Rückfragen ersparen sollte. Aber kommen wir zurück zu den Anwendungsfällen und einigen weiteren Abfragen
Hinzufügen eines Kommentars
Das ist jetzt ganz einfach.
Alle oder einige Kommentare zu einem bestimmten Beitrag erhalten
Für alle Kommentare
db.comments.find({post:objectIdOfPost})
Für die 3 neusten Kommentare
db.comments.find({post:objectIdOfPost}).sort({created:-1}).limit(3)
Für die Anzeige eines Beitrags und aller (oder einiger) seiner Kommentare, einschließlich der Benutzernamen und Bilder, sind wir also bei zwei Abfragen. Mehr als Sie vorher brauchten, aber wir haben die Größenbeschränkung umgangen und Sie können im Grunde eine unbegrenzte Anzahl von Kommentaren für jeden Beitrag haben. Aber kommen wir zu etwas Wirklichem
Die letzten 5 Posts und ihre letzten 3 Kommentare erhalten
Dies ist ein zweistufiger Prozess. Bei richtiger Indizierung (darauf kommen wir später zurück) sollte dies jedoch immer noch schnell (und damit ressourcenschonend) sein:
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);
}
)
Erhalten Sie alle Beiträge eines bestimmten Benutzers sortiert von den neuesten bis zu den ältesten und seinen Kommentaren
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});
Beachten Sie, dass wir hier nur zwei Abfragen haben. Obwohl Sie die Verbindung zwischen Beiträgen und ihren jeweiligen Kommentaren "manuell" herstellen müssen, sollte dies ziemlich einfach sein.
Benutzernamen ändern
Dies ist vermutlich ein selten ausgeführter Anwendungsfall. Mit diesem Datenmodell ist es jedoch nicht sehr kompliziert
Zuerst ändern wir das Benutzerdokument
db.users.update(
{ username: "HotGrrrl96"},
{
$set: { username: "Joe Cool"},
$push: {oldUsernames: "HotGrrrl96" }
},
{
writeConcern: {w: "majority"}
}
);
Wir schieben den alten Benutzernamen in ein entsprechendes Array. Dies ist eine Sicherheitsmaßnahme für den Fall, dass bei den folgenden Vorgängen etwas schief geht. Außerdem haben wir den Schreibschutz ziemlich hoch eingestellt, um sicherzustellen, dass die Daten dauerhaft sind.
db.posts.update(
{ "author.username": "HotGrrrl96"},
{ $set:{ "author.username": "Joe Cool"} },
{
multi:true,
writeConcern: {w:"majority"}
}
)
Nichts besonderes hier. Die Update-Anweisung für die Kommentare sieht ziemlich gleich aus. Obwohl diese Abfragen einige Zeit in Anspruch nehmen, werden sie selten ausgeführt.
Die Indizes
Als Faustregel kann man sagen, dass MongoDB nur einen Index pro Abfrage verwenden kann. Dies ist zwar nicht ganz richtig, da es Indexüberschneidungen gibt, aber es ist einfach, damit umzugehen. Eine andere Sache ist, dass einzelne Felder in einem zusammengesetzten Index unabhängig voneinander verwendet werden können. Ein einfacher Ansatz zur Indexoptimierung besteht also darin, die Abfrage mit den meisten Feldern zu finden, die in Operationen verwendet werden, die Indizes verwenden, und daraus einen zusammengesetzten Index zu erstellen. Beachten Sie, dass die Reihenfolge des Auftretens in der Abfrage wichtig ist. Also los geht's.
Beiträge
db.posts.createIndex({"author.username":1,"created":-1})
Kommentare
db.comments.createIndex({"post":1, "created":-1})
Schlussfolgerung
Ein vollständig eingebettetes Dokument per Post ist zugegebenermaßen der schnellste Weg, es und seine Kommentare zu laden. Es lässt sich jedoch nicht gut skalieren, und aufgrund der Art der möglicherweise komplexen Abfragen, die für die Bearbeitung erforderlich sind, kann dieser Leistungsvorteil genutzt oder sogar eliminiert werden.
Mit der obigen Lösung tauschen Sie etwas Geschwindigkeit (wenn!) gegen eine im Grunde unbegrenzte Skalierbarkeit und einen viel einfacheren Umgang mit den Daten.
Hth.