Google Cloud Spanner es un servicio de base de datos relacional, distribuido de forma global y escalable de forma vertical completamente administrado que proporciona transacciones ACID y semántica de SQL sin renunciar al rendimiento y a la alta disponibilidad.
En este lab, aprenderás a configurar una instancia de Cloud Spanner. Completarás los pasos para crear una base de datos y un esquema que se puedan usar en una tabla de clasificación de videojuegos. Comenzarás por crear una tabla de jugadores a fin de almacenar su información y una tabla de puntuaciones para almacenar sus puntuaciones.
A continuación, propagarás las tablas con datos de muestra. Por último, finalizarás el lab mediante la ejecución de las diez consultas de muestra principales y borrarás la instancia para liberar recursos.
Qué aprenderá
- Cómo configurar una instancia de Cloud Spanner.
- Cómo crear una base de datos y tablas.
- Cómo usar una columna de marca de tiempo de confirmación
- Cómo cargar datos en tu tabla de base de datos de Cloud Spanner con marcas de tiempo
- Cómo consultar tu base de datos de Cloud Spanner.
- Cómo borrar tu instancia de Cloud Spanner.
Qué necesitarás
¿Cómo usarás este instructivo?
¿Cómo calificarías tu experiencia con Google Cloud Platform?
Configuración del entorno a su propio ritmo
Si aún no tienes una Cuenta de Google (Gmail o Google Apps), debes crear una. Accede a Google Cloud Platform Console (console.cloud.google.com) y crea un proyecto nuevo.
Si ya tienes un proyecto, haz clic en el menú desplegable de selección de proyectos en la parte superior izquierda de la Console:
y haz clic en el botón “PROYECTO NUEVO” en el diálogo resultante para crear un proyecto nuevo:
Si aún no tienes un proyecto, deberías ver un cuadro de diálogo como este para crear el primero:
El cuadro de diálogo de creación posterior del proyecto te permite ingresar los detalles de tu proyecto nuevo:
Recuerda el ID del proyecto, que es un nombre único en todos los proyectos de Google Cloud (el nombre anterior ya se encuentra en uso y no lo podrá usar). Se mencionará más adelante en este codelab como PROJECT_ID
.
A continuación, si aún no lo has hecho, deberás habilitar la facturación en Developers Console para usar los recursos de Google Cloud y habilitar la API de Cloud Spanner.
Ejecutar este codelab debería costar solo unos pocos dólares, pero su costo podría aumentar si decides usar más recursos o si los dejas en ejecución (consulta la sección “Limpiar” al final de este documento). Los precios de Google Cloud Spanner se documentan aquí.
Los usuarios nuevos de Google Cloud Platform están aptas para obtener una prueba gratuita de $300, por lo que este codelab es completamente gratuito.
Configuración de Google Cloud Shell
Si bien Google Cloud y Spanner se pueden operar de manera remota desde tu laptop, en este codelab usaremos Google Cloud Shell, un entorno de línea de comandos que se ejecuta en la nube.
Esta máquina virtual basada en Debian está cargada con todas las herramientas de desarrollo que necesitarás. Ofrece un directorio principal persistente de 5 GB y se ejecuta en Google Cloud, lo que permite mejorar considerablemente el rendimiento de la red y la autenticación. Esto significa que todo lo que necesitarás para este Codelab es un navegador (sí, funciona en una Chromebook).
- Para activar Cloud Shell desde Cloud Console, solo haz clic en Activar Cloud Shell (el aprovisionamiento y la conexión al entorno debería llevar solo unos minutos).
Una vez conectado a Cloud Shell, debería ver que ya se autenticó y que el proyecto ya se configuró con tu PROJECT_ID
:
gcloud auth list
Resultado del comando
Credentialed accounts: - <myaccount>@<mydomain>.com (active)
gcloud config list project
Resultado del comando
[core] project = <PROJECT_ID>
Si, por algún motivo, el proyecto no está configurado, solo emite el siguiente comando:
gcloud config set project <PROJECT_ID>
Si no conoce su PROJECT_ID
, Observa el ID que usaste en los pasos de configuración o búscalo en el panel de Cloud Console:
Cloud Shell también configura algunas variables de entorno de forma predeterminada, lo que puede resultar útil cuando ejecutas comandos futuros.
echo $GOOGLE_CLOUD_PROJECT
Resultado del comando
<PROJECT_ID>
- Establece la zona predeterminada y la configuración del proyecto.
gcloud config set compute/zone us-central1-f
Puedes elegir una variedad de zonas diferentes. Para obtener más información, consulta Regiones y zonas.
Resumen
En este paso, configurarás tu entorno.
A continuación
A continuación, configurarás una instancia de Cloud Spanner.
En este paso, configuraremos nuestra instancia de Cloud Spanner para este codelab. Busca la entrada de Spanner en el menú de opciones superior izquierdo o ingresa “/” y escribe “Spanner”
A continuación, haz clic en y completa el formulario. Para ello, ingresa el nombre de la instancia cloudspanner-leaderboard de la instancia, elige una configuración (selecciona una instancia regional) y establece la cantidad de nodos. Para este codelab solo necesitaremos 1 nodo. Para las instancias de producción y a fin de cumplir con los requisitos del ANS de Cloud Spanner, debes ejecutar 3 o más nodos en tu instancia de Cloud Spanner.
Por último, pero no menos importante, haz clic en “Crear” y, en segundos, selecciona una instancia de Cloud Spanner a su disposición.
En el siguiente paso, usaremos la biblioteca cliente de Go para crear una base de datos y un esquema en la instancia nueva.
En este paso, crearemos nuestra base de datos de muestra y un esquema.
Usemos la biblioteca cliente de Go para crear dos tablas, una tabla de jugadores con información sobre ellos y una tabla de puntuaciones a fin de almacenar las puntuaciones Para ello, analizaremos los pasos a fin de crear una aplicación de consola de Go en Cloud Shell.
Primero, clona el código de muestra para este codelab desde GitHub. Para ello, escribe el siguiente comando en Cloud Shell:
go get -u github.com/GoogleCloudPlatform/golang-samples/spanner/...
Luego, cambia el directorio al directorio “tabla de clasificación” en el que crearás tu aplicación.
cd gopath/src/github.com/GoogleCloudPlatform/golang-samples/spanner/spanner_leaderboard
Todo el código necesario para este codelab se encuentra en el directorio golang-samples/spanner/spanner_leaderboard/
existente como una aplicación ejecutable Go llamada leaderboard
que sirve como referencia a medida que avanzas a través del codelab. Crearemos un directorio nuevo y compilaremos una copia de la aplicación Tablas de clasificación en etapas.
Crea un nuevo directorio llamado “codelab” para la aplicación y usa el comando de cambio de directorio a este con el siguiente comando:
mkdir codelab && cd $_
Ahora, actualicemos una aplicación básica de Go llamada “Leaderboard” que usa la biblioteca cliente de Spanner para crear una tabla de clasificación que incluya dos tablas, jugadores y puntuación. Puedes hacerlo directamente en el editor de Cloud Shell:
Haz clic en el siguiente ícono destacado para abrir el editor de Cloud Shell:
Crea un archivo llamado "leaderboard.go" en la carpeta ~/gopath/src/github.com/GoogleCloudPlatform/golang-samples/spanner/codelab.
- Primero, asegúrate de tener la carpeta “codelab” seleccionada en la lista de carpetas del editor de Cloud Shell.
- Luego, seleccione “Archivo nuevo” en el menú “Archivo” del editor de Cloud Shell.
- Ingresa "leaderboard.go" como el nombre del archivo nuevo.
Este es el archivo principal de la aplicación que contendrá el código de la aplicación y las referencias para incluir las dependencias.
Para crear la base de datos leaderboard
y las tablas Players
y Scores
, copia (Ctrl + P) y pega (Ctrl + V) el siguiente código de Go en el archivo leaderboard.go
:
package main
import (
"context"
"flag"
"fmt"
"io"
"log"
"os"
"regexp"
"time"
"cloud.google.com/go/spanner"
database "cloud.google.com/go/spanner/admin/database/apiv1"
adminpb "google.golang.org/genproto/googleapis/spanner/admin/database/v1"
)
type adminCommand func(ctx context.Context, w io.Writer, adminClient *database.DatabaseAdminClient, database string) error
func createDatabase(ctx context.Context, w io.Writer, adminClient *database.DatabaseAdminClient, db string) error {
matches := regexp.MustCompile("^(.*)/databases/(.*)$").FindStringSubmatch(db)
if matches == nil || len(matches) != 3 {
return fmt.Errorf("Invalid database id %s", db)
}
op, err := adminClient.CreateDatabase(ctx, &adminpb.CreateDatabaseRequest{
Parent: matches[1],
CreateStatement: "CREATE DATABASE `" + matches[2] + "`",
ExtraStatements: []string{
`CREATE TABLE Players(
PlayerId INT64 NOT NULL,
PlayerName STRING(2048) NOT NULL
) PRIMARY KEY(PlayerId)`,
`CREATE TABLE Scores(
PlayerId INT64 NOT NULL,
Score INT64 NOT NULL,
Timestamp TIMESTAMP NOT NULL
OPTIONS(allow_commit_timestamp=true)
) PRIMARY KEY(PlayerId, Timestamp),
INTERLEAVE IN PARENT Players ON DELETE NO ACTION`,
},
})
if err != nil {
return err
}
if _, err := op.Wait(ctx); err != nil {
return err
}
fmt.Fprintf(w, "Created database [%s]\n", db)
return nil
}
func createClients(ctx context.Context, db string) (*database.DatabaseAdminClient, *spanner.Client) {
adminClient, err := database.NewDatabaseAdminClient(ctx)
if err != nil {
log.Fatal(err)
}
dataClient, err := spanner.NewClient(ctx, db)
if err != nil {
log.Fatal(err)
}
return adminClient, dataClient
}
func run(ctx context.Context, adminClient *database.DatabaseAdminClient, dataClient *spanner.Client, w io.Writer,
cmd string, db string, timespan int) error {
// createdatabase command
if cmd == "createdatabase" {
err := createDatabase(ctx, w, adminClient, db)
if err != nil {
fmt.Fprintf(w, "%s failed with %v", cmd, err)
}
return err
}
return nil
}
func main() {
flag.Usage = func() {
fmt.Fprintf(os.Stderr, `Usage: leaderboard <command> <database_name> [command_option]
Command can be one of: createdatabase
Examples:
leaderboard createdatabase projects/my-project/instances/my-instance/databases/example-db
- Create a sample Cloud Spanner database along with sample tables in your project.
`)
}
flag.Parse()
flagCount := len(flag.Args())
if flagCount != 2 {
flag.Usage()
os.Exit(2)
}
cmd, db := flag.Arg(0), flag.Arg(1)
// Set timespan to zero, as it's not currently being used
var timespan int = 0
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
defer cancel()
adminClient, dataClient := createClients(ctx, db)
if err := run(ctx, adminClient, dataClient, os.Stdout, cmd, db, timespan); err != nil {
os.Exit(1)
}
}
Para guardar los cambios realizados en el archivo leaderboard.go
, selecciona “Guardar” en el menú “Archivo” de Cloud Shell.
Puedes usar el archivo leaderboard.go
en el directorio golang-samples/spanner/spanner_leaderboard
si deseas ver un ejemplo de cómo debe verse tu archivo leaderboard.go
después de agregar el código para habilitar el comando createdatabase
.
Para compilar tu aplicación en Cloud Shell, ejecuta “go build” desde el directorio codelab
en el que se encuentra tu archivo leaderboard.go
:
go build leaderboard.go
Una vez que su aplicación se haya compilado correctamente, ejecuta el siguiente comando para ejecutar la aplicación resultante en Cloud Shell:
./leaderboard
Deberías ver un resultado como el siguiente:
Usage: leaderboard <command> <database_name> [command_option] Command can be one of: createdatabase Examples: leaderboard createdatabase projects/my-project/instances/my-instance/databases/example-db - Create a sample Cloud Spanner database along with sample tables in your project.
En esta respuesta, podemos ver que esta es la aplicación Leaderboard
, que en la actualidad tiene un comando posible: createdatabase
. Podemos ver que el argumento esperado del comando createdatabase
es una string que contiene un ID de instancia y un ID de base de datos específicos.
Ahora, ejecuta el siguiente comando: Asegúrate de reemplazar el my-project
por el ID del proyecto que creaste al comienzo de este codelab.
./leaderboard createdatabase projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard
Después de unos segundos, deberías ver una respuesta como la siguiente:
Created database [projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard]
En la sección de Cloud Spanner de Cloud Console, deberías ver su nueva base de datos y tablas en el menú del lado izquierdo.
En el próximo paso, actualizaremos nuestra aplicación para cargar algunos datos en tu base de datos nueva.
Ahora tenemos una base de datos llamada leaderboard
que contiene las dos tablas, Players
y Scores
. Ahora usemos la biblioteca cliente de Go a fin de propagar la tabla Players
con los jugadores y nuestra tabla Scores
con puntuaciones aleatorias para cada jugador.
Si aún no se abrió, abre el editor de Cloud Shell y haz clic en el ícono que se destaca a continuación:
A continuación, edita el archivo leaderboard.go
en el editor de Cloud Shell para agregar un comando insertplayers
que se puede usar a fin de insertar 100 jugadores en la tabla Players
. También agregaremos un comando insertscores
que se puede usar a fin de insertar 4 puntuaciones aleatorias en la tabla Scores
para cada jugador de la tabla Players
.
Primero, actualiza la sección imports
en la parte superior del archivo leaderboard.go
. Para ello, reemplaza el contenido actual de modo que, una vez que termines, se vea de la siguiente manera:
import (
"context"
"flag"
"fmt"
"io"
"log"
"math/rand"
"os"
"regexp"
"time"
"cloud.google.com/go/spanner"
database "cloud.google.com/go/spanner/admin/database/apiv1"
"google.golang.org/api/iterator"
adminpb "google.golang.org/genproto/googleapis/spanner/admin/database/v1"
)
A continuación, agrega un nuevo tipo de comando junto con una lista de comandos en la parte superior del archivo, justo debajo de la línea que comienza con “tipo de adminCommand…”, así que, una vez que termines, debería verse de la siguiente manera:
type adminCommand func(ctx context.Context, w io.Writer, adminClient *database.DatabaseAdminClient, database string) error
type command func(ctx context.Context, w io.Writer, client *spanner.Client) error
var (
commands = map[string]command{
"insertplayers": insertPlayers,
"insertscores": insertScores,
}
)
A continuación, agrega las siguientes funciones insertPlayers e insertScores debajo de la función createdatabase()
existente:
func insertPlayers(ctx context.Context, w io.Writer, client *spanner.Client) error {
// Get number of players to use as an incrementing value for each PlayerName to be inserted
stmt := spanner.Statement{
SQL: `SELECT Count(PlayerId) as PlayerCount FROM Players`,
}
iter := client.Single().Query(ctx, stmt)
defer iter.Stop()
row, err := iter.Next()
if err != nil {
return err
}
var numberOfPlayers int64 = 0
if err := row.Columns(&numberOfPlayers); err != nil {
return err
}
// Intialize values for random PlayerId
rand.Seed(time.Now().UnixNano())
min := 1000000000
max := 9000000000
// Insert 100 player records into the Players table
_, err = client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
stmts := []spanner.Statement{}
for i := 1; i <= 100; i++ {
numberOfPlayers++
playerID := rand.Intn(max-min) + min
playerName := fmt.Sprintf("Player %d", numberOfPlayers)
stmts = append(stmts, spanner.Statement{
SQL: `INSERT INTO Players
(PlayerId, PlayerName)
VALUES (@playerID, @playerName)`,
Params: map[string]interface{}{
"playerID": playerID,
"playerName": playerName,
},
})
}
_, err := txn.BatchUpdate(ctx, stmts)
if err != nil {
return err
}
return nil
})
fmt.Fprintf(w, "Inserted players \n")
return nil
}
func insertScores(ctx context.Context, w io.Writer, client *spanner.Client) error {
playerRecordsFound := false
// Create slice for insert statements
stmts := []spanner.Statement{}
// Select all player records
stmt := spanner.Statement{SQL: `SELECT PlayerId FROM Players`}
iter := client.Single().Query(ctx, stmt)
defer iter.Stop()
// Insert 4 score records into the Scores table for each player in the Players table
for {
row, err := iter.Next()
if err == iterator.Done {
break
}
if err != nil {
return err
}
playerRecordsFound = true
var playerID int64
if err := row.ColumnByName("PlayerId", &playerID); err != nil {
return err
}
// Intialize values for random score and date
rand.Seed(time.Now().UnixNano())
min := 1000
max := 1000000
for i := 0; i < 4; i++ {
// Generate random score between 1,000 and 1,000,000
score := rand.Intn(max-min) + min
// Generate random day within the past two years
now := time.Now()
endDate := now.Unix()
past := now.AddDate(0, -24, 0)
startDate := past.Unix()
randomDateInSeconds := rand.Int63n(endDate-startDate) + startDate
randomDate := time.Unix(randomDateInSeconds, 0)
// Add insert statement to stmts slice
stmts = append(stmts, spanner.Statement{
SQL: `INSERT INTO Scores
(PlayerId, Score, Timestamp)
VALUES (@playerID, @score, @timestamp)`,
Params: map[string]interface{}{
"playerID": playerID,
"score": score,
"timestamp": randomDate,
},
})
}
}
if !playerRecordsFound {
fmt.Fprintln(w, "No player records currently exist. First insert players then insert scores.")
} else {
_, err := client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
// Commit insert statements for all scores to be inserted as a single transaction
_, err := txn.BatchUpdate(ctx, stmts)
return err
})
if err != nil {
return err
}
fmt.Fprintln(w, "Inserted scores")
}
return nil
}
Luego, para que el comando insert
funcione, agrega el siguiente código a la función “run” de tu aplicación debajo de la declaración de administración createdatabase
, y reemplaza la declaración return nil
:
// insert and query commands
cmdFn := commands[cmd]
if cmdFn == nil {
flag.Usage()
os.Exit(2)
}
err := cmdFn(ctx, w, dataClient)
if err != nil {
fmt.Fprintf(w, "%s failed with %v", cmd, err)
}
return err
Cuando termines, la función run
debería verse de la siguiente manera:
func run(ctx context.Context, adminClient *database.DatabaseAdminClient, dataClient *spanner.Client, w io.Writer,
cmd string, db string, timespan int) error {
// createdatabase command
if cmd == "createdatabase" {
err := createDatabase(ctx, w, adminClient, db)
if err != nil {
fmt.Fprintf(w, "%s failed with %v", cmd, err)
}
return err
}
// insert and query commands
cmdFn := commands[cmd]
if cmdFn == nil {
flag.Usage()
os.Exit(2)
}
err := cmdFn(ctx, w, dataClient)
if err != nil {
fmt.Fprintf(w, "%s failed with %v", cmd, err)
}
return err
}
El último paso a fin de que termines de agregar la funcionalidad “insert” a tu aplicación es agregar texto de ayuda para los comandos “insertplayers” y “insertscores” a la función flag.Usage()
. Agrega el siguiente texto de ayuda a la función flag.Usage()
a fin de incluir texto de ayuda para los comandos de inserción:
Agrega los dos comandos a la lista de comandos posibles:
Command can be one of: createdatabase, insertplayers, insertscores
Agrega este texto de ayuda adicional debajo del texto de ayuda para el comando createdatabase
.
leaderboard insertplayers projects/my-project/instances/my-instance/databases/example-db
- Insert 100 sample Player records into the database.
leaderboard insertscores projects/my-project/instances/my-instance/databases/example-db
- Insert sample score data into Scores sample Cloud Spanner database table.
Para guardar los cambios realizados en el archivo leaderboard.go
, selecciona “Guardar” en el menú “Archivo” de Cloud Shell.
Puedes usar el archivo leaderboard.go
en el directorio golang-samples/spanner/spanner_leaderboard
para ver un ejemplo de cómo debe verse tu archivo leaderboard.go
después de agregar el código para habilitar los comandos insertplayers
y insertscores
.
Ahora, compila y ejecuta la aplicación para confirmar que los nuevos comandos insertplayers
y insertscores
estén incluidos en la lista de comandos posibles de la aplicación. Ejecute el siguiente comando para compilar la aplicación:
go build leaderboard.go
Ingresa el siguiente comando para ejecutar la aplicación resultante en Cloud Shell:
./leaderboard
Ahora deberías ver los comandos insertplayers
y insertscores
en el resultado predeterminado de la aplicación:
Usage: leaderboard <command> <database_name> [command_option] Command can be one of: createdatabase, insertplayers, insertscores Examples: leaderboard createdatabase projects/my-project/instances/my-instance/databases/example-db - Create a sample Cloud Spanner database along with sample tables in your project. leaderboard insertplayers projects/my-project/instances/my-instance/databases/example-db - Insert 100 sample Player records into the database. leaderboard insertscores projects/my-project/instances/my-instance/databases/example-db - Insert sample score data into Scores sample Cloud Spanner database table.
Ahora, ejecutemos el comando insertplayers
con los mismos valores de argumento que usamos cuando llamamos al comando createdatabase
. Asegúrate de reemplazar el my-project
por el ID del proyecto que creaste al comienzo de este codelab.
./leaderboard insertplayers projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard
Después de unos segundos, deberías ver una respuesta como la siguiente:
Inserted players
Ahora usemos la biblioteca cliente de Go a fin de propagar nuestra tabla Scores
con cuatro puntuaciones aleatorias junto con marcas de tiempo para cada jugador en la tabla Players
.
La columna Timestamp
de la tabla Scores
se definió como una columna de “marca de tiempo de confirmación” a través de la siguiente instrucción de SQL que se ejecutaba cuando ejecutamos el comando create
:
CREATE TABLE Scores(
PlayerId INT64 NOT NULL,
Score INT64 NOT NULL,
Timestamp TIMESTAMP NOT NULL OPTIONS(allow_commit_timestamp=true)
) PRIMARY KEY(PlayerId, Timestamp),
INTERLEAVE IN PARENT Players ON DELETE NO ACTION
Observa el atributo OPTIONS(allow_commit_timestamp=true)
. Esto hace que Timestamp
sea una columna de “marca de tiempo de confirmación” y permite que se propague automáticamente con la marca de tiempo exacta de la transacción para operaciones INSERTAR y ACTUALIZAR en una fila de tabla determinada.
También puedes insertar tus propios valores de marca de tiempo en una columna “marca de tiempo de confirmación”, siempre y cuando insertes una marca de tiempo con un valor anterior, lo que haremos para este codelab.
Ahora, ejecutemos el comando insertscores
con los mismos valores de argumento que usamos cuando llamamos al comando insertplayers
. Asegúrate de reemplazar el my-project
por el ID del proyecto que creaste al comienzo de este codelab.
./leaderboard insertscores projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard
Después de unos segundos, deberías ver una respuesta como la siguiente:
Inserted scores
La ejecución de la función insertScores
usa el siguiente fragmento de código para insertar una marca de tiempo generada de forma aleatoria con una fecha y hora en el pasado:
now := time.Now()
endDate := now.Unix()
past := now.AddDate(0, -24, 0)
startDate := past.Unix()
randomDateInSeconds := rand.Int63n(endDate-startDate) + startDate
randomDate := time.Unix(randomDateInSeconds, 0)
stmts = append(stmts, spanner.Statement{
SQL: `INSERT INTO Scores
(PlayerId, Score, Timestamp)
VALUES (@playerID, @score, @timestamp)`,
Params: map[string]interface{}{
"playerID": playerID,
"score": score,
"timestamp": randomDate,
},
})
Para propagar automáticamente la columna Timestamp
con la marca de tiempo exacta cuando la transacción “Insertar” se instale, puedes insertar la constante spanner.CommitTimestamp
de Go, como en el siguiente fragmento de código:
...
stmts = append(stmts, spanner.Statement{
SQL: `INSERT INTO Scores
(PlayerId, Score, Timestamp)
VALUES (@playerID, @score, @timestamp)`,
Params: map[string]interface{}{
"playerID": playerID,
"score": score,
"timestamp": spanner.CommitTimestamp,
},
})
Ahora que se completó la carga de datos, verificaremos los valores que acabamos de escribir en nuestras tablas nuevas en la sección de Cloud Spanner de Cloud Console. Primero, selecciona la base de datos leaderboard
y, luego, la tabla Players
. Haz clic en la pestaña Data
. Debería ver que tiene datos en las columnas PlayerId
y PlayerName
de la tabla.
A continuación, verifiquemos la tabla de puntuaciones. Para ello, haz clic en la tabla Scores
y selecciona la pestaña Data
. Deberías ver que tiene datos en las columnas PlayerId
, Timestamp
y Score
de la tabla.
¡Bien hecho! Actualicemos nuestra app para ejecutar algunas consultas que podemos usar a fin de crear una tabla de clasificación de videojuegos.
Ahora que configuramos nuestra base de datos y cargamos información en nuestras tablas, vamos a crear una tabla de clasificación con estos datos. Para ello, debemos responder las siguientes cuatro preguntas:
- ¿Cuáles son los diez principales jugadores de todos los tiempos?
- ¿Cuáles son los diez jugadores principales de este año?
- ¿Cuáles son los diez jugadores principales del mes?
- ¿Cuáles son los diez jugadores principales de la semana?
Actualicemos nuestra aplicación para ejecutar las consultas de SQL que responderán estas preguntas.
Agregaremos un comando query
y un comando queryWithTimespan
que proporcionará una forma de ejecutar las consultas a fin de responder las preguntas que producirán la información requerida en nuestra tabla de clasificación.
Edita el archivo leaderboard.go
en el editor de Cloud Shell a fin de actualizar la aplicación para agregar un comando query
y un comando queryWithTimespan
. También agregaremos una función auxiliar formatWithCommas
para dar formato a nuestras puntuaciones con comas.
Primero, actualiza la sección imports
en la parte superior del archivo leaderboard.go
. Para ello, reemplaza el contenido actual de modo que, una vez que termines, se vea de la siguiente manera:
import (
"bytes"
"context"
"flag"
"fmt"
"io"
"log"
"math/rand"
"os"
"regexp"
"strconv"
"time"
"cloud.google.com/go/spanner"
database "cloud.google.com/go/spanner/admin/database/apiv1"
"google.golang.org/api/iterator"
adminpb "google.golang.org/genproto/googleapis/spanner/admin/database/v1"
)
A continuación, agrega las siguientes dos funciones y la función auxiliar debajo del método insertScores
existente:
func query(ctx context.Context, w io.Writer, client *spanner.Client) error {
stmt := spanner.Statement{
SQL: `SELECT p.PlayerId, p.PlayerName, s.Score, s.Timestamp
FROM Players p
JOIN Scores s ON p.PlayerId = s.PlayerId
ORDER BY s.Score DESC LIMIT 10`}
iter := client.Single().Query(ctx, stmt)
defer iter.Stop()
for {
row, err := iter.Next()
if err == iterator.Done {
return nil
}
if err != nil {
return err
}
var playerID, score int64
var playerName string
var timestamp time.Time
if err := row.Columns(&playerID, &playerName, &score, ×tamp); err != nil {
return err
}
fmt.Fprintf(w, "PlayerId: %d PlayerName: %s Score: %s Timestamp: %s\n",
playerID, playerName, formatWithCommas(score), timestamp.String()[0:10])
}
}
func queryWithTimespan(ctx context.Context, w io.Writer, client *spanner.Client, timespan int) error {
stmt := spanner.Statement{
SQL: `SELECT p.PlayerId, p.PlayerName, s.Score, s.Timestamp
FROM Players p
JOIN Scores s ON p.PlayerId = s.PlayerId
WHERE s.Timestamp > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL @Timespan HOUR)
ORDER BY s.Score DESC LIMIT 10`,
Params: map[string]interface{}{"Timespan": timespan},
}
iter := client.Single().Query(ctx, stmt)
defer iter.Stop()
for {
row, err := iter.Next()
if err == iterator.Done {
return nil
}
if err != nil {
return err
}
var playerID, score int64
var playerName string
var timestamp time.Time
if err := row.Columns(&playerID, &playerName, &score, ×tamp); err != nil {
return err
}
fmt.Fprintf(w, "PlayerId: %d PlayerName: %s Score: %s Timestamp: %s\n",
playerID, playerName, formatWithCommas(score), timestamp.String()[0:10])
}
}
func formatWithCommas(n int64) string {
numberAsString := strconv.FormatInt(n, 10)
numberLength := len(numberAsString)
if numberLength < 4 {
return numberAsString
}
var buffer bytes.Buffer
comma := []rune(",")
bufferPosition := numberLength % 3
if (bufferPosition) > 0 {
bufferPosition = 3 - bufferPosition
}
for i := 0; i < numberLength; i++ {
if bufferPosition == 3 {
buffer.WriteRune(comma[0])
bufferPosition = 0
}
bufferPosition++
buffer.WriteByte(numberAsString[i])
}
return buffer.String()
}
En la parte superior del archivo leaderboard.go
, agrega “query” como una opción de comando en la variable commands
, justo debajo de la opción “insertscores": insertScores
”, de modo que la variable commands
se vea de la siguiente manera:
var (
commands = map[string]command{
"insertplayers": insertPlayers,
"insertscores": insertScores,
"query": query,
}
)
A continuación, agregue “queryWithTimespan” como opción de comando dentro de la función run
, debajo de la sección del comando “createdatabase” y sobre la sección de control del comando “insert y query”:
// querywithtimespan command
if cmd == "querywithtimespan" {
err := queryWithTimespan(ctx, w, dataClient, timespan)
if err != nil {
fmt.Fprintf(w, "%s failed with %v", cmd, err)
}
return err
}
Cuando termines, la función run
debería verse de la siguiente manera:
func run(ctx context.Context, adminClient *database.DatabaseAdminClient, dataClient *spanner.Client, w io.Writer,
cmd string, db string, timespan int) error {
// createdatabase command
if cmd == "createdatabase" {
err := createDatabase(ctx, w, adminClient, db)
if err != nil {
fmt.Fprintf(w, "%s failed with %v", cmd, err)
}
return err
}
// querywithtimespan command
if cmd == "querywithtimespan" {
if timespan == 0 {
flag.Usage()
os.Exit(2)
}
err := queryWithTimespan(ctx, w, dataClient, timespan)
if err != nil {
fmt.Fprintf(w, "%s failed with %v", cmd, err)
}
return err
}
// insert and query commands
cmdFn := commands[cmd]
if cmdFn == nil {
flag.Usage()
os.Exit(2)
}
err := cmdFn(ctx, w, dataClient)
if err != nil {
fmt.Fprintf(w, "%s failed with %v", cmd, err)
}
return err
}
Luego, para que el comando queryWithTimespan
funcione, actualiza el bloque de código flag.Parse() en el método “main” de tu aplicación para que se vea de la siguiente manera:
flag.Parse()
flagCount := len(flag.Args())
if flagCount < 2 || flagCount > 3 {
flag.Usage()
os.Exit(2)
}
cmd, db := flag.Arg(0), flag.Arg(1)
// If query timespan flag is specified, parse to int
var timespan int = 0
if flagCount == 3 {
parsedTimespan, err := strconv.Atoi(flag.Arg(2))
if err != nil {
fmt.Println(err)
os.Exit(2)
}
timespan = parsedTimespan
}
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
defer cancel()
adminClient, dataClient := createClients(ctx, db)
if err := run(ctx, adminClient, dataClient, os.Stdout, cmd, db, timespan); err != nil {
os.Exit(1)
}
El último paso a fin de que puedas agregar la funcionalidad de “query” a tu aplicación es agregar texto de ayuda para los comandos “query” y “querywithtimespan” a la función flag.Usage()
. Agrega las siguientes líneas de código a la función flag.Usage()
a fin de incluir texto de ayuda para los comandos de consulta:
Agrega los dos comandos “query” a la lista de comandos posibles:
Command can be one of: createdatabase, insertplayers, insertscores, query, querywithtimespan
Agrega este texto de ayuda adicional debajo del texto de ayuda para el comando insertscores
.
leaderboard query projects/my-project/instances/my-instance/databases/example-db
- Query players with top ten scores of all time.
leaderboard querywithtimespan projects/my-project/instances/my-instance/databases/example-db 168
- Query players with top ten scores within a timespan specified in hours.
Para guardar los cambios realizados en el archivo leaderboard.go
, selecciona “Guardar” en el menú “Archivo” de Cloud Shell.
Puedes usar el archivo leaderboard.go
en el directorio golang-samples/spanner/spanner_leaderboard
para ver un ejemplo de cómo debe verse tu archivo leaderboard.go
después de agregar el código para habilitar los comandos query
y querywithtimespan
.
Ahora, compila y ejecuta la aplicación para confirmar que los nuevos comandos query
y querywithtimespan
estén incluidos en la lista de comandos posibles de la aplicación.
En Cloud Shell, ejecuta el siguiente comando para compilar la aplicación:
go build leaderboard.go
Ingresa el siguiente comando para ejecutar la aplicación resultante en Cloud Shell:
./leaderboard
Los comandos query
y querywithtimespan
ahora se incluyen en el resultado predeterminado de la aplicación como una nueva opción de comando:
Usage: leaderboard <command> <database_name> [command_option] Command can be one of: createdatabase, insertplayers, insertscores, query, querywithtimespan Examples: leaderboard createdatabase projects/my-project/instances/my-instance/databases/example-db - Create a sample Cloud Spanner database along with sample tables in your project. leaderboard insertplayers projects/my-project/instances/my-instance/databases/example-db - Insert 100 sample Player records into the database. leaderboard insertscores projects/my-project/instances/my-instance/databases/example-db - Insert sample score data into Scores sample Cloud Spanner database table. leaderboard query projects/my-project/instances/my-instance/databases/example-db - Query players with top ten scores of all time. leaderboard querywithtimespan projects/my-project/instances/my-instance/databases/example-db 168 - Query players with top ten scores within a timespan specified in hours.
Desde la respuesta, podemos ver que podemos usar el comando query
para obtener una lista de los “diez mejores” jugadores de todos los tiempos. También podemos ver que el comando querywithtimespan
nos permite especificar un intervalo de horas para usar en el filtrado de registros según su valor en la columna Timestamp
de la tabla Scores
.
Ejecutemos el comando query
con los mismos valores de argumento que usamos cuando ejecutamos el comando create
. Asegúrate de reemplazar el my-project
por el ID del proyecto que creaste al comienzo de este codelab.
./leaderboard query projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard
Deberías ver una respuesta que incluya los “diez mejores” jugadores de todos los tiempos, como se indica a continuación:
PlayerId: 4018687297 PlayerName: Player 83 Score: 999,618 Timestamp: 2017-07-01
PlayerId: 4018687297 PlayerName: Player 83 Score: 998,956 Timestamp: 2017-09-02
PlayerId: 4285713246 PlayerName: Player 51 Score: 998,648 Timestamp: 2017-12-01
PlayerId: 5267931774 PlayerName: Player 49 Score: 997,733 Timestamp: 2017-11-09
PlayerId: 1981654448 PlayerName: Player 35 Score: 997,480 Timestamp: 2018-12-06
PlayerId: 4953940705 PlayerName: Player 87 Score: 995,184 Timestamp: 2018-09-14
PlayerId: 2456736905 PlayerName: Player 84 Score: 992,881 Timestamp: 2017-04-14
PlayerId: 8234617611 PlayerName: Player 19 Score: 992,399 Timestamp: 2017-12-27
PlayerId: 1788051688 PlayerName: Player 76 Score: 992,265 Timestamp: 2018-11-22
PlayerId: 7127686505 PlayerName: Player 97 Score: 992,038 Timestamp: 2017-12-02
Ahora, ejecutemos el comando querywithtimespan
con los argumentos necesarios a fin de consultar los “diez mejores” jugadores del año. Para ello, especifica un “intervalo” igual a la cantidad de horas en un año, que es 8,760. Asegúrate de reemplazar el my-project
por el ID del proyecto que creaste al comienzo de este codelab.
./leaderboard querywithtimespan projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard 8760
Deberías ver una respuesta que incluya a los “diez mejores” jugadores del año, como se muestra a continuación:
PlayerId: 1981654448 PlayerName: Player 35 Score: 997,480 Timestamp: 2018-12-06
PlayerId: 4953940705 PlayerName: Player 87 Score: 995,184 Timestamp: 2018-09-14
PlayerId: 1788051688 PlayerName: Player 76 Score: 992,265 Timestamp: 2018-11-22
PlayerId: 6862349579 PlayerName: Player 30 Score: 990,877 Timestamp: 2018-09-14
PlayerId: 5529627211 PlayerName: Player 16 Score: 989,142 Timestamp: 2018-03-30
PlayerId: 9743904155 PlayerName: Player 1 Score: 988,765 Timestamp: 2018-05-30
PlayerId: 6809119884 PlayerName: Player 7 Score: 986,673 Timestamp: 2018-05-16
PlayerId: 2132710638 PlayerName: Player 54 Score: 983,108 Timestamp: 2018-09-11
PlayerId: 2320093590 PlayerName: Player 79 Score: 981,373 Timestamp: 2018-05-07
PlayerId: 9554181430 PlayerName: Player 80 Score: 981,087 Timestamp: 2018-06-21
Ahora, ejecutemos el comando querywithtimespan
a fin de consultar a los “diez mejores” jugadores del mes. Para ello, especifica un “intervalo” igual a la cantidad de horas de un mes, que es 730. Asegúrate de reemplazar el my-project
por el ID del proyecto que creaste al comienzo de este codelab.
./leaderboard querywithtimespan projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard 730
Deberías ver una respuesta que incluya a los “diez mejores” jugadores del mes, como se muestra a continuación:
PlayerId: 3869829195 PlayerName: Player 69 Score: 949,686 Timestamp: 2019-02-19
PlayerId: 7448359883 PlayerName: Player 20 Score: 938,998 Timestamp: 2019-02-07
PlayerId: 1981654448 PlayerName: Player 35 Score: 929,003 Timestamp: 2019-02-22
PlayerId: 9336678658 PlayerName: Player 44 Score: 914,106 Timestamp: 2019-01-27
PlayerId: 6968576389 PlayerName: Player 40 Score: 898,041 Timestamp: 2019-02-21
PlayerId: 5529627211 PlayerName: Player 16 Score: 896,433 Timestamp: 2019-01-29
PlayerId: 9395039625 PlayerName: Player 59 Score: 879,495 Timestamp: 2019-02-09
PlayerId: 2094604854 PlayerName: Player 39 Score: 860,434 Timestamp: 2019-02-01
PlayerId: 9395039625 PlayerName: Player 59 Score: 849,955 Timestamp: 2019-02-21
PlayerId: 4285713246 PlayerName: Player 51 Score: 805,654 Timestamp: 2019-02-02
Ahora, ejecutemos el comando querywithtimespan
a fin de consultar a los “diez mejores” jugadores de la semana. Para ello, especifica un “intervalo” igual a la cantidad de horas de una semana, que es 168. Asegúrate de reemplazar el my-project
por el ID del proyecto que creaste al comienzo de este codelab.
./leaderboard querywithtimespan projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard 168
Deberías ver una respuesta que incluya a los “diez mejores” jugadores de la semana, como se muestra a continuación:
PlayerId: 3869829195 PlayerName: Player 69 Score: 949,686 Timestamp: 2019-02-19
PlayerId: 1981654448 PlayerName: Player 35 Score: 929,003 Timestamp: 2019-02-22
PlayerId: 6968576389 PlayerName: Player 40 Score: 898,041 Timestamp: 2019-02-21
PlayerId: 9395039625 PlayerName: Player 59 Score: 849,955 Timestamp: 2019-02-21
PlayerId: 5954045812 PlayerName: Player 8 Score: 795,639 Timestamp: 2019-02-22
PlayerId: 3889939638 PlayerName: Player 71 Score: 775,252 Timestamp: 2019-02-21
PlayerId: 5529627211 PlayerName: Player 16 Score: 604,695 Timestamp: 2019-02-19
PlayerId: 9006728426 PlayerName: Player 3 Score: 457,208 Timestamp: 2019-02-22
PlayerId: 8289497066 PlayerName: Player 58 Score: 227,697 Timestamp: 2019-02-20
PlayerId: 8065482904 PlayerName: Player 99 Score: 198,429 Timestamp: 2019-02-24
¡Excelente trabajo!
Ahora a medida que agregues los registros, Cloud Spanner escalará tu base de datos del tamaño que necesites que sea. No importa cuánto crezca tu base de datos, la tabla de clasificación de tu juego puede escalar con exactitud con Cloud Spanner y su tecnología Truetime.
Después de la diversión cuando se juega con Spanner, debemos limpiar nuestro zona de prueba para ahorrar dinero y recursos valiosos. Por suerte, este paso es sencillo. Ve a la sección de Cloud Spanner de Cloud Console y borra la instancia que creamos en el paso de codelab denominado “Configurar una instancia de Cloud Spanner”.
Temas abordados:
- Instancias, bases de datos y esquemas de tablas de Google Cloud Spanner para una tabla de clasificación
- Cómo crear una aplicación de la consola de Go
- Cómo crear una base de datos y tablas de Spanner con la biblioteca cliente de Go
- Cómo cargar datos en una base de datos de Spanner con la biblioteca cliente de Go
- Cómo consultar los resultados de los “Diez mejores” de tus datos mediante las marcas de tiempo de confirmación de Spanner y la biblioteca cliente de Go
Próximos pasos:
- Lee el Informe de CAP de Spanner.
- Obtén información sobre el diseño de esquemas y las prácticas recomendadas de consulta.
- Obtén más información sobre las marcas de tiempo de confirmación de Cloud Spanner.
Envíanos tus comentarios
- Tómate un momento para completar nuestra breve encuesta