Spanner für Cassandra-Nutzer

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

  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 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

  1. 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
    }
  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 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

  1. 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));
  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. 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.