Lese- und Schreibvorgänge in großem Maßstab verstehen

Lesen Sie dieses Dokument, um fundierte Entscheidungen bei der Entwicklung von Anwendungen für hohe Leistung und Zuverlässigkeit zu treffen. Dieses Dokument enthält erweiterte Firestore-Themen. Wenn Sie gerade erst mit Informationen zu Firestore finden Sie in der Kurzanleitung.

Firestore ist eine flexible, skalierbare Datenbank für die Entwicklung von Mobilgeräten, Websites und Servern von Firebase und Google Cloud. Der Einstieg in Firestore und das Schreiben umfangreicher und leistungsstarker Anwendungen ist ganz einfach.

Damit Ihre Anwendungen weiterhin eine gute Leistung erzielen, die Datenbankgröße steigt und der Traffic zunimmt, ist es hilfreich, die Mechanismen von Lese- und Schreibvorgängen im Firestore-Back-End zu verstehen. Außerdem müssen Sie die Interaktion von Lese- und Schreibvorgängen mit der Speicherschicht sowie die zugrunde liegenden Einschränkungen verstehen, die sich auf die Leistung auswirken können.

In den folgenden Abschnitten finden Sie Best Practices, bevor Sie Ihre Anwendung entwerfen.

Die übergeordneten Komponenten verstehen

Das folgende Diagramm zeigt die übergeordneten Komponenten, die an einer Firestore API-Anfrage beteiligt sind.

Allgemeine Komponenten

Firestore SDK und Clientbibliotheken

Firestore unterstützt SDKs und Clientbibliotheken für verschiedene Plattformen. Während eine Anwendung direkte HTTP- und RPC-Aufrufe an die Firestore API senden kann, bieten die Clientbibliotheken eine Abstraktionsebene, um die API-Nutzung zu vereinfachen und Best Practices zu implementieren. Sie bieten möglicherweise auch zusätzliche Funktionen wie Offlinezugriff, Caches usw.

Google Front End (GFE)

Dies ist ein Infrastrukturdienst, der allen Google Cloud-Diensten gemeinsam ist. Das GFE akzeptiert eingehende Anfragen und leitet sie an den entsprechenden Google-Dienst (in diesem Kontext Firestore) weiter. Darüber hinaus werden weitere wichtige Funktionen bereitgestellt, einschließlich des Schutzes vor Denial-of-Service-Angriffen.

Firestore-Dienst

Der Firestore-Dienst führt Prüfungen der API-Anfrage durch, einschließlich Authentifizierung, Autorisierung, Kontingentprüfungen und Sicherheitsregeln, und verwaltet außerdem Transaktionen. Dieser Firestore-Dienst enthält einen Speicherclient, der für die Lese- und Schreibvorgänge von Daten mit der Speicherschicht interagiert.

Firestore-Speicherebene

Die Firestore-Speicherebene ist für das Speichern der Daten und Metadaten sowie der zugehörigen Datenbankfunktionen von Firestore verantwortlich. In den folgenden Abschnitten wird beschrieben, wie Daten in der Firestore-Speicherebene organisiert sind und wie das System skaliert wird. Wenn Sie wissen, wie Daten organisiert sind, können Sie ein skalierbares Datenmodell entwerfen und die Best Practices in Firestore besser verstehen.

Schlüsselbereiche und -aufteilungen

Firestore ist eine dokumentenbasierte NoSQL-Datenbank. Sie speichern Daten in Dokumenten, die in Hierarchien von Sammlungen organisiert sind. Die Sammlungshierarchie und die Dokument-ID werden in einen einzigen Schlüssel für jedes Dokument übersetzt. Dokumente werden nach diesem Schlüssel logisch gespeichert und lexikografisch sortiert. Der Begriff „Schlüsselbereich“ bezieht sich auf einen lexikografisch zusammenhängenden Schlüsselbereich.

Eine typische Firestore-Datenbank ist zu groß, um auf eine einzelne physische Maschine zu passen. Es gibt auch Szenarien, in denen die Arbeitslast für die Daten zu hoch ist, um von einer Maschine verarbeitet zu werden. Zur Verarbeitung großer Arbeitslasten partitioniert Firestore die Daten in separate Teile, die auf mehreren Maschinen oder Speicherservern gespeichert und von diesen bereitgestellt werden können. Diese Partitionen werden in den Datenbanktabellen in Blöcken von Schlüsselbereichen erstellt, die als Splits bezeichnet werden.

Synchrone Replikation

Beachten Sie, dass die Datenbank immer automatisch und synchron repliziert wird. Die Datenaufteilungen haben Replikate in verschiedenen Zonen, damit sie auch dann verfügbar bleiben, wenn eine Zone nicht mehr zugänglich ist. Die konsistente Replikation auf die verschiedenen Kopien des Splits wird vom Paxos-Algorithmus für einen Konsens verwaltet. Ein Replikat jedes Splits wird als Paxos-Leader ausgewählt, der für die Verarbeitung von Schreibvorgängen in diesen Split verantwortlich ist. Die synchrone Replikation gibt Ihnen die Möglichkeit, immer die neueste Version der Daten aus Firestore zu lesen.

Das Resultat ist ein skalierbares und hochverfügbares System, das niedrige Latenzen sowohl für Lese- als auch für Schreibvorgänge bietet, unabhängig von hohen Arbeitslasten und in sehr großem Maßstab.

Datenlayout

Firestore ist eine schemalose Dokumentendatenbank. Intern werden die Daten jedoch primär in zwei relationalen datenbankähnlichen Tabellen in ihrer Speicherschicht wie folgt angeordnet:

  • Tabelle Dokumente: In dieser Tabelle werden Dokumente gespeichert.
  • Tabelle Indexe: In dieser Tabelle werden Indexeinträge gespeichert, die ein effizientes Abrufen von Ergebnissen ermöglichen.

Das folgende Diagramm zeigt, wie die Tabellen für eine Firestore-Datenbank mit den Aufteilungen aussehen könnten. Die Splits werden in drei verschiedenen Zonen repliziert und jedem Split ist ein Paxos-Leader zugewiesen.

Datenlayout

Einzelne Region im Vergleich zu mehreren Regionen

Beim Erstellen einer Datenbank müssen Sie eine Region oder mehrere Regionen auswählen.

Ein einzelner regionaler Standort ist ein bestimmter geografischer Standort wie us-west1. Die Datenaufteilungen einer Firestore-Datenbank haben Replikate in verschiedenen Zonen innerhalb der ausgewählten Region, wie zuvor erläutert.

Ein Standort mit mehreren Regionen besteht aus einer festgelegten Gruppe von Regionen, in denen Replikate der Datenbank gespeichert werden. In einer multiregionalen Bereitstellung von Firestore haben zwei der Regionen vollständige Replikate der gesamten Daten in der Datenbank. Eine dritte Region hat ein Zeugenreplikat, das keinen vollständigen Datensatz verwaltet, jedoch an der Replikation beteiligt ist. Durch die Replikation der Daten zwischen mehreren Regionen können die Daten auch bei Verlust einer ganzen Region geschrieben und gelesen werden.

Weitere Informationen zu den Standorten einer Region finden Sie unter Firestore-Standorte.

Einzelne Region im Vergleich zu mehreren Regionen

Lebensdauer eines Schreibvorgangs in Firestore verstehen

Ein Firestore-Client kann Daten schreiben, indem er ein einzelnes Dokument erstellt, aktualisiert oder löscht. Für das Schreiben in ein einzelnes Dokument müssen sowohl das Dokument als auch die zugehörigen Indexeinträge in der Speicherschicht atomar aktualisiert werden. Firestore unterstützt auch atomare Vorgänge mit mehreren Lese- und/oder Schreibvorgängen in einem oder mehreren Dokumenten.

Für alle Arten von Schreibvorgängen bietet Firestore die ACID-Attribute (Atomarität, Konsistenz, Isolation und Langlebigkeit) relationaler Datenbanken. Firestore bietet auch Serialisierbarkeit. Das bedeutet, dass alle Transaktionen so erscheinen, als wären sie in einer seriellen Reihenfolge ausgeführt worden.

Allgemeine Schritte in einer Schreibtransaktion

Wenn der Firestore-Client mit einer der zuvor genannten Methoden einen Schreibvorgang ausgibt oder einen Commit für eine Transaktion durchführt, wird dies intern als Datenbank-Lese-Schreib-Transaktion auf der Speicherebene ausgeführt. Durch die Transaktion kann Firestore die zuvor genannten ACID-Attribute bereitstellen.

Im ersten Schritt einer Transaktion liest Firestore das vorhandene Dokument und bestimmt die Mutationen, die an den Daten in der Tabelle „Dokumente“ vorgenommen werden sollen.

Dazu gehört auch die Durchführung der erforderlichen Aktualisierungen an der Indextabelle wie folgt:

  • Felder, die den Dokumenten hinzugefügt werden, benötigen entsprechende Einfügungen in die Indextabelle.
  • Felder, die aus den Dokumenten entfernt werden, müssen entsprechend in der Indextabelle gelöscht werden.
  • Felder, die in den Dokumenten geändert werden und sowohl gelöscht (für alte Werte) als auch Einfügungen (für neue Werte) müssen in der Indextabelle.

Zur Berechnung der zuvor genannten Mutationen liest Firestore die Indexierungskonfiguration für das Projekt. In der Indexierungskonfiguration werden Informationen zu den Indexen für ein Projekt gespeichert. Firestore verwendet zwei Arten von Indexen: Einzelfeldindexe und zusammengesetzte Indexe. Detaillierte Informationen zu den in Firestore erstellten Indexen finden Sie unter Indextypen in Firestore.

Nachdem die Mutationen berechnet wurden, erfasst Firestore sie innerhalb einer Transaktion und überträgt sie dann mit Commit.

Erläuterung einer Schreibtransaktion auf Speicherebene

Wie bereits erwähnt, umfasst ein Schreibvorgang in Firestore eine Lese-Schreib-Transaktion auf Speicherebene. Je nach Layout der Daten kann ein Schreibvorgang, wie im Datenlayout dargestellt, einen oder mehrere Splits umfassen.

Im folgenden Diagramm enthält die Firestore-Datenbank acht Splits (mit 1–8 gekennzeichnet), die auf drei verschiedenen Speicherservern in einer einzigen Zone gehostet werden. Jeder Split wird in drei(oder mehr) verschiedenen Zonen repliziert. Für jeden Split gibt es einen Paxos-Leader, der sich je nach Split auch in unterschiedlichen Zonen befinden kann.

Firestore-Datenbankaufteilung

Betrachten Sie eine Firestore-Datenbank mit der Sammlung Restaurants so:

Restaurantsammlung

Der Firestore-Client fordert die folgende Änderung an einem Dokument in der Sammlung Restaurant an, indem der Wert des Felds priceCategory aktualisiert wird.

Zu einem Dokument in einer Sammlung wechseln

In den folgenden allgemeinen Schritten wird beschrieben, was beim Schreiben geschieht:

  1. Erstellen Sie eine Lese-Schreib-Transaktion.
  2. Lesen Sie das Dokument restaurant1 in der Sammlung Restaurants in der Tabelle Dokumente aus der Speicherebene.
  3. Lesen Sie die Indexe für das Dokument aus der Tabelle Indexe.
  4. Die an den Daten durchzuführenden Mutationen berechnen. In diesem Fall gibt es fünf Mutationen: <ph type="x-smartling-placeholder">
      </ph>
    • M1: Aktualisieren Sie die Zeile für restaurant1 in der Tabelle Dokumente, um die Wertänderung im Feld priceCategory widerzuspiegeln.
    • M2 und M3: Löschen Sie die Zeilen für den alten Wert von priceCategory in der Tabelle Indexe für ab- und aufsteigende Indexe.
    • M4 und M5: Fügen Sie für absteigende und aufsteigende Indexe die Zeilen für den neuen Wert von priceCategory in die Tabelle Indexe ein.
  5. Führen Sie für diese Mutationen ein Commit durch.

Der Speicherclient im Firestore-Dienst sucht nach den Splits, denen die Schlüssel der zu ändernden Zeilen gehören. Nehmen wir einen Fall an, bei dem Spaltung 3 für M1 und Teilung 6 für M2–M5 genutzt wird. Es gibt eine verteilte Transaktion, die alle diese Splits als Teilnehmer umfasst. Die Teilnehmer-Splits können auch andere Splits enthalten, aus denen Daten früher als Teil der Lese-Schreib-Transaktion gelesen wurden.

In den folgenden Schritten wird beschrieben, was im Rahmen des Commits geschieht:

  1. Der Speicherclient gibt ein Commit aus. Das Commit enthält die Mutationen M1 bis M5.
  2. Die Aufteilungen 3 und 6 sind die Teilnehmer an dieser Transaktion. Einer der Teilnehmer wird als Koordinator ausgewählt, zum Beispiel Split 3. Die Aufgabe des Koordinators besteht darin, sicherzustellen, dass die Transaktion für alle Teilnehmer entweder übergeben oder abgebrochen wird.
    • Die Leader-Replikate dieser Splits sind für die Arbeit der Teilnehmenden und Koordinatoren verantwortlich.
  3. Alle Teilnehmer und Koordinatoren führen einen Paxos-Algorithmus mit ihren jeweiligen Replikaten aus.
    • Der Leader führt einen Paxos-Algorithmus mit den Replikaten aus. Ein Quorum wird erreicht, wenn die meisten Replikate mit der Antwort ok to commit für die führende Variante antworten.
    • Jeder Teilnehmer benachrichtigt dann den Koordinator, wenn er vorbereitet ist (erste Phase des zweiphasigen Commits). Wenn ein Teilnehmer die Transaktion nicht per Commit festschreiben kann, wird die gesamte Transaktion aborts.
  4. Sobald der Koordinator weiß, dass alle Teilnehmer, einschließlich sich selbst, vorbereitet sind, informiert er alle Teilnehmer über das Ergebnis der accept-Transaktion (zweite Phase des zweiphasigen Commits). In dieser Phase zeichnet jeder Teilnehmer die Commit-Entscheidung für einen stabilen Speicher auf und die Transaktion wird festgeschrieben.
  5. Der Koordinator antwortet dem Storage-Client in Firestore, dass die Transaktion mit Commit gespeichert wurde. Parallel dazu wenden der Koordinator und alle Teilnehmenden die Mutationen auf die Daten an.

Commit-Lebenszyklus

Wenn die Firestore-Datenbank klein ist, kann es vorkommen, dass einem einzelnen Split alle Schlüssel in den Mutationen M1 bis M5 gehören. In einem solchen Fall gibt es nur einen Teilnehmer an der Transaktion und das zuvor erwähnte zweiphasige Commit ist nicht erforderlich, wodurch die Schreibvorgänge beschleunigt werden.

Schreibvorgänge in mehreren Regionen

In einer multiregionalen Bereitstellung erhöht die regionsübergreifende Verteilung von Replikaten die Verfügbarkeit, verursacht jedoch Leistungskosten. Die Kommunikation zwischen Replikaten in verschiedenen Regionen dauert länger. Daher ist die Basislatenz für Firestore-Vorgänge im Vergleich zu Bereitstellungen in einer einzelnen Region etwas höher.

Wir konfigurieren die Replikate so, dass die Führungsebene für Splits immer in der primären Region bleibt. Die primäre Region ist die Region, von der Traffic auf den Firestore-Server eingeht. Diese Entscheidung der Führungsebene reduziert die Umlaufverzögerung bei der Kommunikation zwischen dem Speicherclient in Firestore und dem Replikat-Leader (oder Koordinator bei Transaktionen mit mehreren Aufteilungen).

Jeder Schreibvorgang in Firestore beinhaltet auch eine gewisse Interaktion mit der Echtzeit-Engine in Firestore. Weitere Informationen zu Echtzeitabfragen finden Sie unter Echtzeitabfragen in großem Umfang verstehen.

<ph type="x-smartling-placeholder">

Die Lebensdauer eines Lesevorgangs in Firestore verstehen

In diesem Abschnitt werden eigenständige Lesevorgänge in Firestore beschrieben, die nicht in Echtzeit sind. Intern verarbeitet der Firestore-Server die meisten dieser Abfragen in zwei Hauptphasen:

  1. Scan eines einzelnen Bereichs über die Tabelle Indexe
  2. Punktsuchen in der Tabelle Dokumente basierend auf dem Ergebnis des vorherigen Scans
Bestimmte Abfragen erfordern unter Umständen weniger Verarbeitungsmaßnahmen, z. B. ausschließlich schlüsselbasierte Abfragen im Datastore-Modus) oder weitere Verarbeitungen (z. B. IN-Abfragen) in Firestore.

Die Datenlesevorgänge aus der Speicherschicht erfolgen intern mithilfe einer Datenbanktransaktion, um konsistente Lesevorgänge zu gewährleisten. Im Gegensatz zu den Transaktionen, die für Schreibvorgänge verwendet werden, werden diese Transaktionen jedoch nicht gesperrt. Stattdessen wählen sie einen Zeitstempel aus und führen dann alle Lesevorgänge mit diesem Zeitstempel aus. Da sie keine Sperren erhalten, blockieren sie keine gleichzeitigen Lese-Schreib-Transaktionen. Zum Ausführen dieser Transaktion gibt der Speicherclient in Firestore eine Zeitstempelgrenze an, die der Speicherebene mitteilt, wie ein Lesezeitstempel ausgewählt wird. Der Typ der Zeitstempelgrenze, die vom Speicherclient in Firestore ausgewählt wird, wird durch die Leseoptionen für die Leseanfrage bestimmt.

Informationen zu Lesetransaktionen auf Speicherebene

In diesem Abschnitt werden die Arten von Lesevorgängen und ihre Verarbeitung auf der Speicherebene in Firestore beschrieben.

Starke Lesevorgänge

Standardmäßig sind Firestore-Lesevorgänge strikt konsistent. Diese strikte Konsistenz bedeutet, dass ein Firestore-Lesevorgang die neueste Version der Daten zurückgibt, die alle Schreibvorgänge widerspiegelt, die bis zum Start des Lesevorgangs festgeschrieben wurden.

Single Split – Lesevorgang

Der Speicherclient in Firestore sucht die Splits, denen die Schlüssel der zu lesenden Zeilen gehören. Angenommen, er muss einen Lesevorgang aus Teilgruppe 3 aus dem vorherigen Abschnitt ausführen. Der Client sendet die Leseanfrage an das nächste Replikat, um die Umlauflatenz zu reduzieren.

Je nach ausgewähltem Replikat können jetzt die folgenden Fälle eintreten:

  • Die Leseanfrage geht an ein Leader-Replikat (Zone A).
    • Da die führende Variante immer auf dem neuesten Stand ist, kann der Lesevorgang direkt durchgeführt werden.
  • Leseanfrage wird an ein Replikat gesendet, das kein Leader ist (z. B. Zone B) <ph type="x-smartling-placeholder">
      </ph>
    • Split 3 weiß aufgrund seines internen Status möglicherweise, dass er genügend Informationen hat, um den Lesevorgang zu verarbeiten, und der Split tut dies.
    • Split 3 ist sich nicht sicher, ob die neuesten Daten vorliegen. Sie sendet eine Nachricht an den Leader, um nach dem Zeitstempel der letzten Transaktion zu fragen, die für den Lesevorgang angewendet werden muss. Sobald diese Transaktion angewendet wurde, kann der Lesevorgang fortgesetzt werden.

Firestore gibt die Antwort dann an seinen Client zurück.

Lesevorgang mit mehreren Splits

In einer Situation, in der Lesevorgänge aus mehreren Splits erfolgen müssen, wird derselbe Mechanismus für alle Splits angewendet. Sobald die Daten von allen Splits zurückgegeben wurden, kombiniert der Speicherclient in Firestore die Ergebnisse. Firestore antwortet dem Client mit diesen Daten.

Veraltete Lesevorgänge

Starke Lesevorgänge sind der Standardmodus in Firestore. Dies kann jedoch mit einer potenziell höheren Latenz einhergehen, da möglicherweise eine Kommunikation mit der Führungsperson erforderlich ist. Oft muss Ihre Firestore-Anwendung nicht die neueste Version der Daten lesen und die Funktion funktioniert gut mit Daten, die möglicherweise ein paar Sekunden veraltet sind.

In einem solchen Fall kann der Client mithilfe der Leseoptionen read_time veraltete Lesevorgänge empfangen. In diesem Fall werden Lesevorgänge durchgeführt, da sich die Daten unter read_time befinden. Das nächste Replikat hat höchstwahrscheinlich bereits bestätigt, dass es Daten am angegebenen read_time hat. Für eine deutlich bessere Leistung sind 15 Sekunden ein angemessener Veralterungswert. Selbst bei veralteten Lesevorgängen sind die ausgegebenen Zeilen konsistent.

Hotspots vermeiden

Die Splits in Firestore werden automatisch in kleinere Teile aufgeteilt, um die Arbeit für das Bereitstellen von Traffic bei Bedarf oder bei Erweiterung des Schlüsselspeichers auf mehr Speicherserver zu verteilen. Aufteilungen, die zur Bewältigung von übermäßigem Traffic erstellt wurden, werden etwa 24 Stunden lang aufbewahrt, auch wenn der Traffic ganz ausfällt. Wenn es also wiederkehrende Traffic-Spitzen gibt, werden die Aufteilungen beibehalten und bei Bedarf weitere Aufteilungen eingeführt. Diese Mechanismen helfen Firestore-Datenbanken, bei zunehmender Traffic-Last oder Datenbankgröße automatisch zu skalieren. Es gibt jedoch einige Einschränkungen, die im Folgenden erläutert werden.

Die Aufteilung von Speicher und Last nimmt Zeit in Anspruch. Wenn der Traffic zu schnell erhöht wird, kann dies zu einer hohen Latenz oder zu Fehlern bei Zeitüberschreitungen führen, die im Allgemeinen als Hotspots bezeichnet werden, während der Dienst angepasst wird. Die Best Practice besteht darin, Vorgänge über den Schlüsselbereich zu verteilen und gleichzeitig den Traffic für eine Sammlung in einer Datenbank mit 500 Vorgängen pro Sekunde zu erhöhen. Nach dieser allmählichen Anlaufzeit können Sie den Traffic alle fünf Minuten um bis zu 50% erhöhen. Dieser Prozess wird als 500/50/5-Regel bezeichnet und positioniert die Datenbank so, dass sie für Ihre Arbeitslast optimal skaliert wird.

Obwohl Aufteilungen mit zunehmender Last automatisch erstellt werden, kann Firestore einen Schlüsselbereich nur so lange aufteilen, bis ein einzelnes Dokument mit einem dedizierten Satz replizierter Speicherserver bereitgestellt wird. Daher können hohe und anhaltende Mengen gleichzeitiger Vorgänge für ein einzelnes Dokument zu einem Engpass bei diesem Dokument führen. Wenn Sie bei einem einzelnen Dokument dauerhaft hohe Latenzen feststellen, sollten Sie Ihr Datenmodell ändern, um die Daten auf mehrere Dokumente aufzuteilen oder zu replizieren.

Konfliktfehler treten auf, wenn mehrere Vorgänge versuchen, dasselbe Dokument gleichzeitig zu lesen und/oder zu schreiben.

Ein weiterer Sonderfall des Heißlaufens tritt auf, wenn ein sequentiell zunehmender/abnehmender Schlüssel als Dokument-ID in Firestore verwendet wird und es eine erheblich hohe Anzahl von Vorgängen pro Sekunde gibt. Das Erstellen weiterer Aufteilungen ist hier nicht hilfreich, da der Trafficanstieg einfach auf die neu erstellte Aufteilung übertragen wird. Da Firestore standardmäßig alle Felder im Dokument automatisch indexiert, können solche beweglichen Hotspots auch im Indexbereich eines Dokumentfelds erstellt werden, das einen fortlaufend steigenden/abnehmenden Wert wie einen Zeitstempel enthält.

Beachten Sie, dass Firestore mithilfe der oben beschriebenen Praktiken für beliebig große Arbeitslasten skalieren kann, ohne dass Sie eine Konfiguration anpassen müssen.

Fehlerbehebung

Firestore bietet Key Visualizer als Diagnosetool für die Analyse von Nutzungsmustern und die Behebung von Heißlaufensproblemen.

Weitere Informationen