Spanner per gli utenti Cassandra

Questo documento mette a confronto i concetti e le pratiche di Apache Cassandra e Spanner. Si presume che tu abbia familiarità con Cassandra e che voglia eseguire la migrazione di applicazioni esistenti o progettarne di nuove utilizzando Spanner come database.

Cassandra e Spanner sono entrambi database distribuiti su larga scala progettati per applicazioni che richiedono elevata scalabilità e bassa latenza. Sebbene entrambi i database possano supportare carichi di lavoro NoSQL impegnativi, Spanner offre funzionalità avanzate per la definizione del modello di dati, le query e le operazioni transactional. Per saperne di più su come Spanner soddisfa i criteri dei database NoSQL, consulta Spanner per i carichi di lavoro non relazionali.

Esegui la migrazione da Cassandra a Spanner

Per eseguire la migrazione da Cassandra a Spanner, puoi utilizzare l'adattatore proxy Cassandra to Spanner. Questo strumento open source consente di eseguire la migrazione dei carichi di lavoro da Cassandra a Spanner senza alcuna modifica alla logica dell'applicazione.

Concetti principali

Questa sezione mette a confronto i concetti chiave di Cassandra e Spanner.

Terminologia

Cassandra Spanner
Cluster Istanza

Un cluster Cassandra è equivalente a un'istanza Spanner, ovvero una raccolta di server e risorse di archiviazione. Poiché Spanner è un servizio gestito, non devi configurare l'hardware o il software sottostanti. Devi solo specificare il numero di nodi da prenotare per l'istanza o scegliere la scalabilità automatica per eseguire lo scaling automatico dell'istanza. Un'istanza funge da contenitore per i database e la topologia di replica dei dati (regionale, a due regioni o multiregionale) viene scelta a livello di istanza.
Spazio chiavi Database

Uno spazio chiavi Cassandra è equivalente a un database Spanner, ovvero una raccolta di tabelle e altri elementi dello schema (ad esempio indici e ruoli). A differenza di uno spazio chiavi, non è necessario configurare il fattore di replica. Spanner replica automaticamente i dati nella regione designata nell'istanza.
Tabella Tabella

Sia in Cassandra sia in Spanner, le tabelle sono una raccolta di righe identificate da una chiave primaria specificata nello schema della tabella.
Partizione Suddivisione

Sia Cassandra sia Spanner si scalano suddividendo i dati. In Cassandra, ogni shard è chiamato partizione, mentre in Spanner è chiamato split. Cassandra utilizza il partizionamento hash, il che significa che ogni riga viene assegnata in modo indipendente a un nodo di archiviazione in base a un hash della chiave principale. Spanner è suddiviso in parti in base all'intervallo, il che significa che le righe con spazi contigui nella chiave primaria sono contigue anche nello spazio di archiviazione (tranne ai confini della suddivisione). Spanner si occupa di suddividere e unire le parti in base al carico e allo spazio di archiviazione, in modo trasparente per l'applicazione. La conseguenza principale è che, a differenza di Cassandra, le ricerche di intervallo su un prefisso della chiave principale sono un'operazione efficiente in Spanner.
Riga Riga

Sia in Cassandra che in Spanner, una riga è una raccolta di colonne identificate in modo univoco da una chiave primaria. Come Cassandra, Spanner supporta le chiavi primarie composite. A differenza di Cassandra, Spanner non fa distinzione tra chiave di partizione e chiave di ordinamento, perché i dati sono suddivisi in partizioni in base all'intervallo. Si può pensare a Spanner come a un database che ha solo chiavi di ordinamento, con il partizionamento gestito in background.
Colonna Colonna

Sia in Cassandra che in Spanner, una colonna è un insieme di valori di dati dello stesso tipo. Esiste un valore per ogni riga di una tabella. Per ulteriori informazioni sul confronto dei tipi di colonne Cassandra con quelli di Spanner, consulta Tipi di dati.

Architettura

Un cluster Cassandra è costituito da un insieme di server e spazio di archiviazione co-localizzati con questi server. Una funzione di hashing mappa le righe da uno spazio delle chiavi di partizione a un nodo virtuale (vnode). A ciascun server viene quindi assegnato in modo casuale un insieme di vnode per gestire una parte dello spazio delle chiavi del cluster. Lo spazio di archiviazione per i vnode è collegato in locale al nodo di servizio. I driver client si connettono direttamente ai nodi di servizio e gestiscono il bilanciamento del carico e il routing delle query.

Un'istanza Spanner è costituita da un insieme di server in una topologia di replica. Spanner suddivide dinamicamente ogni tabella in intervalli di righe in base all'utilizzo della CPU e del disco. Gli shard vengono assegnati ai nodi di calcolo per la pubblicazione. I dati sono archiviati fisicamente su Colossus, il file system distribuito di Google, separatamente dai nodi di calcolo. I driver client si connettono ai server frontend di Spanner che eseguono il routing delle richieste e il bilanciamento del carico. Per saperne di più, consulta il whitepaper Durata di letture e scritture di Spanner.

A livello generale, entrambe le architetture si adattano quando le risorse vengono aggiunte al cluster sottostante. La separazione tra calcolo e archiviazione di Spanner consente un bilanciamento più rapido del carico tra i nodi di calcolo in risposta alle variazioni del carico di lavoro. A differenza di Cassandra, i trasferimenti di shard non comportano lo spostamento dei dati, che rimangono su Colossus. Inoltre, la suddivisione in base agli intervalli di Spanner potrebbe essere più naturale per le applicazioni che si aspettano che i dati vengano ordinati in base alla chiave di partizione. Il rovescio della medaglia del partitioning basato su intervalli è che i carichi di lavoro che scrivono in un'estremità dello spazio delle chiavi (ad esempio le tabelle con chiave basata sul timestamp corrente) potrebbero riscontrare hotspot senza ulteriori considerazioni sul design dello schema. Per saperne di più sulle tecniche per superare gli hotspot, consulta le best practice per la progettazione degli schemi.

Coerenza

Con Cassandra, devi specificare un livello di coerenza per ogni operazione. Se utilizzi il livello di coerenza del quorum, la maggioranza dei nodi replica deve rispondere al nodo di coordinamento affinché l'operazione venga considerata riuscita. Se utilizzi un livello di coerenza pari a 1, Cassandra ha bisogno di un singolo nodo replica per rispondere affinché l'operazione venga considerata riuscita.

Spanner offre un'elevata coerenza. L'API Spanner non espone le repliche al client. I client di Spanner interagiscono con Spanner come se fosse un database di una singola macchina. Un'operazione di scrittura viene sempre eseguita su una maggioranza di repliche prima di essere confermata all'utente. Eventuali letture successive riflettono i dati appena scritti. Le applicazioni possono scegliere di leggere uno snapshot del database in un momento passato, il che potrebbe avere vantaggi in termini di prestazioni rispetto alle letture sicure. Per ulteriori informazioni sulle proprietà di coerenza di Spanner, consulta la Panoramica delle transazioni.

Spanner è stato creato per supportare la coerenza e la disponibilità necessarie nelle applicazioni su larga scala. Spanner offre elevata coerenza su larga scala e con prestazioni elevate. Per i casi d'uso che lo richiedono, Spanner supporta le letture di snapshot che riducono i requisiti di aggiornamento.

Modellazione dei dati

Questa sezione mette a confronto i modelli di dati di Cassandra e Spanner.

Dichiarazione della tabella

La sintassi di dichiarazione delle tabelle è abbastanza simile in Cassandra e Spanner. Specifica il nome della tabella, i nomi e i tipi di colonna e la chiave primaria che identifica in modo univoco una riga. La differenza principale è che Cassandra è suddivisa in partizioni hash e fa una distinzione tra chiave di partizione e chiave di ordinamento, mentre Spanner è suddivisa in partizioni di intervallo. Spanner può essere considerato come un database che ha solo chiavi di ordinamento, con le partizioni gestite automaticamente in background. Come Cassandra, Spanner supporta le chiavi primarie composite.

Singola parte della chiave primaria

La differenza tra Cassandra e Spanner sta nei nomi dei tipi e nella posizione della clausola della chiave primaria.

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)
    

Più parti della chiave primaria

Per Cassandra, la prima parte della chiave primaria è la "chiave di partizione" e le parti successive della chiave primaria sono le "chiavi di ordinamento". Per Spanner, non esiste una chiave di partizione distinta. I dati vengono archiviati in ordine per l'intera chiave primaria composita.

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)
    

Chiave di partizione composita

Per Cassandra, le chiavi di partizione possono essere composte. In Spanner non è presente una chiave di partizione distinta. I dati vengono archiviati in ordine per l'intera chiave primaria composita.

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)
    

Tipi di dati

Questa sezione mette a confronto i tipi di dati di Cassandra e Spanner. Per maggiori informazioni sui tipi di Spanner, consulta Tipi di dati in GoogleSQL.

Cassandra Spanner
Tipi numerici Numeri interi standard:

bigint (numero intero a 64 bit firmato)
int (numero intero a 32 bit firmato)
smallint (numero intero a 16 bit firmato)
tinyint (numero intero a 8 bit firmato)
int64 (numero intero a 64 bit con segno)

Spanner supporta un singolo tipo di dati a 64 bit per gli interi con segno.
Floating point standard:

double (floating point IEEE-754 a 64 bit)
float (floating point IEEE-754 a 32 bit)
float64 (punti fluttuanti IEEE-754 a 64 bit)
float32 (punti fluttuanti IEEE-754 a 32 bit)
Numeri con precisione variabile:

varint (numero intero con precisione variabile)
decimal (numero decimale con precisione variabile)
Per i numeri decimali con precisione fissa, utilizza numeric (precisione 38, scala 9). In caso contrario, utilizza string in combinazione con una libreria di interi con precisione variabile a livello di applicazione.
Tipi di stringhe text
varchar
string(max)

SIA text CHE varchar memorizzano e convalidano le stringhe UTF-8. In Spanner, le colonne string devono specificare la loro lunghezza massima (non influisce sullo spazio di archiviazione; è per motivi di convalida).
blob bytes(max)

Per archiviare dati binari, utilizza il tipo di dati bytes.
Tipi di data e ora date date
duration int64

Spanner non supporta un tipo di dati dedicato per la durata. Utilizza int64 per memorizzare la durata in nanosecondi.
time int64

Spanner non supporta un tipo di dati dedicato per l'ora del giorno. Utilizza int64 per memorizzare l'offset in nanosecondi all'interno di un giorno.
timestamp timestamp
Tipi di contenitori Tipi definiti dall'utente json o proto
list array

Utilizza array per memorizzare un elenco di oggetti digitati.
map json o proto

Spanner non supporta un tipo di mappa dedicato. Utilizza le colonne json o proto per rappresentare le mappe. Per ulteriori informazioni, consulta Archiviare mappe di grandi dimensioni come tabelle con interleaving.
set array

Spanner non supporta un tipo di set dedicato. Utilizza le colonne array per rappresentare un set, con l'applicazione che gestisce l'univocità dell'insieme. Per ulteriori informazioni, consulta Archiviare mappe di grandi dimensioni come tabelle con interleaving, che possono essere utilizzate anche per archiviare set di grandi dimensioni.

Pattern di utilizzo di base

I seguenti esempi di codice mostrano la differenza tra il codice client di Cassandra e Spanner in Go. Per ulteriori informazioni, consulta le librerie client Spanner.

Inizializzazione del client

Nei client Cassandra, crei un oggetto cluster che rappresenta il cluster Cassandra sottostante, esegui l'inizializzazione di un oggetto sessione che astrae una connessione al cluster ed esegui query sulla sessione. In Spanner, crei un oggetto client associato a un database specifico ed emetti richieste di database sull'oggetto client.

Esempio di Cassandra

Vai
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(...)

Esempio di Spanner

Vai
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(...)

Lettura di dati

Le letture in Spanner possono essere eseguite sia tramite un'API in stile chiave-valore sia tramite un'API di query. In qualità di utente Cassandra, potresti trovare l'API di query più familiare. Una differenza fondamentale nell'API di query è che Spanner richiede argomenti denominati (a differenza degli argomenti posizionali ? in Cassandra). Il nome di un argomento in una query Spanner deve essere preceduto da @.

Esempio di Cassandra

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

Esempio di Spanner

Vai
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)
})

Inserisci i dati

Un INSERT di Cassandra è equivalente a un INSERT OR UPDATE di Spanner. Devi specificare la chiave primaria completa per un'inserzione. Spanner supporta sia la DML sia un'API di mutazione in stile chiave-valore. L'API di mutazione di stile chiave-valore è consigliata per le scritture banali a causa della latenza inferiore. L'API Spanner DML offre più funzionalità in quanto supporta l'intera piattaforma SQL (incluso l'utilizzo di espressioni nell'istruzione DML).

Esempio di Cassandra

Vai
stmt := `INSERT INTO
           users (user_id, first_name, last_name)
         VALUES
           (?, ?, ?)`
err := session.Query(stmt, 1, "John", "Doe").Exec()

Esempio di Spanner

Vai
_, err := client.Apply(ctx, []*spanner.Mutation{
  spanner.InsertOrUpdateMap(
    "users", map[string]any{
      "user_id":    1,
      "first_name": "John",
      "last_name":  "Doe",
    }
  )})

Inserire i dati in batch

In Cassandra, puoi inserire più righe utilizzando un'istruzione batch. In Spanner, un'operazione di commit può contenere più mutazioni. Spanner inserisce queste mutazioni nel database in modo atomico.

Esempio di Cassandra

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

Esempio di Spanner

Vai
_, 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",
    },
  ),
})

Elimina dati

Le eliminazioni di Cassandra richiedono la specifica della chiave primaria delle righe da eliminare. È simile alla mutazione DELETE in Spanner.

Esempio di Cassandra

Vai
stmt := `DELETE FROM
           users
         WHERE
           user_id = ?`
err := session.Query(stmt, 1).Exec()

Esempio di Spanner

Vai
_, err := client.Apply(ctx, []*spanner.Mutation{
  spanner.Delete("users", spanner.Key{1}),
})

Argomenti avanzati

Questa sezione contiene informazioni su come utilizzare le funzionalità di Cassandra più avanzate in Spanner.

Timestamp di scrittura

Cassandra consente alle mutazioni di specificare esplicitamente un timestamp di scrittura per una determinata cella utilizzando la clausola USING TIMESTAMP. In genere, questa funzionalità viene impiegata per manipolare la semantica dell'ultimo scrittore vince di Cassandra.

Spanner non consente ai client di specificare il timestamp di ogni scrittura. Ogni cella è contrassegnata internamente con il timestamp TrueTime al momento dell'commit del valore della cella. Poiché Spanner fornisce un'interfaccia fortemente coerente e rigorosamente serializzabile, la maggior parte delle applicazioni non ha bisogno della funzionalità di USING TIMESTAMP.

Se utilizzi USING TIMESTAMP di Cassandra per la logica specifica dell'applicazione, puoi aggiungere un'altra colonna TIMESTAMP allo schema di Spanner, che può monitorare la data e l'ora di modifica a livello di applicazione. Gli aggiornamenti di una riga possono quindi essere racchiusi in una transazione di lettura e scrittura. Ad esempio:

Esempio di Cassandra

Vai
stmt := `INSERT INTO
           users (user_id, first_name, last_name)
         VALUES
           (?, ?, ?)
         USING TIMESTAMP
           ?`
err := session.Query(stmt, 1, "John", "Doe", ts).Exec()

Esempio di Spanner

  1. Crea lo schema con una colonna timestamp di aggiornamento esplicita.

    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. Personalizza la logica per aggiornare la riga e includere un timestamp.

    Vai
    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. Controlla la condizione personalizzata prima di aggiornare la riga.

    Vai
    _, 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
    })

Mutazioni condizionali

L'istruzione INSERT ... IF EXISTS in Cassandra è equivalente all'istruzione INSERT in Spanner. In entrambi i casi, l'inserimento non va a buon fine se la riga esiste già.

In Cassandra puoi anche creare istruzioni DML che specificano una condizione e l'istruzione non va a buon fine se la condizione restituisce false. In Spanner, puoi utilizzare le mutazioni UPDATE condizionali nelle transazioni di lettura-scrittura. Ad esempio, per aggiornare una riga solo se esiste una determinata condizione:

Esempio di Cassandra

Vai
stmt := `UPDATE
           users
         SET
           last_name = ?
         WHERE
           user_id = ?
         IF
           first_name = ?`
err := session.Query(stmt, 1, "Smith", "John").Exec()

Esempio di Spanner

  1. Personalizza la logica per aggiornare la riga e includere una condizione.

    Vai
    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. Controlla la condizione personalizzata prima di aggiornare la riga.

    Vai
    _, 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 supporta l'impostazione di un valore TTL (Time to Live) a livello di riga o colonna. In Spanner, il TTL è configurato a livello di riga e puoi designare una colonna denominata come data e ora di scadenza della riga. Per ulteriori informazioni, consulta la panoramica della durata (TTL).

Esempio di Cassandra

Vai
stmt := `INSERT INTO
           users (user_id, first_name, last_name)
         VALUES
           (?, ?, ?)
         USING TTL 86400
           ?`
err := session.Query(stmt, 1, "John", "Doe", ts).Exec()

Esempio di Spanner

  1. Creare uno schema con una colonna timestamp di aggiornamento esplicita

    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. Inserisci righe con un timestamp di commit.

    Vai
    _, 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}),
    })

Memorizza mappe di grandi dimensioni come tabelle con interfoliazione.

Cassandra supporta il tipo map per l'archiviazione di coppie chiave/valore ordinate. Per archiviare tipi map che contengono una piccola quantità di dati in Spanner, puoi utilizzare i tipi JSON o PROTO, che ti consentono di archiviare rispettivamente dati semistrutturati e strutturati. Gli aggiornamenti di queste colonne richiedono la riscrittura dell'intero valore della colonna. Se hai un caso d'uso in cui una grande quantità di dati è archiviata in un map Cassandra e solo una piccola parte del map deve essere aggiornata, l'utilizzo delle tabelle INTERLEAVED potrebbe essere una buona soluzione. Ad esempio, per associare una grande quantità di dati chiave-valore a un determinato utente:

Esempio di Cassandra

CREATE TABLE users (
  user_id     bigint,
  attachments map<string, string>,
  PRIMARY KEY (user_id)
)

Esempio di Spanner

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 questo caso, una riga degli allegati utente viene archiviata insieme alla riga utente corrispondente e può essere recuperata e aggiornata in modo efficiente insieme alla riga utente. Puoi utilizzare le API di lettura/scrittura in Spanner per interagire con le tabelle con interleaving. Per ulteriori informazioni sull'interlacciamento, consulta Creare tabelle principali e secondarie.

Esperienza di sviluppo

Questa sezione mette a confronto gli strumenti per sviluppatori di Spanner e Cassandra.

Sviluppo locale

Puoi eseguire Cassandra localmente per lo sviluppo e i test di unità. Spanner fornisce un ambiente simile per lo sviluppo locale tramite l'emulatore Spanner. L'emulatore fornisce un ambiente ad alta fedeltà per lo sviluppo interattivo e i test di unità. Per ulteriori informazioni, consulta Emulare Spanner localmente.

Riga di comando

L'equivalente di Spanner per nodetool di Cassandra è Google Cloud CLI. Puoi eseguire operazioni nel piano di controllo e nel piano di dati utilizzando gcloud spanner. Per ulteriori informazioni, consulta la guida di riferimento di Spanner per Google Cloud CLI.

Se hai bisogno di un'interfaccia REPL per eseguire query su Spanner simile a cqlsh, puoi utilizzare lo strumento spanner-cli. Per installare ed eseguire spanner-cli in Go:

go install github.com/cloudspannerecosystem/spanner-cli@latest

$(go env GOPATH)/bin/spanner-cli

Per maggiori informazioni, consulta il repository GitHub di spanner-cli.