Wie effizient kann Meteor sein, wenn es eine riesige Sammlung unter vielen Kunden teilt?


100

Stellen Sie sich folgenden Fall vor:

  • 1.000 Clients sind mit einer Meteor-Seite verbunden, auf der der Inhalt der Sammlung "Somestuff" angezeigt wird.

  • "Somestuff" ist eine Sammlung mit 1.000 Artikeln.

  • Jemand fügt einen neuen Artikel in die "Somestuff" -Sammlung ein

Was wird passieren:

  • Alle Meteor.Collections auf Clients werden aktualisiert, dh die an alle weitergeleitete Einfügung (dh eine an 1.000 Clients gesendete Einfügungsnachricht).

Wie hoch sind die CPU-Kosten für den Server, um zu bestimmen, welcher Client aktualisiert werden muss?

Ist es richtig, dass nur der eingefügte Wert an die Clients weitergeleitet wird und nicht die gesamte Liste?

Wie funktioniert das im wirklichen Leben? Gibt es Benchmarks oder Experimente dieser Größenordnung?

Antworten:


119

Die kurze Antwort lautet, dass nur neue Daten über das Kabel gesendet werden. So funktioniert das.

Es gibt drei wichtige Teile des Meteor-Servers, die Abonnements verwalten: die Veröffentlichungsfunktion , die die Logik für die vom Abonnement bereitgestellten Daten definiert; der Mongo-Treiber , der die Datenbank auf Änderungen überwacht; und das Zusammenführungsfeld , in dem alle aktiven Abonnements eines Clients zusammengefasst und über das Netzwerk an den Client gesendet werden.

Funktionen veröffentlichen

Jedes Mal, wenn ein Meteor-Client eine Sammlung abonniert, führt der Server eine Veröffentlichungsfunktion aus . Die Aufgabe der Veröffentlichungsfunktion besteht darin, den Satz von Dokumenten herauszufinden, über den der Client verfügen sollte, und jede Dokumenteigenschaft in das Zusammenführungsfeld zu senden. Es wird einmal für jeden neuen abonnierenden Client ausgeführt. Sie können beliebiges JavaScript in die Veröffentlichungsfunktion einfügen, z. B. eine beliebig komplexe Zugriffssteuerung mit this.userId. Die veröffentlichen Funktionsdaten in die Mergebox senden durch den Aufruf this.added, this.changedund this.removed. Weitere Informationen finden Sie in der vollständigen Veröffentlichungsdokumentation .

Die meisten veröffentlichen Funktionen müssen mit dem Low-Level nicht verarschen added, changedund removedAPI, though. Wenn eine Funktion gibt einen Mongo Cursor veröffentlicht, verbindet der Meteor - Server automatisch die Ausgabe des Mongo Treiber ( insert, updateund removedRückrufe) mit dem Eingang der Merge - Box ( this.added, this.changedund this.removed). Es ist ziemlich ordentlich, dass Sie alle Berechtigungsprüfungen in einer Veröffentlichungsfunktion im Voraus durchführen und dann den Datenbanktreiber direkt mit dem Zusammenführungsfeld verbinden können, ohne dass ein Benutzercode im Weg steht. Und wenn die automatische Veröffentlichung aktiviert ist, wird auch dieses kleine bisschen ausgeblendet: Der Server richtet automatisch eine Abfrage für alle Dokumente in jeder Sammlung ein und schiebt sie in das Zusammenführungsfeld.

Andererseits sind Sie nicht darauf beschränkt, Datenbankabfragen zu veröffentlichen. Sie können beispielsweise eine Veröffentlichungsfunktion schreiben, die eine GPS-Position von einem Gerät in einem liest Meteor.setIntervaloder eine ältere REST-API von einem anderen Webdienst abfragt. In diesen Fällen würden Sie Änderungen an der Mergebox emittieren durch die Low-Level - Aufruf added, changedund removedDDP - API.

Der Mongo-Fahrer

Die Aufgabe des Mongo-Treibers besteht darin, die Mongo-Datenbank auf Änderungen an Live-Abfragen zu überwachen. Diese Abfragen laufen kontinuierlich und Rück Updates wie die Ergebnisse Änderung durch den Aufruf added, removedund changedRückrufe.

Mongo ist keine Echtzeitdatenbank. Also fragt der Fahrer ab. Es speichert eine speicherinterne Kopie des letzten Abfrageergebnisses für jede aktive Live-Abfrage. An jedem Abfragezyklus vergleicht es das neue Ergebnis mit dem vorherigen gespeicherten Ergebnis der Berechnung des Mindestsatzes an added, removedund changed Ereignisse, die den Unterschied beschreiben. Wenn mehrere Anrufer Rückrufe für dieselbe Live-Abfrage registrieren, überwacht der Treiber nur eine Kopie der Abfrage und ruft jeden registrierten Rückruf mit demselben Ergebnis auf.

Jedes Mal, wenn der Server eine Sammlung aktualisiert, berechnet der Treiber jede Live-Abfrage in dieser Sammlung neu (zukünftige Versionen von Meteor stellen eine Skalierungs-API zur Verfügung, um zu begrenzen, welche Live-Abfragen bei der Aktualisierung neu berechnet werden.) Der Treiber fragt auch jede Live-Abfrage in einem 10-Sekunden-Timer ab Abrufen von Out-of-Band-Datenbankaktualisierungen, die den Meteor-Server umgangen haben.

Das Zusammenführungsfeld

Die Aufgabe der Merge - Box ist , die Ergebnisse zu kombinieren ( added, changedund removed Anrufe) alle ein aktiven veröffentlichen Funktionen in einen einzigen Datenstrom des Kunden. Für jeden verbundenen Client gibt es ein Zusammenführungsfeld. Es enthält eine vollständige Kopie des Minimongo-Cache des Clients.

In Ihrem Beispiel mit nur einem Abonnement ist das Zusammenführungsfeld im Wesentlichen ein Durchgang. Eine komplexere App kann jedoch mehrere Abonnements haben, die sich möglicherweise überschneiden. Wenn zwei Abonnements dasselbe Attribut für dasselbe Dokument festlegen, entscheidet das Zusammenführungsfeld, welcher Wert Priorität hat, und sendet diesen nur an den Client. Wir haben die API zum Festlegen der Abonnementpriorität noch nicht verfügbar gemacht. Derzeit wird die Priorität durch die Reihenfolge bestimmt, in der der Client Datensätze abonniert. Das erste Abonnement, das ein Client abschließt, hat die höchste Priorität, das zweite Abonnement hat die nächsthöhere Priorität und so weiter.

Da das Zusammenführungsfeld den Status des Clients enthält, kann es die Mindestdatenmenge senden, um jeden Client auf dem neuesten Stand zu halten, unabhängig davon, welche Veröffentlichungsfunktion ihn speist.

Was passiert bei einem Update?

Jetzt haben wir die Voraussetzungen für Ihr Szenario geschaffen.

Wir haben 1.000 verbundene Kunden. Jeder hat dieselbe Live-Mongo-Abfrage ( Somestuff.find({})) abonniert . Da die Abfrage für jeden Client gleich ist, führt der Treiber nur eine Live-Abfrage aus. Es gibt 1.000 aktive Zusammenführungsfelder. Die Veröffentlichungsfunktion jedes Clients registrierte eine added, changedund removedin dieser Live-Abfrage, die in eines der Zusammenführungsfelder eingespeist wird. Mit den Zusammenführungsfeldern ist nichts anderes verbunden.

Zuerst der Mongo-Fahrer. Wenn einer der Clients ein neues Dokument einfügt Somestuff, wird eine Neuberechnung ausgelöst. Der Mongo-Treiber führt die Abfrage für alle Dokumente erneut aus Somestuff, vergleicht das Ergebnis mit dem vorherigen Ergebnis im Speicher, stellt fest, dass ein neues Dokument vorhanden ist, und ruft jeden der 1.000 registrierten insertRückrufe auf.

Als nächstes werden die Veröffentlichungsfunktionen ausgeführt. Hier passiert sehr wenig: Jeder der 1.000 insertRückrufe schiebt Daten durch Aufrufen in die Zusammenführungsbox added.

Schließlich vergleicht jedes Zusammenführungsfeld diese neuen Attribute mit seiner speicherinternen Kopie des Client-Cache. In jedem Fall wird festgestellt, dass die Werte noch nicht auf dem Client vorhanden sind und keinen vorhandenen Wert beschatten. Das Merge-Box sendet also eine DDP- DATANachricht über die SockJS-Verbindung an seinen Client und aktualisiert seine serverseitige In-Memory-Kopie.

Die Gesamt-CPU-Kosten sind die Kosten für die Differenzierung einer Mongo-Abfrage sowie die Kosten für 1.000 Zusammenführungsfelder, die den Status ihrer Clients überprüfen und eine neue Nutzlast für DDP-Nachrichten erstellen. Die einzigen Daten, die über die Leitung fließen, sind ein einzelnes JSON-Objekt, das an jeden der 1.000 Clients gesendet wird und dem neuen Dokument in der Datenbank entspricht, sowie eine RPC-Nachricht an den Server vom Client, der die ursprüngliche Einfügung vorgenommen hat.

Optimierungen

Folgendes haben wir definitiv geplant.

  • Effizienterer Mongo-Fahrer. Wir haben den Treiber in 0.5.1 so optimiert , dass nur ein einziger Beobachter pro eindeutiger Abfrage ausgeführt wird.

  • Nicht jede DB-Änderung sollte eine Neuberechnung einer Abfrage auslösen. Wir können einige automatisierte Verbesserungen vornehmen, aber der beste Ansatz ist eine API, mit der der Entwickler angeben kann, welche Abfragen erneut ausgeführt werden müssen. Für einen Entwickler ist es beispielsweise offensichtlich, dass das Einfügen einer Nachricht in einen Chatroom eine Live-Abfrage für die Nachrichten in einem zweiten Raum nicht ungültig machen sollte.

  • Der Mongo-Treiber, die Veröffentlichungsfunktion und das Zusammenführungsfeld müssen nicht im selben Prozess oder sogar auf demselben Computer ausgeführt werden. Einige Anwendungen führen komplexe Live-Abfragen aus und benötigen mehr CPU, um die Datenbank zu überwachen. Andere haben nur wenige unterschiedliche Abfragen (stellen Sie sich eine Blog-Engine vor), aber möglicherweise viele verbundene Clients - diese benötigen mehr CPU für Merge-Boxen. Durch die Trennung dieser Komponenten können wir jedes Stück unabhängig skalieren.

  • Viele Datenbanken unterstützen Trigger, die beim Aktualisieren einer Zeile ausgelöst werden und die alten und neuen Zeilen bereitstellen. Mit dieser Funktion könnte ein Datenbanktreiber einen Trigger registrieren, anstatt nach Änderungen abzufragen.


Gibt es ein Beispiel für die Verwendung von Meteor.publish zum Veröffentlichen von Nicht-Cursor-Daten? Wie Ergebnisse einer in der Antwort erwähnten Legacy-Rest-API?
Tony

@ Tony: Es ist in der Dokumentation. Überprüfen Sie das Beispiel für die Raumzählung.
Mitar

Es ist erwähnenswert, dass in Versionen 0.7, 0.7.1, 0.7.2 Meteor zu OpLog Treiber für die meisten Abfragen beachten geschaltet (Ausnahmen sind skip, $nearund $whereenthalten Abfragen) , die wesentlich effizienter in der CPU - Auslastung, Netzwerk - Bandbreite und ermöglicht Anwendung horizontale Skalierung Server.
Imslavko

Was ist, wenn nicht jeder Benutzer die gleichen Daten sieht? 1. Sie haben verschiedene Themen abonniert .2. Sie haben unterschiedliche Rollen, sodass es innerhalb desselben Hauptthemas einige Nachrichten gibt, die sie nicht erreichen sollen.
Tgkprog

@debergalis bezüglich der Ungültigmachung des Caches, vielleicht finden Sie Ideen aus meinem Artikel vanisoft.pl/~lopuszanski/public/cache_invalidation.pdf lohnenswert
qbolec

29

Nach meiner Erfahrung ist die Verwendung vieler Clients mit einer großen Sammlung in Meteor ab Version 0.7.0.1 im Wesentlichen nicht mehr möglich. Ich werde versuchen zu erklären warum.

Wie im obigen Beitrag und auch unter https://github.com/meteor/meteor/issues/1821 beschrieben , muss der Meteorserver eine Kopie der veröffentlichten Daten für jeden Client im Zusammenführungsfeld aufbewahren . Dies ermöglicht die Meteor-Magie, führt aber auch dazu, dass große gemeinsam genutzte Datenbanken wiederholt im Speicher des Knotenprozesses gespeichert werden. Selbst bei Verwendung einer möglichen Optimierung für statische Sammlungen wie in ( Gibt es eine Möglichkeit, Meteor mitzuteilen, dass eine Sammlung statisch ist (sich nie ändern wird? ) ? ) Trat ein großes Problem mit der CPU- und Speichernutzung des Knotenprozesses auf.

In unserem Fall haben wir für jeden Kunden eine Sammlung von 15.000 Dokumenten veröffentlicht, die vollständig statisch war. Das Problem besteht darin, dass das Kopieren dieser Dokumente in die Zusammenführungsbox eines Clients (im Speicher) bei der Verbindung den Knotenprozess für fast eine Sekunde auf 100% CPU brachte und zu einer großen zusätzlichen Speichernutzung führte. Dies ist von Natur aus nicht skalierbar, da jeder verbindende Client den Server in die Knie zwingt (und gleichzeitige Verbindungen sich gegenseitig blockieren) und die Speichernutzung in der Anzahl der Clients linear ansteigt. In unserem Fall verursachte jeder Client eine zusätzliche Speicherauslastung von ~ 60 MB , obwohl die übertragenen Rohdaten nur etwa 5 MB betrugen.

In unserem Fall haben wir dieses Problem gelöst, da die Sammlung statisch war, indem wir alle Dokumente als .jsonDatei gesendet haben, die von nginx komprimiert wurde, und sie in eine anonyme Sammlung geladen haben, was nur zu einer Datenübertragung von ~ 1 MB ohne zusätzliche CPU führte oder Speicher im Knotenprozess und eine viel schnellere Ladezeit. Alle Operationen über diese Sammlung wurden unter Verwendung von _ids aus viel kleineren Veröffentlichungen auf dem Server durchgeführt, wodurch die meisten Vorteile von Meteor beibehalten wurden. Dadurch konnte die App auf viele weitere Clients skaliert werden. Da unsere App größtenteils schreibgeschützt ist, haben wir die Skalierbarkeit weiter verbessert, indem wir mehrere Meteor-Instanzen hinter nginx mit Lastenausgleich (allerdings mit einem einzelnen Mongo) ausgeführt haben, da jede Node-Instanz Single-Threaded ist.

Das Problem, große, beschreibbare Sammlungen für mehrere Clients freizugeben, ist jedoch ein technisches Problem, das von Meteor gelöst werden muss. Es gibt wahrscheinlich einen besseren Weg, als für jeden Client eine Kopie von allem aufzubewahren, aber dies erfordert einige ernsthafte Überlegungen als Problem mit verteilten Systemen. Die aktuellen Probleme der massiven CPU- und Speicherauslastung lassen sich einfach nicht skalieren.


@ Harry Oplog spielt in dieser Situation keine Rolle; Die Daten waren statisch.
Andrew Mao

Warum macht es keine Unterschiede der serverseitigen Minimongo-Kopien? Vielleicht hat sich das in 1.0 geändert? Ich meine, normalerweise sind sie die gleichen, von denen ich hoffe, dass sogar Funktionen, die es
zurückruft

@MistereeDevlord Unterschiedliche Änderungen und Caches von Client-Daten sind derzeit getrennt. Selbst wenn jeder die gleichen Daten hat und nur ein Diff benötigt wird, unterscheidet sich der Cache pro Client, da der Server sie nicht identisch behandeln kann. Dies könnte definitiv intelligenter als die bestehende Implementierung erfolgen.
Andrew Mao

@AndrewMao Wie stellen Sie sicher, dass die komprimierten Dateien geschützt sind, wenn Sie sie an den Client senden, dh nur ein angemeldeter Client kann darauf zugreifen?
FullStack

4

Das Experiment, mit dem Sie diese Frage beantworten können:

  1. Installieren Sie einen Testmeteor: meteor create --example todos
  2. Führen Sie es unter Webkit Inspector (WKI) aus.
  3. Untersuchen Sie den Inhalt der XHR- Nachrichten, die sich über das Kabel bewegen.
  4. Beachten Sie, dass die gesamte Sammlung nicht über den Draht bewegt wird.

Tipps zur Verwendung von WKI finden Sie in diesem Artikel . Es ist etwas veraltet, aber meistens noch gültig, besonders für diese Frage.


2
Eine Erklärung des Abfragemechanismus
cmather

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.