Echtzeitabfragen im großen Maßstab verstehen

In diesem Dokument finden Sie eine Anleitung zum Skalieren Ihrer serverlosen Anwendung über Tausende von Vorgängen pro Sekunde oder Hunderttausende gleichzeitige Nutzer hinaus. Dieses Dokument enthält Themen für Fortgeschrittene, die Ihnen helfen, das System im Detail zu verstehen. Wenn Sie gerade erst mit Firestore begonnen haben, lesen Sie stattdessen die Kurzanleitung.

Firestore und die Firebase Mobile/Web SDKs bieten ein leistungsstarkes Modell für die Entwicklung serverloser Anwendungen, bei denen clientseitiger Code direkt auf die Datenbank zugreift. Mit den SDKs können Clients in Echtzeit auf Datenaktualisierungen warten. Sie können Echtzeitaktualisierungen nutzen, um reaktionsschnelle Anwendungen zu erstellen, die keine Serverinfrastruktur erfordern. Es ist zwar sehr einfach, etwas einzurichten und auszuführen, es hilft aber, die Einschränkungen in den Systemen von Firestore zu verstehen, damit Ihre serverlose Anwendung skaliert und gut funktioniert, wenn der Traffic zunimmt.

In den folgenden Abschnitten finden Sie Tipps zum Skalieren Ihrer App.

Wählen Sie einen Datenbankspeicherort in der Nähe Ihrer Nutzer aus

Das folgende Diagramm zeigt die Architektur einer Echtzeitanwendung:

Beispiel für eine Echtzeit-Anwendungsarchitektur

Wenn eine Anwendung, die auf dem Gerät eines Nutzers (Mobilgerät oder Web) ausgeführt wird, eine Verbindung zu Firestore herstellt, wird die Verbindung an einen Firestore-Front-End-Server in derselben Region weitergeleitet, in der sich auch Ihre Datenbank befindet. Wenn sich Ihre Datenbank beispielsweise in us-east1 befindet, erfolgt die Verbindung auch zu einem Firestore-Front-End in us-east1. Diese Verbindungen sind langlebig und bleiben geöffnet, bis sie von der Anwendung explizit geschlossen werden. Das Front-End 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 des Nutzers aus. Beispielsweise kann ein Nutzer in Indien, dessen Anwendung mit einer Datenbank in einer Google Cloud-Region in Nordamerika kommuniziert, die Abläufe möglicherweise langsamer und die Anwendung weniger zügig empfinden, als wenn sich die Datenbank stattdessen näher befinden würde, z. B. in Indien oder in einem anderen Teil Asiens.

Einhaltung von Zuverlässigkeitsvorgaben

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

Offlinemodus aktivieren

Die Firebase SDKs bieten Offline-Datenpersistenz. Wenn die App auf dem Gerät des Nutzers keine Verbindung zu Firestore herstellen kann, kann sie weiterhin mit lokal im Cache gespeicherten Daten verwendet werden. Dadurch wird der Datenzugriff auch dann gewährleistet, wenn Nutzer Probleme mit der 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 übernehmen die Wiederholung von Vorgängen und stellen unterbrochene Verbindungen wieder her. So lassen sich vorübergehende Fehler durch einen Neustart von Servern oder Netzwerkprobleme zwischen dem Client und der Datenbank umgehen.

Regionale und multiregionale Standorte auswählen

Bei der Auswahl zwischen regionalen und multiregionalen Standorten müssen mehrere Vor- und Nachteile geknüpft werden. Der Hauptunterschied besteht darin, wie Daten repliziert werden. Dies wirkt sich auf die Verfügbarkeitsgarantien Ihrer Anwendung aus. Eine multiregionale Instanz sorgt für eine höhere Bereitstellungszuverlässigkeit und die Langlebigkeit Ihrer Daten, allerdings müssen die Kosten dafür in Rechnung gestellt werden.

Das Echtzeit-Abfragesystem verstehen

Mit Echtzeitabfragen, auch Snapshot-Listener genannt, kann die Anwendung Änderungen in der Datenbank überwachen und Benachrichtigungen mit niedriger Latenz erhalten, sobald sich die Daten ändern. Eine Anwendung kann dasselbe Ergebnis erhalten, indem sie die Datenbank regelmäßig nach Updates abfragt. Diese Methode ist jedoch oft langsamer, teurer und erfordert mehr Code. Beispiele zum Einrichten und Verwenden von Echtzeitabfragen finden Sie unter Echtzeitabfragen abrufen. In den folgenden Abschnitten wird die Funktionsweise von Snapshot-Listenern ausführlich beschrieben. 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:

Architektur einer Snapshot-Listener-Verbindung

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

  1. Client B öffnet eine Verbindung zu Firestore und registriert einen Listener, indem er onSnapshot(collection("chatroom")) über das Firebase SDK aufruft. Dieser Listener kann stundenlang aktiv bleiben.
  2. Das Firestore-Front-End fragt das zugrunde liegende Speichersystem ab, um ein Bootstrapping des Datasets durchzuführen. Damit wird der gesamte Ergebnissatz übereinstimmender Dokumente geladen. Dies wird als Abfrageabfrage bezeichnet. Das System wertet dann die Firebase-Sicherheitsregeln der Datenbank aus, um sicherzustellen, dass der Nutzer auf diese Daten zugreifen kann. Wenn der Nutzer autorisiert ist, gibt die Datenbank die Daten an den Nutzer zurück.
  3. Die Anfrage von Client B wechselt dann in den Listener-Modus. Der Listener registriert sich mit einem Abo-Handler und wartet auf Aktualisierungen der Daten.
  4. Client A sendet nun einen Schreibvorgang, um ein Dokument zu ändern.
  5. Die Datenbank überträgt die Dokumentänderung per Commit an ihr Speichersystem.
  6. Transaktionll übergibt das System dieselbe Aktualisierung in einem internen Änderungsprotokoll. Das Änderungsprotokoll legt eine strenge Reihenfolge der Änderungen fest, sobald sie eintreten.
  7. Das Änderungsprotokoll wiederum teilt die aktualisierten Daten auf einen Pool von Abo-Handlern auf.
  8. Ein Reverse Query Matcher wird ausgeführt, um festzustellen, ob das aktualisierte Dokument mit einem der aktuell registrierten Snapshot-Listener übereinstimmt. In diesem Beispiel entspricht das Dokument dem Snapshot-Listener von Client B. Wie der Name schon sagt, können Sie sich den umgekehrten Abfrage-Matcher als normale Datenbankabfrage vorstellen, aber in umgekehrter Richtung. Anstatt Dokumente zu durchsuchen, um diejenigen zu finden, die mit einer Abfrage übereinstimmen, sucht es effizient in Abfragen nach denjenigen, die mit einem eingehenden Dokument übereinstimmen. 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 Nutzer die Daten erhalten.
  9. 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 die Aktualisierung auch auf den lokalen Cache an.

Ein wichtiger Teil der Skalierbarkeit von Firestore hängt vom Fan-Out aus dem Änderungsprotokoll zu den Abo-Handlern und den Front-End-Servern ab. Durch das 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 all dieser Komponenten in mehreren Zonen (oder bei einer multiregionalen Bereitstellung in mehreren Regionen) erzielt Firestore Hochverfügbarkeit und Skalierbarkeit.

Beachten Sie, dass alle Lesevorgänge, die von mobilen SDKs und Web-SDKs stammen, dem obigen Modell folgen. Sie führen eine Abfrageabfrage gefolgt vom Überwachungsmodus aus, um Konsistenzgarantien aufrechtzuerhalten. Dies gilt auch für Echtzeit-Listener, Aufrufe zum Abrufen eines Dokuments und Abfragen mit nur einer Aufnahme. Sie können sich das Abrufen einzelner Dokumente und Abfragen mit nur einer Aufnahme als kurzlebige Snapshot-Listener vorstellen, die ähnliche Leistungseinschränkungen haben.

Best Practices zum Skalieren von Echtzeitabfragen anwenden

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

Hoher Schreibtraffic im System verstehen

In diesem Abschnitt wird erläutert, wie das System auf eine zunehmende Anzahl von Schreibanfragen reagiert.

Die Firestore-Änderungslogs, die die Echtzeitabfragen steuern, werden automatisch horizontal skaliert, wenn der Schreibtraffic 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 Abo-Handlern statt von einem zu verbrauchen. Aus Sicht des Clients und des SDKs ist das alles transparent und es sind keine Maßnahmen in der App erforderlich, wenn Aufteilungen erfolgen. Das folgende Diagramm zeigt, wie Echtzeitabfragen skaliert werden:

Architektur des Fan-Outs des Änderungsprotokolls

Mit der automatischen Skalierung können Sie den Schreibtraffic ohne Einschränkungen 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 das Erstellen eines Schreibschwerpunkts zu vermeiden. Key Visualizer ist ein nützliches Tool zur Analyse von Schreibschwerpunkten.

Viele Anwendungen haben ein vorhersehbares organisches Wachstum, auf das Firestore ohne Vorsichtsmaßnahmen reagiert. Batcharbeitslasten wie das Importieren eines großen Datasets können jedoch die Schreibvorgänge zu schnell erhöhen. Achten Sie beim Entwerfen Ihrer Anwendung darauf, woher der Schreibtraffic stammt.

Verstehen, 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 Änderungslogstruktur von Firestore sorgt für strikte Konsistenz. Das bedeutet, dass Ihre Anwendung nie Benachrichtigungen über Aktualisierungen erhält, die im Vergleich zum Zeitpunkt des Commits der Datenänderungen durch die Datenbank falsch angeordnet sind. Dies vereinfacht die App-Entwicklung, da Grenzfälle rund um die Datenkonsistenz beseitigt werden.

Diese verbundene Pipeline bedeutet, dass ein Schreibvorgang, der Hotspots oder Sperrenkonflikte verursacht, die Lesevorgänge negativ beeinflussen kann. Wenn Schreibvorgänge fehlschlagen oder gedrosselt werden, wartet ein Lesevorgang möglicherweise auf konsistente Daten aus dem Änderungsprotokoll. Wenn dies bei Ihrer Anwendung der Fall ist, treten möglicherweise sowohl langsame Schreibvorgänge als auch korrelierte lange Antwortzeiten für Abfragen auf. Die Vermeidung von Hotspots ist der Schlüssel, um dieses Problem zu umgehen.

Halten Sie Dokumente und Schreibvorgänge klein.

Wenn Sie Anwendungen mit Snapshot-Listenern erstellen, möchten Sie in der Regel, dass Nutzer 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 übertragen. Größere Dokumente mit Hunderten von Feldern und großen Daten dauern 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 Autors zu einem höheren Durchsatz führen, erhöhen aber möglicherweise auch die Benachrichtigungszeit für Snapshot-Listener. Dies ist im Vergleich zur Verwendung anderer Datenbanksysteme, bei denen Sie Batching zur Leistungsverbesserung nutzen können, häufig kontraintuitiv.

Effiziente Listener verwenden

Wenn die Schreibraten für Ihre Datenbank steigen, teilt Firestore die Datenverarbeitung auf viele Server auf. Der Fragmentierungsalgorithmus von 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 trotzdem zu einem suboptimalen Verhalten bei Snapshot-Listenern führen. Wenn Ihre Anwendung beispielsweise die meisten Daten in einer großen Sammlung speichert, muss der Listener möglicherweise eine Verbindung zu vielen Servern herstellen, um alle erforderlichen Daten zu empfangen. Dies gilt auch dann, wenn Sie einen Abfragefilter anwenden. Bei Verbindungen zu vielen Servern erhöht sich das Risiko langsamer Antworten.

Um diese langsameren Antworten zu vermeiden, sollten Sie Schema und Anwendung so gestalten, dass das System Listener bereitstellen kann, ohne viele verschiedene Server zu nutzen. Es kann sinnvoll sein, Ihre Daten in kleinere Sammlungen mit geringeren Schreibraten aufzuteilen.

Dies ähnelt den Leistungsabfragen in einer relationalen Datenbank, die vollständige Tabellenscans erfordern. In einer relationalen Datenbank entspricht eine Abfrage, für die ein vollständiger Tabellenscan erforderlich ist, einem Snapshot-Listener, der eine Sammlung mit hoher Abwanderung überwacht. Die Leistung ist im Vergleich zu einer Abfrage, die die Datenbank mit einem spezifischeren Index verarbeiten kann, möglicherweise langsam. Eine Abfrage mit einem spezifischeren Index ist wie ein Snapshot-Listener, der ein einzelnes Dokument oder eine Sammlung, die sich weniger häufig ändert, überwacht. Sie sollten einen Belastungstest Ihrer Anwendung durchführen, um das Verhalten und die Anforderungen Ihres Anwendungsfalls am besten zu verstehen.

Abfragen schnell durchführen

Ein weiterer wichtiger Bestandteil responsiver Echtzeitabfragen besteht darin, dass die Abfrageabfrage 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 Nutzers senden. Langsame Abfragen machen deine App weniger reaktionsschnell. Dazu gehören beispielsweise Abfragen, die versuchen, viele Dokumente zu lesen, oder Abfragen, die nicht die entsprechenden Indexe verwenden.

Unter bestimmten Umständen wechselt ein Listener auch vom Überwachungsstatus zurück in einen Abfragestatus. Dies geschieht automatisch und ist für die SDKs und Ihre App transparent. Die folgenden Bedingungen können einen Abfragestatus auslösen:

  • Das System gewichtet ein Änderungsprotokoll aufgrund von Änderungen der Last neu.
  • Hotspots verursachen fehlgeschlagene oder verzögerte Schreibvorgänge in die Datenbank.
  • Vorübergehende Serverneustarts wirken sich vorübergehend auf Listener aus.

Wenn Ihre Abfrageabfragen schnell genug sind, wird ein Abfragestatus für die Nutzer Ihrer Anwendung transparent.

Langlebige Zuhörer bevorzugen

Das Öffnen und Aufrechterhalten von Listenern so lange wie möglich ist oft die kostengünstigste Methode, eine Anwendung zu erstellen, die Firestore verwendet. Wenn Sie Firestore verwenden, werden Ihnen die an Ihre Anwendung zurückgegebenen Dokumente in Rechnung gestellt, nicht die Aufrechterhaltung einer offenen Verbindung. Ein langlebiger Snapshot-Listener liest nur die Daten, die er zur Verarbeitung der Abfrage während seiner gesamten Lebensdauer benötigt. Dazu gehört ein erster Abfragevorgang, gefolgt von Benachrichtigungen, wenn sich die Daten tatsächlich ändern. Einzelabfragen dagegen werden bei Abfragen noch einmal gelesen, die sich seit der letzten Ausführung der Abfrage durch die Anwendung möglicherweise nicht geändert haben.

In Fällen, in denen Ihre Anwendung eine hohe Datenmenge nutzen muss, sind Snapshot-Listener möglicherweise nicht geeignet. Wenn in Ihrem Anwendungsfall beispielsweise viele Dokumente pro Sekunde über einen längeren Zeitraum über eine Verbindung gesendet werden, ist es möglicherweise besser, sich für One-Shot-Abfragen zu entscheiden, die mit einer niedrigeren Häufigkeit ausgeführt werden.

Weitere Informationen