Echtzeitabfragen im großen Maßstab verstehen
In diesem Dokument finden Sie Informationen zum Skalieren Ihrer serverlosen App über Tausende von Vorgängen pro Sekunde oder Hunderttausende von gleichzeitigen Nutzern hinaus. Dieses Dokument enthält weiterführende Themen, die Ihnen helfen sollen, das System im Detail zu verstehen. Wenn Sie Firestore gerade erst kennenlernen, sollten Sie stattdessen die Kurzanleitung lesen.
Firestore und die mobilen/Web-SDKs von Firebase bieten ein leistungsstarkes Modell für die Entwicklung serverloser Apps, bei denen clientseitiger Code direkt auf die Datenbank zugreift. Mit den SDKs können Clients in Echtzeit auf Datenaktualisierungen warten. Mit Echtzeit-Updates können Sie reaktionsschnelle Apps ohne Serverinfrastruktur erstellen. Es ist zwar sehr einfach, etwas zum Laufen zu bringen, aber es ist hilfreich, die Einschränkungen in den Systemen zu verstehen, aus denen Firestore besteht, damit Ihre serverlose App skaliert und bei steigendem Traffic gut funktioniert.
In den folgenden Abschnitten finden Sie Tipps zur Skalierung Ihrer App.
Datenbankstandort in der Nähe Ihrer Nutzer auswählen
Das folgende Diagramm zeigt die Architektur einer Echtzeit-App:
Wenn eine App, die auf dem Gerät eines Nutzers (mobil oder Web) ausgeführt wird, eine Verbindung zu Firestore herstellt, wird die Verbindung an einen Firestore-Frontend-Server in derselben Region weitergeleitet, in der sich Ihre Datenbank befindet. Wenn sich Ihre Datenbank beispielsweise in us-east1
befindet, wird die Verbindung auch zu einem Firestore-Frontend in us-east1
hergestellt. Diese Verbindungen sind langlebig und bleiben so lange geöffnet, bis sie von der App explizit geschlossen werden. Das Frontend liest Daten aus den zugrunde liegenden Firestore-Speichersystemen.
Die Entfernung zwischen dem physischen Standort eines Nutzers und dem Standort der Firestore-Datenbank wirkt sich auf die Latenz aus, die der Nutzer erlebt. Wenn beispielsweise ein Nutzer in Indien eine App verwendet, die mit einer Datenbank in einer Google Cloud -Region in Nordamerika kommuniziert, kann die App langsamer und weniger reaktionsschnell sein, als wenn sich die Datenbank näher befindet, z. B. in Indien oder in einem anderen Teil Asiens.
Einhaltung von Zuverlässigkeitsvorgaben
Die folgenden Themen verbessern oder beeinträchtigen die Zuverlässigkeit Ihrer App:
Offlinemodus aktivieren
Die Firebase-SDKs bieten Offlinedatenpersistenz. Wenn die App auf dem Gerät des Nutzers keine Verbindung zu Firestore herstellen kann, bleibt sie nutzbar, da sie mit lokal im Cache gespeicherten Daten arbeitet. So ist der Datenzugriff auch dann gewährleistet, wenn Nutzer eine instabile Internetverbindung haben oder den Zugriff für mehrere Stunden oder Tage vollständig verlieren. Weitere Informationen zum Offlinemodus finden Sie unter Offlinedaten aktivieren.
Automatische Wiederholungsversuche
Die Firebase SDKs kümmern sich um das Wiederholen von Vorgängen und das Wiederherstellen unterbrochener Verbindungen. So lassen sich vorübergehende Fehler umgehen, die durch das Neustarten von Servern oder Netzwerkprobleme zwischen dem Client und der Datenbank verursacht werden.
Zwischen regionalen und multiregionalen Standorten wählen
Bei der Auswahl zwischen regionalen und multiregionalen Standorten müssen einige Aspekte berücksichtigt werden. Der Hauptunterschied besteht darin, wie Daten repliziert werden. Dies wirkt sich auf die Verfügbarkeitsgarantien Ihrer App aus. Eine multiregionale Instanz bietet eine höhere Zuverlässigkeit und erhöht die Langlebigkeit Ihrer Daten. Der Nachteil sind jedoch die Kosten.
Echtzeit-Abfragesystem
Mit Echtzeitabfragen, auch Snapshot-Listener genannt, kann die App Änderungen in der Datenbank überwachen und Benachrichtigungen mit niedriger Latenz erhalten, sobald sich die Daten ändern. Eine App kann dasselbe Ergebnis erzielen, indem sie die Datenbank regelmäßig nach Updates abfragt. Das ist jedoch oft langsamer, teurer und erfordert mehr Code. Beispiele für das Einrichten und Verwenden von Echtzeitabfragen finden Sie unter Echtzeitaktualisierungen abrufen. In den folgenden Abschnitten wird genauer beschrieben, wie Snapshot-Listener funktionieren. Außerdem werden einige Best Practices für das Skalieren von Echtzeitabfragen bei gleichbleibender Leistung beschrieben.
Stellen Sie sich zwei Nutzer vor, die über eine Messaging-App, die mit einem der mobilen SDKs erstellt wurde, eine Verbindung zu Firestore herstellen.
Client A schreibt in die Datenbank, um Dokumente in einer Sammlung namens chatroom
hinzuzufügen und zu aktualisieren:
collection chatroom:
document message1:
from: 'Sparky'
message: 'Welcome to Firestore!'
document message2:
from: 'Santa'
message: 'Presents are coming'
Client B wartet mit einem Snapshot-Listener auf Aktualisierungen in derselben Sammlung. Kunde B wird sofort benachrichtigt, wenn jemand eine neue Nachricht erstellt. Das folgende Diagramm zeigt die Architektur eines Snapshot-Listeners:
Die folgende Abfolge von Ereignissen findet statt, wenn Client B einen Snapshot-Listener mit der Datenbank verbindet:
- Client B öffnet eine Verbindung zu Firestore und registriert einen Listener, indem er über das Firebase SDK einen Aufruf an
onSnapshot(collection("chatroom"))
sendet. Dieser Zuhörer kann stundenlang aktiv bleiben. - Das Firestore-Frontend fragt das zugrunde liegende Speichersystem ab, um das Dataset zu initialisieren. Es wird die gesamte Ergebnismenge der übereinstimmenden Dokumente geladen. Wir bezeichnen dies als Polling-Abfrage. Das System wertet dann die Firebase-Sicherheitsregeln der Datenbank aus, um zu prüfen, ob der Nutzer auf diese Daten zugreifen kann. Wenn der Nutzer autorisiert ist, gibt die Datenbank die Daten an den Nutzer zurück.
- Die Anfrage von Client B wechselt dann in den Wiedergabemodus. Der Listener registriert sich bei einem Abo-Handler und wartet auf Aktualisierungen der Daten.
- Client A sendet jetzt einen Schreibvorgang, um ein Dokument zu ändern.
- Die Datenbank überträgt die Dokumentänderung in ihr Speichersystem.
- Das System führt dieselbe Aktualisierung transaktional in einem internen Änderungslog aus. Das Änderungsprotokoll legt eine strenge Reihenfolge der Änderungen fest, wie sie erfolgen.
- Das Änderungs-Log leitet die aktualisierten Daten dann an einen Pool von Abo-Handlern weiter.
- Ein Reverse-Query-Matcher wird ausgeführt, um zu prüfen, ob das aktualisierte Dokument mit aktuell registrierten Snapshot-Listenern übereinstimmt. In diesem Beispiel entspricht das Dokument dem Snapshot-Listener von Client B. Wie der Name schon sagt, können Sie sich den Reverse Query Matcher als normale Datenbankabfrage vorstellen, die jedoch in umgekehrter Richtung erfolgt. Anstatt Dokumente zu durchsuchen, um diejenigen zu finden, die einer Abfrage entsprechen, werden effizient Abfragen durchsucht, um diejenigen zu finden, die einem eingehenden Dokument entsprechen. Wenn eine Übereinstimmung gefunden wird, leitet das System das entsprechende Dokument an die Snapshot-Listener weiter. Anschließend werden die Firebase-Sicherheitsregeln der Datenbank ausgewertet, um sicherzustellen, dass nur autorisierte Nutzer die Daten erhalten.
- Das System leitet das Dokumentupdate an das SDK auf dem Gerät von Client B weiter und der
onSnapshot
-Callback wird ausgelöst. Wenn die lokale Persistenz aktiviert ist, wendet das SDK das Update auch auf den lokalen Cache an.
Ein wichtiger Teil der Skalierbarkeit von Firestore hängt von der Verteilung des Changelogs auf die Abohandler und die Frontend-Server ab. Durch den Fan-out kann eine einzelne Datenänderung effizient weitergegeben werden, um Millionen von Echtzeitabfragen und verbundenen Nutzern zu bedienen. Durch die Ausführung vieler Replikate aller dieser Komponenten in mehreren Zonen (oder mehreren Regionen bei einer multiregionalen Bereitstellung) erreicht Firestore eine hohe Verfügbarkeit und Skalierbarkeit.
Alle Lesevorgänge, die über mobile und Web-SDKs ausgegeben werden, folgen dem oben beschriebenen Modell. Sie führen eine Polling-Abfrage gefolgt vom Listen-Modus aus, um die Konsistenz zu gewährleisten. Das gilt auch für Echtzeit-Listener, Aufrufe zum Abrufen eines Dokuments und Einmalabfragen. Einzelne Dokumentabrufe und One-Shot-Abfragen können als kurzlebige Snapshot-Listener betrachtet werden, die ähnliche Leistungsbeschränkungen haben.
Best Practices für die Skalierung von Echtzeitabfragen anwenden
Wenden Sie die folgenden Best Practices an, um skalierbare Echtzeitabfragen zu entwerfen.
Hohen Schreibtraffic im System verstehen
In diesem Abschnitt erfahren Sie, wie das System auf eine steigende Anzahl von Schreibanfragen reagiert.
Die Firestore-Changelogs, die die Echtzeitabfragen ermöglichen, werden automatisch horizontal skaliert, wenn der Schreibtraffic zunimmt. Wenn die Schreibgeschwindigkeit für eine Datenbank über das hinaus ansteigt, was ein einzelner Server verarbeiten kann, wird das Änderungslog auf mehrere Server aufgeteilt und die Abfrageverarbeitung beginnt, Daten von mehreren Abfrage-Handlern anstelle von einem zu verwenden. Aus Sicht des Clients und des SDK ist das alles transparent und es sind keine Maßnahmen von der App erforderlich, wenn Splits auftreten. Das folgende Diagramm zeigt, wie Echtzeitabfragen skaliert werden:
Durch die automatische Skalierung können Sie den Schreibtraffic unbegrenzt erhöhen. Wenn der Traffic jedoch zunimmt, kann es einige Zeit dauern, bis das System reagiert. Halten Sie sich an die Empfehlungen der 5-5-5-Regel, um das Erstellen eines Schreib-Hotspots zu vermeiden. Key Visualizer ist ein nützliches Tool zur Analyse von Schreib-Hotspots.
Viele Apps haben ein vorhersehbares organisches Wachstum, das Firestore ohne Vorsichtsmaßnahmen bewältigen kann. Bei Batcharbeitslasten wie dem Importieren eines großen Datensatzes kann die Anzahl der Schreibvorgänge jedoch zu schnell ansteigen. Achten Sie beim Entwerfen Ihrer App darauf, woher der Schreibtraffic stammt.
Zusammenwirken von Schreib- und Lesevorgängen
Das Echtzeit-Abfragesystem kann als Pipeline betrachtet werden, die Schreibvorgänge mit Lesern verbindet. Wenn ein Dokument erstellt, aktualisiert oder gelöscht wird, wird die Änderung vom Speichersystem an die aktuell registrierten Listener weitergegeben. Die Changelog-Struktur von Firestore garantiert eine starke Konsistenz. Das bedeutet, dass Ihre App niemals Benachrichtigungen über Updates erhält, die im Vergleich dazu, wann die Datenbank die Datenänderungen übernommen hat, nicht in der richtigen Reihenfolge sind. Das vereinfacht die App-Entwicklung, da Grenzfälle im Zusammenhang mit der Datenkonsistenz entfallen.
Diese verbundene Pipeline bedeutet, dass sich ein Schreibvorgang, der Hotspots oder Sperrkonflikte verursacht, negativ auf Lesevorgänge auswirken kann. Wenn Schreibvorgänge fehlschlagen oder gedrosselt werden, kann ein Lesevorgang blockiert werden, weil er auf konsistente Daten aus dem Änderungslog wartet. Wenn dies in Ihrer App passiert, kann es zu langsamen Schreibvorgängen und damit verbundenen langsamen Antwortzeiten für Abfragen kommen. Um dieses Problem zu vermeiden, müssen Sie Hotspots umgehen.
Dokumente und Schreibvorgänge klein halten
Wenn Sie Apps mit Snapshot-Listenern erstellen, möchten Sie in der Regel, dass Nutzer schnell über Datenänderungen informiert werden. Versuchen Sie, die Dinge klein zu halten. Das System kann kleine Dokumente mit Dutzenden von Feldern sehr schnell verarbeiten. Die Verarbeitung großer Dokumente mit Hunderten von Feldern und großen Datenmengen dauert länger.
Ebenso sollten Sie kurze, schnelle Commit- und Schreibvorgänge bevorzugen, um die Latenz niedrig zu halten. Große Batches können aus Sicht des Writers einen höheren Durchsatz ermöglichen, aber die Benachrichtigungszeit für Snapshot-Listener verlängern. Das ist oft kontraintuitiv im Vergleich zu anderen Datenbanksystemen, in denen Sie möglicherweise Batching verwenden, um die Leistung zu verbessern.
Effiziente Listener verwenden
Wenn die Schreibvorgänge für Ihre Datenbank zunehmen, verteilt Firestore die Datenverarbeitung auf viele Server. Der Sharding-Algorithmus von Firestore versucht, Daten aus derselben Sammlung oder Sammlungsgruppe auf demselben Changelog-Server zu platzieren. Das System versucht, den möglichen Schreibdurchsatz zu maximieren und gleichzeitig die Anzahl der Server, die an der Verarbeitung einer Anfrage beteiligt sind, so gering wie möglich zu halten.
Bestimmte Muster können jedoch weiterhin zu suboptimalem Verhalten für Snapshot-Listener führen. Wenn Ihre App beispielsweise die meisten ihrer Daten in einer großen Sammlung speichert, muss der Listener möglicherweise eine Verbindung zu vielen Servern herstellen, um alle benötigten Daten zu empfangen. Das gilt auch dann, wenn Sie einen Abfragefilter anwenden. Wenn Sie sich mit vielen Servern verbinden, steigt das Risiko langsamer Antworten.
Um diese langsameren Antworten zu vermeiden, sollten Sie Ihr Schema und Ihre App so gestalten, dass das System Listener bereitstellen kann, ohne auf viele verschiedene Server zugreifen zu müssen. Möglicherweise ist es am besten, Ihre Daten in kleinere Sammlungen mit niedrigeren Schreibraten aufzuteilen.
Das ist vergleichbar mit Leistungsabfragen in einer relationalen Datenbank, für die vollständige Tabellenscans erforderlich sind. In einer relationalen Datenbank entspricht eine Abfrage, für die ein vollständiger Tabellenscan erforderlich ist, einem Snapshot-Listener, der eine Sammlung mit hoher Churn-Rate überwacht. Sie ist möglicherweise langsamer als eine Abfrage, die die Datenbank mit einem spezifischeren Index ausführen kann. Eine Abfrage mit einem spezifischeren Index ist wie ein Snapshot-Listener, der ein einzelnes Dokument oder eine Sammlung überwacht, die sich seltener ändert. Sie sollten Ihre App einem Lasttest unterziehen, um das Verhalten und die Anforderungen Ihres Anwendungsfalls besser zu verstehen.
Abfrageintervalle kurz halten
Ein weiterer wichtiger Aspekt von reaktionsschnellen Echtzeitabfragen ist, dass die Polling-Abfrage zum Bootstrapping der Daten schnell und effizient ist. Wenn ein neuer Snapshot-Listener zum ersten Mal eine Verbindung herstellt, muss er das gesamte Ergebnis-Set laden und an das Gerät des Nutzers senden. Langsame Abfragen machen Ihre App weniger reaktionsschnell. Dazu gehören beispielsweise Abfragen, mit denen versucht wird, viele Dokumente zu lesen, oder Abfragen, die nicht die entsprechenden Indexe verwenden.
Unter bestimmten Umständen kann ein Listener auch vom Listening- in den Polling-Zustand zurückkehren. Das geschieht automatisch und ist für die SDKs und Ihre App transparent. Die folgenden Bedingungen können einen Polling-Status auslösen:
- Das System gleicht ein Änderungsprotokoll neu ab, wenn sich die Last ändert.
- Hotspots führen zu fehlgeschlagenen oder verzögerten Schreibvorgängen in der Datenbank.
- Vorübergehende Serverneustarts wirken sich vorübergehend auf Listener aus.
Wenn Ihre Polling-Abfragen schnell genug sind, wird der Polling-Status für die Nutzer Ihrer App transparent.
Langlebige Listener bevorzugen
Das Öffnen und Aufrechterhalten von Listenern für einen möglichst langen Zeitraum ist oft die kostengünstigste Methode, um eine App zu erstellen, die Firestore verwendet. Bei der Verwendung von Firestore wird Ihnen die Anzahl der an Ihre App zurückgegebenen Dokumente in Rechnung gestellt, nicht die Aufrechterhaltung einer offenen Verbindung. Ein Snapshot-Listener mit langer Lebensdauer liest während seiner gesamten Lebensdauer nur die Daten, die zum Ausführen der Abfrage erforderlich sind. Dazu gehört ein anfänglicher Abfragevorgang, gefolgt von Benachrichtigungen, wenn sich die Daten tatsächlich ändern. Bei einmaligen Abfragen werden dagegen Daten noch einmal gelesen, die sich möglicherweise seit der letzten Ausführung der Abfrage durch die App nicht geändert haben.
Wenn Ihre App eine hohe Datenrate verarbeiten muss, sind Snapshot-Listener möglicherweise nicht geeignet. Wenn in Ihrem Anwendungsfall beispielsweise über einen längeren Zeitraum viele Dokumente pro Sekunde über eine Verbindung übertragen werden, sind möglicherweise einmalige Abfragen, die seltener ausgeführt werden, besser geeignet.