Spanner für Cassandra-Nutzer

In diesem Dokument werden Apache Cassandra und Spanner-Konzepte und -Praktiken Dabei wird vorausgesetzt, dass Sie mit Cassandra möchte bestehende Anwendungen migrieren oder neue Anwendungen entwickeln. wenn Sie Spanner als Datenbank verwenden.

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, wie Spanner mit NoSQL verbunden ist Datenbankkriterien 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 migrieren von Cassandra oder DataStax Enterprise (DSE) zu Spanner Ihre Anwendungslogik zu prüfen.

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. Ich Sie müssen nur die Anzahl der Knoten angeben, die Sie für die Instanz reservieren möchten oder Autoscaling 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üsselraum Datenbank

Ein Cassandra-Schlüsselraum entspricht einem Spanner- database, d. h. eine Sammlung von Tabellen und anderen Schemaelementen (z. B. Indexe und Rollen). Im Gegensatz zu einem Schlüsselraum 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 jedes Shard als Partition bezeichnet, in Spanner hingegen wird als Aufteilung bezeichnet. 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. Dies ist für die Anwendung transparent. Der Schlüssel Im Gegensatz zu Cassandra führen Bereichsscans über ein Präfix der primären Schlüssel ist ein effizienter Vorgang in Spanner.
Zeile Zeile

Sowohl in Cassandra als auch in Spanner ist eine Zeile eine Sammlung von Spalten durch einen Primärschlüssel eindeutig identifiziert werden. Wie Cassandra, Spanner unterstützt zusammengesetzte Primärschlüssel. Im Gegensatz zu Cassandra unterscheidet Spanner nicht zwischen Partitions- und Sortierschlüssel, da die Daten nach Bereich gruppiert werden. Bei Spanner sind nur Sortierschlüssel, wobei die Partitionierung im Hintergrund verwaltet wird.
Spalte Spalte

Sowohl in Cassandra als auch in Spanner ist eine Spalte ein Dataset Werte desselben Typs haben. 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 Speicher, die sich zusammen mit für diese Server. 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 Abfragen des Routings.

Eine Spanner-Instanz besteht aus einer Reihe von Servern in Replikationstopologie. Spanner fragmentiert jede Tabelle dynamisch in Zeilenbereiche basierend auf CPU- und Laufwerksnutzung. Shards werden Rechenknoten für die Auslieferung zugewiesen. Daten sind physisch in Colossus gespeichert sind, dem verteilten Dateisystem von Google, getrennt von die Compute-Knoten. Clienttreiber stellen eine Verbindung zum Frontend von Spanner her die Anfragenrouting und Load-Balancing durchfü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 ein schnelleres Neuausgleichen der Last zwischen Compute-Knoten als Reaktion auf Änderungen der Arbeitslast. Im Gegensatz zu Cassandra werden bei Shard-Verschiebungen keine Daten verschoben, da die Daten weiter auf einer Seite bleiben. Colossus. 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 bereichsbasierte Partitionierung ist, dass Arbeitslasten, die in ein Ende des Schlüsselbereichs schreiben, (z. B. Tabellen mit dem aktuellen Zeitstempel) kann es zu einem Heißlaufen kommen, ohne dass zusätzliche Schemadesign berücksichtigen. 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 ein Konsistenzniveau 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 die Konsistenzstufe 1 verwenden, Cassandra benötigt einen einzelnen Replikatknoten um zu antworten, damit der Vorgang als erfolgreich gilt.

Spanner bietet eine hohe Konsistenz. Spanner Die 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 zur Konsistenz Informationen zu den Attributen von Spanner finden Sie in der Übersicht zu 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 Cassandra- und Spanner-Datenmodelle 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, der eine Zeile eindeutig identifiziert. Der Hauptunterschied besteht darin, dass Cassandra hash-partitioniert ist und zwischen Partitions- und Sortierschlüssel unterscheidet, während Spanner bereichspartitioniert ist. Sie können sich Spanner als eine Art Sortierschlüssel vorstellen, 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

Bei 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. Daten werden nach dem gesamten zusammengesetzten Element sortiert gespeichert Primärschlüssel.

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

Bei Cassandra können Partitionierungsschlüssel zusammengesetzte Schlüssel sein. In Spanner gibt es keinen separaten Partitionsschlüssel. Daten werden nach dem gesamten zusammengesetzten Element sortiert gespeichert Primärschlüssel.

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 Standard-Ganzzahlen:

bigint (vorzeichenbehaftete 64-Bit-Ganzzahl)
int (32-Bit-Ganzzahl mit Vorzeichen)
smallint (16-Bit-Ganzzahl mit Vorzeichen)
tinyint (vorzeichenbehaftete 8-Bit-Ganzzahl)
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. int64 für Folgendes verwenden: Nanosekunden-Abweichung innerhalb eines Tages 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

Spanner unterstützt keinen dedizierten Kartentyp. json oder proto verwenden Spalten zur Darstellung von Karten. 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, wobei die Eindeutigkeit des Datasets durch die Anwendung verwaltet wird. 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 Siehe 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 Ein an eine bestimmte Datenbank gebundenes Clientobjekt erstellen und Datenbankanfragen ausgeben auf dem Client-Objekt.

Beispiel für Cassandra

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 für Schlüssel/Wert-Paare als auch über eine Abfrage-API ausgeführt werden. Als Cassandra-Nutzer ist Ihnen die Query API vielleicht vertrauter. Ein wichtiger Unterschied bei der Abfrage-API besteht darin, dass in Spanner benannte Argumente erforderlich sind, im Gegensatz zu Positionsargumenten ? in Cassandra. Der Name eines Arguments in einer Spanner-Abfrage muss mit einem @ beginnen.

Beispiel für Cassandra

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

Cassandra-INSERT entspricht einem Spanner-INSERT OR UPDATE. Sie müssen für eine Einfügung den vollständigen Primärschlüssel angeben. Spanner unterstützt sowohl DML als auch eine API zur Mutation für Schlüssel/Wert-Paare. Der Stil des Schlüssel/Wert-Paars Mutation API wird aufgrund der geringeren Latenz für einfache 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 im Batch einfügen

In Cassandra können Sie mithilfe 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.

Beispiel für Cassandra

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

Für Cassandra-Löschvorgänge müssen Sie den Primärschlüssel der zu löschenden Zeilen angeben. Dies entspricht der DELETE-Mutation in Spanner.

Beispiel für Cassandra

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

In diesem Abschnitt erfahren Sie, wie Sie erweiterte Cassandra-Funktionen in Spanner verwenden.

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.

Spanner lässt nicht zu, dass Clients für jedes Element den Zeitstempel angeben Schreiben. Jede Zelle wird intern mit dem Zeitstempel TrueTime zum Zeitpunkt der Commit-Aktion des Zellenwerts gekennzeichnet. Da Spanner eine starke konsistente und streng serialisierbare Schnittstelle zur Verfügung steht, benötigen die meisten Anwendungen von USING TIMESTAMP.

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. Aktualisierungen einer Zeile können dann in einen Lese-/Schreibmodus umschlossen werden. Transaktion. 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

  1. 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)
  2. 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
    }
  3. 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 Anweisung INSERT. in Spanner erstellen. 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 als falsch ausgewertet wird. In Spanner haben, können Sie das bedingte UPDATE verwenden. Mutationen in Lese-Schreib-Transaktionen. 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

  1. Passen Sie die Logik so an, dass die Zeile aktualisiert und eine Bedingung eingefügt wird.

    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
    }
  2. 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 einen benannten als Ablaufzeit für die Zeile ein. Weitere Informationen finden Sie in der Übersicht über die Gültigkeitsdauer (TTL):

Beispiel für Cassandra

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

  1. Schema mit einer expliziten Spalte mit 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));
  2. 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. Zum Speichern map-Typen, die eine kleine Datenmenge in Spanner enthalten, kann JSON oder PROTO verwenden -Typen, mit denen Sie semistrukturierte bzw. strukturierte Daten speichern können. Bei Aktualisierungen solcher Spalten muss der gesamte Spaltenwert neu geschrieben werden. Wenn Sie haben einen Anwendungsfall, bei dem eine große Menge an Daten in einer Cassandra-map gespeichert ist. muss nur ein kleiner Teil des map aktualisiert werden. Dabei wird INTERLEAVED Tabellen gut passen. 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 die Zeile „Nutzeranhang“ zusammen mit dem entsprechenden Nutzerzeile und kann gemeinsam mit der Nutzerzeile abgerufen und aktualisiert werden. Sie können die Funktion read-write APIs in Spanner für die Interaktion mit verschränkten Tabellen Weitere Informationen zum Interleaving finden Sie unter Übergeordnete und untergeordnete Tabellen erstellen.

Für Entwickler

In diesem Abschnitt werden Spanner und Cassandra verglichen Entwicklertools.

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 hohe Fidelity-Umgebung für interaktive Entwicklung und Einheitentests. Weitere Informationen 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 Steuerungsebenen- und Datenebenen-Vorgänge ausführen. Weitere Informationen finden Sie in der Referenzhandbuch für Google Cloud CLI Spanner

Wenn Sie eine REPL-Schnittstelle benötigen, um Abfragen an Spanner zu senden ähnlich wie cqlsh können Sie das spanner-cli-Tool verwenden. Installation und Ausführung spanner-cli in Go:

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.