Ce document compare les concepts et les pratiques d'Apache Cassandra et de Spanner. Il suppose que vous connaissez Cassandra et que vous souhaitez migrer des applications existantes ou concevoir de nouvelles applications tout en utilisant Spanner comme base de données.
Cassandra et Spanner sont deux bases de données distribuées à grande échelle conçues pour les applications nécessitant une évolutivité élevée et une faible latence. Bien que les deux peuvent prendre en charge les charges de travail NoSQL exigeantes, Spanner fournit des fonctionnalités avancées pour la modélisation des données, l'interrogation et les transactions opérations. Pour en savoir plus sur la façon dont Spanner répond aux critères des bases de données NoSQL, consultez Spanner pour les charges de travail non relationnelles.
Migrer de Cassandra vers Spanner
Pour migrer de Cassandra vers Spanner, vous pouvez utiliser l'adaptateur de proxy Cassandra vers Spanner. Cet outil Open Source vous permet de migrer des charges de travail de Cassandra ou de DataStax Enterprise (DSE) vers Spanner sans modifier votre logique d'application.
Concepts fondamentaux
Cette section compare les concepts clés de Cassandra et de Spanner.
Terminologie
Cassandra | Spanner |
---|---|
Cluster |
Instance Un cluster Cassandra équivaut à une instance Spanner, c'est-à-dire une collection de serveurs et de ressources de stockage. Spanner étant un service géré, vous n'avez pas besoin de configurer le matériel ou les logiciels sous-jacents. Toi il vous suffit de spécifier le nombre de nœuds à réserver pour votre instance ou choisissez l'autoscaling pour automatiquement l'instance. Une instance agit comme un conteneur bases de données et topologie de réplication des données (régional, birégional ou multirégional) est choisi au niveau de l'instance. |
Espace de clés |
Base de données Un espace de clés Cassandra est équivalent à une base de données Spanner, qui est un ensemble de tables et d'autres éléments de schéma (par exemple, des index et des rôles). Contrairement à un espace de clés, vous n'avez pas besoin de configurer le facteur de réplication. Spanner réplique automatiquement vos données dans la région désignée dans votre Compute Engine. |
Table |
Tableau Dans Cassandra comme dans Spanner, les tables sont un ensemble les lignes identifiées par une clé primaire spécifiée dans le schéma de la table. |
Partition |
Diviser Cassandra et Spanner évoluent tous deux en segmentant les données. Dans Cassandra, chaque segment est appelé partition, tandis que dans Spanner, chaque segment est appelé "split". Cassandra utilise le partitionnement par hachage, ce qui signifie que chaque ligne est attribuée indépendamment à un nœud de stockage en fonction d'un hachage de la clé primaire. Spanner est segmenté par plage, ce qui signifie que les lignes contigus dans l'espace de clé primaire sont également contigus dans le stockage (sauf au limites de division). Spanner se charge de la division et de la fusion en fonction de la charge et du stockage, et ce de manière transparente pour l'application. La clé En d'autres termes, contrairement à Cassandra, l'analyse de plages s'effectue sur un préfixe est une opération efficace dans Spanner. |
Ligne |
Ligne Dans Cassandra et dans Spanner, une ligne est un ensemble de colonnes. identifiés de manière unique par une clé primaire. Comme Cassandra, Spanner accepte les clés primaires composites. Contrairement à Cassandra, Spanner ne fait pas de distinction entre la clé de partition et la clé de tri, car les données sont segmentée par plage. On peut considérer que Spanner ne comporte que des clés de tri, le partitionnement étant géré en coulisses. |
Colonne |
Colonne Dans Cassandra comme dans Spanner, une colonne est un ensemble de données de valeurs de même type. Il y a une valeur pour chaque ligne d'un tableau. Pour en savoir plus sur la comparaison des types de colonnes Cassandra à Spanner, consultez la section Types de données. |
Architecture
Un cluster Cassandra est constitué d'un ensemble de serveurs et de systèmes de stockage en colocation ces serveurs. Une fonction de hachage mappe les lignes d'un espace de clé de partition à un nœud virtuel (nœud v). Un ensemble de nœuds virtuels est ensuite attribué de manière aléatoire à chaque serveur pour desservir une partie de l'espace de clés du cluster. Le stockage des vnodes est associé localement au nœud de diffusion. Les pilotes client se connectent directement aux nœuds de diffusion de l'équilibrage de charge et du routage des requêtes.
Une instance Spanner se compose d'un ensemble de serveurs dans un topologie de réplication. Spanner divise dynamiquement chaque table en plages de lignes en fonction de l'utilisation du processeur et du disque. Les fragments sont attribués aux nœuds de calcul pour la diffusion. Les données sont physiquement stockées sur Colossus, le système de fichiers distribué de Google, séparément des nœuds de calcul. Les pilotes client se connectent à l'interface de Spanner qui assurent le routage des requêtes et l'équilibrage de charge. Pour en savoir plus, consultez le livre blanc Déroulement des opérations de lecture et d'écriture Spanner.
De manière générale, les deux architectures s'étendent à mesure que des ressources sont ajoutées au cluster sous-jacent. La séparation du calcul et du stockage de Spanner permet le rééquilibrage de la charge entre les nœuds de calcul en réponse à l'évolution de la charge de travail. Contrairement à Cassandra, les déplacements de fragments n'impliquent pas de déplacement de données, car les données restent sur Colossus. De plus, le partitionnement basé sur la plage de Spanner peut être plus naturel pour les applications qui s'attendent à ce que les données soient triées par clé de partition. L'inconvénient du partitionnement par plage est que les charges de travail qui écrivent à une extrémité de l'espace de clés (par exemple, les tables indexées par l'horodatage actuel) peuvent être confrontées à des points chauds sans considération supplémentaire pour la conception du schéma. Pour en savoir plus sur les techniques pour surmonter le hotspotting, consultez la section Bonnes pratiques pour la conception de schémas.
Cohérence
Avec Cassandra, vous devez spécifier un niveau de cohérence pour chaque opération. Si vous utilisez le niveau de cohérence de quorum, une majorité de nœuds d'instance dupliquée doit répondre au nœud coordinateur pour que l'opération soit considérée comme réussie. Si vous utilisez un niveau de cohérence de un, Cassandra a besoin d'un nœud d'instance dupliquée pour répondre afin que l'opération soit considérée comme réussie.
Spanner offre une cohérence forte. Spanner L'API n'expose pas les instances répliquées au client. Les clients de Spanner interagissent avec Spanner comme s'il s'agissait d'une base de données à machine unique. Une écriture est toujours écrites sur une majorité d'instances répliquées avant d'être reconnues par l'utilisateur. Les lectures ultérieures reflètent les données nouvellement écrites. Les applications peuvent choisir de lire un instantané de la base de données à une date antérieure, ce qui peut avoir sur les performances par rapport à des lectures fortes. Pour en savoir plus sur les propriétés de cohérence de Spanner, consultez la présentation des transactions.
Spanner a été conçu pour assurer la cohérence et la disponibilité dans les applications à grande échelle. Spanner offre une cohérence forte à grande échelle et avec des performances élevées. Pour les cas d'utilisation qui l'exigent, Spanner prend en charge les lectures d'instantanés qui assouplissent les exigences de fraîcheur.
Modélisation des données
Cette section compare les modèles de données Cassandra et Spanner.
Déclaration de table
La syntaxe de déclaration de table est assez similaire entre Cassandra et Spanner. Vous spécifiez le nom de la table, les noms et les types de colonnes et la clé primaire qui identifie une ligne de manière unique. La principale différence est que Cassandra est partitionné par hachage et fait la distinction entre la clé de partition et la clé de tri, tandis que Spanner est partitionné par plage. Spanner ne dispose que de clés de tri, avec sont gérées automatiquement en arrière-plan. Comme Cassandra, Spanner est compatible avec les clés primaires composites.
Partie de clé primaire unique
La différence entre Cassandra et Spanner réside dans les noms de type et l'emplacement de la clause de clé primaire.
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) |
Plusieurs parties de la clé primaire
Pour Cassandra, le premier élément de clé primaire est la "clé de partition" et les parties de clé primaire suivantes sont les "clés de tri". Pour Spanner, il existe n'est pas une clé de partition distincte. Les données sont stockées, triées par l'ensemble du composite clé primaire.
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) |
Clé de partition composite
Pour Cassandra, les clés de partition peuvent être un composite. Il n'y a pas de clé de partition distincte dans Spanner. Les données sont stockées, triées par l'ensemble du composite clé primaire.
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) |
Types de données
Cette section compare les types de données Cassandra et Spanner. Pour en savoir plus sur les types Spanner, consultez la section Types de données GoogleSQL.
Cassandra | Spanner | |
---|---|---|
Types numériques |
Entiers standards:bigint (entier signé de 64 bits)int (entier signé de 32 bits)smallint (entier signé de 16 bits)tinyint (entier signé de 8 bits)
|
int64 (entier signé de 64 bits)Spanner accepte un seul type de données de 64 bits pour les entiers signés. |
Valeur à virgule flottante standard:double (valeur à virgule flottante IEEE-754 64 bits)float (valeur à virgule flottante IEEE-754 32 bits) |
float64 (format à virgule flottante IEEE-754 64 bits)float32 (format à virgule flottante IEEE-754 32 bits)
|
|
Nombres à précision variable :varint (entier à précision variable)decimal (décimal à précision variable)
|
Pour les nombres décimaux à précision fixe, utilisez numeric (précision 38 sur l'échelle 9).
Sinon, utilisez string avec une bibliothèque d'entiers à précision variable de couche application.
|
|
Types de chaînes |
text varchar
|
string(max) text et varchar stockent et valident les chaînes UTF-8. Dans Spanner,
string colonnes doivent spécifier leur longueur maximale (cela n'a aucune incidence sur
storage; à des fins de validation).
|
blob |
bytes(max) Pour stocker des données binaires, utilisez le type de données bytes .
|
|
Types de date et d'heure | date |
date |
duration |
int64 Spanner n'est pas compatible avec les types de données de durée dédiés. Utilisez int64 pour stocker la durée en nanosecondes.
|
|
time |
int64 Spanner n'est pas compatible avec le type de données "Temps dans la journée" dédié. Utilisez int64 pour
stocker un décalage de l'ordre de la nanoseconde dans la journée.
|
|
timestamp |
timestamp |
|
Types de conteneurs | Types définis par l'utilisateur | json ou proto |
list |
array Utilisez array pour stocker une liste d'objets typés.
|
|
map |
json ou proto Spanner n'est pas compatible avec un type de carte dédié. Utiliser json ou proto
des colonnes pour
représenter des cartes. Pour en savoir plus, consultez la section Stocker les grandes cartes sous forme de tables entrelacées.
|
|
set |
array Spanner n'accepte pas les types d'ensembles dédiés. Utilisez les colonnes array pour représenter
un set , où l'application gère l'unicité des ensembles. Pour en savoir plus, consultez Stocker de grandes cartes en tant que tables entrelacées, qui peut également être utilisé pour stocker de grands ensembles.
|
Schémas d'utilisation de base
Les exemples de code suivants montrent la différence entre le code client Cassandra et le code client Spanner en Go. Pour plus d'informations, consultez la page Bibliothèques clientes Spanner.
Initialisation du client
Dans les clients Cassandra, vous créez un objet de cluster représentant le cluster Cassandra sous-jacent, instanciez un objet de session qui abstrait une connexion au cluster, puis émettez des requêtes sur la session. Dans Spanner, vous créez un objet client associé à une base de données spécifique et envoyez des requêtes de base de données sur l'objet client.
Exemple 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(...)
Exemple Spanner
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(...)
Lire des données
Les lectures dans Spanner peuvent être effectuées à la fois via une API de style clé-valeur et une API de requête. En tant qu'utilisateur de Cassandra, vous trouverez peut-être l'API de requête plus familière. Une différence clé de l'API de requête est que Spanner nécessite des arguments nommés (contrairement aux arguments de position ?
dans Cassandra). Le nom d'un argument dans une requête Spanner doit être précédé d'un @
.
Exemple 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)
Exemple Spanner
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) })
Insérer des données
Un INSERT
Cassandra équivaut à un INSERT OR UPDATE
Spanner.
Vous devez spécifier la clé primaire complète pour une insertion. Spanner
est compatible avec le LMD et une API de mutation de style clé-valeur. Style de clé-valeur
L'API mutation est recommandée pour les écritures simples, car elle offre une latence plus faible.
L'API LMD Spanner offre davantage de fonctionnalités, car elle est compatible avec l'intégralité de la surface SQL (y compris
l'utilisation d'expressions dans l'instruction LMD).
Exemple Cassandra
Go
stmt := `INSERT INTO users (user_id, first_name, last_name) VALUES (?, ?, ?)` err := session.Query(stmt, 1, "John", "Doe").Exec()
Exemple Spanner
Go
_, err := client.Apply(ctx, []*spanner.Mutation{ spanner.InsertOrUpdateMap( "users", map[string]any{ "user_id": 1, "first_name": "John", "last_name": "Doe", } )})
Insérer des données par lots
Dans Cassandra, vous pouvez insérer plusieurs lignes à l'aide d'une instruction de traitement par lot. Dans Spanner, une opération de validation peut contenir plusieurs mutations. Spanner insère ces mutations dans la base de données de manière atomique.
Exemple 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)
Exemple Spanner
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", }, ), })
Supprimer des données
Les suppressions Cassandra nécessitent de spécifier la clé primaire des lignes à supprimer.
Cette méthode est semblable à la mutation DELETE
dans Spanner.
Exemple Cassandra
Go
stmt := `DELETE FROM users WHERE user_id = ?` err := session.Query(stmt, 1).Exec()
Exemple Spanner
Go
_, err := client.Apply(ctx, []*spanner.Mutation{ spanner.Delete("users", spanner.Key{1}), })
Rubriques avancées
Cette section explique comment utiliser des fonctionnalités Cassandra plus avancées dans Spanner.
Code temporel d'écriture
Cassandra permet aux mutations de spécifier explicitement un code temporel d'écriture pour une cellule donnée à l'aide de la clause USING TIMESTAMP
. En règle générale, cette fonctionnalité est utilisée pour manipuler la sémantique de dernier éditeur gagnant de Cassandra.
Spanner ne permet pas aux clients de spécifier le code temporel de chaque écriture. Chaque cellule est marquée en interne avec l'horodatage TrueTime au moment où la valeur de la cellule a été validée. Étant donné que Spanner fournit
cohérente et strictement sérialisable, la plupart des applications n'ont pas besoin
les fonctionnalités de USING TIMESTAMP
.
Si vous utilisez le USING TIMESTAMP
de Cassandra pour la logique spécifique à l'application, vous pouvez ajouter une
une colonne TIMESTAMP
supplémentaire à votre schéma Spanner, qui peut suivre l'heure de la modification
au niveau de l'application. Les modifications apportées à une ligne peuvent ensuite être encapsulées dans une transaction en lecture-écriture. Exemple :
Exemple Cassandra
Go
stmt := `INSERT INTO users (user_id, first_name, last_name) VALUES (?, ?, ?) USING TIMESTAMP ?` err := session.Query(stmt, 1, "John", "Doe", ts).Exec()
Exemple Spanner
Créez un schéma avec une colonne d'horodatage de mise à jour explicite.
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)
Personnalisez la logique pour mettre à jour la ligne et inclure un code temporel.
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 }
Vérifiez la condition personnalisée avant de mettre à jour la ligne.
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 })
Mutations conditionnelles
L'instruction INSERT ... IF EXISTS
dans Cassandra est équivalente à l'instruction INSERT
dans Spanner. Dans les deux cas, l'insertion échoue si la ligne existe déjà.
Dans Cassandra, vous pouvez également créer des instructions DML qui spécifient une condition. L'instruction échoue si la condition est évaluée à "false". Dans
Spanner, vous pouvez utiliser des UPDATE
conditionnelles
mutations dans les transactions en lecture-écriture. Par exemple, pour mettre à jour une ligne uniquement si
une condition particulière existe:
Exemple Cassandra
Go
stmt := `UPDATE users SET last_name = ? WHERE user_id = ? IF first_name = ?` err := session.Query(stmt, 1, "Smith", "John").Exec()
Exemple Spanner
Personnalisez la logique pour mettre à jour la ligne et inclure une condition.
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 }
Vérifiez la condition personnalisée avant de mettre à jour la ligne.
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 permet de définir une valeur TTL (Time To Live) au niveau des lignes ou des colonnes. Dans Spanner, le TTL est configuré au niveau de la ligne, et vous désignez une colonne nommée comme date d'expiration de la ligne. Pour en savoir plus, consultez la présentation de la valeur TTL (Time To Live).
Exemple 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()
Exemple Spanner
Créer un schéma avec une colonne d'horodatage de mise à jour explicite
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));
Insérez des lignes avec un code temporel de commit.
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}), })
Stockez les cartes volumineuses sous forme de tables entrelacées.
Cassandra accepte le type map
pour le stockage de paires clé/valeur ordonnées. Stocker
map
contenant une petite quantité de données dans Spanner, vous
pouvez utiliser JSON
ou PROTO
qui vous permettent de stocker respectivement des données structurées et semi-structurées.
Les mises à jour de ces colonnes nécessitent la réécriture de la valeur de la colonne entière. Si, dans un cas d'utilisation, une grande quantité de données est stockée dans un map
Cassandra et qu'une petite partie seulement de l'map
doit être mise à jour, l'utilisation de tables INTERLEAVED
peut être adaptée. Par exemple, pour associer une grande quantité de données clé-valeur à un utilisateur particulier :
Exemple Cassandra
CREATE TABLE users (
user_id bigint,
attachments map<string, string>,
PRIMARY KEY (user_id)
)
Exemple 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);
Dans ce cas, une ligne de pièce jointe d'utilisateur est stockée au même endroit que la ligne ligne utilisateur, et peuvent être récupérées et mises à jour efficacement avec la ligne utilisateur. Vous pouvez utiliser le mode lecture/écriture API dans Spanner pour interagir avec les tables entrelacées. Pour en savoir plus sur l'entrelacement, consultez la section Créer des tables parent et enfant.
Expérience développeur
Cette section compare les outils de développement Spanner et Cassandra.
Développement local
Vous pouvez exécuter Cassandra en local pour le développement et les tests unitaires. Spanner fournit un environnement similaire pour le développement local via l'émulateur Spanner. L'émulateur offre un débit élevé de fidélité pour le développement interactif et les tests unitaires. Pour en savoir plus, consultez Émuler Spanner localement.
Ligne de commande
L'équivalent Spanner du fichier nodetool
de Cassandra est
Google Cloud CLI ; Vous pouvez effectuer des opérations de plan de contrôle et de plan de données à l'aide de gcloud spanner
. Pour en savoir plus, consultez les
Guide de référence Spanner de Google Cloud CLI
Si vous avez besoin d'une interface REPL pour émettre des requêtes vers Spanner
comme pour cqlsh
, vous pouvez utiliser l'outil spanner-cli
. Pour installer et exécuter
spanner-cli
dans Go:
go install github.com/cloudspannerecosystem/spanner-cli@latest
$(go env GOPATH)/bin/spanner-cli
Pour en savoir plus, consultez le dépôt GitHub de spanner-cli.