Spanner para usuarios de Cassandra

En este documento, se comparan los conceptos y las prácticas de Apache Cassandra y Spanner. Se da por sentado que conoces Cassandra y deseas migrar aplicaciones existentes o diseñar aplicaciones nuevas mientras usas Spanner como base de datos.

Cassandra y Spanner son bases de datos distribuidas a gran escala que se compilan para aplicaciones que requieren alta escalabilidad y baja latencia. Si bien ambas bases de datos pueden admitir cargas de trabajo NoSQL exigentes, Spanner proporciona funciones avanzadas para el modelado de datos, las consultas y las operaciones transaccionales. Para obtener más información sobre cómo Spanner cumple con los criterios de las bases de datos NoSQL, consulta Spanner para cargas de trabajo no relacionales.

Migra de Cassandra a Spanner

Para migrar de Cassandra a Spanner, puedes usar el adaptador de proxy de Cassandra a Spanner. Esta herramienta de código abierto te permite migrar cargas de trabajo de Cassandra a Spanner sin realizar cambios en la lógica de tu aplicación.

Conceptos básicos

En esta sección, se comparan los conceptos clave de Cassandra y Spanner.

Terminología

Cassandra Spanner
Clúster Instancia

Un clúster de Cassandra equivale a una instancia de Spanner, que es una colección de servidores y recursos de almacenamiento. Debido a que Spanner es un servicio administrado, no tienes que configurar el hardware o software subyacente. Solo debes especificar la cantidad de nodos que deseas reservar para tu instancia o elegir el ajuste de escala automático para escalar la instancia automáticamente. Una instancia actúa como un contenedor para las bases de datos, y la topología de replicación de datos (regional, birregional o multirregional) se elige a nivel de la instancia.
Espacio de claves Base de datos

Un espacio de claves de Cassandra equivale a una base de datos de Spanner, que es una colección de tablas y otros elementos de esquema (por ejemplo, índices y roles). A diferencia de un espacio de claves, no necesitas configurar el factor de replicación. Spanner replica automáticamente tus datos en la región designada en tu instancia.
Tabla Tabla

En Cassandra y Spanner, las tablas son una colección de filas identificadas por una clave primaria especificada en el esquema de la tabla.
Partición División

Tanto Cassandra como Spanner se escalan mediante el particionamiento de datos. En Cassandra, cada fragmento se denomina partición, mientras que en Spanner, cada fragmento se denomina división. Cassandra usa el particionamiento de hash, lo que significa que cada fila se asigna de forma independiente a un nodo de almacenamiento según un hash de la clave primaria. Spanner se divide en intervalos, lo que significa que las filas que son contiguas en el espacio de la clave primaria también lo son en el almacenamiento (excepto en los límites de división). Spanner se encarga de dividir y combinar los datos según la carga y el almacenamiento, y esto es transparente para la aplicación. La implicación clave es que, a diferencia de Cassandra, el análisis de rangos sobre un prefijo de la clave primaria es una operación eficiente en Spanner.
Fila Fila

En Cassandra y Spanner, una fila es un conjunto de columnas identificadas de forma única por una clave primaria. Al igual que Cassandra, Spanner admite claves primarias compuestas. A diferencia de Cassandra, Spanner no hace una distinción entre la clave de partición y la clave de orden, ya que los datos se fragmentan por intervalo. Se puede pensar en Spanner como si solo tuviera claves de orden, con la partición administrada en segundo plano.
Columna Columna

En Cassandra y Spanner, una columna es un conjunto de valores de datos que tienen el mismo tipo. Hay un valor para cada fila de una tabla. Para obtener más información sobre cómo comparar los tipos de columnas de Cassandra con los de Spanner, consulta Tipos de datos.

Arquitectura

Un clúster de Cassandra consta de un conjunto de servidores y almacenamiento ubicados junto con esos servidores. Una función hash asigna filas de un espacio de claves de partición a un nodo virtual (vnode). Luego, se asigna de forma aleatoria un conjunto de vnodes a cada servidor para entregar una parte del espacio de claves del clúster. El almacenamiento de los vnodes se conecta de forma local al nodo de entrega. Los controladores de cliente se conectan directamente a los nodos de entrega y controlan el balanceo de cargas y el enrutamiento de consultas.

Una instancia de Spanner consta de un conjunto de servidores en una topología de replicación. Spanner particiona cada tabla de forma dinámica en rangos de filas según el uso de la CPU y el disco. Los fragmentos se asignan a los nodos de procesamiento para la publicación. Los datos se almacenan físicamente en Colossus, el sistema de archivos distribuidos de Google, por separado de los nodos de procesamiento. Los controladores de cliente se conectan a los servidores de frontend de Spanner, que realizan el enrutamiento de solicitudes y el balanceo de cargas. Para obtener más información, consulta el informe técnico Ciclo de vida de las operaciones de lectura y escritura de Spanner.

En un nivel alto, ambas arquitecturas se escalan a medida que se agregan recursos al clúster subyacente. La separación de procesamiento y almacenamiento de Spanner permite un reequilibrio más rápido de la carga entre los nodos de procesamiento en respuesta a los cambios en la carga de trabajo. A diferencia de Cassandra, los traslados de fragmentos no implican traslados de datos, ya que estos permanecen en Colosus. Además, la partición basada en rangos de Spanner podría ser más natural para las aplicaciones que esperan que los datos se ordenen por clave de partición. El inconveniente de la partición basada en el rango es que las cargas de trabajo que escriben en un extremo del espacio de claves (por ejemplo, tablas con claves por marca de tiempo actual) pueden tener problemas de hotspots sin consideración adicional del diseño del esquema. Para obtener más información sobre las técnicas para superar los hotspots, consulta Prácticas recomendadas para el diseño de esquemas.

Coherencia

Con Cassandra, debes especificar un nivel de coherencia para cada operación. Si usas el nivel de coherencia de quórum, la mayoría de los nodos de réplica deben responder al nodo coordinador para que la operación se considere correcta. Si usas un nivel de coherencia de uno, Cassandra necesita un nodo de réplica único para responder para que la operación se considere correcta.

Spanner proporciona coherencia sólida. La API de Spanner no expone réplicas al cliente. Los clientes de Spanner interactúan con él como si fuera una base de datos de una sola máquina. Una operación de escritura siempre se escribe en la mayoría de las réplicas antes de confirmarse al usuario. Las lecturas posteriores reflejan los datos que se escribieron recientemente. Las aplicaciones pueden optar por leer una instantánea de la base de datos en un momento anterior, lo que podría tener beneficios de rendimiento en comparación con las lecturas sólidas. Para obtener más información sobre las propiedades de coherencia de Spanner, consulta la Descripción general de las transacciones.

Spanner se creó para admitir la coherencia y la disponibilidad necesarias en aplicaciones a gran escala. Spanner proporciona una coherencia sólida a gran escala y con alto rendimiento. Para los casos de uso que lo requieran, Spanner admite lecturas de instantáneas que relajan los requisitos de actualización.

Modelado de datos

En esta sección, se comparan los modelos de datos de Cassandra y Spanner.

Declaración de tabla

La sintaxis de declaración de tablas es bastante similar en Cassandra y Spanner. Especificas el nombre de la tabla, los nombres y tipos de las columnas, y la clave primaria que identifica de forma única una fila. La diferencia clave es que Cassandra está particionada por hash y hace una distinción entre la clave de partición y la clave de orden, mientras que Spanner está particionada por rango. Se puede considerar que Spanner solo tiene claves de ordenamiento, con particiones que se mantienen automáticamente en segundo plano. Al igual que Cassandra, Spanner admite claves primarias compuestas.

Parte única de clave primaria

La diferencia entre Cassandra y Spanner está en los nombres de los tipos y la ubicación de la cláusula de clave principal.

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)
    

Varias partes de la clave primaria

En Cassandra, la primera parte de la clave primaria es la "clave de partición" y las partes posteriores de la clave primaria son "claves de orden". En Spanner, no hay una clave de partición independiente. Los datos se almacenan ordenados por toda la clave primaria compuesta.

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)
    

Clave de partición compuesta

En Cassandra, las claves de partición pueden ser un compuesto. No hay una clave de partición independiente en Spanner. Los datos se almacenan ordenados por toda la clave primaria compuesta.

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)
    

Tipos de datos

En esta sección, se comparan los tipos de datos de Cassandra y Spanner. Para obtener más información sobre los tipos de Spanner, consulta Tipos de datos en GoogleSQL.

Cassandra Spanner
Tipos numéricos Números enteros estándar:

bigint (número entero de 64 bits con firma)
int (número entero de 32 bits con firma)
smallint (número entero de 16 bits con firma)
tinyint (número entero de 8 bits con firma)
int64 (número entero de 64 bits con firma)

Spanner admite un solo tipo de datos de 64 bits para números enteros con firma.
Número de punto flotante estándar:

double (número de punto flotante IEEE-754 de 64 bits)
float (número de punto flotante IEEE-754 de 32 bits)
float64 (número de punto flotante IEEE 754 de 64 bits)
float32 (número de punto flotante IEEE 754 de 32 bits)
Números de precisión variable:

varint (número entero de precisión variable)
decimal (número decimal de precisión variable)
Para números decimales de precisión fija, usa numeric (precisión 38, escala 9). De lo contrario, usa string junto con una biblioteca de números enteros de precisión variable de la capa de aplicación.
Tipos de cadenas text
varchar
string(max)

Tanto text como varchar almacenan y validan cadenas UTF-8. En Spanner, las columnas string deben especificar su longitud máxima (no hay impacto en el almacenamiento; esto es para fines de validación).
blob bytes(max)

Para almacenar datos binarios, usa el tipo de datos bytes.
Tipos de fecha y hora date date
duration int64

Spanner no admite un tipo de datos de duración dedicado. Usa int64 para almacenar la duración en nanosegundos.
time int64

Spanner no admite un tipo de datos de hora del día dedicada. Usa int64 para almacenar el desplazamiento en nanosegundos dentro de un día.
timestamp timestamp
Tipos de contenedores Tipos de usuarios definidos json o proto
list array

Usa array para almacenar una lista de objetos escritos.
map json o proto

Spanner no admite un tipo de mapa dedicado. Usa columnas json o proto para representar mapas. Para obtener más información, consulta Cómo almacenar mapas grandes como tablas intercaladas.
set array

Spanner no admite un tipo de conjunto dedicado. Usa columnas array para representar un set, con la aplicación que administra la unicidad establecida. Para obtener más información, consulta Almacena mapas grandes como tablas intercaladas, que también se pueden usar para almacenar conjuntos grandes.

Patrones de uso básicos

En los siguientes ejemplos de código, se muestra la diferencia entre el código cliente de Cassandra y Spanner en Go. Para obtener más información, consulta Bibliotecas cliente de Spanner.

Inicialización del cliente

En los clientes de Cassandra, creas un objeto de clúster que representa el clúster de Cassandra subyacente, creas una instancia de un objeto de sesión que abstrae una conexión al clúster y emites consultas en la sesión. En Spanner, creas un objeto cliente vinculado a una base de datos específica y emites solicitudes de base de datos en el objeto cliente.

Ejemplo de 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(...)

Ejemplo de 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(...)

Lee datos

Las operaciones de lectura en Spanner se pueden realizar a través de una API de estilo par clave-valor y una API de consulta. Como usuario de Cassandra, es posible que la API de consulta te resulte más familiar. Una diferencia clave en la API de consulta es que Spanner requiere argumentos nombrados (a diferencia de los argumentos posicionales ? en Cassandra). El nombre de un argumento en una consulta de Spanner debe tener el prefijo @.

Ejemplo de 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)

Ejemplo de 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)
})

Inserta datos

Un INSERT de Cassandra equivale a un INSERT OR UPDATE de Spanner. Debes especificar la clave primaria completa para una inserción. Spanner admite la DML y una API de mutación de estilo clave-valor. Se recomienda la API de mutación de estilo de par clave-valor para las operaciones de escritura triviales debido a la menor latencia. La API de DML de Spanner tiene más funciones, ya que admite la plataforma de SQL completa (incluido el uso de expresiones en la sentencia DML).

Ejemplo de Cassandra

Go

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

Ejemplo de Spanner

Go

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

Cómo insertar datos por lotes

En Cassandra, puedes insertar varias filas con una sentencia por lotes. En Spanner, una operación de confirmación puede contener varias mutaciones. Spanner inserta estas mutaciones en la base de datos de forma atómica.

Ejemplo de 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)

Ejemplo de 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",
    },
  ),
})

Borra datos

Las operaciones de eliminación de Cassandra requieren especificar la clave primaria de las filas que se borrarán. Esto es similar a la mutación DELETE en Spanner.

Ejemplo de Cassandra

Go

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

Ejemplo de Spanner

Go

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

Temas avanzados

En esta sección, se incluye información para usar funciones más avanzadas de Cassandra en Spanner.

Marca de tiempo de escritura

Cassandra permite que las mutaciones especifiquen de forma explícita una marca de tiempo de escritura para una celda en particular con la cláusula USING TIMESTAMP. Por lo general, esta función se usa para manipular la semántica de último escritor gana de Cassandra.

Spanner no permite que los clientes especifiquen la marca de tiempo de cada escritura. Cada celda se marca internamente con la marca de tiempo TrueTime en el momento en que se confirmó el valor de la celda. Debido a que Spanner proporciona una interfaz con coherencia sólida y estrictamente serializable, la mayoría de las aplicaciones no necesitan la funcionalidad de USING TIMESTAMP.

Si dependes de USING TIMESTAMP de Cassandra para la lógica específica de la aplicación, puedes agregar una columna TIMESTAMP adicional a tu esquema de Spanner, que puede hacer un seguimiento del tiempo de modificación a nivel de la aplicación. Luego, las actualizaciones de una fila se pueden unir en una transacción de lectura-escritura. Por ejemplo:

Ejemplo de Cassandra

Go

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

Ejemplo de Spanner

  1. Crea un esquema con una columna de marca de tiempo de actualización explícita.

    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. Personaliza la lógica para actualizar la fila y, luego, incluye una marca de tiempo.

    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. Verifica la condición personalizada antes de actualizar la fila.

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

Mutaciones condicionales

La sentencia INSERT ... IF EXISTS en Cassandra equivale a la sentencia INSERT en Spanner. En ambos casos, la inserción falla si la fila ya existe.

En Cassandra, también puedes crear instrucciones DML que especifiquen una condición, y la instrucción fallará si la condición se evalúa como falsa. En Spanner, puedes usar mutaciones UPDATE condicionales en transacciones de lectura y escritura. Por ejemplo, para actualizar una fila solo si existe una condición particular, haz lo siguiente:

Ejemplo de Cassandra

Go

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

Ejemplo de Spanner

  1. Personaliza la lógica para actualizar la fila y, luego, incluye una condición.

    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. Verifica la condición personalizada antes de actualizar la fila.

    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 admite la configuración de un valor de tiempo de actividad (TTL) a nivel de la fila o la columna. En Spanner, el TTL se configura a nivel de la fila, y designas una columna con nombre como la hora de vencimiento de la fila. Para obtener más información, consulta la descripción general del tiempo de actividad (TTL).

Ejemplo de 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()

Ejemplo de Spanner

  1. Crea un esquema con una columna de marca de tiempo de actualización explícita

    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. Inserta filas con una marca de tiempo de confirmación.

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

Almacena mapas grandes como tablas intercaladas.

Cassandra admite el tipo map para almacenar pares clave-valor ordenados. Para almacenar tipos map que contengan una pequeña cantidad de datos en Spanner, puedes usar los tipos JSON o PROTO, que te permiten almacenar datos semiestructurados y estructurados, respectivamente. Las actualizaciones de esas columnas requieren que se vuelva a escribir todo el valor de la columna. Si tienes un caso de uso en el que se almacena una gran cantidad de datos en un map de Cassandra y solo se debe actualizar una pequeña parte del map, usar tablas INTERLEAVED podría ser una buena opción. Por ejemplo, para asociar una gran cantidad de datos de par clave-valor con un usuario en particular, haz lo siguiente:

Ejemplo de Cassandra

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

Ejemplo de 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);

En este caso, una fila de archivos adjuntos del usuario se almacena junto con la fila correspondiente del usuario y se puede recuperar y actualizar de manera eficiente junto con la fila del usuario. Puedes usar las APIs de lectura y escritura en Spanner para interactuar con tablas intercaladas. Para obtener más información sobre la intercalación, consulta Cómo crear tablas principales y secundarias.

Experiencia de los desarrolladores

En esta sección, se comparan las herramientas para desarrolladores de Spanner y Cassandra.

Desarrollo local

Puedes ejecutar Cassandra de forma local para el desarrollo y las pruebas de unidades. Spanner proporciona un entorno similar para el desarrollo local a través del emulador de Spanner. El emulador proporciona un entorno de alta fidelidad para el desarrollo interactivo y las pruebas de unidades. Para obtener más información, consulta Cómo emular Spanner de forma local.

Línea de comandos

El equivalente de Spanner a nodetool de Cassandra es Google Cloud CLI. Puedes realizar operaciones del plano de control y del plano de datos con gcloud spanner. Para obtener más información, consulta la guía de referencia de Spanner de Google Cloud CLI.

Si necesitas una interfaz de REPL para emitir consultas a Spanner similar a cqlsh, puedes usar la herramienta spanner-cli. Para instalar y ejecutar spanner-cli en Go, haz lo siguiente:

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

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

Para obtener más información, consulta el repositorio de GitHub de spanner-cli.