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 o DataStax Enterprise (DSE) a Spanner, cualquier cambio 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 es equivalente a un clúster de Spanner instancia: un conjunto de servidores y de almacenamiento de Google. Debido a que Spanner es un servicio administrado, no tienes que configurar el hardware o software subyacente. Tú solo debes especificar la cantidad de nodos que quieres reservar para tu instancia o elige ajuste de escala automático 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 teclas Base de datos

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

Tanto en Cassandra como en 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 escalan fragmentando datos. En Cassandra, cada fragmento se denomina partición, mientras que, en Spanner, cada fragmento se llama 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 está fragmentado por rangos, lo que significa que las filas contiguas en el espacio de clave primaria también son contiguas en el almacenamiento (excepto en 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 una colección 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 clave de partición y clave de orden, ya que los datos fragmentados por rango. Se puede pensar en Spanner como si solo tuviera claves de ordenamiento, con la partición administrada en segundo plano.
Columna Columna

Tanto en Cassandra como en Spanner, una columna es un conjunto de datos con valores del mismo tipo. Hay un valor para cada fila de una tabla. Para obtener más información sobre cómo comparar tipos de columnas de Cassandra con 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 (vnode). Luego, se asigna de forma aleatoria un conjunto de vnodos a cada servidor para que entregue 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 cliente se conectan directamente a los nodos de entrega se encargan del balanceo de cargas y el enrutamiento de consultas.

Una instancia de Spanner consta de un conjunto de servidores en un topología de replicación. Spanner particiona cada tabla de forma dinámica en rangos de filas según el uso de CPU y 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 del cliente se conectan al 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 una mayor velocidad El rebalanceo de la carga entre los nodos de procesamiento en respuesta a los cambios de 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 generación de hotspots, consulta Prácticas recomendadas para el diseño del esquema.

Coherencia

Con Cassandra, debes especificar un nivel de coherencia para cada operación. Si usas nivel de coherencia de quórum, una mayoría de nodos de réplica debe responder al coordinador para que la operación se considere exitosa. 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 Spanner como si fuera una base de datos de una sola máquina. Una escritura es siempre se escribe en la mayoría de las réplicas antes de confirmarla al usuario. Todas las lecturas posteriores reflejan los datos recién escritos. Las aplicaciones pueden elige leer una instantánea de la base de datos en un momento del pasado, lo que podría tener en comparación con lecturas sólidas. Para obtener más información sobre la 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 protección coherente 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 de 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 inequívoca una fila. La diferencia clave es que Cassandra está particionada con hash y distingue entre clave de partición y clave de ordenación, mientras que Spanner está particionado 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 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)
    

Varias partes de la clave primaria

Para Cassandra, la primera parte de la clave primaria es la “clave de partición” y las las partes de la clave primaria subsiguientes son “claves de ordenación”. En el caso de Spanner, no es una clave de partición independiente. Los datos se almacenan ordenados por todo el compuesto clave 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)
    

Clave de partición compuesta

Para Cassandra, las claves de partición pueden ser compuestas. No hay una partición independiente en Spanner. Los datos se almacenan ordenados por todo el compuesto clave primaria.

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 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 firmado de 64 bits)
int (número entero firmado de 32 bits)
smallint (número entero firmado de 16 bits)
tinyint (número entero firmado de 8 bits)
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 con 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, string columnas deben especificar su longitud máxima (no se produce ningún impacto en storage; esto se hace con 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 dedicado de tiempo dentro del día. 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 exclusividad del conjunto Para obtener más información, consulta Cómo almacenar mapas grandes como tablas intercaladas. que también se puede usar para almacenar grandes conjuntos.

Patrones de uso básicos

En los siguientes ejemplos de código, se muestra la diferencia entre el código de cliente de Cassandra y de 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, puedes crear un objeto de cliente vinculado a una base de datos específica y emitir solicitudes de base de datos en el objeto del 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). Es el nombre de un en una consulta de Spanner debe tener un 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 es equivalente 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 Spanner DML tiene más funciones, ya que admite la superficie completa de SQL (incluidas las el uso de expresiones en la declaración 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",
    }
  )})

Inserta 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 eliminaciones de Cassandra requieren que se especifique 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.

Escribir marca de tiempo

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 es para manipular la semántica de la última escritura de Cassandra.

Spanner no permite que los clientes especifiquen la marca de tiempo de cada escribir. Cada celda se marca internamente con la marca de tiempo TrueTime en el la hora en que se confirmó el valor de la celda. Dado que Spanner proporciona un coherente y estrictamente serializable, la mayoría de las aplicaciones no necesitan 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. Las actualizaciones de una fila se pueden unir en una regla de lectura y escritura transacción. 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 e incluir 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 es equivalente a INSERT en Spanner. En ambos casos, la inserción falla si la fila ya existe.

En Cassandra, también puedes crear declaraciones DML que especifiquen una condición. la instrucción falla 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 fila y tú designas un como la fecha y 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 explícita de marca de tiempo de actualización

    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 map que contienen una pequeña cantidad de datos en Spanner, puedes puedes usar JSON o PROTO que te permiten almacenar datos semiestructurados y estructurados, respectivamente. Las actualizaciones a esas columnas requieren que se vuelva a escribir el valor de la columna completa. Si tienes un caso de uso en el que se almacena una gran cantidad de datos en un map de Cassandra. solo se necesita actualizar una pequeña parte de map, con INTERLEAVED tablas pueden ser una buena opción. Por ejemplo, para asociar una gran cantidad de datos de pares clave-valor con un usuario en particular:

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 adjuntos de usuario se almacena junto con el nombre de usuario de la fila de usuario y se pueden recuperar y actualizar de manera eficiente junto con la fila de usuario. Puedes usar la función de lectura y escritura APIs en Spanner para interactuar con tablas intercaladas. Para ver más para obtener más información sobre la intercalación, consulta Crear tablas superiores y secundarias

Experiencia del desarrollador

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

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 una alta 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 Spanner equivalente a nodetool de Cassandra es el 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 REPL para emitir consultas a Spanner De manera similar a cqlsh, puedes usar la herramienta spanner-cli. Para instalar y ejecutar spanner-cli en Go:

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.