In diesem Dokument werden die Konzepte und Praktiken von Apache Cassandra und Spanner verglichen. Es wird davon ausgegangen, dass Sie mit Cassandra vertraut sind und vorhandene Anwendungen migrieren oder neue Anwendungen entwerfen möchten, wobei Spanner als Datenbank verwendet wird.
Cassandra und Spanner sind beides groß angelegte verteilte Datenbanken, die für Anwendungen entwickelt wurden, die eine hohe Skalierbarkeit und eine geringe Latenz erfordern. Beide Datenbanken können anspruchsvolle NoSQL-Arbeitslasten unterstützen. Spanner bietet jedoch erweiterte Funktionen für die Datenmodellierung, Abfragen und Transaktionsvorgänge. Weitere Informationen dazu, inwiefern Spanner die Kriterien für NoSQL-Datenbanken erfüllt, finden Sie unter Spanner für nicht relationale Arbeitslasten.
Von Cassandra zu Spanner migrieren
Für die Migration von Cassandra zu Spanner können Sie den Cassandra to Spanner Proxy Adapter verwenden. Mit diesem Open-Source-Tool können Sie Arbeitslasten von Cassandra zu Spanner migrieren, ohne Änderungen an der Anwendungslogik vornehmen zu müssen.
Wichtige Konzepte
In diesem Abschnitt werden wichtige Cassandra- und Spanner-Konzepte verglichen.
Terminologie
Cassandra | Spanner |
---|---|
Cluster |
Instanz Ein Cassandra-Cluster entspricht einer Spanner-Instanz – einer Sammlung von Servern und Speicherressourcen. Da es sich bei Spanner um einen verwalteten Dienst handelt, müssen Sie die zugrunde liegende Hardware oder Software nicht konfigurieren. Sie müssen nur die Anzahl der Knoten angeben, die Sie für Ihre Instanz reservieren möchten, oder Autoscaling auswählen, um die Instanz automatisch zu skalieren. Eine Instanz fungiert als Container für Datenbanken und die Topologie der Datenreplizierung (regional, biregional oder multiregional) wird auf Instanzebene ausgewählt. |
Schlüsselbereich |
Datenbank Ein Cassandra-Schlüsselraum entspricht einer Spanner-Datenbank, einer Sammlung von Tabellen und anderen Schemaelementen (z. B. Indexen und Rollen). Im Gegensatz zu einem Schlüsselbereich müssen Sie den Replikationsfaktor nicht konfigurieren. Spanner repliziert Ihre Daten automatisch in der in Ihrer Instanz angegebenen Region. |
Tabelle |
Tabelle Sowohl in Cassandra als auch in Spanner sind Tabellen eine Sammlung von Zeilen, die durch einen im Tabellenschema angegebenen Primärschlüssel identifiziert werden. |
Partition |
Aufteilen Sowohl Cassandra als auch Spanner skalieren durch das Sharding von Daten. In Cassandra wird jeder Shard als Partition bezeichnet, in Spanner als Split. Cassandra verwendet die Hash-Partitionierung. Das bedeutet, dass jede Zeile basierend auf einem Hash des Primärschlüssels unabhängig einem Speicherknoten zugewiesen wird. Spanner ist nach Bereichsshards organisiert. Das bedeutet, dass Zeilen, die im Primärschlüsselbereich zusammenhängen, auch im Speicher zusammenhängen (außer an den Grenzwerten der Splits). Spanner übernimmt die Aufteilung und Zusammenführung basierend auf Last und Speicherplatz. Für die Anwendung ist das transparent. Die wichtigste Folge ist, dass Intervallscans über ein Präfix des Primärschlüssels in Spanner im Gegensatz zu Cassandra effizient sind. |
Zeile |
Zeile Sowohl in Cassandra als auch in Spanner ist eine Zeile eine Sammlung von Spalten, die durch einen Primärschlüssel eindeutig identifiziert werden. Wie Cassandra unterstützt Spanner zusammengesetzte Primärschlüssel. Im Gegensatz zu Cassandra unterscheidet Spanner nicht zwischen Partitionsschlüssel und Sortierschlüssel, da die Daten nach Bereich gesplittet werden. Man kann sich Spanner als Datenbank vorstellen, die nur Sortierschlüssel hat und bei der die Partitionierung im Hintergrund verwaltet wird. |
Spalte |
Spalte Sowohl in Cassandra als auch in Spanner ist eine Spalte eine Gruppe von Datenwerten mit demselben Typ. Für jede Zeile einer Tabelle gibt es einen Wert. Weitere Informationen zum Vergleichen von Cassandra-Spaltentypen mit Spanner finden Sie unter Datentypen. |
Architektur
Ein Cassandra-Cluster besteht aus einer Reihe von Servern und Speichern, die sich an denselben Standorten befinden. Eine Hash-Funktion ordnet Zeilen aus einem Partitionsschlüsselbereich einem virtuellen Knoten (vnode) zu. Jedem Server wird dann nach dem Zufallsprinzip eine Reihe von VNodes zugewiesen, um einen Teil des Clusterschlüsselbereichs bereitzustellen. Der Speicher für die VNodes ist lokal an den Bereitstellungsknoten angehängt. Client-Treiber stellen eine direkte Verbindung zu den Auslieferungsknoten her und übernehmen den Lastenausgleich und das Abfragenetzwerk.
Eine Spanner-Instanz besteht aus einer Reihe von Servern in einer Replikationstopologie. Spanner teilt jede Tabelle basierend auf der CPU- und Festplattennutzung dynamisch in Zeilenbereiche auf. Shards werden Rechenknoten für die Auslieferung zugewiesen. Die Daten werden physisch in Colossus, dem verteilten Dateisystem von Google, getrennt von den Compute-Knoten gespeichert. Client-Treiber stellen eine Verbindung zu den Frontend-Servern von Spanner her, die das Anfragen-Routing und das Load Balancing ausführen. Weitere Informationen finden Sie im Whitepaper Lebensdauer von Lese- und Schreibvorgängen in Cloud Spanner.
Im Großen und Ganzen skalieren beide Architekturen, wenn dem zugrunde liegenden Cluster Ressourcen hinzugefügt werden. Die Trennung von Computing und Speicher bei Spanner ermöglicht eine schnellere Neuausrichtung der Last zwischen Compute-Knoten als Reaktion auf Änderungen der Arbeitslast. Im Gegensatz zu Cassandra sind beim Verschieben von Shards keine Datenverschiebungen erforderlich, da die Daten auf Colossus verbleiben. Außerdem ist die bereichsbasierte Partitionierung von Spanner für Anwendungen, bei denen Daten nach Partitionsschlüssel sortiert werden sollen, möglicherweise natürlicher. Die Kehrseite der bereichsbasierten Partitionierung ist, dass bei Arbeitslasten, die an einem Ende des Schlüsselbereichs schreiben (z. B. Tabellen, die nach dem aktuellen Zeitstempel sortiert sind), Hotspots auftreten können, ohne dass zusätzliche Schema-Designüberlegungen erforderlich sind. Weitere Informationen zu Methoden zur Behebung von Hotspots finden Sie unter Best Practices für das Schemadesign.
Konsistenz
Bei Cassandra müssen Sie für jeden Vorgang eine Konsistenzebene angeben. Wenn Sie die Konsistenzebene „Quorum“ verwenden, muss eine Mehrheit der Replikatknoten auf den Koordinatorknoten antworten, damit der Vorgang als erfolgreich betrachtet wird. Wenn Sie eine Konsistenzebene von „1“ verwenden, muss Cassandra eine Antwort von einem einzelnen Replikatknoten erhalten, damit der Vorgang als erfolgreich betrachtet wird.
Spanner bietet eine hohe Konsistenz. Die Spanner API stellt dem Client keine Replikate zur Verfügung. Die Spanner-Clients interagieren mit Spanner, als wäre es eine Datenbank auf einem einzelnen Computer. Ein Schreibvorgang wird immer in die Mehrheit der Replikate geschrieben, bevor er dem Nutzer bestätigt wird. Alle nachfolgenden Lesevorgänge spiegeln die neu geschriebenen Daten wider. Anwendungen können einen Snapshot der Datenbank zu einem bestimmten Zeitpunkt in der Vergangenheit lesen. Dies kann Leistungsvorteile gegenüber starken Lesevorgängen bieten. Weitere Informationen zu den Konsistenzeigenschaften von Spanner finden Sie in der Übersicht über Transaktionen.
Spanner wurde entwickelt, um die Konsistenz und Verfügbarkeit zu unterstützen, die für Anwendungen im großen Maßstab erforderlich sind. Spanner bietet eine hohe Konsistenz in großem Maßstab und mit hoher Leistung. Für Anwendungsfälle, bei denen dies erforderlich ist, unterstützt Spanner Snapshot-Lesungen, bei denen die Anforderungen an die Aktualität gelockert werden.
Datenmodellierung
In diesem Abschnitt werden die Datenmodelle von Cassandra und Spanner verglichen.
Tabellendeklaration
Die Syntax der Tabellendeklaration ist in Cassandra und Spanner ziemlich ähnlich. Sie geben den Tabellennamen, die Spaltennamen und ‑typen sowie den Primärschlüssel an, mit dem eine Zeile eindeutig identifiziert wird. Der Hauptunterschied besteht darin, dass Cassandra hash-partitioniert ist und zwischen Partitions- und Sortierschlüssel unterscheidet, während Spanner bereichspartitioniert ist. Spanner kann als Datenbank mit nur Sortierschlüsseln betrachtet werden, bei der Partitionen automatisch im Hintergrund verwaltet werden. Wie Cassandra unterstützt Spanner zusammengesetzte Primärschlüssel.
Einzelner Primärschlüsselteil
Der Unterschied zwischen Cassandra und Spanner besteht in den Typennamen und der Position der Primärschlüsselklausel.
Cassandra | Spanner |
---|---|
CREATE TABLE users ( user_id bigint, first_name text, last_name text, PRIMARY KEY (user_id) ) |
CREATE TABLE users ( user_id int64, first_name string(max), last_name string(max), ) PRIMARY KEY (user_id) |
Mehrere Primärschlüsselteile
In Cassandra ist der erste Teil des Primärschlüssels der „Partitionsschlüssel“ und die nachfolgenden Teile des Primärschlüssels sind „Sortierschlüssel“. Für Spanner gibt es keinen separaten Partitionsschlüssel. Die Daten werden nach dem gesamten zusammengesetzten Primärschlüssel sortiert gespeichert.
Cassandra | Spanner |
---|---|
CREATE TABLE user_items ( user_id bigint, item_id bigint, first_name text, last_name text, PRIMARY KEY (user_id, item_id) ) |
CREATE TABLE user_items ( user_id int64, item_id int64, first_name string(max), last_name string(max), ) PRIMARY KEY (user_id, item_id) |
Zusammengesetzter Partitionsschlüssel
In Cassandra können Partitionsschlüssel zusammengesetzt sein. In Spanner gibt es keinen separaten Partitionsschlüssel. Die Daten werden nach dem gesamten zusammengesetzten Primärschlüssel sortiert gespeichert.
Cassandra | Spanner |
---|---|
CREATE TABLE user_category_items ( user_id bigint, category_id bigint, item_id bigint, first_name text, last_name text, PRIMARY KEY ((user_id, category_id), item_id) ) |
CREATE TABLE user_category_items ( user_id int64, category_id int64, item_id int64, first_name string(max), last_name string(max), ) PRIMARY KEY (user_id, category_id, item_id) |
Datentypen
In diesem Abschnitt werden die Datentypen von Cassandra und Spanner verglichen. Weitere Informationen zu Spanner-Typen finden Sie unter Datentypen in GoogleSQL.
Cassandra | Spanner | |
---|---|---|
Numerische Typen |
Standardganzzahlen:bigint (64‑Bit-Ganzzahl mit Vorzeichen)int (32‑Bit-Ganzzahl mit Vorzeichen)smallint (16‑Bit-Ganzzahl mit Vorzeichen)tinyint (8‑Bit-Ganzzahl mit Vorzeichen)
|
int64 (64‑Bit-Ganzzahl mit Vorzeichen)Spanner unterstützt einen einzelnen 64‑Bit-Datentyp für Ganzzahlen mit Vorzeichen. |
Standard-Gleitkomma:double (64-Bit-IEEE-754-Gleitkomma)float (32-Bit-IEEE-754-Gleitkomma) |
float64 (64-Bit-IEEE-754-Gleitkomma)float32 (32-Bit-IEEE-754-Gleitkomma)
|
|
Zahlen mit variabler Genauigkeit:varint (ganze Zahl mit variabler Genauigkeit)decimal (Dezimalzahl mit variabler Genauigkeit)
|
Verwenden Sie für Dezimalzahlen mit fester Genauigkeit numeric (Genauigkeit 38, Skalierung 9).
Andernfalls verwenden Sie string in Verbindung mit einer Ganzzahlbibliothek mit variabler Genauigkeit der Anwendungsebene.
|
|
Stringtypen |
text varchar
|
string(max) Sowohl text als auch varchar speichern und validieren UTF-8-Strings. In Spanner muss für string -Spalten die maximale Länge angegeben werden. Dies hat keine Auswirkungen auf die Speicherung, dient aber zu Validierungszwecken.
|
blob |
bytes(max) Verwenden Sie den Datentyp bytes , um Binärdaten zu speichern.
|
|
Datums- und Uhrzeittypen | date |
date |
duration |
int64 In Spanner gibt es keinen speziellen Datentyp für die Dauer. Verwenden Sie int64 , um die Dauer in Nanosekunden zu speichern.
|
|
time |
int64 In Spanner gibt es keinen speziellen Datentyp für die Tageszeit. Verwenden Sie int64 , um den Nanosekunden-Offset innerhalb eines Tages zu speichern.
|
|
timestamp |
timestamp |
|
Containertypen | Benutzerdefinierte Typen | json oder proto |
list |
array Mit array können Sie eine Liste von getippten Objekten speichern.
|
|
map |
json oder proto In Spanner wird kein spezieller Kartentyp unterstützt. Verwenden Sie json - oder proto -Spalten, um Karten darzustellen. Weitere Informationen finden Sie unter Große Karten als verschachtelte Tabellen speichern.
|
|
set |
array In Spanner wird kein spezieller Satztyp unterstützt. Verwenden Sie array -Spalten, um eine set darzustellen. Die Anwendung verwaltet die Eindeutigkeit des Sets. Weitere Informationen finden Sie unter Große Karten als verschachtelte Tabellen speichern. Diese Methode kann auch zum Speichern großer Datensätze verwendet werden.
|
Grundlegende Nutzungsmuster
Die folgenden Codebeispiele zeigen den Unterschied zwischen Cassandra- und Spanner-Clientcode in Go. Weitere Informationen finden Sie unter Spanner-Clientbibliotheken.
Clientinitialisierung
In Cassandra-Clients erstellen Sie ein Clusterobjekt, das den zugrunde liegenden Cassandra-Cluster darstellt, ein Sitzungsobjekt instanziieren, das eine Verbindung zum Cluster abstrahiert, und Abfragen an die Sitzung senden. In Spanner erstellen Sie ein Clientobjekt, das an eine bestimmte Datenbank gebunden ist, und stellen Datenbankanfragen an das Clientobjekt.
Cassandra-Beispiel
Go
import "github.com/gocql/gocql" ... cluster := gocql.NewCluster("<address>") cluster.Keyspace = "<keyspace>" session, err := cluster.CreateSession() if err != nil { return err } defer session.Close() // session.Query(...)
Spanner-Beispiel
Go
import "cloud.google.com/go/spanner" ... client, err := spanner.NewClient(ctx, fmt.Sprintf("projects/%s/instances/%s/databases/%s", project, instance, database)) defer client.Close() // client.Apply(...)
Daten lesen
Lesevorgänge in Spanner können sowohl über eine API im Schlüssel/Wert-Format als auch über eine Abfrage-API ausgeführt werden. Als Cassandra-Nutzer ist Ihnen die Abfrage-API möglicherweise schon bekannt. Ein wichtiger Unterschied bei der Abfrage-API besteht darin, dass in Spanner benannte Argumente erforderlich sind, im Gegensatz zu Positional Arguments ?
in Cassandra. Der Name eines Arguments in einer Spanner-Abfrage muss mit einem @
beginnen.
Cassandra-Beispiel
Go
stmt := `SELECT user_id, first_name, last_name FROM users WHERE user_id = ?` var ( userID int firstName string lastName string ) err := session.Query(stmt, 1).Scan(&userID, &firstName, &lastName)
Spanner-Beispiel
Go
stmt := spanner.Statement{ SQL: `SELECT user_id, first_name, last_name FROM users WHERE user_id = @user_id`, Params: map[string]any{"user_id": 1}, } var ( userID int64 firstName string lastName string ) err := client.Single().Query(ctx, stmt).Do(func(row *spanner.Row) error { return row.Columns(&userID, &firstName, &lastName) })
Daten einfügen
Eine Cassandra-INSERT
entspricht einer Spanner-INSERT OR UPDATE
.
Sie müssen den vollständigen Primärschlüssel für eine Einfügung angeben. Spanner unterstützt sowohl DML als auch eine Mutations-API im Schlüssel/Wert-Format. Die Mutation API im Schlüssel/Wert-Format wird aufgrund der geringeren Latenz für triviale Schreibvorgänge empfohlen.
Die Spanner DML API bietet mehr Funktionen, da sie die gesamte SQL-Oberfläche unterstützt, einschließlich der Verwendung von Ausdrücken in der DML-Anweisung.
Cassandra-Beispiel
Go
stmt := `INSERT INTO users (user_id, first_name, last_name) VALUES (?, ?, ?)` err := session.Query(stmt, 1, "John", "Doe").Exec()
Spanner-Beispiel
Go
_, err := client.Apply(ctx, []*spanner.Mutation{ spanner.InsertOrUpdateMap( "users", map[string]any{ "user_id": 1, "first_name": "John", "last_name": "Doe", } )})
Daten per Batch einfügen
In Cassandra können Sie mit einer Batch-Anweisung mehrere Zeilen einfügen. In Spanner kann ein Commitvorgang mehrere Mutationen enthalten. Spanner fügt diese Mutationen in kleinstmöglichen Schritten in die Datenbank ein.
Cassandra-Beispiel
Go
stmt := `INSERT INTO users (user_id, first_name, last_name) VALUES (?, ?, ?)` b := session.NewBatch(gocql.UnloggedBatch) b.Entries = []gocql.BatchEntry{ {Stmt: stmt, Args: []any{1, "John", "Doe"}}, {Stmt: stmt, Args: []any{2, "Mary", "Poppins"}}, } err = session.ExecuteBatch(b)
Spanner-Beispiel
Go
_, err := client.Apply(ctx, []*spanner.Mutation{ spanner.InsertOrUpdateMap( "users", map[string]any{ "user_id": 1, "first_name": "John", "last_name": "Doe" }, ), spanner.InsertOrUpdateMap( "users", map[string]any{ "user_id": 2, "first_name": "Mary", "last_name": "Poppins", }, ), })
Daten löschen
Beim Löschen von Cassandra-Zeilen muss der Primärschlüssel der zu löschenden Zeilen angegeben werden.
Dies entspricht der DELETE
-Mutation in Spanner.
Cassandra-Beispiel
Go
stmt := `DELETE FROM users WHERE user_id = ?` err := session.Query(stmt, 1).Exec()
Spanner-Beispiel
Go
_, err := client.Apply(ctx, []*spanner.Mutation{ spanner.Delete("users", spanner.Key{1}), })
Themen für Fortgeschrittene
Dieser Abschnitt enthält Informationen zur Verwendung erweiterter Cassandra-Funktionen in Spanner.
Schreibzeitstempel
In Cassandra können Sie mit der Klausel USING TIMESTAMP
einen Schreibzeitstempel für eine bestimmte Zelle explizit angeben. Normalerweise wird diese Funktion verwendet, um die „Letzter-Schreiber-gewinnt“-Semantik von Cassandra zu manipulieren.
In Spanner können Clients den Zeitstempel für jedes Schreiben nicht angeben. Jede Zelle wird intern mit dem Zeitstempel TrueTime gekennzeichnet, der zum Zeitpunkt der Commit-Aktion des Zellenwerts festgelegt wurde. Da Spanner eine stark konsistente und streng serialisierbare Schnittstelle bietet, benötigen die meisten Anwendungen die Funktionen von USING TIMESTAMP
nicht.
Wenn Sie für die anwendungsspezifische Logik auf die USING TIMESTAMP
-Spalte von Cassandra zurückgreifen, können Sie Ihrem Spanner-Schema eine zusätzliche USING TIMESTAMP
-Spalte hinzufügen, mit der die Änderungszeit auf Anwendungsebene erfasst werden kann.TIMESTAMP
Aktualisierungen einer Zeile können dann in eine Lese-Schreib-Transaktion verpackt werden. Beispiel:
Cassandra-Beispiel
Go
stmt := `INSERT INTO users (user_id, first_name, last_name) VALUES (?, ?, ?) USING TIMESTAMP ?` err := session.Query(stmt, 1, "John", "Doe", ts).Exec()
Spanner-Beispiel
Erstellen Sie ein Schema mit einer expliziten Spalte für den Zeitstempel der Aktualisierung.
GoogleSQL
CREATE TABLE users ( user_id INT64, first_name STRING(MAX), last_name STRING(MAX), update_ts TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true), ) PRIMARY KEY (user_id)
Passen Sie die Logik an, um die Zeile zu aktualisieren und einen Zeitstempel hinzuzufügen.
Go
func ShouldUpdateRow(ctx context.Context, txn *spanner.ReadWriteTransaction, updateTs time.Time) (bool, error) { // Read the existing commit timestamp. row, err := txn.ReadRow(ctx, "users", spanner.Key{1}, []string{"update_ts"}) // Treat non-existent row as NULL timestamp - the row should be updated. if spanner.ErrCode(err) == codes.NotFound { return true, nil } // Propagate unexpected errors. if err != nil { return false, err } // Check if the committed timestamp is newer than the update timestamp. var committedTs *time.Time err = row.Columns(&committedTs) if err != nil { return false, err } if committedTs != nil && committedTs.Before(updateTs) { return false, nil } // Committed timestamp is older than update timestamp - the row should be updated. return true, nil }
Prüfen Sie die benutzerdefinierte Bedingung, bevor Sie die Zeile aktualisieren.
Go
_, err := client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error { // Check if the row should be updated. ok, err := ShouldUpdateRow(ctx, txn, time.Now()) if err != nil { return err } if !ok { return nil } // Update the row. txn.BufferWrite([]*spanner.Mutation{ spanner.InsertOrUpdateMap("users", map[string]any{ "user_id": 1, "first_name": "John", "last_name": "Doe", "update_ts": spanner.CommitTimestamp, })}) return nil })
Bedingte Mutationen
Die INSERT ... IF EXISTS
-Anweisung in Cassandra entspricht der INSERT
-Anweisung in Spanner. In beiden Fällen schlägt das Einfügen fehl, wenn die Zeile bereits vorhanden ist.
In Cassandra können Sie auch DML-Anweisungen erstellen, die eine Bedingung angeben. Die Anweisung schlägt fehl, wenn die Bedingung zu „false“ führt. In Spanner können Sie bedingte UPDATE
-Mutationen in Lese-Schreib-Transaktionen verwenden. So können Sie beispielsweise eine Zeile nur aktualisieren, wenn eine bestimmte Bedingung erfüllt ist:
Cassandra-Beispiel
Go
stmt := `UPDATE users SET last_name = ? WHERE user_id = ? IF first_name = ?` err := session.Query(stmt, 1, "Smith", "John").Exec()
Spanner-Beispiel
Passen Sie die Logik an, um die Zeile zu aktualisieren, und fügen Sie eine Bedingung hinzu.
Go
func ShouldUpdateRow(ctx context.Context, txn *spanner.ReadWriteTransaction) (bool, error) { row, err := txn.ReadRow(ctx, "users", spanner.Key{1}, []string{"first_name"}) if err != nil { return false, err } var firstName *string err = row.Columns(&firstName) if err != nil { return false, err } if firstName != nil && firstName == "John" { return false, nil } return true, nil }
Prüfen Sie die benutzerdefinierte Bedingung, bevor Sie die Zeile aktualisieren.
Go
_, err := client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error { ok, err := ShouldUpdateRow(ctx, txn, time.Now()) if err != nil { return err } if !ok { return nil } txn.BufferWrite([]*spanner.Mutation{ spanner.InsertOrUpdateMap("users", map[string]any{ "user_id": 1, "last_name": "Smith", "update_ts": spanner.CommitTimestamp, })}) return nil })
TTL
Cassandra unterstützt das Festlegen eines TTL-Werts (Time to Live) auf Zeilen- oder Spaltenebene. In Spanner wird die TTL auf Zeilenebene konfiguriert und Sie legen eine benannte Spalte als Ablaufzeit für die Zeile fest. Weitere Informationen finden Sie unter Gültigkeitsdauer (TTL).
Cassandra-Beispiel
Go
stmt := `INSERT INTO users (user_id, first_name, last_name) VALUES (?, ?, ?) USING TTL 86400 ?` err := session.Query(stmt, 1, "John", "Doe", ts).Exec()
Spanner-Beispiel
Schema mit einer Spalte für den Zeitstempel der Aktualisierung erstellen
GoogleSQL
CREATE TABLE users ( user_id INT64, first_name STRING(MAX), last_name STRING(MAX), update_ts TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true), ) PRIMARY KEY (user_id), ROW DELETION POLICY (OLDER_THAN(update_ts, INTERVAL 1 DAY));
Fügen Sie Zeilen mit einem Commit-Zeitstempel ein.
Go
_, err := client.Apply(ctx, []*spanner.Mutation{ spanner.InsertOrUpdateMap("users", map[string]any{ "user_id": 1, "first_name": "John", "last_name": "Doe", "update_ts": spanner.CommitTimestamp}), })
Große Karten als verschränkte Tabellen speichern
Cassandra unterstützt den Typ map
für das Speichern sortierter Schlüssel/Wert-Paare. Wenn Sie map
-Typen mit einer kleinen Datenmenge in Spanner speichern möchten, können Sie die Typen JSON
oder PROTO
verwenden, mit denen Sie jeweils semistrukturierte und strukturierte Daten speichern können.
Bei Aktualisierungen solcher Spalten muss der gesamte Spaltenwert neu geschrieben werden. Wenn in einem Anwendungsfall eine große Datenmenge in einer Cassandra-map
gespeichert wird und nur ein kleiner Teil der map
aktualisiert werden muss, eignen sich INTERLEAVED
-Tabellen möglicherweise gut. So ordnen Sie beispielsweise einer großen Menge von Schlüssel/Wert-Daten einen bestimmten Nutzer zu:
Cassandra-Beispiel
CREATE TABLE users (
user_id bigint,
attachments map<string, string>,
PRIMARY KEY (user_id)
)
Spanner-Beispiel
CREATE TABLE users (
user_id INT64,
) PRIMARY KEY (user_id);
CREATE TABLE user_attachments (
user_id INT64,
attachment_key STRING(MAX),
attachment_val STRING(MAX),
) PRIMARY KEY (user_id, attachment_key);
In diesem Fall wird eine Zeile mit Nutzeranhängen zusammen mit der entsprechenden Nutzerzeile gespeichert und kann zusammen mit der Nutzerzeile effizient abgerufen und aktualisiert werden. Sie können die Lese-/Schreib-APIs in Spanner verwenden, um mit verschachtelten Tabellen zu interagieren. Weitere Informationen zum Interleaving finden Sie unter Übergeordnete und untergeordnete Tabellen erstellen.
Für Entwickler
In diesem Abschnitt werden die Entwicklertools von Spanner und Cassandra verglichen.
Lokale Entwicklung
Sie können Cassandra lokal für die Entwicklung und für Unit-Tests ausführen. Spanner bietet über den Spanner-Emulator eine ähnliche Umgebung für die lokale Entwicklung. Der Emulator bietet eine High-Fidelity-Umgebung für die interaktive Entwicklung und Unit-Tests. Weitere Informationen finden Sie unter Spanner lokal emulieren.
Befehlszeile
Das Spanner-Äquivalent zu Cassandras nodetool
ist die Google Cloud CLI. Mit gcloud spanner
können Sie Steuerungs- und Datenebenenvorgänge ausführen. Weitere Informationen finden Sie im Referenzhandbuch für die Google Cloud CLI für Spanner.
Wenn Sie eine REPL-Schnittstelle benötigen, um Abfragen an Spanner zu senden, die cqlsh
ähneln, können Sie das Tool spanner-cli
verwenden. So installieren und führen Sie spanner-cli
in Go aus:
go install github.com/cloudspannerecosystem/spanner-cli@latest
$(go env GOPATH)/bin/spanner-cli
Weitere Informationen finden Sie im GitHub-Repository für spanner-cli.