Mit der verteilten Architektur von Spanner können Sie Ihr Schema so entwerfen, dass Hotspots vermieden werden. Das sind Situationen, in denen zu viele Anfragen an denselben Server gesendet werden, was die Ressourcen des Servers überlastet und möglicherweise zu hohen Latenzen führt.
Auf dieser Seite werden Best Practices zum Entwerfen von Schemas beschrieben, die Sie vermeiden sollten. und Hotspots entstehen. Eine Möglichkeit, Hotspots zu vermeiden, besteht darin, das Schemadesign so anzupassen, dass Spanner die Daten auf mehrere Server aufteilen und verteilen kann. Die Verteilung von Daten auf mehrere Server unterstützt den Betrieb Ihrer Spanner-Datenbank besonders bei Bulk-Dateneinfügungen.
Primärschlüssel zur Vermeidung von Hotspots auswählen
Wie unter Schema und Datenmodell beschrieben, sollten Sie bei der Auswahl eines Primärschlüssels im Schemadesign vorsichtig vorgehen, damit Sie nicht versehentlich Hotspots in der Datenbank erzeugen. Eine Ursache für Hotspots ist eine Spalte, deren Wert monoton ändert sich als erster Schlüsselteil, da dies dazu führt, dass alle Einfügungen das Ende des Schlüsselraums. Dieses Muster ist nicht erwünscht, da Spanner verwendet Schlüsselbereiche, um die Daten auf die Server aufzuteilen. Das bedeutet, dass alle Einfügungen die an einen Server geleitet werden, der die gesamte Arbeit übernimmt.
Angenommen, Sie möchten eine Spalte mit dem Zeitstempel des letzten Zugriffs für Zeilen der Tabelle UserAccessLog
beibehalten. In der folgenden Tabellendefinition wird ein
zeitstempelbasierten Primärschlüssel als ersten Schlüsselteil. Wir empfehlen dies nicht, wenn in die Tabelle häufig Daten eingefügt werden:
GoogleSQL
CREATE TABLE UserAccessLog ( LastAccess TIMESTAMP NOT NULL, UserId INT64 NOT NULL, ... ) PRIMARY KEY (LastAccess, UserId);
PostgreSQL
CREATE TABLE UserAccessLog ( LastAccess TIMESTAMPTZ NOT NULL, UserId bigint NOT NULL, ... PRIMARY KEY (LastAccess, UserId) );
Das Problem ist, dass Zeilen in der Reihenfolge der letzten und weil sich die Zeitstempel des letzten Zugriffs ständig erhöhen, werden sie immer an das Ende der Tabelle gesetzt. Der Hotspot entsteht dadurch, dass ein einzelner Spanner-Server alle Schreibvorgänge erhält und somit überlastet wird.
Das folgende Diagramm veranschaulicht dieses Problem:
Die oben dargestellte Tabelle UserAccessLog
enthält fünf Beispieldatenzeilen, die fünf verschiedene Nutzer darstellen, wobei alle fünf eine Nutzeraktion im Abstand von etwa einer Millisekunde voneinander ausführen. Aus dem Diagramm geht auch die Reihenfolge, in der Spanner die Zeilen einfügt, hervor (die beschrifteten Pfeile geben die Reihenfolge der Schreibvorgänge für die Zeilen an). Da die Einfügungen nach Zeitstempel sortiert werden und der Zeitstempelwert stetig zunimmt, werden die Einfügungen immer am Ende der Tabelle vorgenommen und demselben Split zugewiesen. (Wie in den
Schema und Datenmodell,
Split ist ein Satz von Zeilen aus einer oder mehreren verbundenen Tabellen,
in der Reihenfolge des Zeilenschlüssels gespeichert.)
Dies ist problematisch, da Spanner verschiedenen Servern in der Split-Einheiten, sodass der Server, der diesem Split zugewiesen ist, die alle Einfügeanfragen verarbeitet. Je häufiger Nutzerzugriffe stattfinden, desto häufiger erhält der entsprechende Server Einfügungsanfragen. Der Server wird dann anfällig für Hotspots und sieht die rote Rahmenlinie und den Hintergrund. In dieser vereinfachten Illustration Jeder Server verarbeitet höchstens einen Split. Tatsächlich kann Spanner jedoch jedem Server mehrere Splits zuweisen.
Wenn Spanner weitere Zeilen an die Tabelle anhängt, wird der Split größer. ca. 8 GB erreicht hat, erstellt Spanner eine weitere wie unter Lastbasierte Aufteilung beschrieben. Spanner hängt nachfolgende neue Zeilen an diesen neuen Split an und der Server die der Aufteilung zugewiesen ist, wird zum neuen potenziellen Hotspot.
Beim Auftreten von Hotspots können Sie beobachten, dass Einfügungen langsam verarbeitet werden und auch andere Arbeiten auf demselben Server langsamer vorangehen. Die Änderung der Reihenfolge der Spalte LastAccess
in aufsteigender Reihenfolge löst dieses Problem nicht, da dann alle Schreibvorgänge stattdessen am Anfang der Tabelle eingefügt werden. Auch in diesem Fall würden alle Einfügungen an einen einzigen Server gesendet.
Best Practice 1 für das Schemadesign: Wählen Sie keine Spalte aus, deren Wert als erster Schlüssel für eine Tabelle mit hoher Schreibrate monoton zu- oder abnimmt.
Universally Unique Identifier verwenden
Sie können als Primärschlüssel eine UUID (Universally Unique Identifier) gemäß RFC 4122 verwenden. Wir empfehlen die Verwendung der UUID Version 4, da werden zufällige Werte in der Bitsequenz verwendet. UUIDs der Version 1 werden nicht empfohlen weil sie den Zeitstempel in den Bits höherer Ordnung speichern.
Die UUID kann auf verschiedene Arten als Primärschlüssel gespeichert werden:
- In einer
STRING(36)
-Spalte. - In einem
INT64
-Spaltenpaar - In einer
BYTES(16)
-Spalte.
Für eine STRING(36)
-Spalte können Sie den Spanner-GENERATE_UUID()
verwenden
(GoogleSQL oder PostgreSQL) als Spaltenstandardwert für
Spanner automatisch UUID-Werte generieren kann.
Die Verwendung einer UUID bringt einige Nachteile mit sich:
- Ihre beträchtliche Größe belegt mindestens 16 Byte. Andere mögliche Primärschlüssel erfordern nicht so viel Speicherplatz.
- Sie enthalten keine Informationen zum Datensatz. Ein Primärschlüssel von
SingerId
undAlbumId
haben eine inhärente Bedeutung, eine UUID jedoch nicht. - Da die Lokalität zwischen verwandten Einträgen verloren geht, ist die Verwendung einer UUID beseitigt Hotspots.
Bit-Umkehrungen für sequenzielle Werte
Sie sollten darauf achten, dass numerisch (INT64
in GoogleSQL oder bigint
in PostgreSQL)
Primärschlüssel ändern sich nicht sequentiell. Sequenzielle primäre
Schlüssel können in großem Maßstab zu Heißlaufen führen. Eine Möglichkeit,
besteht das Problem darin, die sequentiellen Werte in Bit-Umkehrung zu kehren,
Primärschlüsselwerte gleichmäßig über den Schlüsselbereich verteilt sind.
Spanner unterstützt eine bitumkehrte Sequenz, die eindeutige Bitumgekehrte Ganzzahlwerte. Sie können eine Sequenz in der ersten (oder nur) Komponente in einem Primärschlüssel, um Heißlaufensprobleme zu vermeiden. Weitere Informationen finden Sie unter Bit-reversed sequence.
Schlüsselreihenfolge vertauschen
Eine Möglichkeit, Schreibvorgänge gleichmäßiger über den Schlüsselbereich zu verteilen, besteht darin, die Reihenfolge sodass die Spalte mit dem monotonen Wert nicht der erster wichtiger Teil:
GoogleSQL
CREATE TABLE UserAccessLog ( LastAccess TIMESTAMP NOT NULL, UserId INT64 NOT NULL, ... ) PRIMARY KEY (UserId, LastAccess);
PostgreSQL
CREATE TABLE UserAccessLog ( LastAccess TIMESTAMPTZ NOT NULL, UserId bigint NOT NULL, ... PRIMARY KEY (UserId, LastAccess) );
In diesem geänderten Schema werden die Einfügungen nun nach UserId
und nicht in chronologischer Reihenfolge nach dem Zeitstempel des letzten Zugriffs angeordnet. Dieses Schema verteilt Schreibvorgänge auf
da es unwahrscheinlich ist, dass ein einzelner Nutzer Tausende
von Ereignissen pro Sekunde.
Im Folgenden sehen Sie die fünf Zeilen aus der Tabelle UserAccessLog
.
die Spanner mit UserId
statt mit dem Zugriffszeitstempel anordnet:
Hier teilt Spanner die UserAccessLog
-Daten in drei Splits auf, wobei jeder Split etwa tausend Zeilen mit geordneten UserId
-Werten enthält.
Dies ist eine vernünftige Schätzung, wie die Nutzerdaten aufgeteilt werden könnten, vorausgesetzt,
Zeile enthält etwa 1 MB Nutzerdaten mit einer maximalen Aufteilungsgröße von
etwa 8 GB. Auch wenn die Nutzerereignisse
wird jedes Ereignis von einem anderen Nutzer ausgelöst.
bei Einfügungen ist es viel geringer, dass ein Hotspot entsteht als
Zeitstempel für die Sortierung.
Siehe auch die verwandte Best Practice zur Reihenfolge des Zeitstempels Schlüssel.
Eindeutigen Schlüssel hashen und Schreibvorgänge auf logische Shards aufteilen
Die Last kann auch auf mehrere Server verteilt werden. Erstellen Sie zu diesem Zweck eine Spalte, die den Hash des eindeutigen Schlüssels enthält, und nutzen Sie diese Hash-Spalte (oder die Hash-Spalte und die Spalten mit dem eindeutigen Schlüssel) als Primärschlüssel. Mit diesem Muster können Hotspots vermieden werden, da neue Zeilen gleichmäßiger über den Schlüsselbereich verteilt werden.
Sie können den Hash-Wert verwenden, um logische Shards oder Partitionen in einer Datenbank zu erstellen. In einer physisch fragmentierten Datenbank sind die Zeilen auf mehrere
Datenbankservern. In einer logisch fragmentierten Datenbank definieren die Daten in der Tabelle die
Shards. Wenn Sie zum Beispiel Schreibvorgänge in die Tabelle UserAccessLog
auf N logische Shards verteilen möchten, fügen Sie am Anfang der Tabelle eine Schlüsselspalte ShardId
ein:
GoogleSQL
CREATE TABLE UserAccessLog ( ShardId INT64 NOT NULL, LastAccess TIMESTAMP NOT NULL, UserId INT64 NOT NULL, ... ) PRIMARY KEY (ShardId, LastAccess, UserId);
PostgreSQL
CREATE TABLE UserAccessLog ( ShardId bigint NOT NULL, LastAccess TIMESTAMPTZ NOT NULL, UserId bigint NOT NULL, ... PRIMARY KEY (ShardId, LastAccess, UserId) );
Hashen Sie zum Berechnen des ShardId
eine Kombination der Primärschlüsselspalten und dann
Modulo N des Hashwerts berechnen. Beispiel:
ShardId = hash(LastAccess and UserId) % N
Durch die Auswahl der Hash-Funktion und die Kombination der Spalten bestimmen Sie, wie die Zeilen über den Schlüsselbereich verteilt werden. Spanner erstellt dann Splits für die Zeilen, um die Leistung zu optimieren.
Im folgenden Diagramm wird dargestellt, wie durch die Verwendung eines Hash zum Erstellen dreier logischer Shards der Durchsatz für Schreibvorgänge gleichmäßiger auf die Server verteilt werden kann:
In diesem Fall wird die Tabelle UserAccessLog
nach ShardId
sortiert, die als Hash-Funktion der Schlüsselspalten berechnet wird. Die fünf UserAccessLog
-Zeilen werden in drei logische Shards aufgeteilt, die sich zufälligerweise jeweils in einem anderen Split befinden. Die Einfügungen werden gleichmäßig auf die Splits aufgeteilt. So wird auch der Durchsatz für Schreibvorgänge gleichmäßig auf die drei Server verteilt, die die Splits verarbeiten.
Mit Spanner können Sie auch eine Hash-Funktion in einem generierte Spalte.
Verwenden Sie dazu in Google SQL die Methode FARM_FINGERPRINT während der Schreibzeit, wie im folgenden Beispiel gezeigt:
GoogleSQL
CREATE TABLE UserAccessLog (
ShardId INT64 NOT NULL
AS (MOD(FARM_FINGERPRINT(CAST(LastAccess AS STRING)), 2048)) STORED,
LastAccess TIMESTAMP NOT NULL,
UserId INT64 NOT NULL,
) PRIMARY KEY (ShardId, LastAccess, UserId);
Die ausgewählte Hash-Funktion bestimmt, wie gut die Einfügungen gestreut werden im gesamten Schlüsselbereich. Sie benötigen keinen kryptografischen Hash, obwohl ein könnte ein kryptografischer Hash eine gute Wahl sein. Wenn Sie eine Hash-Funktion auswählen, sind folgende Faktoren zu berücksichtigen:
- Hotspot-Vermeidung. Eine Funktion, die zu mehr Hashwerten führt, reduziert in der Regel Hotspots.
- Leseeffizienz. Lesevorgänge in allen Hashwerten sind schneller, wenn weniger Hashwerte zu scannen sind.
- Knotenanzahl.
Bei zeitstempelbasierten Schlüsseln absteigende Reihenfolge verwenden
Wenn Sie für Ihren Verlauf eine Tabelle mit Zeitstempel als Schlüssel haben, sollten Sie eine absteigende Reihenfolge für die Schlüsselspalte in Betracht ziehen, wenn eine der folgenden Bedingungen zutrifft:
- Wenn Sie den aktuellen Verlauf lesen möchten, verwenden Sie ein
für den Verlauf verschränkt.
in der übergeordneten Zeile lesen. In diesem Fall werden bei einer
DESC
-Zeitstempelspalte die neuesten Verlaufseinträge neben der übergeordneten Zeile gespeichert. Andernfalls erfordert das Lesen der übergeordneten Zeile und des neusten Verlaufs einen Suchvorgang in der Mitte, um den älteren Verlauf zu überspringen. - Wenn Sie sequenzielle Einträge in umgekehrter chronologischer Reihenfolge lesen und nicht genau wissen, wie weit Sie zurückgehen. Sie können beispielsweise mit einer SQL-Abfrage mit einem
LIMIT
die neuesten N-Ereignisse abrufen oder Sie brechen möglicherweise den Lesevorgang ab, nachdem Sie eine bestimmte Anzahl von Zeilen gelesen haben. In diesen sollten Sie mit den neuesten Einträgen beginnen und die Einträge der Reihe nach lesen. ältere Einträge, bis die Bedingung erfüllt ist. Das funktioniert bei Spanner. effizienter für Zeitstempelschlüssel, die Spanner in absteigend sortiert.
Fügen Sie das Schlüsselwort DESC
hinzu, damit Sie den Zeitstempelschlüssel in absteigender Reihenfolge festlegen. Beispiel:
GoogleSQL
CREATE TABLE UserAccessLog ( UserId INT64 NOT NULL, LastAccess TIMESTAMP NOT NULL, ... ) PRIMARY KEY (UserId, LastAccess DESC);
Best Practice 2 für das Schemadesign: Absteigende oder aufsteigende Reihenfolge hängt von den Nutzeranfragen ab, z. B. ist „Top“ die neueste oder „Top“ die älteste.
Verschränkte Indexe für eine Spalte, deren Wert monoton zu- oder abnimmt, verwenden
Ähnlich wie beim vorherigen Primärschlüsselbeispiel, das Sie vermeiden sollten, ist es auch ein schlechte Idee, nicht verschränkte Indexe für Spalten zu erstellen, deren Werte monoton zu- oder abnehmen, auch wenn es sich nicht um Primärschlüsselspalten handelt.
Beispiel: Angenommen, Sie definieren die folgende Tabelle, in der LastAccess
keine Primärschlüsselspalte ist.
GoogleSQL
CREATE TABLE Users ( UserId INT64 NOT NULL, LastAccess TIMESTAMP, ... ) PRIMARY KEY (UserId);
PostgreSQL
CREATE TABLE Users ( UserId bigint NOT NULL, LastAccess TIMESTAMPTZ, ... PRIMARY KEY (UserId) );
Es mag auf den ersten Blick praktisch erscheinen, einen Index für die Spalte LastAccess
zu definieren, um die Nutzerzugriffe "seit dem Zeitpunkt X" schnell aus der Datenbank abrufen zu können:
GoogleSQL
CREATE NULL_FILTERED INDEX UsersByLastAccess ON Users(LastAccess);
PostgreSQL
CREATE INDEX UsersByLastAccess ON Users(LastAccess) WHERE LastAccess IS NOT NULL;
Dies führt jedoch zum selben Problem wie in den vorherigen Da Spanner Indexe im Hintergrund als Tabellen implementiert, und die resultierende Indextabelle eine Spalte verwendet, deren Wert monoton zunimmt, als ersten wichtigen Teil an.
Es ist jedoch in Ordnung, einen verschachtelten Index wie diesen zu erstellen, verschränkte Indexe in entsprechenden übergeordneten Zeilen verschränkt. Es ist unwahrscheinlich, dass eine einzelne übergeordnete Zeile Tausende von Ereignissen pro Sekunde erzeugt.
Best Practice Nr. 3 für das Schemadesign: Kein nicht verschränkter Index auf einer Spalte mit hoher Schreibrate, deren Wert monoton steigt oder sinkt. Anstelle von verschränkten Indexen sollten Sie Techniken wie die verwenden, die Sie verwenden würden. für das Primärschlüsseldesign der Basistabelle, wenn Sie Indexspalten entwerfen – für Fügen Sie beispielsweise „shardId“ hinzu.
Nächste Schritte
- Beispiele für Schemadesigns ansehen
- Informationen zum Laden von Daten im Bulk .