Lese- und Schreibvorgänge im großen Maßstab

Lesen Sie dieses Dokument, um fundierte Entscheidungen bei der Architektur Ihrer Anwendungen für hohe Leistung und Zuverlässigkeit zu treffen. Dieses Dokument enthält erweiterte Cloud Firestore-Themen. Wenn Sie gerade erst mit Cloud Firestore beginnen, lesen Sie stattdessen die Kurzanleitung.

Cloud Firestore ist eine flexible, skalierbare Datenbank für die Mobilgeräte-, Web- und Serverentwicklung über Firebase und Google Cloud. Der Einstieg in Cloud Firestore ist denkbar einfach und Sie können schnell leistungsstarke Anwendungen erstellen.

Damit deine Anwendungen weiterhin gut funktionieren und die Datenbankgröße steigt und der Traffic zunimmt, ist es hilfreich, die Mechanismen von Lese- und Schreibvorgängen im Cloud 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, die Sie vor der Architektur Ihrer Anwendung beachten sollten.

Allgemeine Komponenten

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

Allgemeine Komponenten

Cloud Firestore SDK und Clientbibliotheken

Cloud Firestore unterstützt SDKs und Clientbibliotheken für verschiedene Plattformen. Eine App kann zwar direkte HTTP- und RPC-Aufrufe an die Cloud Firestore API senden, die Clientbibliotheken bieten jedoch eine Abstraktionsschicht, um die API-Nutzung zu vereinfachen und Best Practices zu implementieren. Sie können auch zusätzliche Funktionen wie Offlinezugriff oder Caches bieten.

Google Front End (GFE)

Dies ist ein Infrastrukturdienst, der für alle Google Cloud-Dienste gilt. Das GFE akzeptiert eingehende Anfragen und leitet sie an den entsprechenden Google-Dienst (in diesem Kontext Cloud Firestore) weiter. Außerdem bietet es andere wichtige Funktionen, darunter Schutz vor Denial-of-Service-Angriffen.

Cloud Firestore-Dienst

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

Cloud Firestore Speicherschicht

Die Speicherebene Cloud Firestore ist für das Speichern der Daten und Metadaten sowie der zugehörigen Datenbankfunktionen von Cloud Firestore verantwortlich. In den folgenden Abschnitten wird beschrieben, wie Daten auf der Speicherebene Cloud Firestore 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 Cloud Firestore besser verstehen.

Schlüsselbereiche und ‑aufteilungen

Cloud 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 für jedes Dokument in einen einzelnen Schlüssel umgewandelt. Dokumente werden logisch gespeichert und lexikographisch nach diesem einzelnen Schlüssel sortiert. Der Begriff „Schlüsselbereich“ bezieht sich auf einen lexikografisch zusammenhängenden Schlüsselbereich.

Eine typische Cloud Firestore-Datenbank ist zu groß für einen einzelnen physischen Computer. Es gibt auch Szenarien, in denen die Arbeitslast für die Daten zu hoch ist, um von einem einzelnen Computer verarbeitet zu werden. Um große Arbeitslasten zu bewältigen, partitioniert Cloud Firestore die Daten in separate Teile, die auf mehreren Maschinen oder Speicherservern gespeichert und bereitgestellt werden können. Diese Partitionen werden in den Datenbanktabellen in Blöcken von Schlüsselbereichen vorgenommen, die als Splits bezeichnet werden.

Synchrone Replikation

Die Datenbank wird immer automatisch und synchron repliziert. 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 in den verschiedenen Kopien des Splits wird vom Paxos-Algorithmus für den Konsens verwaltet. Ein Replikat jedes Splits wird zum Paxos-Leader gewählt, der für die Verarbeitung von Schreibvorgängen für diesen Split verantwortlich ist. Durch die synchrone Replikation können Sie immer die neueste Datenversion von Cloud Firestore lesen.

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

Datenlayout

Cloud Firestore ist eine schemalose Dokumentendatenbank. Intern werden die Daten jedoch hauptsächlich in zwei Tabellen im Stil relationaler Datenbanken in der Speicherschicht angeordnet:

  • Tabelle Dokumente: Dokumente werden in dieser Tabelle 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 Cloud 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

Eine Region im Vergleich zu mehreren Regionen

Wenn Sie eine Datenbank erstellen, müssen Sie eine Region oder Multi-Region auswählen.

Ein einzelner regionaler Standort ist ein bestimmter geografischer Ort, z. B. us-west1. Die Datenaufteilungen einer Cloud Firestore-Datenbank haben, wie bereits erwähnt, Repliken in verschiedenen Zonen innerhalb der ausgewählten Region.

Ein multiregionaler Standort besteht aus einer definierten Gruppe von Regionen, in denen Repliken der Datenbank gespeichert werden. Bei einer multiregionalen Bereitstellung von Cloud 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 sie auch bei einem Ausfall einer ganzen Region geschrieben und gelesen werden.

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

Eine Region im Vergleich zu mehreren Regionen

Lebensdauer einer Schreiboperation in Cloud Firestore

Ein Cloud Firestore-Client kann Daten schreiben, indem er ein einzelnes Dokument erstellt, aktualisiert oder löscht. Ein Schreibvorgang in ein einzelnes Dokument erfordert, dass sowohl das Dokument als auch die zugehörigen Indexeinträge atomar in der Speicherschicht aktualisiert werden. Cloud Firestore unterstützt auch atomare Vorgänge, die aus mehreren Lese- und/oder Schreibvorgängen für ein oder mehrere Dokumente bestehen.

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

Allgemeine Schritte bei einer Schreibtransaktion

Wenn der Cloud Firestore-Client eine Schreib- oder Commit-Transaktion mit einer der oben genannten Methoden ausführt, wird dies intern als Datenbank-Lese-/Schreibtransaktion in der Speicherschicht ausgeführt. Durch die Transaktion kann Cloud Firestore die oben genannten ACID-Attribute bereitstellen.

Als ersten Schritt einer Transaktion liest Cloud Firestore das vorhandene Dokument und ermittelt die Änderungen, die an den Daten in der Tabelle „Dokumente“ vorgenommen werden müssen.

Dazu gehören auch die erforderlichen Aktualisierungen an der Tabelle „Indexes“:

  • 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.
  • Für Felder, die in den Dokumenten geändert werden, müssen sowohl Löschvorgänge (für alte Werte) als auch Einfügungen (für neue Werte) in der Tabelle „Indexes“ ausgeführt werden.

Zur Berechnung der zuvor erwähnten Mutationen liest Cloud Firestore die Indexierungskonfiguration für das Projekt. In der Indexierungskonfiguration werden Informationen zu den Indexen für ein Projekt gespeichert. Cloud Firestore verwendet zwei Arten von Indexen: Einzelfeld- und zusammengesetzte Indexe. Ausführliche Informationen zu den in Cloud Firestore erstellten Indexen finden Sie unter Indextypen in Cloud Firestore.

Sobald die Mutationen berechnet wurden, sammelt Cloud Firestore sie in einer Transaktion und führt sie dann aus.

Schreibtransaktionen in der Speicherschicht

Wie bereits erwähnt, beinhaltet ein Schreibvorgang in Cloud Firestore eine Lese-Schreib-Transaktion auf der Speicherebene. Je nach Datenlayout kann ein Schreibvorgang eine oder mehrere Aufteilungen umfassen, wie im Datenlayout dargestellt.

Im folgenden Diagramm hat die Cloud Firestore-Datenbank acht Teilungen (1–8), die auf drei verschiedenen Speicherservern in einer einzigen Zone gehostet werden. Jede Teilung wird in drei oder mehr verschiedenen Zonen repliziert. Jeder Split hat einen Paxos-Leader, der sich für verschiedene Splits in einer anderen Zone befinden kann.

<span class=Cloud Firestore-Datenbankaufteilung">

Sehen Sie sich eine Cloud Firestore-Datenbank mit der Sammlung Restaurants so an:

Restaurantsammlung

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

Zu einem Dokument in der Sammlung wechseln

Im Folgenden werden die allgemeinen Schritte beschrieben, die beim Schreiben ausgeführt werden:

  1. Erstellen Sie eine Lese-Schreib-Transaktion.
  2. Lesen Sie das Dokument restaurant1 in der Sammlung Restaurants aus der Tabelle Dokumente der Speicherebene.
  3. Lesen Sie die Indexe für das Dokument aus der Tabelle Indexe.
  4. Berechnen Sie die Mutationen, die an den Daten vorgenommen werden sollen. In diesem Fall gibt es fünf Mutationen:
    • M1: Aktualisieren Sie die Zeile für restaurant1 in der Tabelle Dokumente, um die Änderung des Werts des Felds priceCategory widerzuspiegeln.
    • M2 und M3: Löschen Sie die Zeilen für den alten Wert priceCategory in der Tabelle Indexe für absteigende 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 ein Commit für diese Mutationen aus.

Der Speicherclient im Dienst Cloud Firestore sucht nach den Splits, denen die Schlüssel der zu ändernden Zeilen gehören. Angenommen, Split 3 dient M1 und Split 6 dient M2–M5. Es gibt eine verteilte Transaktion, an der alle diese Teilungen als Teilnehmer beteiligt sind. Die Teilnehmeraufteilungen können auch andere Aufteilungen enthalten, aus denen Daten im Rahmen der Lese-/Schreibtransaktion zuvor gelesen wurden.

In den folgenden Schritten wird beschrieben, was beim Commit passiert:

  1. Der Speicherclient führt einen Commit aus. Der Commit enthält die Mutationen M1–M5.
  2. Die Splits 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 Replicas aus. Das Quorum wird erreicht, wenn die meisten Replikate dem Leader mit einer ok to commit-Antwort antworten.
    • Jeder Teilnehmer benachrichtigt dann den Koordinator, wenn er bereit ist (erste Phase des zweistufigen Commits). Wenn ein Teilnehmer den Commit der Transaktion nicht durchführen kann, wird die gesamte Transaktion aborts.
  4. Sobald der Koordinator weiß, dass alle Teilnehmer, einschließlich seiner selbst, bereit sind, teilt er allen Teilnehmern das accept-Transaktionsergebnis mit (zweite Phase des zweiphasigen Commits). In dieser Phase zeichnet jeder Teilnehmer die Commit-Entscheidung an einem stabilen Speicherplatz auf und die Transaktion wird übergeben.
  5. Der Koordinator antwortet dem Speicherclient in Cloud Firestore, dass die Transaktion übergeben wurde. Parallel dazu wenden der Koordinator und alle Teilnehmer die Mutationen auf die Daten an.

Commit-Lebenszyklus

Wenn die Cloud Firestore-Datenbank klein ist, kann es vorkommen, dass ein einzelner Split alle Schlüssel in den Mutationen M1–M5 enthält. In diesem Fall gibt es nur einen Teilnehmer an der Transaktion und der oben erwähnte zweiphasige Commit ist nicht erforderlich. Dadurch sind die Schreibvorgänge schneller.

Schreibvorgänge in mehreren Regionen

Bei einer Bereitstellung in mehreren Regionen erhöht die Verteilung von Replikas über die Regionen hinweg die Verfügbarkeit, hat aber Leistungseinbußen zur Folge. Die Kommunikation zwischen Replikaten in verschiedenen Regionen dauert länger. Daher ist die Grundlatenz für Cloud Firestore-Vorgänge im Vergleich zu Bereitstellungen in einer einzelnen Region etwas höher.

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

Jede Schreiboperation in Cloud Firestore erfordert auch eine Interaktion mit der Echtzeit-Engine in Cloud Firestore. Weitere Informationen zu Echtzeitabfragen finden Sie unter Echtzeitabfragen im großen Maßstab.

Lebenszyklus einer Leseaktion in Cloud Firestore

Dieser Abschnitt befasst sich mit eigenständigen Nicht-Echtzeit-Lesevorgängen in Cloud Firestore. Intern verarbeitet der Cloud Firestore-Server die meisten dieser Abfragen in zwei Hauptphasen:

  1. Scan eines einzelnen Bereichs über die Tabelle Indexe
  2. Punktlookups in der Tabelle Documents basierend auf dem Ergebnis des vorherigen Scans
Es kann bestimmte Abfragen geben, die in Cloud Firestore weniger oder mehr Verarbeitung erfordern (z. B. IN-Abfragen).

Die Datenlesevorgänge aus der Speicherschicht werden intern mithilfe einer Datenbanktransaktion durchgeführt, um konsistente Lesevorgänge zu ermöglichen. Im Gegensatz zu den für Schreibvorgänge verwendeten Transaktionen werden für diese Transaktionen jedoch keine Sperren verwendet. Stattdessen wird ein Zeitstempel ausgewählt und dann werden alle Lesevorgänge zu diesem Zeitstempel ausgeführt. Da sie keine Sperren erwerben, blockieren sie keine gleichzeitigen Lese-Schreib-Transaktionen. Um diese Transaktion auszuführen, gibt der Speicherclient in Cloud Firestore eine Zeitstempelgrenze an, die der Speicherschicht mitteilt, wie ein Lesezeitstempel ausgewählt werden soll. Die Art der Zeitstempelgrenze, die vom Speicherclient in Cloud Firestore ausgewählt wird, wird durch die Leseoptionen für die Leseanfrage bestimmt.

Lesetransaktionen in der Speicherschicht

In diesem Abschnitt werden die Arten von Lesezugriffen und ihre Verarbeitung in der Speicherschicht in Cloud Firestore beschrieben.

Starke Lesevorgänge

Standardmäßig sind Cloud Firestore-Lesevorgänge strikt konsistent. Diese starke Konsistenz bedeutet, dass bei einem Cloud Firestore-Lesevorgang die neueste Version der Daten zurückgegeben wird, die alle Schreibvorgänge widerspiegelt, die bis zum Beginn des Lesevorgangs durchgeführt wurden.

Lesevorgang mit einzelner Aufteilung

Der Speicherclient in Cloud Firestore sucht die Splits mit den Schlüsseln der zu lesenden Zeilen. Angenommen, es muss eine Leseoperation aus Split 3 aus dem vorherigen Abschnitt ausgeführt werden. Der Client sendet die Leseanfrage an das nächstgelegene Replikat, um die RTT zu reduzieren.

Je nach ausgewähltem Replikat kann es zu folgenden Fällen kommen:

  • Die Leseanfrage wird an ein Leader-Replikat (Zone A) gesendet.
    • Da der Leader immer auf dem neuesten Stand ist, kann der Lesevorgang direkt ausgeführt werden.
  • Die Leseanfrage wird an ein Replikat gesendet, das nicht als Leader fungiert (z. B. Zone B).
    • 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.
    • Bei Split 3 ist nicht sicher, ob die aktuellen Daten verwendet werden. 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.

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

Lesevorgänge mit mehreren Splits

Wenn die Lesevorgänge aus mehreren Teilungen erfolgen müssen, wird derselbe Mechanismus für alle Teilungen verwendet. Sobald die Daten von allen Splits zurückgegeben wurden, kombiniert der Speicherclient in Cloud Firestore die Ergebnisse. Cloud Firestore antwortet dann mit diesen Daten an seinen Kunden.

Veraltete Lesevorgänge

Starke Lesevorgänge sind der Standardmodus in Cloud Firestore. Dies geht jedoch mit einer potenziell höheren Latenz einher, da möglicherweise eine Kommunikation mit dem Leiter erforderlich ist. Oft muss Ihre Cloud Firestore-Anwendung nicht die neueste Version der Daten lesen und die Funktion funktioniert auch mit Daten, die einige Sekunden alt sind.

In diesem Fall kann der Client mithilfe der Leseoptionen read_time veraltete Lesevorgänge erhalten. In diesem Fall werden Lesevorgänge so ausgeführt, als wären die Daten bei read_time. Das nächste Replikat hat mit hoher Wahrscheinlichkeit bereits bestätigt, dass es Daten unter der angegebenen read_time hat. Für eine deutlich bessere Leistung ist ein Veralterungswert von 15 Sekunden angemessen. Auch bei veralteten Lesevorgängen sind die zurückgegebenen Zeilen miteinander konsistent.

Hotspots vermeiden

Die Splits in Cloud Firestore werden automatisch in kleinere Teile aufgeteilt, um die Auslieferung von Traffic bei Bedarf oder bei Erweiterung des Schlüsselbereichs auf mehr Speicherserver zu verteilen. Aufteilungen, die zur Bewältigung von übermäßigem Traffic erstellt wurden, bleiben etwa 24 Stunden lang erhalten, auch wenn der Traffic nachlässt. Wenn es also wiederkehrende Traffic-Spitzen gibt, werden die Aufteilungen beibehalten und bei Bedarf weitere Aufteilungen eingeführt. Diese Mechanismen tragen dazu bei, dass Cloud Firestore-Datenbanken bei steigender Trafficlast oder Datenbankgröße automatisch skaliert werden. Es gibt jedoch einige Einschränkungen, die unten 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. Es empfiehlt sich, Vorgänge auf den Schlüsselbereich zu verteilen und gleichzeitig den Traffic auf einer Sammlung in einer Datenbank mit 500 Vorgängen pro Sekunde zu erhöhen. Nach dieser schrittweisen Erhöhung können Sie den Traffic alle fünf Minuten um bis zu 50% erhöhen. Dieser Vorgang wird als 500/50/5-Regel bezeichnet und sorgt dafür, dass die Datenbank optimal skaliert wird, um Ihre Arbeitslast zu bewältigen.

Obwohl Aufteilungen bei zunehmender Auslastung automatisch erstellt werden, kann Cloud Firestore einen Schlüsselbereich nur so lange aufteilen, bis ein einzelnes Dokument mithilfe einer speziellen Gruppe von replizierten Speicherservern 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 Hotspots tritt auf, wenn ein sequenziell zunehmender/abnehmender Schlüssel als Dokument-ID in Cloud Firestore verwendet wird und es eine sehr hohe Anzahl von Vorgängen pro Sekunde gibt. Das Erstellen weiterer Aufteilungen hilft hier nicht, da der Anstieg des Traffics einfach auf die neu erstellte Aufteilung verlagert wird. Da Cloud Firestore standardmäßig alle Felder im Dokument automatisch indexiert, können solche beweglichen Hotspots auch im Indexbereich für ein Dokumentfeld erstellt werden, das einen sequenziell ansteigenden/abnehmenden Wert wie einen Zeitstempel enthält.

Wenn Sie die oben beschriebenen Praktiken befolgen, kann Cloud Firestore skaliert werden, um beliebig große Arbeitslasten zu bedienen, ohne dass Sie die Konfiguration anpassen müssen.

Fehlerbehebung

Cloud Firestore bietet den Key Visualizer als Diagnosetool an, mit dem sich Nutzungsmuster analysieren und Hotspot-Probleme beheben lassen.

Weitere Informationen