Follower - mongodb Datenbankdesign

8

Also ich benutze mongodb und ich bin unsicher, ob ich das richtige / beste Datenbanksammlungsdesign für das habe, was ich versuche zu tun.

Es kann viele Elemente geben, und ein Benutzer kann mit diesen Elementen neue Gruppen erstellen. Jeder Benutzer kann einer Gruppe folgen!

Ich habe nicht einfach die Follower und Items in die Gruppensammlung aufgenommen, da es 5 Items in der Gruppe geben könnte, oder 10000 (und das gleiche für Follower) und ich glaube, dass Sie keine ungebundenen Arrays verwenden sollten (wobei das Limit unbekannt ist) aufgrund von Leistungsproblemen, wenn das Dokument aufgrund seiner expandierenden Größe verschoben werden muss. (Gibt es ein empfohlenes Maximum für Array-Längen, bevor Leistungsprobleme auftreten?)

Ich denke mit dem folgenden Design könnte ein echtes Performance-Problem sein, wenn ich alle Gruppen, die ein Benutzer verfolgt, für ein bestimmtes Element (basierend auf der user_id und item_id) bekommen möchte, denn dann muss ich alles finden der Gruppen, denen der Benutzer folgt, und von diesem alle item_groups mit der group_id $ in und der Artikel-ID finden. (Aber ich kann eigentlich keinen anderen Weg sehen, dies zu tun)

%Vor%

Gibt es bessere DB-Muster für diese Art von Setup?

UPDATE: Beispiel für einen Anwendungsfall, der im Kommentar unten hinzugefügt wurde.

Jede Hilfe / Beratung wird sehr geschätzt.

Vielen Dank, Mac

    
Mac 09.02.2015, 23:32
quelle

4 Antworten

8

Ich stimme der allgemeinen Vorstellung anderer Antworten zu, dass dies ein relationales Borderline Problem ist.

Der Schlüssel zu MongoDB-Datenmodellen ist Schreibschwierigkeit, 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 möchten (eine Änderung an einer Gruppe, die gefolgt von vielen Benutzern würde eine große Anzahl von Schreibvorgängen, und Sie benötigen einige Arbeiter, um dies zu tun).

Lassen Sie uns untersuchen, ob das Read-Heavy-Modell hier nicht anwendbar ist oder ob wir eine vorzeitige Optimierung vornehmen.

Der schwere Leseansatz

Ihr wichtigstes Anliegen ist der folgende Anwendungsfall:

  

Ein wirkliches Leistungsproblem könnte sein, wenn ich alle Gruppen, die ein Benutzer verfolgt, für ein bestimmtes Element bekommen möchte, [...] weil ich dann alle Gruppen finden muss, denen der Benutzer folgt und von denen Finde alle item_groups mit der group_id $in und der Item-ID.

Lassen Sie uns das analysieren:

  • Erhalte alle Gruppen, denen der Benutzer folgt

    Das ist eine einfache Abfrage: db.followers.find({userId : userId}) . Wir werden einen Index für userId brauchen, der die Laufzeit dieser Operation O (log n) oder sogar für große n rasend schnell macht.

  • davon finden Sie alle Artikelgruppen mit der Gruppennummer $in und der Artikel-ID

    Jetzt ist das der schwierigere Teil. Nehmen wir einmal an, dass es unwahrscheinlich ist, dass Elemente Teil einer großen Anzahl von Gruppen sind. Dann würde ein zusammengesetzter Index { itemId, groupId } am besten funktionieren, weil wir den Kandidatensatz durch das erste Kriterium drastisch reduzieren können - wenn ein Element in nur 800 Gruppen geteilt ist und der Benutzer 220 Gruppen folgt, muss mongodb nur die Schnittmenge dieser finden Das ist vergleichsweise einfach, weil beide Sets klein sind.

Wir müssen jedoch noch 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 skalenfrei ist auch so ziemlich der schlimmste Fall. In einem skalenfreien Netzwerk, eine sehr kleine Anzahl von Knoten (Prominente, Super Bowl, Wikipedia) ziehen eine ganze Menge Aufmerksamkeit (dh haben viele Verbindungen), während eine viel größere Anzahl von Knoten Schwierigkeiten haben, die gleiche Menge an Aufmerksamkeit zu bekommen kombiniert .

Die kleinen Knoten sind kein Grund zur Besorgnis , die obigen Abfragen, einschließlich Roundtrips zur Datenbank, liegen im Bereich 2ms auf meinem Entwicklungscomputer in einem Dataset mit Millionen von Verbindungen und & gt; 5 GB Daten. Nun, da der Datensatz nicht riesig ist, aber egal welche Technologie Sie wählen, wird RAM gebunden, da die Indizes in jedem Fall im RAM sein müssen (Datenlokalität und Trennbarkeit in Netzwerken ist im Allgemeinen schlecht), und die eingestellte Kreuzungsgröße ist klein per Definition. Mit anderen Worten: Dieses Regime wird von Hardware-Engpässen dominiert.

Was ist mit den supernodes ?

Da das Raten ist und ich mich sehr für Netzwerkmodelle interessiere, habe ich mir die Freiheit genommen, ein drastisch vereinfachtes Netzwerkwerkzeug zu implementieren basierend auf Ihrem Datenmodell, um einige Messungen durchzuführen. (Sorry, es ist in C #, aber gut strukturierte Netzwerke zu generieren ist schwer genug in der Sprache, in der ich am meisten fließend bin ...).

Wenn ich die Supernodes abfrage, bekomme ich Ergebnisse im Bereich von 7ms tops (das sind 12M Einträge in 1,3GB db, 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 von einem Benutzer nicht groß ist, aber das scheint hier sinnvoll. Wenn das nicht der Fall ist, würde ich mich für den schreibbetonten Ansatz entscheiden.

Fühlen Sie sich frei, mit dem Code zu spielen. Leider wird es ein wenig Optimierung benötigen, wenn Sie dies mit mehr als ein paar GB Daten versuchen wollen, weil es einfach nicht optimiert ist und hier und da einige sehr ineffiziente Berechnungen durchführt (besonders der beta-gewichtete Zufalls-Shuffle könnte verbessert werden) ).

Mit anderen Worten: Ich würde mir keine Gedanken über die Leistung des Read-Heavy-Ansatzes machen noch . Das Problem ist oft nicht so sehr, dass die Anzahl der Benutzer wächst , aber dass Benutzer das System auf unerwartete Weise verwenden.

Der schwere Ansatz schreiben

Der alternative Ansatz besteht wahrscheinlich darin, die Reihenfolge der Verknüpfung umzukehren:

%Vor%

Dies ist wahrscheinlich das am besten skalierbare Datenmodell, aber ich würde mich nicht dafür entscheiden, wenn wir nicht über riesige Datenmengen sprechen, bei denen Sharding eine Schlüsselanforderung ist. Der Hauptunterschied besteht darin, dass wir die Daten jetzt effizient unterteilen können, indem wir die Benutzer-ID als Teil des Shard-Schlüssels verwenden. Dies hilft bei der Parallelisierung von Abfragen, der effizienten Shard-Funktion und der Verbesserung der Datenlokalisierung in Multi-Datencenter-Szenarien.

Dies könnte mit einer ausgefeilteren Version des Testbeds getestet werden, aber ich habe die Zeit noch nicht gefunden, und ehrlich gesagt, denke ich, dass es für die meisten Anwendungen übertrieben ist.

    
mnemosyn 18.02.2015, 13:57
quelle
3

Sie sind auf dem richtigen Weg, um ein performantes NoSQL-Schemadesign zu erstellen, und ich denke, Sie stellen die richtigen Fragen, wie Sie die Dinge richtig gestalten können.

Hier ist mein Verständnis Ihrer Anwendung:

Es sieht so aus, als ob Gruppen sowohl viele Follower (Zuordnung von Benutzern zu Gruppen) als auch viele Items haben können, aber Items sind nicht notwendigerweise in vielen Gruppen (obwohl es möglich ist). Und aus Ihrem gegebenen Beispiel für einen Anwendungsfall klingt es so, als würde man alle Gruppen abrufen, in denen sich ein Element befindet, und alle Elemente in einer Gruppe sind einige gemeinsame Leseoperationen.

In Ihrem aktuellen Schemadesign haben Sie ein Modell zwischen der Zuordnung von Benutzern zu Gruppen als Follower und Elementen zu Gruppen als item_groups implementiert. Dies funktioniert in Ordnung, bis Sie das Problem mit komplexeren Abfragen erwähnen:

  

Ich denke, mit dem folgenden Entwurf könnte ein echtes Leistungsproblem entstehen, wenn ich alle Gruppen erhalten möchte, die ein Benutzer für ein bestimmtes Element verfolgt (basierend auf der Benutzer-ID und der Element-ID)

Ich denke, ein paar Dinge könnten Ihnen in dieser Situation helfen:

  • Nutzen Sie die leistungsstarken Indizierungsfunktionen von MongoDB. Insbesondere sollten Sie in Erwägung ziehen, zusammengesetzte Indizes auf Ihren Follower-Objekten zu erstellen, die Ihre Gruppe und Ihren Benutzer abdecken. und Ihre Item_Groups für Item bzw. Group. Sie sollten auch sicherstellen, dass diese Art von Beziehung einzigartig ist, da ein Benutzer nur einmal einer Gruppe folgen kann und ein Element nur einmal zu einer Gruppe hinzugefügt werden kann. Dies wird am besten in einigen Pre-Save-Hooks erreicht, die in Ihrem Schema definiert sind, oder Sie verwenden ein Plugin, um die Gültigkeit zu überprüfen.

FollowerSchema.index({ group: 1, user: 1 }, { unique: true }); Item_GroupsSchema.index({ group: 1, item: 1 }, { unique: true });

Wenn Sie einen Index für diese Felder erstellen, entsteht beim Schreiben in die Sammlung ein gewisser Mehraufwand, aber es klingt so, als würde das Lesen aus der Sammlung eine häufigere Interaktion sein, so dass es sich lohnt (ich würde vorschlagen, mehr auf Indexleistung ).

  • Da ein Benutzer wahrscheinlich nicht tausenden von Gruppen folgt, denke ich, dass es sich lohnen würde, in das Benutzermodell ein Array von Gruppen aufzunehmen, denen der Benutzer folgt. Dies wird Ihnen bei dieser komplexen Abfrage helfen, wenn Sie alle Instanzen eines Elements in Gruppen suchen möchten, denen ein Benutzer gerade folgt, da Sie die Liste der Gruppen direkt dort haben. Sie haben immer noch die Implementierung, in der Sie $in: groups verwenden, aber es wird mit einer Abfrage weniger für die Sammlung sein.

  • Wie ich bereits erwähnt habe, scheint es, dass Elemente nicht notwendigerweise in so vielen Gruppen enthalten sein müssen (genauso wie Benutzer nicht notwendigerweise Tausenden von Gruppen folgen). Wenn der Fall im Allgemeinen darin besteht, dass ein Objekt in vielleicht ein paar hundert Gruppen ist, würde ich einfach ein Array zum Objektmodell für jede Gruppe hinzufügen, zu der es hinzugefügt wird. Dies würde Ihre Leistung beim Lesen aller Gruppen, in denen sich ein Element befindet, erhöhen. Eine von Ihnen erwähnte Abfrage wäre eine häufige. Hinweis: Sie können das Item_Groups-Modell weiterhin verwenden, um alle Elemente in einer Gruppe abzurufen, indem Sie die (jetzt indizierte) group_id abfragen.

jket 17.02.2015 11:09
quelle
3

Ich habe deinen Kommentar / Anwendungsfall gelesen. Also aktualisiere ich meine Antwort.

Ich schlage vor, den Entwurf gemäß diesem Artikel zu ändern: MongoDB Viele-zu-Viele

Der Design-Ansatz ist anders und Sie möchten vielleicht Ihren Ansatz dazu umgestalten. Ich werde versuchen, dir eine Idee zu geben, mit der du anfangen kannst. Ich nehme an, dass ein Benutzer und ein Follower im Grunde die gleichen Entitäten sind. Ich denke, der interessanteste Punkt ist, dass Sie in MongoDB array Felder speichern können, und das ist, was ich verwenden werde, um Ihren Entwurf für MongoDB zu vereinfachen / zu korrigieren.

Die zwei Entitäten, die ich weglassen würde, sind: Follower und ItemGroups

  • Follower: Es ist einfach ein User, der Gruppen folgen kann. Ich würde hinzufügen Array von Gruppen-IDs , um eine Liste von Gruppen zu erhalten, denen der Benutzer folgt. Anstatt also einen Entity-Follower zu haben, würde ich nur Benutzer mit einem Array-Feld haben, das eine Liste von Group-Ids enthält.
  • ItemGroups: Ich würde diese Entität auch entfernen. Stattdessen würde ich ein Array von Item-IDs in der Group-Entität und ein Array von Group-IDs in der Item-Entität verwenden.

Das ist es im Grunde. Sie können das tun, was Sie in Ihrem Anwendungsfall beschrieben haben. Das Design ist einfacher und genauer in dem Sinne, dass es die Designentscheidungen einer dokumentenbasierten Datenbank widerspiegelt.

Hinweise :

  • Sie können Indizes für Array-Felder in MongoDB definieren. Siehe zum Beispiel Multikey-Indizes .
  • Seien Sie vorsichtig bei der Verwendung von Indizes für Array-Felder. Sie müssen Ihren Anwendungsfall verstehen, um zu entscheiden, ob dies sinnvoll ist oder nicht. Siehe diesen Artikel . Da Sie nur auf ObjectIds verweisen, dachte ich, Sie könnten es versuchen, aber es könnte andere Fälle geben, in denen es besser ist, das Design zu ändern.
  • Beachten Sie auch, dass das ID-Feld _id eine MongoDB ist spezifischer Feldtyp der ObjectID, die als Primärschlüssel verwendet wird. Um auf die IDs zuzugreifen, können Sie sich darauf beziehen, z.B. B. user.id, group.id usw. Sie können einen Index verwenden, um die Eindeutigkeit gemäß dieser Frage sicherzustellen.

Ihr Schemadesign könnte so aussehen:

Was Ihre anderen Fragen betrifft

  

Gibt es ein empfohlenes Maximum für Array-Längen, bevor überhaupt Leistungsprobleme auftreten?

Die Antwort ist in MongoDB die Dokumentgröße ist auf 16 MB begrenzt und es gibt jetzt Möglichkeiten wie man das umgehen kann. Allerdings werden 16 MB als ausreichend angesehen; Wenn Sie die 16 MB drücken, muss Ihr Design verbessert werden. Siehe hier für Informationen, Abschnitt Dokumentgrößenbeschränkung.

  

Ich denke, mit dem folgenden Entwurf könnte ein echtes Leistungsproblem entstehen, wenn ich alle Gruppen erhalten möchte, die ein Benutzer für ein bestimmtes Element verfolgt (basierend auf der Benutzer-ID und der Element-ID) ...

Ich würde es so machen. Beachten Sie, wie "einfacher" es klingt, wenn Sie MongoDB verwenden.

  1. Erhalte das Element des Benutzers
  2. erhält Gruppen, die auf dieses Element verweisen

Ich wäre eher besorgt, wenn die Arrays sehr groß werden und Sie Indizes für sie verwenden. Dies könnte insgesamt die Schreiboperationen für das / die jeweilige (n) Dokument (e) verlangsamen. Vielleicht nicht so sehr in deinem Fall, aber nicht ganz sicher.

    
Elyasin 16.02.2015 02:41
quelle
2

Leider sind NoSQL-Datenbanken in diesem Fall nicht geeignet. Ihr Datenmodell scheint exakt relational zu sein. Laut der MongoDB-Dokumentation können wir nur diese durchführen und nur diese .

Es gibt einige Praktiken . MongoDB empfiehlt uns, Followers collection zu verwenden, um zu ermitteln, welcher Benutzer welcher Gruppe bei guter Leistung folgt und umgekehrt. Auf der Folie 14 finden Sie auf dieser Seite am besten den Fall auf dieser Seite . Aber ich denke, die Folien können ausgewählt werden, wenn Sie jedes Ergebnis auf einer anderen Seite erhalten möchten. Zum Beispiel; Sie sind ein Twitter-Nutzer und wenn Sie auf die Schaltfläche followers klicken, sehen Sie alle Ihre Follower. Und dann klickst du auf einen Follower-Namen, dann siehst du die Nachrichten des Follower und was immer du sehen kannst. Wie wir sehen können, arbeiten alle Schritt für Schritt . No benötigt eine relationale Abfrage .

  

Ich glaube, dass Sie aufgrund von Leistungsproblemen, wenn das Dokument aufgrund seiner expandierenden Größe verschoben werden muss, keine ungebundenen Arrays verwenden sollten (wobei das Limit unbekannt ist). (Gibt es ein empfohlenes Maximum für Array-Längen, bevor Leistungsprobleme auftreten?)

Ja, du hast Recht. Ссылка . Aber wenn Sie etwa hundert Elemente in Ihrem Array haben, gibt es kein Problem. Wenn Sie feste Größe einige Daten Tausende haben, können Sie sie in Ihre relationalen Sammlungen als Array einbetten. Und Sie können Ihre indizierten eingebetteten Dokumentfelder schnell abfragen.

Nach meiner bescheidenen Meinung sollten Sie Hunderttausende Testdaten erstellen und überprüfen Sie die Leistung von eingebetteten Dokumenten und Arrays, die für Ihren Fall geeignet sind. Vergessen Sie nicht, Indizes zu erstellen, die Ihren Abfragen entsprechen. Sie können versuchen, Dokumentverweise auf Ihrer Website zu verwenden Tests. Nach den Tests, wenn Sie die Leistung der Ergebnisse mögen, gehen Sie voran ..

Sie haben versucht, group_id Datensätze zu finden, denen ein bestimmter Benutzer folgt, und dann haben Sie versucht, einen bestimmten Artikel mit diesen group_id zu finden. Wäre es möglich, dass Item_Groups und Followers Sammlungen eine viele-zu-viele Beziehung haben? Wenn dies der Fall ist, wird die Viele-zu-Viele-Beziehung von NoSQL-Datenbanken nicht unterstützt.

Gibt es eine Chance, dass Sie Ihre Datenbank in MySQL ändern können?

Wenn ja, sollten Sie dies überprüfen.

%Vor%

Wenn Sie an Node.js arbeiten, können Sie Ссылка und Ссылка

Viel Glück ...

    
efkan 17.02.2015 08:33
quelle