O Google Cloud Spanner é um serviço de banco de dados relacional horizontalmente escalonável e distribuído globalmente que fornece transações ACID e semântica SQL sem deixar de oferecer desempenho e alta disponibilidade.
Neste laboratório, você aprenderá a configurar uma instância do Cloud Spanner. Você conhecerá as etapas para criar um banco de dados e um esquema que possam ser usados em um placar de jogos. Primeiro, crie uma tabela de jogadores para armazenar informações sobre o jogador e uma tabela de pontuações para armazenar as pontuações dos jogadores.
Em seguida, preencha as tabelas com dados de amostra. Para concluir o laboratório, execute algumas consultas de exemplo do Top 10 e, por fim, exclua a instância para liberar recursos.
O que você aprenderá
- Como configurar uma instância do Cloud Spanner.
- Como criar um banco de dados e tabelas.
- Como usar uma coluna de carimbo de data/hora de confirmação.
- Como carregar dados em uma tabela de banco de dados do Cloud Spanner com carimbos de data/hora.
- Como consultar o banco de dados do Cloud Spanner.
- Como excluir uma instância do Cloud Spanner.
Pré-requisitos
Como você usará este tutorial?
Como você classificaria sua experiência com o Google Cloud Platform?
Configuração de ambiente personalizada
Se você ainda não tem uma Conta do Google (Gmail ou Google Apps), crie uma. Faça login no Console do Google Cloud Platform ( console.cloud.google.com) e crie um novo projeto.
Se você já tiver um projeto, clique no menu suspenso de seleção no canto superior esquerdo do console:
e clique no botão "NEW PROJECT" na caixa de diálogo exibida para criar um novo projeto:
Se você ainda não tiver um projeto, uma caixa de diálogo como esta será exibida para criar seu primeiro:
A caixa de diálogo de criação de projeto subsequente permite que você insira os detalhes do novo projeto:
Lembre-se do código do projeto, um nome exclusivo em todos os projetos do Google Cloud. O nome acima já foi escolhido e não servirá para você. Faremos referência a ele mais adiante neste codelab como PROJECT_ID
.
Em seguida, será preciso ativar o faturamento no Developers Console para usar os recursos do Google Cloud e ativar a API Cloud Spanner, caso ainda não tenha feito isso.
A execução por meio deste codelab terá um custo baixo, mas poderá ser mais se você decidir usar mais recursos ou se deixá-los em execução. Consulte a seção "limpeza" no final deste documento. Os preços do Google Cloud Spanner estão documentados neste link.
Novos usuários do Google Cloud Platform estão qualificados para uma avaliação gratuita de US$ 300, o que torna este codelab totalmente gratuito.
Configuração do Google Cloud Shell
Embora o Google Cloud e o Spanner possam ser operados remotamente do seu laptop, neste codelab usaremos o Google Cloud Shell, um ambiente de linha de comando executado no Cloud.
O Cloud Shell é uma máquina virtual com base em Debian que contém todas as ferramentas de desenvolvimento necessárias. Ela oferece um diretório principal persistente de 5 GB, além de ser executada no Google Cloud. Isso aprimora o desempenho e a autenticação da rede. Isso significa que tudo que você precisa para este codelab é um navegador (sim, funciona em um Chromebook).
- Para ativar o Cloud Shell no Console do Cloud, basta clicar em Ativar o Cloud Shell . Leva apenas alguns instantes para provisionar e se conectar ao ambiente.
Depois de se conectar ao Cloud Shell, você já estará autenticado e o projeto estará configurado com seu PROJECT_ID
.
gcloud auth list
Resposta ao comando
Credentialed accounts: - <myaccount>@<mydomain>.com (active)
gcloud config list project
Resposta ao comando
[core] project = <PROJECT_ID>
Se, por algum motivo, o projeto não estiver definido, basta emitir o seguinte comando:
gcloud config set project <PROJECT_ID>
Quer encontrar seu PROJECT_ID
? Veja qual ID você usou nas etapas de configuração ou procure-o no painel do Console do Cloud:
O Cloud Shell também define algumas variáveis de ambiente por padrão, o que pode ser útil ao executar comandos futuros.
echo $GOOGLE_CLOUD_PROJECT
Resposta ao comando
<PROJECT_ID>
- Defina a zona padrão e a configuração do projeto:
gcloud config set compute/zone us-central1-f
É possível escolher uma variedade de zonas diferentes. Para mais informações, consulte Regiões e zonas.
Resumo
Nesta etapa, você configurará seu ambiente.
A seguir
Agora configure uma instância do Cloud Spanner.
Nesta etapa, configuramos a instância do Cloud Spanner para este codelab. Pesquise a entrada do Spanner no menu de navegação superior esquerdo ou procure o Spanner pressionando "/" e digite "Spanner".
Depois, clique em e preencha o formulário inserindo o nome da instância cloudspanner-leaderboard da sua instância, escolhendo uma configuração (selecione uma instância regional) e defina o número de nós para este codelab só precisaremos de um nó. Para instâncias de produção e se qualificar para o SLA do Cloud Spanner, você precisará executar três ou mais nós na sua instância do Cloud Spanner.
Por fim, mas não menos importante, clique em "Criar" e, em segundos, uma instância do Cloud Spanner está à sua disposição.
Na próxima etapa, usaremos a biblioteca do cliente Go para criar um banco de dados e esquema em nossa nova instância.
Nesta etapa, criaremos nosso banco de dados e esquema de amostra.
Vamos usar a biblioteca de cliente Go para criar duas tabelas, uma tabela de jogadores para informações do jogador e uma tabela de pontuações para armazenar pontuações de jogadores. Para isso, mostraremos as etapas para criar um aplicativo de console Go no Cloud Shell.
Primeiro, clone o código de amostra do codelab digitando o seguinte comando no Cloud Shell:
go get -u github.com/GoogleCloudPlatform/golang-samples/spanner/...
Altere o diretório para o diretório "leaderboard" onde você criará seu aplicativo.
cd gopath/src/github.com/GoogleCloudPlatform/golang-samples/spanner/spanner_leaderboard
Todo o código necessário para este codelab está localizado no diretório golang-samples/spanner/spanner_leaderboard/
existente como um aplicativo Go executável como leaderboard
para servir como referência enquanto você avança no codelab. Criaremos um novo diretório e criaremos uma cópia do aplicativo Leaderboard em etapas.
Crie um novo diretório chamado "codelab" para o aplicativo e altere o diretório nele para ele usando o seguinte comando:
mkdir codelab && cd $_
Agora vamos atualizar um aplicativo básico do Go chamado "Cebçalho", que usa a biblioteca de cliente do Spanner para criar um placar de formação de duas tabelas, jogadores e pontuações. É possível fazer isso diretamente no editor do Cloud Shell:
Abra o editor do Cloud Shell clicando no ícone destacado abaixo:
Crie um arquivo chamado "leaderboard.go" na pasta ~/gopath/src/github.com/GoogleCloudPlatform/golang-samples/spanner/codelab.
- Primeiro, verifique se a pasta "codelab" está selecionada na lista de pastas do editor do Cloud Shell.
- Em seguida, selecione "Novo arquivo" no menu "Arquivo" do editor do Cloud Shell.
- Insira "leaderboard.go" como o nome do novo arquivo.
Este é o arquivo principal do aplicativo que conterá o nosso código e referências para incluir as dependências.
Para criar o banco de dados leaderboard
e as tabelas Players
e Scores
, copie (Ctrl + P) e cole (Ctrl + V) o seguinte código Go no arquivo 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)
}
}
Salve as alterações feitas no arquivo leaderboard.go
selecionando "Salvar" no menu "Arquivo" do editor do Cloud Shell.
Use o arquivo leaderboard.go
no diretório golang-samples/spanner/spanner_leaderboard
para ver um exemplo de como seu arquivo leaderboard.go
deve ficar depois de você adicionar o código para ativar o comando createdatabase
.
Para criar seu aplicativo no Cloud Shell, execute "go build" no diretório codelab
em que seu arquivo leaderboard.go
está localizado:
go build leaderboard.go
Após a criação do aplicativo, execute o seguinte comando no Cloud Shell:
./leaderboard
O resultado será assim:
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.
Nesta resposta, vemos que este é o aplicativo Leaderboard
que atualmente tem um comando possível: createdatabase
. O argumento esperado do comando createdatabase
é uma string que contém um ID de instância específico e um ID de banco de dados.
Agora, execute o seguinte comando. Verifique se você substitui my-project
pelo ID do projeto que você criou no início deste codelab.
./leaderboard createdatabase projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard
Após alguns segundos, será exibida uma resposta semelhante a esta:
Created database [projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard]
Na seção Cloud Spanner do Console do Cloud, você verá o novo banco de dados e as tabelas que aparecem no menu do lado esquerdo.
Na próxima etapa, atualizaremos o nosso aplicativo para carregar alguns dados no novo banco de dados.
Agora temos um banco de dados chamado leaderboard
contendo duas tabelas; Players
e Scores
. Agora, usaremos a biblioteca de cliente Go para preencher a tabela Players
com os jogadores e nossa tabela Scores
com pontuações aleatórias para cada jogador.
Se ele ainda não estiver aberto, clique no ícone destacado abaixo para abrir o editor do Cloud Shell:
Em seguida, edite o arquivo leaderboard.go
no editor do Cloud Shell para adicionar um comando insertplayers
que pode ser usado para inserir 100 jogadores na tabela Players
. Também adicionaremos um comando insertscores
que pode ser usado para inserir quatro pontuações aleatórias na tabela Scores
para cada jogador na tabela Players
.
Primeiro, atualize a seção imports
na parte superior do arquivo leaderboard.go
, substituindo o que está lá de modo que, assim que terminar, você tenha a seguinte aparência:
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"
)
Em seguida, adicione um novo tipo de comando com uma lista de comandos na parte superior do arquivo, logo abaixo da linha que começa com "type adminCommand ...". Assim, quando você terminar, o resultado será semelhante a este:
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,
}
)
Em seguida, adicione as seguintes funções insertPlayers e insertScores abaixo da função createdatabase()
:
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
}
Em seguida, para fazer o comando insert
funcionar, adicione o seguinte código à função "run" do seu aplicativo abaixo da instrução de manipulação createdatabase
, substituindo a instrução 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
Quando você terminar, a função run
será semelhante a esta:
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
}
A etapa final para adicionar a funcionalidade "inserir" ao seu aplicativo é adicionar o texto de ajuda aos comandos "insertplayers" e "insertscores" à função flag.Usage()
. Adicione o texto de ajuda a seguir à função flag.Usage()
para incluir o texto de ajuda para os comandos de inserção:
Adicione os dois comandos à lista de comandos possíveis:
Command can be one of: createdatabase, insertplayers, insertscores
E adicione este texto de ajuda abaixo do texto de ajuda para o 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.
Salve as alterações feitas no arquivo leaderboard.go
selecionando "Salvar" no menu "Arquivo" do editor do Cloud Shell.
Use o arquivo leaderboard.go
no diretório golang-samples/spanner/spanner_leaderboard
para ver um exemplo de como seu arquivo leaderboard.go
deve ficar depois de você adicionar o código para ativar os comandos insertplayers
e insertscores
.
Agora, vamos criar e executar o aplicativo para confirmar se os novos comandos insertplayers
e insertscores
estão incluídos na lista de comandos possíveis do aplicativo. Execute o seguinte comando para criar o aplicativo:
go build leaderboard.go
Execute o aplicativo resultante no Cloud Shell inserindo o seguinte comando:
./leaderboard
Você verá os comandos insertplayers
e insertscores
agora incluídos na saída padrão do aplicativo:
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.
Agora, vamos executar o comando insertplayers
com os mesmos valores de argumento que usamos quando chamamos o comando createdatabase
. Verifique se você substitui my-project
pelo ID do projeto que você criou no início deste codelab.
./leaderboard insertplayers projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard
Após alguns segundos, será exibida uma resposta semelhante a esta:
Inserted players
Agora, vamos usar a biblioteca de cliente Go para preencher nossa tabela Scores
com quatro pontuações aleatórias com carimbos de data/hora de cada jogador na tabela Players
.
A coluna Timestamp
da tabela Scores
foi definida como uma coluna de "carimbo de data/hora de confirmação" por meio da seguinte instrução SQL que foi executada quando executamos o comando create
anteriormente:
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
Observe o atributo OPTIONS(allow_commit_timestamp=true)
. Isso torna Timestamp
uma coluna de "timestamp de confirmação" e permite que ela seja preenchida automaticamente com o carimbo de data/hora exato da transação para operações INSERT e UPDATE em uma determinada linha da tabela.
Também é possível inserir seus próprios valores de carimbo de data/hora em uma coluna de "carimbo de data/hora de confirmação", desde que você insira um carimbo de data/hora com um valor que esteja no passado, que é o que vamos fazer para este fim de codelab.
Agora, vamos executar o comando insertscores
com os mesmos valores de argumento que usamos quando chamamos o comando insertplayers
. Verifique se você substitui my-project
pelo ID do projeto que você criou no início deste codelab.
./leaderboard insertscores projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard
Após alguns segundos, será exibida uma resposta semelhante a esta:
Inserted scores
A execução da função insertScores
usa o seguinte snippet de código para inserir um carimbo de data/hora gerado aleatoriamente com uma data/hora anterior ao passado:
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 preencher automaticamente a coluna Timestamp
com o carimbo de data/hora do momento exato em que a transação "Inserir" ocorre, insira a constante Go spanner.CommitTimestamp
como no snippet de código a seguir:
...
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,
},
})
Agora que concluímos o carregamento de dados, vamos verificar os valores que acabamos de gravar nas nossas novas tabelas na seção Cloud Spanner do Console do Cloud. Primeiro, selecione o banco de dados leaderboard
e depois a tabela Players
. Clique na guia Data
. Você verá dados nas colunas PlayerId
e PlayerName
da tabela.
Em seguida, para verificar se a tabela "Scores" também tem dados, clique na tabela Scores
e selecione a guia Data
. Você deve ter dados nas colunas PlayerId
, Timestamp
e Score
da tabela.
Parabéns! Vamos atualizar nosso app para executar algumas consultas que podemos usar para criar um placar de jogos.
Agora que configuramos o banco de dados e carregamos as informações nas tabelas, vamos criar um placar usando esses dados. Para fazer isso, precisamos responder a estas quatro perguntas:
- Quais jogadores são o "Top 10" de todos os tempos?
- Quais jogadores são os "Top 10" do ano?
- Quais jogadores são o "Top 10" do mês?
- Quais jogadores são os "Top 10" da semana?
Vamos atualizar nosso aplicativo para executar as consultas SQL que responderão a essas perguntas.
Adicionaremos os comandos query
e queryWithTimespan
que darão uma maneira de executar as consultas para responder às perguntas que produzirão as informações necessárias para nosso placar.
Edite o arquivo leaderboard.go
no editor do Cloud Shell para atualizar o aplicativo e adicionar um comando query
e um comando queryWithTimespan
. Também adicionaremos uma função auxiliar formatWithCommas
para formatar nossas pontuações com vírgulas.
Primeiro, atualize a seção imports
na parte superior do arquivo leaderboard.go
, substituindo o que está lá de modo que, assim que terminar, você tenha a seguinte aparência:
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"
)
Em seguida, adicione as duas funções a seguir e a função auxiliar abaixo do método insertScores
atual:
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()
}
Na parte superior do arquivo leaderboard.go
, adicione "consulta" como uma opção de comando na variável commands
, logo abaixo da opção "insertscores": insertScores
" para que a variável commands
fique assim:
var (
commands = map[string]command{
"insertplayers": insertPlayers,
"insertscores": insertScores,
"query": query,
}
)
Adicione "queryWithTimespan" como uma opção de comando na função run
, abaixo da seção de comando "createdatabase" e acima da seção de manipulação de comandos "insert and 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
}
Quando você terminar, a função run
será semelhante a esta:
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
}
Em seguida, para tornar o comando queryWithTimespan
funcional, atualize o bloco de código flag.Parse() no método "main" do aplicativo para que ele fique assim:
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)
}
A etapa final para adicionar a funcionalidade "query" ao seu aplicativo é adicionar o texto de ajuda aos comandos "query" e "querywithtimespan" à função flag.Usage()
. Adicione as linhas de código a seguir à função flag.Usage()
para incluir o texto de ajuda para os comandos de consulta:
Adicione os dois comandos "query" à lista de comandos possíveis:
Command can be one of: createdatabase, insertplayers, insertscores, query, querywithtimespan
E adicione este texto de ajuda abaixo do texto de ajuda para o 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.
Salve as alterações feitas no arquivo leaderboard.go
selecionando "Salvar" no menu "Arquivo" do editor do Cloud Shell.
Use o arquivo leaderboard.go
no diretório golang-samples/spanner/spanner_leaderboard
para ver um exemplo de como seu arquivo leaderboard.go
deve ficar depois de você adicionar o código para ativar os comandos query
e querywithtimespan
.
Agora, vamos criar e executar o aplicativo para confirmar se os novos comandos query
e querywithtimespan
estão incluídos na lista de comandos possíveis do aplicativo.
Execute o seguinte comando no Cloud Shell para criar o aplicativo:
go build leaderboard.go
Execute o aplicativo resultante no Cloud Shell inserindo o seguinte comando:
./leaderboard
Você verá os comandos query
e querywithtimespan
agora incluídos na saída padrão do app como uma nova opção 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.
Você pode ver na resposta que é possível usar o comando query
para ver uma lista dos nossos "Top 10 jogadores" de todos os tempos. Também é possível ver que o comando querywithtimespan
nos permite especificar um período em número de horas a ser usado para filtrar registros com base no valor deles na coluna Timestamp
da tabela Scores
.
Execute o comando query
usando os mesmos valores de argumento que usamos quando executamos o comando create
Verifique se você substitui my-project
pelo ID do projeto que você criou no início deste codelab.
./leaderboard query projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard
Você deverá ver uma resposta que inclui os players "Top 10" de todos os tempos, como a seguinte:
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
Agora, vamos executar o comando querywithtimespan
com os argumentos necessários para consultar os "Top 10" do ano especificando um "timespan" igual ao número de horas em um ano que é 8760. Verifique se você substitui my-project
pelo ID do projeto que você criou no início deste codelab.
./leaderboard querywithtimespan projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard 8760
Você deverá ver uma resposta que inclui os "Top 10" do ano, como a seguir:
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
Agora, vamos executar o comando querywithtimespan
para consultar os players "Top 10" do mês, especificando um "timespan" igual ao número de horas em um mês que é 730. Verifique se você substitui my-project
pelo ID do projeto que você criou no início deste codelab.
./leaderboard querywithtimespan projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard 730
Você verá uma resposta que inclui os "Top 10" do mês, como a seguinte:
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
Agora execute o comando querywithtimespan
para consultar os "Top 10" da semana, especificando um "timespan" igual ao número de horas em uma semana de 168. Verifique se você substitui my-project
pelo ID do projeto que você criou no início deste codelab.
./leaderboard querywithtimespan projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard 168
Você verá uma resposta que inclui os jogadores "Top 10" da semana, como a seguinte:
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
Ótimo trabalho!
Agora, conforme você adicionar registros, o Cloud Spanner fará o escalonamento do seu banco de dados para o tamanho que for necessário. Não importa o quanto seu banco de dados aumente, o placar do jogo pode continuar a ser dimensionado com a precisão do Cloud Spanner e da tecnologia Truetime.
Depois de se divertir com o Spanner, precisamos limpar nosso playground, economizando recursos preciosos e dinheiro. Felizmente, esta é uma etapa fácil, basta acessar a seção Cloud Spanner do Console do Cloud e excluir a instância que criamos na etapa do codelab chamada "Setup a Cloud Spanner Instance".
O que vimos:
- Instâncias, bancos de dados e esquema de tabelas do Google Cloud Spanner para um placar
- Como criar o aplicativo Go Console
- Como criar um banco de dados do Spanner e tabelas usando a biblioteca de cliente do Go
- Como carregar dados em um banco de dados do Spanner usando a biblioteca de cliente Go
- Como consultar os "Top 10" dos seus dados usando carimbos de data/hora de confirmação do Spanner e a biblioteca de cliente Go
Próximas etapas:
- Leia o whitepaper sobre CAPs do Spanner
- Saiba mais sobre o Design de esquema e as práticas recomendadas de consulta.
- Saiba mais sobre os carimbos de data/hora de confirmação do Cloud Spanner
Envie um feedback
- Reserve um momento para completar nossa pesquisa curta