Ich stimme der allgemeinen Vorstellung anderer Antworten zu, dass dies eine Grenzlinie ist Beziehungsproblem.
Der Schlüssel zu MongoDB-Datenmodellen ist die Schreiblast, aber das kann für diesen Anwendungsfall schwierig sein, hauptsächlich wegen der Buchhaltung, die erforderlich wäre, wenn Sie Benutzer direkt mit Elementen verknüpfen wollten (eine Änderung an einer Gruppe, der viele folgen der Benutzer würde eine große Anzahl von Schreibvorgängen verursachen, und Sie benötigen einige Arbeiter, um dies zu tun).
Lassen Sie uns untersuchen, ob das leselastige Modell hier nicht anwendbar ist oder ob wir eine vorzeitige Optimierung durchführen.
Der Read-Heavy-Ansatz
Ihr Hauptanliegen ist der folgende Anwendungsfall:
Ein echtes Leistungsproblem könnte sein, wenn ich alle Gruppen abrufen möchte, denen ein Benutzer für ein bestimmtes Element folgt, [...] weil ich dann alle Gruppen finden muss, denen der Benutzer folgt, und daraus alle finden die item_groups mit der group_id $in
und die Artikel-ID.
Lassen Sie uns das analysieren:
-
Alle Gruppen abrufen, denen der Benutzer folgt
Das ist eine einfache Abfrage:
db.followers.find({userId : userId})
. Wir brauchen einen Index füruserId
Dadurch wird die Laufzeit dieser Operation O (log n) oder sogar für große n blitzschnell. -
daraus alle item_groups mit der group_id
$in
finden und die Artikel-IDJetzt ist dies der schwierigere Teil. Nehmen wir für einen Moment an, dass es unwahrscheinlich ist, dass Elemente Teil einer großen Anzahl von Gruppen sind. Dann ein zusammengesetzter Index
{ itemId, groupId }
würde am besten funktionieren, da wir die Kandidatenmenge durch das erste Kriterium drastisch reduzieren können - wenn ein Element in nur 800 Gruppen geteilt wird und der Benutzer 220 Gruppen folgt, muss mongodb nur die Schnittmenge dieser Gruppen finden, was vergleichsweise einfach ist, da beides Sätze sind klein.
Wir müssen jedoch tiefer gehen:
Die Struktur Ihrer Daten ist wahrscheinlich die eines komplexen Netzwerks . Komplexe Netzwerke gibt es in vielen Varianten, aber es ist sinnvoll anzunehmen, dass Ihr Follower-Graph nahezu skalierungsfrei ist, was auch so ziemlich der schlimmste Fall ist. In einem skalierungsfreien Netzwerk zieht eine sehr kleine Anzahl von Knoten (Prominente, Super Bowl, Wikipedia) eine ganze Menge „Aufmerksamkeit“ auf sich (d. h. haben viele Verbindungen), während eine viel größere Anzahl von Knoten Schwierigkeiten hat, die gleiche Menge an Aufmerksamkeit zu erhalten kombiniert .
Die kleinen Knoten sind kein Grund zur Sorge , liegen die obigen Abfragen, einschließlich Roundtrips zur Datenbank, im 2-ms-Bereich auf meinem Entwicklungscomputer auf einem Datensatz mit mehreren zehn Millionen Verbindungen und> 5 GB Daten. Nun, dieser Datensatz ist nicht riesig, aber egal für welche Technologie Sie sich entscheiden, er wird RAM-gebunden sein, da die Indizes auf jeden Fall im RAM sein müssen (Datenlokalität und Trennbarkeit in Netzwerken sind im Allgemeinen schlecht) und die eingestellte Schnittmenge ist per Definition klein. Mit anderen Worten:Dieses Regime wird von Hardware-Engpässen dominiert.
Was ist mit den Supernodes? obwohl?
Da das Vermutungen wären und ich mich sehr für Netzwerkmodelle interessiere, habe ich mir erlaubt, ein stark vereinfachtes Netzwerktool auf der Grundlage Ihres Datenmodells zu implementieren, um einige Messungen durchzuführen. (Entschuldigung, es ist in C#, aber das Generieren gut strukturierter Netzwerke ist schwer genug in der Sprache, die ich am fließendsten beherrsche...).
Bei der Abfrage der Supernodes erhalte ich Ergebnisse im Bereich von 7ms-Tops (Das sind 12 Millionen Einträge in einer 1,3 GB großen Datenbank, wobei die größte Gruppe 133.000 Elemente enthält und ein Benutzer, der 143 Gruppen folgt.)
Die Annahme in diesem Code ist, dass die Anzahl der Gruppen, denen ein Benutzer folgt, nicht sehr groß ist, aber das scheint hier vernünftig zu sein. Wenn nicht, würde ich mich für den schreiblastigen Ansatz entscheiden.
Fühlen Sie sich frei, mit dem Code zu spielen. Leider muss es ein wenig optimiert werden, wenn Sie dies mit mehr als ein paar GB Daten versuchen möchten, da es einfach nicht optimiert ist und hier und da einige sehr ineffiziente Berechnungen durchführt (insbesondere das Beta-gewichtete zufällige Mischen könnte verbessert werden ).
Mit anderen Worten:Ich würde mir noch keine Gedanken über die Leistung des Read-Heavy-Ansatzes machen . Das Problem besteht oft nicht so sehr darin, dass die Anzahl der Benutzer wächst, sondern darin, dass Benutzer das System auf unerwartete Weise verwenden.
Der Write-Heavy-Ansatz
Der alternative Ansatz besteht wahrscheinlich darin, die Reihenfolge der Verknüpfung umzukehren:
UserItemLinker
{
userId,
itemId,
groupIds[] // for faster retrieval of the linker. It's unlikely that this grows large
}
Dies ist wahrscheinlich das am besten skalierbare Datenmodell, aber ich würde es nicht wählen, es sei denn, wir sprechen über RIESIGE Datenmengen, bei denen Sharding eine Schlüsselanforderung ist. Der Hauptunterschied hier besteht darin, dass wir die Daten jetzt effizient unterteilen können, indem wir die userId als Teil des Shard-Schlüssels verwenden. Das hilft, Abfragen zu parallelisieren, effizient zu fragmentieren und die Datenlokalität in Szenarien mit mehreren Rechenzentren zu verbessern.
Dies könnte mit einer ausgefeilteren Version des Testbeds getestet werden, aber ich habe noch keine Zeit gefunden, und ehrlich gesagt denke ich, dass es für die meisten Anwendungen übertrieben ist.