Verstehen Sie Echtzeitabfragen im großen Maßstab

Lesen Sie dieses Dokument, um Anleitungen zur Skalierung Ihrer serverlosen App über Tausende von Vorgängen pro Sekunde oder Hunderttausende gleichzeitiger Benutzer hinaus zu erhalten. Dieses Dokument enthält fortgeschrittene Themen, die Ihnen helfen, das System eingehend zu verstehen. Wenn Sie gerade erst mit Cloud Firestore beginnen, lesen Sie stattdessen die Schnellstartanleitung .

Cloud 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 Aktualisierungen der Daten warten. Mithilfe von Echtzeitaktualisierungen können Sie reaktionsfähige Apps erstellen, die keine Serverinfrastruktur erfordern. Es ist zwar sehr einfach, etwas zum Laufen zu bringen, aber es hilft, die Einschränkungen in den Systemen zu verstehen, aus denen Cloud Firestore besteht, damit Ihre serverlose App bei steigendem Datenverkehr skaliert und gut funktioniert.

In den folgenden Abschnitten finden Sie Hinweise zur Skalierung Ihrer App.

Wählen Sie einen Datenbankstandort in der Nähe Ihrer Benutzer

Das folgende Diagramm veranschaulicht die Architektur einer Echtzeit-App:

Beispiel einer Echtzeit-App-Architektur

Wenn eine App, die auf dem Gerät eines Benutzers (mobil oder im Internet) ausgeführt wird, eine Verbindung zu Cloud Firestore herstellt, wird die Verbindung an einen Cloud Firestore-Frontend-Server in derselben Region weitergeleitet, in der sich Ihre Datenbank befindet. Wenn sich Ihre Datenbank beispielsweise in us-east1 befindet, erfolgt die Verbindung auch zu einem Cloud Firestore-Frontend, ebenfalls in us-east1 . Diese Verbindungen sind langlebig und bleiben geöffnet, 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 Benutzers und dem Standort der Cloud Firestore-Datenbank wirkt sich auf die Latenz aus, die der Benutzer erfährt. Beispielsweise könnte ein Benutzer in Indien, dessen App mit einer Datenbank in einer Google Cloud-Region in Nordamerika kommuniziert, die Erfahrung langsamer und die App weniger schnell finden, als wenn die Datenbank stattdessen näher gelegen wäre, beispielsweise in Indien oder in einem anderen Teil Asiens .

Design für Zuverlässigkeit

Die folgenden Themen verbessern oder beeinträchtigen die Zuverlässigkeit Ihrer App:

Offline-Modus aktivieren

Die Firebase SDKs bieten Offline-Datenpersistenz. Wenn die App auf dem Gerät des Benutzers keine Verbindung zum Cloud Firestore herstellen kann, bleibt die App durch die Arbeit mit lokal zwischengespeicherten Daten nutzbar. Dies stellt den Datenzugriff auch dann sicher, wenn Benutzer unregelmäßige Internetverbindungen haben oder den Zugriff für mehrere Stunden oder Tage vollständig verlieren. Weitere Informationen zum Offline-Modus finden Sie unter Offline-Daten aktivieren .

Automatische Wiederholungsversuche verstehen

Die Firebase SDKs kümmern sich um die Wiederholung von Vorgängen und die Wiederherstellung unterbrochener Verbindungen. Dies hilft, vorübergehende Fehler zu umgehen, die durch Serverneustarts oder Netzwerkprobleme zwischen dem Client und der Datenbank verursacht werden.

Wählen Sie zwischen regionalen und überregionalen Standorten

Bei der Wahl zwischen regionalen und multiregionalen Standorten gibt es mehrere Kompromisse. Der Hauptunterschied besteht darin, wie Daten repliziert werden. Dies bestimmt die Verfügbarkeitsgarantien Ihrer App. Eine Multi-Region-Instanz bietet eine höhere Bereitstellungszuverlässigkeit und erhöht die Haltbarkeit Ihrer Daten, aber der Nachteil sind die Kosten.

Verstehen Sie das Echtzeit-Abfragesystem

Echtzeitabfragen, auch Snapshot-Listener genannt, ermöglichen es der App, Änderungen in der Datenbank abzuhören und Benachrichtigungen mit geringer Latenz zu erhalten, sobald sich die Daten ändern. Eine App kann das gleiche Ergebnis erzielen, indem sie die Datenbank regelmäßig nach Updates abfragt. Dies ist jedoch häufig langsamer, teurer und erfordert mehr Code. Beispiele zum Einrichten und Verwenden von Echtzeitabfragen finden Sie unter Erhalten von Echtzeitaktualisierungen . In den folgenden Abschnitten wird detailliert beschrieben, wie Snapshot-Listener funktionieren, und einige der Best Practices für die Skalierung von Echtzeitabfragen bei gleichzeitiger Beibehaltung der Leistung beschrieben.

Stellen Sie sich zwei Benutzer vor, die über eine Messaging-App, die mit einem der mobilen SDKs erstellt wurde, eine Verbindung zu Cloud 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 Cloud Firestore!'

    document message2:
      from: 'Santa'
      message: 'Presents are coming'

Client B wartet mithilfe eines Snapshot-Listeners auf Aktualisierungen in derselben Sammlung. Client B erhält sofort eine Benachrichtigung, wenn jemand eine neue Nachricht erstellt. Das folgende Diagramm zeigt die Architektur hinter einem Snapshot-Listener:

Architektur einer Snapshot-Listener-Verbindung

Die folgende Abfolge von Ereignissen findet statt, wenn Client B einen Snapshot-Listener mit der Datenbank verbindet:

  1. Client B öffnet eine Verbindung zu Cloud Firestore und registriert einen Listener, indem er über das Firebase SDK einen Aufruf an onSnapshot(collection("chatroom")) durchführt. Dieser Zuhörer kann stundenlang aktiv bleiben.
  2. Das Cloud Firestore-Frontend fragt das zugrunde liegende Speichersystem ab, um den Datensatz zu booten. Es lädt den gesamten Ergebnissatz übereinstimmender Dokumente. Wir bezeichnen dies als Polling-Abfrage . Das System wertet dann die Firebase-Sicherheitsregeln der Datenbank aus, um zu überprüfen, ob der Benutzer auf diese Daten zugreifen kann. Wenn der Benutzer berechtigt ist, gibt die Datenbank die Daten an den Benutzer zurück.
  3. Die Anfrage von Client B wechselt dann in den Listen-Modus . Der Listener registriert sich bei einem Abonnementhandler und wartet auf Aktualisierungen der Daten.
  4. Client A sendet nun einen Schreibvorgang, um ein Dokument zu ändern.
  5. Die Datenbank übergibt die Dokumentänderung an ihr Speichersystem.
  6. Transaktionell schreibt das System dasselbe Update in ein internes Änderungsprotokoll. Das Änderungsprotokoll legt eine strikte Reihenfolge der vorgenommenen Änderungen fest.
  7. Das Changelog wiederum verteilt die aktualisierten Daten an einen Pool von Abonnement-Handlern.
  8. Ein Reverse-Query-Matcher wird ausgeführt, um zu sehen, ob das aktualisierte Dokument mit allen derzeit registrierten Snapshot-Listenern übereinstimmt. In diesem Beispiel stimmt das Dokument mit dem Snapshot-Listener von Client B überein. Wie der Name schon sagt, können Sie sich den Reverse-Query-Matcher wie eine normale Datenbankabfrage vorstellen, die jedoch umgekehrt ausgeführt wird. Anstatt Dokumente zu durchsuchen, um diejenigen zu finden, die einer Abfrage entsprechen, werden Abfragen effizient durchsucht, um diejenigen zu finden, die einem eingehenden Dokument entsprechen. Wenn eine Übereinstimmung gefunden wird, leitet das System das betreffende Dokument an die Snapshot-Listener weiter. Anschließend wertet das System die Firebase-Sicherheitsregeln der Datenbank aus, um sicherzustellen, dass nur autorisierte Benutzer die Daten erhalten.
  9. Das System leitet die Dokumentaktualisierung an das SDK auf dem Gerät von Client B weiter und der onSnapshot Rückruf wird ausgelöst. Wenn die lokale Persistenz aktiviert ist, wendet das SDK das Update auch auf den lokalen Cache an.

Ein wesentlicher Teil der Skalierbarkeit von Cloud Firestore hängt von der Weitergabe vom Änderungsprotokoll an die Abonnement-Handler und die Frontend-Server ab. Durch das Fan-Out kann sich eine einzelne Datenänderung effizient verbreiten, um Millionen von Echtzeitabfragen und verbundenen Benutzern zu bedienen. Durch die Ausführung vieler Replikate all dieser Komponenten über mehrere Zonen (oder mehrere Regionen im Falle einer Bereitstellung mit mehreren Regionen) erreicht Cloud Firestore eine hohe Verfügbarkeit und Skalierbarkeit.

Es ist erwähnenswert, dass alle von Mobil- und Web-SDKs ausgegebenen Lesevorgänge dem oben genannten Modell folgen. Sie führen eine Polling-Abfrage und anschließend einen Listen-Modus durch, um Konsistenzgarantien aufrechtzuerhalten. Dies gilt auch für Echtzeit-Listener, Aufrufe zum Abrufen eines Dokuments und einmalige Abfragen . Sie können sich den Abruf einzelner Dokumente und einmalige Abfragen als kurzlebige Snapshot-Listener vorstellen, die mit ähnlichen Leistungseinschränkungen verbunden sind.

Wenden Sie Best Practices zur Skalierung von Echtzeitabfragen an

Wenden Sie die folgenden Best Practices an, um skalierbare Echtzeitabfragen zu entwerfen.

Verstehen Sie den hohen Schreibverkehr im System

Dieser Abschnitt hilft Ihnen zu verstehen, wie das System auf eine zunehmende Anzahl von Schreibanforderungen reagiert.

Die Cloud Firestore-Änderungsprotokolle, die die Echtzeitabfragen steuern, werden automatisch horizontal skaliert, wenn der Schreibverkehr zunimmt. Wenn die Schreibrate für eine Datenbank über das hinausgeht, was ein einzelner Server verarbeiten kann, wird das Änderungsprotokoll auf mehrere Server aufgeteilt und die Abfrageverarbeitung beginnt, Daten von mehreren Abonnement-Handlern statt von einem zu verbrauchen. Aus Sicht des Kunden und des SDK ist dies alles transparent und es sind keine Maßnahmen seitens der App erforderlich, wenn es zu Aufteilungen kommt. Das folgende Diagramm zeigt, wie Echtzeitabfragen skaliert werden:

Architektur des Changelog-Fanouts

Durch die automatische Skalierung können Sie Ihren Schreibverkehr unbegrenzt steigern. Wenn der Datenverkehr jedoch zunimmt, kann es einige Zeit dauern, bis das System reagiert. Befolgen Sie die Empfehlungen der 5-5-5-Regel , um die Entstehung eines Schreib-Hotspots zu vermeiden. Key Visualizer ist ein nützliches Tool zur Analyse von Schreib-Hotspots.

Viele Apps weisen ein vorhersehbares organisches Wachstum auf, das Cloud Firestore ohne Vorkehrungen bewältigen kann. Batch-Arbeitslasten wie das Importieren eines großen Datensatzes können jedoch dazu führen, dass die Schreibvorgänge zu schnell ansteigen. Achten Sie beim Entwerfen Ihrer App darauf, woher Ihr Schreibverkehr kommt.

Verstehen Sie, wie Schreib- und Lesevorgänge interagieren

Sie können sich das Echtzeit-Abfragesystem als eine 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 aktuell registrierten Listener weitergegeben. Die Änderungsprotokollstruktur von Cloud Firestore garantiert eine starke Konsistenz, was bedeutet, dass Ihre App niemals Benachrichtigungen über Aktualisierungen erhält, die im Vergleich zu dem Zeitpunkt, als die Datenbank die Datenänderungen festgeschrieben hat, nicht in der richtigen Reihenfolge sind. Dies vereinfacht die App-Entwicklung, indem Randfälle im Zusammenhang mit der Datenkonsistenz beseitigt werden.

Diese verbundene Pipeline bedeutet, dass ein Schreibvorgang, der Hotspots oder Sperrkonflikte verursacht, Lesevorgänge negativ beeinflussen kann. Wenn Schreibvorgänge fehlschlagen oder eine Drosselung auftritt, kann ein Lesevorgang beim Warten auf konsistente Daten aus dem Änderungsprotokoll ins Stocken geraten. Wenn dies in Ihrer App passiert, kann es zu langsamen Schreibvorgängen und damit verbundenen langsamen Antwortzeiten für Abfragen kommen. Der Schlüssel zur Vermeidung dieses Problems ist die Vermeidung von Hotspots.

Halten Sie Dokumente und Schreibvorgänge klein

Beim Erstellen von Apps mit Snapshot-Listenern möchten Sie normalerweise, dass Benutzer schnell über Datenänderungen informiert werden. Um dies zu erreichen, versuchen Sie, die Dinge klein zu halten. Das System kann kleine Dokumente mit Dutzenden von Feldern sehr schnell durch das System schicken. Die Verarbeitung größerer Dokumente mit Hunderten von Feldern und großen Datenmengen dauert länger.

Bevorzugen Sie ebenfalls kurze, schnelle Commit- und Schreibvorgänge, um die Latenz niedrig zu halten. Große Stapel können aus Sicht des Autors zu einem höheren Durchsatz führen, verlängern jedoch möglicherweise die Benachrichtigungszeit für Snapshot-Listener. Dies ist im Vergleich zur Verwendung anderer Datenbanksysteme, bei denen Sie möglicherweise Batchverarbeitung verwenden, um die Leistung zu verbessern, oft kontraintuitiv.

Nutzen Sie effiziente Zuhörer

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 unterzubringen. Das System versucht, den möglichen Schreibdurchsatz zu maximieren und gleichzeitig die Anzahl der an der Bearbeitung einer Anfrage beteiligten Server so gering wie möglich zu halten.

Bestimmte Muster können jedoch immer noch zu einem suboptimalen Verhalten für Snapshot-Listener 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 empfangen. Dies gilt auch dann, wenn Sie einen Abfragefilter anwenden. Wenn Sie eine Verbindung zu vielen Servern herstellen, erhöht sich das Risiko langsamerer Antworten.

Um diese langsameren Antworten zu vermeiden, entwerfen Sie Ihr Schema und Ihre App so, dass das System Listener bedienen kann, ohne viele verschiedene Server nutzen zu müssen. Möglicherweise funktioniert es am besten, Ihre Daten in kleinere Sammlungen mit geringeren Schreibraten aufzuteilen.

Dies ähnelt dem Nachdenken über die Leistungsabfragen in einer relationalen Datenbank, die vollständige Tabellenscans erfordern. In einer relationalen Datenbank entspricht eine Abfrage, die einen vollständigen Tabellenscan erfordert, einem Snapshot-Listener, der eine Sammlung mit hoher Abwanderung überwacht. Im Vergleich zu einer Abfrage, die die Datenbank mithilfe eines spezifischeren Index bedienen kann, ist die Leistung möglicherweise langsamer. 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 bestmöglich zu verstehen.

Halten Sie Abfragen schnell

Ein weiterer wichtiger Teil reaktionsfähiger Echtzeitabfragen besteht darin, sicherzustellen, dass die Polling-Abfrage zum Bootstrapping der Daten schnell und effizient ist. Wenn ein neuer Snapshot-Listener zum ersten Mal eine Verbindung herstellt, muss der Listener den gesamten Ergebnissatz laden und an das Gerät des Benutzers senden. Langsame Abfragen führen dazu, dass Ihre App weniger reagiert. Dazu gehören beispielsweise Abfragen, die versuchen, viele Dokumente zu lesen, oder Abfragen, die nicht die entsprechenden Indizes verwenden.

Unter bestimmten Umständen kann ein Zuhörer auch von einem Zuhörerzustand in einen Abfragezustand zurückkehren. Dies geschieht automatisch und ist für die SDKs und Ihre App transparent. Die folgenden Bedingungen können einen Abfragestatus auslösen:

Wenn Ihre Abfragen schnell genug sind, wird ein Abfragestatus für die Benutzer Ihrer App transparent.

Bevorzugen Sie langlebige Zuhörer

Hörer so lange wie möglich zu öffnen und am Leben zu halten, ist oft die kostengünstigste Möglichkeit, eine App zu erstellen, die Cloud Firestore nutzt. Bei der Nutzung von Cloud Firestore werden Ihnen die an Ihre App zurückgegebenen Dokumente in Rechnung gestellt und nicht die Aufrechterhaltung einer offenen Verbindung. Ein langlebiger Snapshot-Listener liest während seiner gesamten Lebensdauer nur die Daten, die er benötigt, um die Abfrage zu bedienen. Dazu gehört ein erster Abfragevorgang, gefolgt von Benachrichtigungen, wenn sich die Daten tatsächlich ändern. Bei einmaligen Abfragen hingegen werden Daten erneut gelesen, die sich seit der letzten Ausführung der Abfrage durch die App möglicherweise nicht geändert haben.

In Fällen, in denen Ihre App eine hohe Datenrate verbrauchen muss, sind Snapshot-Listener möglicherweise nicht geeignet. Wenn Ihr Anwendungsfall beispielsweise über einen längeren Zeitraum viele Dokumente pro Sekunde über eine Verbindung überträgt, ist es möglicherweise besser, sich für einmalige Abfragen zu entscheiden, die mit einer geringeren Häufigkeit ausgeführt werden.

Was kommt als nächstes