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 migrare i carichi di lavoro da Cassandra o DataStax Enterprise (DSE) a Spanner senza modifiche alla logica dell'applicazione.
Concetti principali
Questa sezione confronta 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 di base. Devi solo specificare la quantità di nodi da prenotare per l'istanza o scegliere la scalabilità automatica per eseguire lo scaling automatico dell'istanza. Un'istanza agisce come un contenitore per i database e la topologia di replica dei dati (regionale, a due regioni o multiregionale) viene scelta a livello di istanza. |
Spazio delle chiavi |
Database Uno spazio delle chiavi Cassandra è equivalente a uno Spanner database, 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 che in Spanner, le tabelle sono una raccolta righe identificate da una chiave primaria specificata nello schema della tabella. |
Partizione |
Dividi Sia Cassandra che Spanner scalano mediante lo sharding dei dati. In Cassandra, ogni shard viene chiamato partizione, mentre in Spanner ogni shard è chiamato suddivisione. 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 intervalli, il che significa che le righe che sono contigui nello spazio della chiave primaria sono contigui anche nello spazio di archiviazione (tranne nei 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 chiave è che, a differenza di Cassandra, le scansioni dell'intervallo su un prefisso è un'operazione efficiente in Spanner. |
Riga |
Riga Sia in Cassandra che in Spanner, una riga è una raccolta di colonne identificati in modo univoco da una chiave primaria. Come Cassandra, Spanner supporta chiavi primarie composte. 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 dati che presentano lo stesso tipo. Esiste un valore per ogni riga di una tabella. Per ulteriori informazioni sul confronto dei tipi di colonna Cassandra con Spanner, vedi Tipi di dati. |
Architettura
Un cluster Cassandra è costituito da un insieme di server e spazio di archiviazione collocati in su quei server. Una funzione hash mappa le righe dallo spazio di una chiave di partizione a uno spazio Node (vnode). A ogni 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 vnodi è collegato localmente il nodo di pubblicazione. I driver client si connettono direttamente ai nodi di gestione per gestire il bilanciamento del carico e il routing delle query.
Un'istanza Spanner è costituita da un insieme di server in una topologia di replica. Spanner esegue lo sharding dinamico di ogni tabella in intervalli di righe in base a Utilizzo di CPU e disco. Gli shard vengono assegnati ai nodi di computing per la pubblicazione. I dati sono archiviati fisicamente su Colossus, il file system distribuito di Google, dei nodi di computing. 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 scalano man mano che le risorse vengono aggiunte del cluster sottostante. La separazione di computing e archiviazione di Spanner consente più ribilanciamento del carico tra i nodi di computing in risposta alle modifiche dei carichi di lavoro. A differenza di Cassandra, i trasferimenti di shard non comportano trasferimenti di dati, poiché i dati rimangono su Colossus. Inoltre, il partizionamento basato sull'intervallo di Spanner potrebbe essere più naturale applicazioni che si aspettano che i dati vengano ordinati per chiave di partizione. Il rovescio della porta Il partizionamento basato sull'intervallo è che i carichi di lavoro che scrivono in un'estremità dello spazio delle chiavi (ad esempio, le tabelle codificate dal timestamp corrente) potrebbero essere soggette all'hotspotting senza ulteriori progettazione 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, una maggioranza dei nodi di replica deve rispondere per far sì che l'operazione venga considerata riuscita. Se utilizzi il livello di coerenza 1, Cassandra ha bisogno di un nodo di replica singola affinché l'operazione venga considerata riuscita.
Spanner offre elevata coerenza. L'API Spanner non espone le repliche al client. I client di Spanner interagiscono 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. Le letture successive riflettono i nuovi dati scritti. Le applicazioni possono di leggere uno snapshot del database in un momento passato, che potrebbe essere in termini di prestazioni rispetto a letture efficaci. 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 delle dichiarazioni 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 è partizionata con hash e fa una distinzione tra chiave di partizione di ordinamento, mentre Spanner è partizionato per intervallo. Si può pensare che Spanner abbia solo chiavi di ordinamento, con le partizioni vengono mantenute 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 la posizione della clausola di 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 ai le parti successive della chiave primaria sono le "chiavi di ordinamento". Per Spanner, non esiste una chiave di partizione separata. I dati vengono archiviati ordinati in base all'intero elemento composito e la chiave primaria.
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 confronta i tipi di dati di Cassandra e Spanner. Per per saperne di più sui tipi di Spanner, 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 con segno a 64 bit)Spanner supporta un singolo tipo di dati a 64 bit per i numeri interi firmati. |
Virgola mobile standard:double (valore in virgola mobile IEEE-754 a 64 bit)float (rappresentazione in virgola mobile IEEE-754 a 32 bit) |
float64 (virgola mobile IEEE-754 a 64 bit)float32 (virgola mobile IEEE-754 a 32 bit)
|
|
Numeri di precisione variabili:varint (numero intero a precisione variabile)decimal (decimale con precisione variabile)
|
Per i numeri decimali con precisione fissa, utilizza numeric (precisione 38, scala 9).
In caso contrario, utilizza string insieme a una variabile di livello dell'applicazione
libreria di numeri interi di precisione.
|
|
Tipi di stringhe |
text varchar
|
string(max) Sia text sia 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 relativi alla durata dedicata. 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 archiviare 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, vedi 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à del set. Per ulteriori informazioni, consulta Archiviare mappe di grandi dimensioni come tabelle interlacciate, 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 Cassandra e il codice client Spanner in Go. Per ulteriori informazioni, consulta Librerie client di Spanner.
Inizializzazione 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. Come utente di Cassandra, potresti trovare più familiare l'API Query. Una differenza fondamentale nell'API di query è che Spanner richiede argomenti denominati (a differenza degli argomenti posizionali ?
in Cassandra). Il nome di un
in una query Spanner deve essere preceduto da un @
.
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 inserimento. Chiave inglese
supporta sia DML sia un'API di mutazione dello stile delle coppie chiave-valore. Lo stile della coppia chiave-valore
l'API Mutazione è consigliata per le scritture di poco conto grazie alla minore latenza.
L'API Spanner DML offre più funzionalità in quanto supporta l'intera piattaforma SQL (incluso l'uso 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 Cassandra richiedono di specificare la 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à più avanzate di Cassandra in Spanner.
Scrittura timestamp
Cassandra consente alle mutazioni di specificare esplicitamente un timestamp di scrittura per una determinata cella utilizzando la clausola USING TIMESTAMP
. In genere, questa funzione è
utilizzato per manipolare la semantica dell'ultimo scrittore di Cassandra.
Spanner non consente ai client di specificare il timestamp di ogni
e scrivere. 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 aggregati in operazioni di lettura e scrittura
transazione. 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
Crea lo schema con una colonna esplicita del timestamp di aggiornamento.
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)
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 }
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 riesce se la riga
esiste già.
In Cassandra, puoi anche creare istruzioni DML che specificano una condizione
l'istruzione non riesce se la condizione è 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
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 }
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 designi una chiamata come data di scadenza della riga. Per ulteriori informazioni, consulta 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
Creare uno schema con una colonna timestamp di aggiornamento esplicita
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));
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
map
di tipi che contengono una piccola quantità di dati in Spanner,
puoi utilizzare JSON
o PROTO
che 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 dell'utente viene archiviata e collocata insieme riga dell'utente e può essere recuperata e aggiornata in modo efficiente insieme alla riga dell'utente. Puoi utilizzare la funzionalità di lettura/scrittura API 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 confronta Spanner e Cassandra strumenti per sviluppatori.
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 offre un alto di verifica delle immagini per lo sviluppo interattivo e i test delle unità. Per ulteriori informazioni, consulta Emulare Spanner localmente.
Riga di comando
L'equivalente di Spanner al valore nodetool
di Cassandra è
Google Cloud CLI. Puoi eseguire il piano di controllo e le richieste
le operazioni dei piani 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 Spanner-cli.