In diesem Dokument finden Sie eine Anleitung zum Skalieren Ihrer serverlosen App auf mehr als Tausende von Vorgängen pro Sekunde oder Hunderttausende von gleichzeitigen Nutzern. Dieses Dokument enthält erweiterte Themen, die Ihnen helfen, das System besser zu verstehen. Wenn Sie gerade erst mit Cloud Firestore beginnen, lesen Sie stattdessen die Kurzanleitung.
Cloud Firestore und die Firebase Mobile/Web SDKs 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 Daten in Echtzeit überwachen. Mit Echtzeitaktualisierungen können Sie responsive Apps erstellen, für die keine Serverinfrastruktur erforderlich ist. Es ist zwar sehr einfach, etwas in Betrieb zu nehmen, aber es ist hilfreich, die Einschränkungen der Systeme zu kennen, aus denen Cloud Firestore besteht, damit Ihre serverlose App skaliert und bei steigendem Traffic eine gute Leistung erbringt.
In den folgenden Abschnitten finden Sie Tipps zur Skalierung Ihrer App.
Speicherort der Datenbank 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 Cloud Firestore herstellt, wird die Verbindung zu einem Cloud 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 Cloud Firestore-Frontend in us-east1
hergestellt. Diese Verbindungen sind langlebig und bleiben offen, bis sie von der App explizit geschlossen werden. Das Frontend liest Daten aus den zugrunde liegenden Cloud Firestore-Speichersystemen.
Die Entfernung zwischen dem physischen Standort eines Nutzers und dem Speicherort der Cloud Firestore-Datenbank wirkt sich auf die Latenz aus, die der Nutzer erfährt. Ein Nutzer in Indien, dessen App mit einer Datenbank in einer Google Cloud-Region in Nordamerika kommuniziert, könnte beispielsweise feststellen, dass die App langsamer und weniger reaktionsschnell ist 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 wirken sich auf die Zuverlässigkeit Ihrer App aus:
Offlinemodus aktivieren
Die Firebase SDKs bieten eine Offlinedatenpersistenz. Wenn die App auf dem Gerät des Nutzers keine Verbindung zu Cloud Firestore herstellen kann, bleibt die App nutzbar, da sie mit lokal im Cache gespeicherten Daten arbeitet. So ist der Datenzugriff auch dann gewährleistet, wenn Nutzer eine schwankende 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 Wiederholungsversuche und die Wiederherstellung unterbrochener Verbindungen. So lassen sich vorübergehende Fehler vermeiden, die durch das Neustarten von Servern oder Netzwerkprobleme zwischen dem Client und der Datenbank verursacht werden.
Zwischen regionalen und multiregionalen Speicherorten wählen
Bei der Auswahl zwischen regionalen und multiregionalen Standorten gibt es mehrere Abwägungen. Der Hauptunterschied besteht darin, wie Daten repliziert werden. Dadurch werden die Verfügbarkeitsgarantien Ihrer App erhöht. Eine multiregionale Instanz bietet eine höhere Zuverlässigkeit beim Bereitstellen und erhöht die Langlebigkeit Ihrer Daten. Der Nachteil sind jedoch die Kosten.
Echtzeitabfragesystem
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 auf Updates prüft. Das ist jedoch oft langsamer, teurer und erfordert mehr Code. Beispiele zum Einrichten und Verwenden von Echtzeitabfragen finden Sie unter Echtzeitaktualisierungen abrufen. In den folgenden Abschnitten erfahren Sie mehr über die Funktionsweise von Snapshot-Listenern und einige Best Practices für die Skalierung von Echtzeitabfragen bei gleichzeitiger Beibehaltung der Leistung.
Angenommen, zwei Nutzer verbinden sich über eine Messaging-App, die mit einem der mobilen SDKs erstellt wurde, mit Cloud Firestore.
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 Cloud Firestore!'
document message2:
from: 'Santa'
message: 'Presents are coming'
Client B wartet mit einem Snapshot-Listener auf Aktualisierungen in derselben Sammlung. Kunde B erhält sofort eine Benachrichtigung, wenn jemand eine neue Nachricht erstellt. Das folgende Diagramm zeigt die Architektur eines Snapshot-Empfängers:
Wenn Client B einen Snapshot-Listener mit der Datenbank verbindet, geschieht Folgendes:
- Client B öffnet eine Verbindung zu Cloud Firestore und registriert einen Listener, indem er über das Firebase SDK einen Aufruf an
onSnapshot(collection("chatroom"))
sendet. Dieser Listener kann stundenlang aktiv bleiben. - Das Cloud Firestore-Frontend fragt das zugrunde liegende Speichersystem ab, um den Datensatz zu initialisieren. Es wird die gesamte Ergebnismenge der übereinstimmenden Dokumente geladen. Wir bezeichnen dies als Polling-Abfrage. Das System prüft dann die Firebase-Sicherheitsregeln der Datenbank, um zu bestätigen, dass der Nutzer auf diese Daten zugreifen kann. Wenn der Nutzer autorisiert ist, gibt die Datenbank die Daten an den Nutzer zurück.
- Die Abfrage von Client B wechselt dann in den Liefermodus. 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 an das Speichersystem.
- Transaktional speichert das System dieselbe Aktualisierung in einem internen Änderungslog. Das Änderungsprotokoll legt eine strenge Reihenfolge der Änderungen fest.
- Das Änderungs-Log verteilt die aktualisierten Daten an einen Pool von Abo-Handlern.
- Ein Reverse-Query-Matcher wird ausgeführt, um zu prüfen, ob das aktualisierte Dokument mit derzeit 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 umgekehrten Abfrageabgleich als eine normale Datenbankabfrage vorstellen, die aber in umgekehrter Reihenfolge ausgeführt wird. Anstatt nach Dokumenten zu suchen, die mit einer Suchanfrage übereinstimmen, werden effizient Suchanfragen nach solchen gesucht, die mit einem eingehenden Dokument übereinstimmen. Wenn eine Übereinstimmung gefunden wird, leitet das System das betreffende Dokument an die Snapshot-Listener weiter. Anschließend prüft das System die Firebase-Sicherheitsregeln der Datenbank, um sicherzustellen, dass nur autorisierte Nutzer die Daten erhalten.
- Das System leitet die Dokumentaktualisierung 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 Cloud Firestore hängt von der Verzweigung vom Änderungslog an die Abo-Handler und die Frontend-Server ab. Durch die Verzweigung kann eine einzelne Datenänderung effizient weitergegeben werden, um Millionen von Echtzeitabfragen und verbundenen Nutzern zu dienen. Durch die Ausführung vieler Repliken all dieser Komponenten in mehreren Zonen (oder mehreren Regionen bei einer Bereitstellung mit mehreren Regionen) wird bei Cloud Firestore eine hohe Verfügbarkeit und Skalierbarkeit erreicht.
Alle Lesevorgänge, die über mobile und Web-SDKs ausgeführt werden, folgen dem oben beschriebenen Modell. Sie führen eine Abfrage aus, gefolgt vom Listenermodus, um Konsistenzgarantien aufrechtzuerhalten. Dies gilt auch für Echtzeit-Listener, Aufrufe zum Abrufen eines Dokuments und Einmalabfragen. Sie können sich die Abrufvorgänge einzelner Dokumente und einmalige Abfragen als kurzlebige Snapshot-Listener vorstellen, die ähnliche Leistungseinschränkungen haben.
Best Practices für die Skalierung von Echtzeitabfragen anwenden
Wenden Sie die folgenden Best Practices an, um skalierbare Echtzeitabfragen zu entwerfen.
Hohe Schreibzugriffe im System
In diesem Abschnitt erfahren Sie, wie das System auf eine steigende Anzahl von Schreibanfragen reagiert.
Die Cloud Firestore-Änderungslisten, die die Echtzeitabfragen steuern, werden automatisch horizontal skaliert, wenn der Schreibverkehr zunimmt. Wenn die Schreibrate für eine Datenbank über die Kapazität eines einzelnen Servers hinausgeht, wird der Änderungsverlauf auf mehrere Server aufgeteilt und die Abfrageverarbeitung beginnt, Daten von mehreren Abo-Handlern anstelle von einem zu verwenden. Aus Sicht des Clients und des SDKs ist das alles transparent und es sind keine Maßnahmen von der App erforderlich, wenn es zu Aufteilungen kommt. Das folgende Diagramm zeigt, wie sich Echtzeitabfragen skalieren lassen:
Mit der automatischen Skalierung können Sie Ihren Schreibtraffic unbegrenzt erhöhen. Wenn der Traffic jedoch ansteigt, kann es einige Zeit dauern, bis das System reagiert. Folgen Sie den Empfehlungen der 5-5-5-Regel, um Hotspots zu vermeiden. Key Visualizer ist ein nützliches Tool zur Analyse von Schreib-Hotspots.
Viele Apps verzeichnen ein vorhersehbares organisches Wachstum, das Cloud Firestore ohne Vorkehrungen bewältigen kann. Bei Batch-Arbeitslasten wie dem Importieren eines großen Datensatzes können Schreibvorgänge jedoch zu schnell ansteigen. Berücksichtigen Sie beim Entwerfen Ihrer App, woher die Schreibzugriffe stammen.
Interaktionen zwischen Schreib- und Lesevorgängen
Sie können sich das Echtzeit-Abfragesystem als Pipeline vorstellen, die Schreibvorgänge mit Lesern verbindet. Jedes Mal, wenn ein Dokument erstellt, aktualisiert oder gelöscht wird, wird die Änderung vom Speichersystem an die derzeit registrierten Listener weitergegeben. Die Änderungsliste von Cloud Firestore sorgt für eine hohe Konsistenz. Das bedeutet, dass Ihre App nie Benachrichtigungen zu Updates erhält, die nicht der Reihenfolge entsprechen, in der die Datenänderungen in der Datenbank vorgenommen wurden. Dies vereinfacht die App-Entwicklung, da Grenzfälle in Bezug auf die Datenkonsistenz vermieden werden.
Diese verbundene Pipeline bedeutet, dass ein Schreibvorgang, der Hotspots oder Sperrkonflikte verursacht, sich negativ auf Lesevorgänge auswirken kann. Wenn Schreibvorgänge fehlschlagen oder gedrosselt werden, kann ein Lesevorgang ins Stocken geraten, weil auf konsistente Daten aus dem Änderungsprotokoll gewartet wird. Wenn dies in Ihrer App der Fall ist, kann es sowohl zu langsamen Schreibvorgängen als auch zu entsprechenden langen Reaktionszeiten bei Abfragen kommen. Hotspots zu vermeiden ist der Schlüssel, um dieses Problem zu vermeiden.
Dokumente und Schreibvorgänge klein halten
Wenn Sie Apps mit Snapshot-Listenern entwickeln, 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 durch das System leiten. Größere Dokumente mit Hunderten von Feldern und großen Datenmengen dauern länger.
Bevorzugen Sie außerdem kurze, schnelle Commit- und Schreibvorgänge, um die Latenz niedrig zu halten. Große Batches können zwar aus Sicht des Schreibers zu einem höheren Durchsatz führen, aber die Benachrichtigungszeit für Snapshot-Listener verlängern. Das ist oft kontraintuitiv im Vergleich zu anderen Datenbanksystemen, bei denen Sie die Leistung mithilfe von Batchverarbeitungen verbessern können.
Effizientere Listener verwenden
Wenn die Schreibraten für Ihre Datenbank steigen, verteilt Cloud Firestore die Datenverarbeitung auf viele Server. Der Sharding-Algorithmus von Cloud Firestore versucht, Daten aus derselben Sammlung oder Sammlungsgruppe auf demselben Änderungsprotokollserver zu speichern. Das System versucht, den möglichen Schreibdurchsatz zu maximieren und gleichzeitig die Anzahl der Server, die an der Verarbeitung einer Abfrage beteiligt sind, so gering wie möglich zu halten.
Bestimmte Muster können jedoch weiterhin zu einem suboptimalen Verhalten bei Snapshot-Listenern führen. Wenn Ihre App beispielsweise die meisten Daten in einer großen Sammlung speichert, muss der Listener möglicherweise eine Verbindung zu vielen Servern herstellen, um alle benötigten Daten zu erhalten. Das gilt auch dann, wenn Sie einen Abfragefilter anwenden. Je mehr Server verbunden sind, desto höher ist das Risiko für langsamere Antworten.
Um diese langsameren Antworten zu vermeiden, solltest du dein Schema und deine App so gestalten, dass das System Hörer bedienen kann, ohne viele verschiedene Server aufzurufen. Unter Umständen ist es am besten, Ihre Daten in kleinere Sammlungen mit niedrigeren Schreibraten aufzuteilen.
Das ist vergleichbar mit Leistungsabfragen in einer relationalen Datenbank, die einen vollständigen Tabellenscan erfordern. In einer relationalen Datenbank entspricht eine Abfrage, die einen vollständigen Tabellenscan erfordert, einem Snapshot-Listener, der eine Sammlung mit hoher Fluktuation beobachtet. Die Leistung ist möglicherweise langsamer als bei einer Abfrage, die die Datenbank mit einem spezifischeren Index bedienen kann. Eine Abfrage mit einem spezifischeren Index ist wie ein Snapshot-Listener, der ein einzelnes Dokument oder eine Sammlung beobachtet, die sich seltener ändert. Sie sollten einen Lasttest für Ihre App ausführen, um das Verhalten und die Anforderungen Ihres Anwendungsfalls besser zu verstehen.
Schnelle Abfragen beibehalten
Ein weiterer wichtiger Aspekt bei responsiven Echtzeitabfragen besteht darin, dafür zu sorgen, dass die Abfrage zum Bootstrapping der Daten schnell und effizient ist. Wenn ein neuer Snapshot-Listener zum ersten Mal eine Verbindung herstellt, muss er die gesamte Ergebnismenge laden und an das Gerät des Nutzers senden. Langsame Abfragen verlangsamen Ihre App. Dazu gehören beispielsweise Abfragen, bei denen versucht wird, viele Dokumente zu lesen, oder Abfragen, bei denen die entsprechenden Indexe nicht verwendet werden.
Unter bestimmten Umständen kann ein Listener auch von einem aktiven Status in einen Polling-Status wechseln. 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 einen Änderungslog aufgrund von Laständerungen neu aus.
- 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 Abfragen schnell genug sind, ist der Abfragestatus für die Nutzer Ihrer App nicht sichtbar.
Langlebige Listener bevorzugen
Listener so lange wie möglich geöffnet zu lassen, ist oft die kostengünstigste Methode, eine App zu entwickeln, die Cloud Firestore verwendet. Wenn Sie Cloud Firestore verwenden, werden Ihnen die Dokumente in Rechnung gestellt, die an Ihre App zurückgegeben werden, und nicht die Aufrechterhaltung einer offenen Verbindung. Ein langlebiger Snapshot-Listener liest während seiner Lebensdauer nur die Daten, die für die Abfrage erforderlich sind. Dazu gehört eine anfängliche Abfrage, gefolgt von Benachrichtigungen, wenn sich die Daten tatsächlich ändern. Bei einmaligen Abfragen werden dagegen Daten noch einmal gelesen, die sich möglicherweise nicht geändert haben, seit die App die Abfrage zuletzt ausgeführt hat.
Wenn Ihre App eine hohe Datenrate verbrauchen muss, sind Snapshot-Listener möglicherweise nicht geeignet. Wenn bei Ihrem Anwendungsfall beispielsweise über einen längeren Zeitraum viele Dokumente pro Sekunde über eine Verbindung gesendet werden, sollten Sie besser einmalige Abfragen mit einer geringeren Häufigkeit verwenden.