Spanner pour les utilisateurs de Cassandra

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 bases de données puissent prendre en charge des charges de travail NoSQL exigeantes, Spanner offre des fonctionnalités avancées pour la modélisation, la création de requêtes et les opérations transactionnelles des données. 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 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 le logiciel sous-jacent. Il vous suffit de spécifier le nombre de nœuds que vous souhaitez réserver pour votre instance ou de choisir l'autoscaling pour faire évoluer automatiquement l'instance. Une instance agit comme un conteneur pour les bases de données, et la topologie de réplication des données (régionale, birégionale ou multirégionale) est choisie 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 instance.
Table Table

Dans Cassandra et Spanner, les tables sont un ensemble de lignes identifiées par une clé primaire spécifiée dans le schéma de la table.
Partition Division

Cassandra et Spanner s'étendent en divisant 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 partitionné par plage, ce qui signifie que les lignes contiguës dans l'espace de clé primaire sont également contiguës dans l'espace de stockage (sauf aux limites de fractionnement). 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. L'implication clé est que, contrairement à Cassandra, les balayages de plage sur un préfixe de la clé primaire sont une opération efficace dans Spanner.
Ligne Ligne

Dans Cassandra et Spanner, une ligne est un ensemble de colonnes identifiées 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 partitionnées 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 et Spanner, une colonne est un ensemble de valeurs de données de même type. Il existe 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 se compose d'un ensemble de serveurs et d'un espace de stockage colocalisé avec ces serveurs. Une fonction de hachage mappe les lignes d'un espace de clés 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 clients se connectent directement aux nœuds de traitement et gèrent l'équilibrage de charge et le routage des requêtes.

Une instance Spanner se compose d'un ensemble de serveurs dans une 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 clients se connectent aux serveurs d'interface de Spanner, qui effectuent 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 de rééquilibrer plus rapidement la charge entre les nœuds de calcul en réponse aux changements de 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 permettant de remédier aux hotspots, consultez les bonnes pratiques de 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 seul nœud d'instance dupliquée pour que l'opération soit considérée comme réussie.

Spanner offre une cohérence forte. L'API Spanner n'expose pas les réplicas au client. Les clients de Spanner interagissent avec Spanner comme s'il s'agissait d'une base de données sur une seule machine. Une écriture est toujours écrite sur la majorité des réplicas avant d'être confirmée à 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 à un moment donné dans le passé, ce qui peut avoir des avantages en termes de performances par rapport aux lectures strictes. 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é requises 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, ainsi que la clé primaire qui identifie de manière unique une ligne. 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. On peut considérer que Spanner ne comporte que des clés de tri, les partitions étant gérées automatiquement en arrière-plan. Comme Cassandra, Spanner prend en charge les clés primaires composites.

Partie unique de la clé primaire

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, la première partie de la clé primaire est la "clé de partition", et les parties de clé primaire suivantes sont des "clés de tri". Pour Spanner, il n'existe pas de clé de partition distincte. Les données sont stockées triées par l'ensemble de la clé primaire composite.

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'existe pas de clé de partition distincte dans Spanner. Les données sont stockées triées par l'ensemble de la clé primaire composite.

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é 64 bits)
int (entier signé 32 bits)
smallint (entier signé 16 bits)
tinyint (entier signé 8 bits)
int64 (entier signé de 64 bits)

Spanner accepte un seul type de données de 64 bits pour les entiers signés.
Format à virgule flottante standard:

double (format à virgule flottante IEEE-754 64 bits)
float (format à 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, é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)

Les éléments text et varchar stockent et valident les chaînes UTF-8. Dans Spanner, les colonnes string doivent spécifier leur longueur maximale (cela n'a aucun impact sur le stockage, mais c'est à 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'accepte pas de type de données de durée dédié. Utilisez int64 pour stocker la durée en nanosecondes.
time int64

Spanner n'accepte pas de type de données dédié à l'heure de la journée. Utilisez int64 pour stocker le décalage en nanosecondes sur une 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é. Utilisez des colonnes json ou proto pour représenter des cartes. Pour en savoir plus, consultez Stocker de grandes cartes sous forme de tables entrelacées.
set array

Spanner n'accepte pas de type d'ensemble dédié. Utilisez des colonnes array pour représenter un set, l'application gérant l'unicité de l'ensemble. 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 Spanner en Go. Pour en savoir plus, consultez la section 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 langage de manipulation de données (LMD) et une API de mutation de style clé-valeur. L'API de mutation de style clé-valeur est recommandée pour les écritures triviales en raison de la latence réduite. L'API Spanner DML offre plus de fonctionnalités, car elle est compatible avec l'ensemble de la surface SQL (y compris l'utilisation d'expressions dans l'instruction DML).

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 lot

Dans Cassandra, vous pouvez insérer plusieurs lignes à l'aide d'une instruction de 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 spécifique à l'aide de la clause USING TIMESTAMP. En règle générale, cette fonctionnalité est utilisée pour manipuler la sémantique de la stratégie "le dernier éditeur l'emporte" 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. Comme Spanner fournit une interface fortement cohérente et strictement sérialisable, la plupart des applications n'ont pas besoin de la fonctionnalité de USING TIMESTAMP.

Si vous vous appuyez sur USING TIMESTAMP de Cassandra pour la logique spécifique à l'application, vous pouvez ajouter une colonne TIMESTAMP supplémentaire à votre schéma Spanner, qui peut suivre l'heure de 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

  1. 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)
  2. 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
    }
  3. 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 mutations UPDATE conditionnelles dans les transactions en lecture/écriture. Par exemple, pour mettre à jour une ligne uniquement si une condition particulière est remplie:

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

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

  1. 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));
  2. Insérez des lignes avec un code temporel de validation.

    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 est compatible avec le type map pour stocker des paires clé-valeur ordonnées. Pour stocker des types map contenant une petite quantité de données dans Spanner, vous pouvez utiliser les types JSON ou PROTO, qui vous permettent de stocker respectivement des données semi-structurées et structurées. Les modifications apportées à ces colonnes nécessitent de réécrire l'intégralité de la valeur de la colonne. 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 d'annexes utilisateur est stockée avec la ligne utilisateur correspondante. Elle peut être récupérée et mise à jour efficacement avec la ligne utilisateur. Vous pouvez utiliser les API lecture-écriture dans Spanner pour interagir avec des 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 fournit un environnement haute 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 de nodetool de Cassandra est la 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 le guide de référence de Google Cloud CLI Spanner.

Si vous avez besoin d'une interface REPL pour envoyer des requêtes à Spanner semblable à 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.