Google Cloud Spanner est un service de base de données relationnelle entièrement géré, distribué à l'échelle mondiale et évolutif horizontalement qui fournit des transactions ACID et une sémantique SQL sans compromis sur les performances et le niveau de disponibilité.
Dans cet atelier, vous allez apprendre à configurer une instance Cloud Spanner. Au fil des étapes, vous allez créer une base de données et un schéma qui peuvent être utilisés pour un classement de jeu. Vous allez commencer par créer une table "Players" (Joueurs) pour stocker les informations concernant les joueurs et un tableau "Scores" pour stocker les scores des joueurs.
Vous remplirez ensuite ces tables avec des exemples de données. Vous finirez l'atelier en exécutant quelques exemples de requêtes de "Top 10" avant de supprimer l'instance pour libérer les ressources.
Points abordés
- Comment configurer une instance Cloud Spanner.
- Comment créer une base de données et des tables.
- Comment utiliser une colonne d'horodatage de commit.
- Comment charger des données dans une table de base de données Cloud Spanner avec des horodatages.
- Comment interroger votre base de données Cloud Spanner.
- Comment supprimer une instance Cloud Spanner.
Prérequis
Comment allez-vous utiliser ce tutoriel ?
Quel est votre niveau d'expérience avec Google Cloud Platform ?
Configuration de l'environnement au rythme de chacun
Si vous ne possédez pas encore de compte Google (Gmail ou Google Apps), vous devez en créer un. Connectez-vous à la console Google Cloud Platform (console.cloud.google.com) et créez un projet.
Si vous avez déjà un projet, cliquez sur le menu déroulant de sélection du projet dans l'angle supérieur gauche de la console :
Cliquez ensuite sur le bouton "NEW PROJECT" (NOUVEAU PROJET) dans la boîte de dialogue qui s'affiche pour créer un projet :
Si vous n'avez pas encore de projet, une boîte de dialogue semblable à celle-ci apparaîtra pour vous permettre d'en créer un :
La boîte de dialogue de création de projet suivante vous permet de saisir les détails de votre nouveau projet :
Notez l'ID du projet. Il s'agit d'un nom unique pour tous les projets Google Cloud, ce qui implique que le nom ci-dessus n'est plus disponible pour vous… Désolé ! Tout au long de cet atelier de programmation, nous utiliserons PROJECT_ID
pour faire référence à cet ID.
Ensuite, si ce n'est pas déjà fait, vous devez activer la facturation dans Developers Console afin de pouvoir utiliser les ressources Google Cloud puis activer l'API Cloud Spanner.
Suivre cet atelier de programmation ne devrait pas vous coûter plus d'un euro. Cependant, cela peut s'avérer plus coûteux si vous décidez d'utiliser davantage de ressources ou si vous n'interrompez pas les ressources (voir la section "Effectuer un nettoyage" à la fin du présent document). Les tarifs de Google Cloud Spanner sont décrits sur cette page.
Les nouveaux utilisateurs de Google Cloud Platform peuvent bénéficier d'un Essai gratuit avec 300 $ de crédits afin de suivre gratuitement le présent atelier.
Configuration de Google Cloud Shell
Bien que Google Cloud et Spanner puissent être utilisés à distance depuis votre ordinateur portable, nous allons utiliser Google Cloud Shell pour cet atelier de programmation, un environnement de ligne de commande exécuté dans le cloud.
Cette machine virtuelle basée sur Debian contient tous les outils de développement dont vous aurez besoin. Elle intègre un répertoire d'accueil persistant de 5 Go et s'exécute sur Google Cloud, ce qui améliore nettement les performances du réseau et l'authentification. Cela signifie que tout ce dont vous avez besoin pour cet atelier de programmation est un navigateur (oui, tout fonctionne sur un Chromebook).
- Pour activer Cloud Shell à partir de Cloud Console, cliquez simplement sur Activer Cloud Shell (l'opération de provisionnement et la connexion à l'environnement ne devraient prendre que quelques minutes).
Une fois connecté à Cloud Shell, vous êtes normalement déjà authentifié et le projet PROJECT_ID
est sélectionné :
gcloud auth list
Résultat de la commande
Credentialed accounts: - <myaccount>@<mydomain>.com (active)
gcloud config list project
Résultat de la commande
[core] project = <PROJECT_ID>
Si, pour une raison quelconque, le projet n'est pas défini, exécutez simplement la commande suivante :
gcloud config set project <PROJECT_ID>
Vous recherchez votre PROJECT_ID
? Vérifiez l'ID que vous avez utilisé pendant les étapes de configuration ou recherchez-le dans le tableau de bord Cloud Console :
Par défaut, Cloud Shell définit certaines variables d'environnement qui pourront s'avérer utiles pour exécuter certaines commandes dans le futur.
echo $GOOGLE_CLOUD_PROJECT
Résultat de la commande
<PROJECT_ID>
- Pour finir, définissez la configuration du projet et de la zone par défaut :
gcloud config set compute/zone us-central1-f
Vous pouvez choisir parmi différentes zones. Pour en savoir plus, consultez la page Régions et zones.
Résumé
Cette étape consiste à configurer votre environnement.
Étapes suivantes
Vous allez maintenant configurer une instance Cloud Spanner.
Dans cette étape, nous allons configurer notre instance Cloud Spanner pour cet atelier de programmation. Recherchez l'entrée Spanner dans le menu principal en haut à gauche ou recherchez Spanner en appuyant sur "/" avant de saisir "Spanner".
Cliquez ensuite sur et remplissez le formulaire en renseignant le nom d'instance cloudspanner-leaderboard, en choisissant une configuration (sélectionnez une instance régionale), puis en définissant le nombre de nœuds (pour le présent atelier, nous n'aurons besoin que d'un seul nœud). Pour les instances en production et pour bénéficier du contrat de niveau de service de Cloud Spanner, vous devez exécuter au moins trois nœuds dans votre instance Cloud Spanner.
Dernière étape mais pas des moindres, cliquez sur "Créer". L'instance Cloud Spanner sera prête en quelques secondes.
Pour l'étape suivante, nous allons utiliser la bibliothèque cliente Go afin de créer une base de données et un schéma dans notre nouvelle instance.
Au cours de cette étape, nous allons créer notre exemple de base de données et notre schéma.
Utilisons la bibliothèque cliente Go pour créer deux tables : une table "Players" (Joueurs) pour stocker les informations concernant les joueurs et une table "Scores" pour stocker les scores des joueurs. Pour ce faire, nous allons vous indiquer comment créer une application de console Go dans Cloud Shell.
Commencez par cloner l'exemple de code du présent atelier de programmation à partir de GitHub en saisissant la commande suivante dans Cloud Shell :
go get -u github.com/GoogleCloudPlatform/golang-samples/spanner/...
Accédez ensuite au répertoire "leaderboard" dans lequel vous allez créer votre application.
cd gopath/src/github.com/GoogleCloudPlatform/golang-samples/spanner/spanner_leaderboard
L'ensemble du code requis pour cet atelier de programmation se trouve dans le répertoire golang-samples/spanner/spanner_leaderboard/
existant en tant qu'application Go exécutable leaderboard
, pour vous servir de référence tout au long de cet atelier. Nous allons créer un répertoire et créer une copie de l'application de classement étape par étape.
Créez un répertoire nommé "codelab" pour l'application et accédez-y avec la commande suivante :
mkdir codelab && cd $_
À présent, nous allons créer une application Go de base nommée "Leaderboard" qui utilise la bibliothèque cliente Spanner pour créer un classement composé des deux tables "Players" (Joueurs) et "Scores". Vous pouvez faire tout cela directement depuis l'éditeur Cloud Shell :
Ouvrez l'éditeur Cloud Shell en cliquant sur l'icône en surbrillance ci-dessous :
Créez un fichier nommé "leaderboard.go" dans le dossier ~/gopath/src/github.com/GoogleCloudPlatform/golang-samples/spanner/codelab.
- Vérifiez d'abord que le dossier "codelab" est bien sélectionné dans la liste de dossiers de l'éditeur Cloud Shell.
- Sélectionnez ensuite "New File" (Nouveau fichier) dans le menu "File " (Fichier) de l'éditeur Cloud Shell.
- Nommez le nouveau fichier "leaderboard.go".
Il s'agit du fichier principal de l'application qui contiendra le code de l'application ainsi que les références permettant d'inclure des dépendances.
Pour créer la base de données leaderboard
et les tables Players
et Scores
, copiez (Ctrl+P) puis collez (Ctrl+V) le code Go suivant dans le fichier 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)
}
}
Enregistrez les modifications apportées au fichier leaderboard.go
en sélectionnant "Enregistrer" dans le menu "Fichier" de l'éditeur Cloud Shell.
Vous pouvez utiliser le fichier leaderboard.go
du répertoire golang-samples/spanner/spanner_leaderboard
comme référence de ce à quoi doit ressembler votre fichier leaderboard.go
après l'ajout du code pour activer la commande createdatabase
.
Pour créer votre application dans Cloud Shell, exécutez la commande "go build" à partir du répertoire codelab
où se trouve votre fichier leaderboard.go
:
go build leaderboard.go
Une fois votre application créée, exécutez-la dans Cloud Shell à l'aide de la commande suivante :
./leaderboard
Vous devriez voir une sortie semblable à ce qui suit.
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.
À partir de cette réponse, nous pouvons voir qu'il s'agit bien de l'application Leaderboard
qui possède actuellement une seule commande possible : createdatabase
. Nous constatons que l'argument attendu de la commande createdatabase
est une chaîne contenant un ID d'instance et un ID de base de données spécifiques.
Exécutez maintenant la commande suivante : Assurez-vous de bien remplacer my-project
par l'ID de projet que vous avez créé au début du présent atelier de programmation.
./leaderboard createdatabase projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard
Après quelques secondes, une réponse de ce type doit s'afficher :
Created database [projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard]
Dans la section Cloud Spanner de Cloud Console, vous devriez voir votre nouvelle base de données et vos tables dans le menu de gauche.
À l'étape suivante, nous mettrons à jour notre application afin de charger des données dans votre nouvelle base de données.
Nous disposons à présent d'une base de données appelée leaderboard
et contenant deux tables, Players
et Scores
. Utilisons maintenant la bibliothèque cliente Go pour remplir la table Players
avec des joueurs et la table Scores
avec des scores aléatoires pour chaque joueur.
S'il n'est pas déjà ouvert, ouvrez l'éditeur Cloud Shell en cliquant sur l'icône en surbrillance ci-dessous :
Ensuite, modifiez le fichier leaderboard.go
dans l'éditeur Cloud Shell afin d'ajouter une commande insertplayers
permettant d'insérer 100 joueurs dans le tableau Players
. Nous allons également ajouter une commande insertscores
permettant d'insérer quatre résultats aléatoires dans la table Scores
pour chaque joueur de la table Players
.
Commencez par mettre à jour la section imports
en haut du fichier leaderboard.go
en la remplaçant de sorte à obtenir le résultat ci-dessous :
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"
)
Ajoutez ensuite un nouveau type de commande ainsi qu'une liste de commandes en haut du fichier, juste en dessous de la ligne qui commence par "type adminCommand ...". Une fois cette opération terminée, vous devriez obtenir un résultat semblable à ceci :
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,
}
)
Ajoutez ensuite les fonctions insertPlayers et insertScores suivantes sous la fonction createdatabase()
existante :
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
}
Pour que la commande insert
fonctionne, ajoutez le code suivant à la fonction "run" de votre application sous l'instruction de traitement createdatabase
, en remplaçant l'instruction 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
Une fois l'opération terminée, la fonction run
doit ressembler à ceci :
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
}
Pour finaliser l'ajout de la fonctionnalité "insert" à votre application, la dernière étape consiste à ajouter un texte d'aide pour les commandes "insertplayers" et "insertscores" dans la fonction flag.Usage()
. Ajoutez le texte d'aide suivant à la fonction flag.Usage()
afin d'inclure le texte d'aide pour les commandes d'insertion :
Ajoutez les deux commandes à la liste des commandes possibles :
Command can be one of: createdatabase, insertplayers, insertscores
Ajoutez ce texte d'aide supplémentaire sous le texte d'aide pour la commande 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.
Enregistrez les modifications apportées au fichier leaderboard.go
en sélectionnant "Enregistrer" dans le menu "Fichier" de l'éditeur Cloud Shell.
Vous pouvez utiliser le fichier leaderboard.go
dans le répertoire golang-samples/spanner/spanner_leaderboard
comme référence de ce à quoi doit ressembler votre fichier leaderboard.go
après avoir ajouté le code pour activer les commandes insertplayers
et insertscores
.
Maintenant, nous allons créer et exécuter l'application pour vérifier que les nouvelles commandes insertplayers
et insertscores
sont bien incluses dans la liste des commandes possibles de l'application. Exécutez la commande suivante pour créer l'application :
go build leaderboard.go
Exécutez l'application créée dans Cloud Shell en saisissant la commande suivante :
./leaderboard
Les commandes insertplayers
et insertscores
devraient maintenant être incluses dans la sortie par défaut de l'application :
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.
Nous allons maintenant exécuter la commande insertplayers
avec les mêmes valeurs d'argument que celles utilisées lors de l'appel de la commande createdatabase
. Assurez-vous de bien remplacer my-project
par l'ID de projet que vous avez créé au début du présent atelier de programmation.
./leaderboard insertplayers projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard
Après quelques secondes, une réponse de ce type doit s'afficher :
Inserted players
Utilisons maintenant la bibliothèque cliente Go pour remplir notre table Scores
avec quatre scores et horodatages aléatoires pour chaque joueur dans la table Players
.
La colonne Timestamp
de la table Scores
a été définie comme colonne d'horodatage de commit ("commit timestamp") via l'instruction SQL suivante, exécutée lors de l'exécution de la commande 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
Notez l'attribut OPTIONS(allow_commit_timestamp=true)
. Cela fait de Timestamp
une colonne d'horodatage de commit qu'il est possible de renseigner avec l'horodatage de transaction exact des opérations INSERT et UPDATE sur une ligne de table donnée.
Vous pouvez également insérer vos propres valeurs d'horodatage dans une colonne "commit timestamp" à condition d'utiliser un horodatage situé dans le passé, ce qui est exactement ce que nous avons fait dans le présent atelier de programmation.
Nous allons maintenant exécuter la commande insertscores
avec les mêmes valeurs d'argument que celles utilisées lors de l'appel de la commande insertplayers
. Assurez-vous de bien remplacer my-project
par l'ID de projet que vous avez créé au début du présent atelier de programmation.
./leaderboard insertscores projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard
Après quelques secondes, une réponse de ce type doit s'afficher :
Inserted scores
L'exécution de la fonction insertScores
utilise l'extrait de code suivant pour insérer un horodatage généré de manière aléatoire avec une date et une heure dans le passé :
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,
},
})
Pour automatiquement remplir la colonne Timestamp
avec l'horodatage correspondant à l'heure exacte de la transaction "Insert", vous pouvez insérer la constante Go spanner.CommitTimestamp
comme dans l'extrait de code suivant :
...
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,
},
})
Maintenant que le chargement des données est terminé, vérifions les valeurs que nous venons d'écrire dans les nouvelles tables en consultant la section Cloud Spanner de Cloud Console. Sélectionnez d'abord la base de données leaderboard
, puis la table Players
. Cliquez sur l'onglet Data
. Vous devriez voir des données dans les colonnes PlayerId
et PlayerName
de la table.
Vérifions maintenant le tableau des scores contient bien des données en cliquant sur le tableau Scores
et en sélectionnant l'onglet Data
. Vous devriez voir des données dans les colonnes PlayerId
, Timestamp
et Score
de la table.
Bravo ! Mettons à jour notre application pour exécuter des requêtes qui nous permettront de créer un classement de jeu.
Maintenant que nous avons configuré notre base de données et chargé des informations dans nos tables, nous allons créer un classement à l'aide de ces données. Pour ce faire, nous devons répondre aux quatre questions suivantes :
- Quels joueurs sont les "Top 10" joueurs de tous les temps ?
- Quels joueurs sont les "Top 10" joueurs de l'année ?
- Quels joueurs sont les "Top 10" joueurs du mois ?
- Quels joueurs sont les "Top 10" joueurs de la semaine ?
Mettons à jour notre application pour exécuter les requêtes SQL qui répondent à ces questions.
Nous allons ajouter une commande query
et une commande queryWithTimespan
afin d'exécuter les requêtes permettant de répondre à ces questions en générant les informations requises pour notre classement.
Modifiez le fichier leaderboard.go
dans l'éditeur Cloud Shell afin de mettre à jour l'application et d'ajouter des commandes query
et queryWithTimespan
. Nous allons également ajouter une fonction d'assistance formatWithCommas
pour mettre en forme nos scores avec des virgules.
Commencez par mettre à jour la section imports
en haut du fichier leaderboard.go
en la remplaçant de sorte à obtenir le résultat ci-dessous :
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"
)
Ajoutez ensuite les deux fonctions et la fonction d'assistance suivantes, sous la méthode insertScores
existante :
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()
}
Dans la partie supérieure du fichier leaderboard.go
, ajoutez "query" comme option de commande dans la variable commands
, juste en dessous de l'option "insertscores": insertScores
", afin que la variable commands
ressemble à ceci :
var (
commands = map[string]command{
"insertplayers": insertPlayers,
"insertscores": insertScores,
"query": query,
}
)
Ajoutez ensuite "queryWithTimespan" comme option de commande dans la fonction run
, sous la section de la commande "createdatabase" et au-dessus de la section de gestion des commandes "insert" et "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
}
Une fois l'opération terminée, la fonction run
doit ressembler à ceci :
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
}
Pour que la commande queryWithTimespan
fonctionne, mettez à jour le bloc de code flag.Parse() dans la méthode "main" de votre application de sorte qu'elle se présente comme suit :
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)
}
La dernière étape pour finaliser l'ajout de la fonctionnalité "query" à votre application consiste à ajouter du texte d'aide pour les commandes "query" et "querywithtimespan" dans la fonction flag.Usage()
. Ajoutez les lignes de code suivantes à la fonction flag.Usage()
afin d'inclure du texte d'aide pour les commandes "query" :
Ajoutez les deux commandes "query" à la liste des commandes possibles :
Command can be one of: createdatabase, insertplayers, insertscores, query, querywithtimespan
Ajoutez ce texte d'aide supplémentaire sous le texte d'aide pour la commande 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.
Enregistrez les modifications apportées au fichier leaderboard.go
en sélectionnant "Enregistrer" dans le menu "Fichier" de l'éditeur Cloud Shell.
Vous pouvez utiliser le fichier leaderboard.go
dans le répertoire golang-samples/spanner/spanner_leaderboard
comme référence de ce à quoi doit ressembler votre fichier leaderboard.go
après avoir ajouté le code pour activer les commandes query
et querywithtimespan
.
Vous allez maintenant créer et exécuter l'application pour confirmer que les nouvelles commandes query
et querywithtimespan
sont bien incluses dans la liste des commandes possibles.
Exécutez la commande suivante dans Cloud Shell pour créer l'application :
go build leaderboard.go
Exécutez l'application créée dans Cloud Shell en saisissant la commande suivante :
./leaderboard
Vous devriez à présent voir s'afficher les commandes query
et querywithtimespan
en tant que nouvelles options de commande dans la sortie par défaut de l'application :
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.
Vous pouvez voir dans la réponse que nous pouvons utiliser la commande query
pour obtenir la liste des joueurs du "Top 10" sur l'intégralité des données disponibles. Nous pouvons également voir que la commande querywithtimespan
nous permet de spécifier une période en nombre d'heures à utiliser pour filtrer les enregistrements en fonction de leur valeur dans la colonne Timestamp
de la table Scores
.
Nous allons exécuter la commande query
en utilisant les mêmes valeurs d'argument que celles utilisées lors de l'exécution de la commande create
. Assurez-vous de bien remplacer my-project
par l'ID de projet que vous avez créé au début du présent atelier de programmation.
./leaderboard query projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard
Vous devriez voir une réponse similaire à celle ci-dessous et incluant les joueurs du "Top 10" :
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
Exécutez maintenant la commande querywithtimespan
avec les arguments nécessaires pour obtenir les joueurs du "Top 10" de l'année en spécifiant une valeur "timespan" égale au nombre d'heures dans une année (8760). Assurez-vous de bien remplacer my-project
par l'ID de projet que vous avez créé au début du présent atelier de programmation.
./leaderboard querywithtimespan projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard 8760
Vous devriez voir une réponse similaire à celle ci-dessous et incluant les joueurs du "Top 10" de l'année :
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
À présent, vous allez exécuter la commande querywithtimespan
pour obtenir les joueurs du "Top 10 " du mois en spécifiant une valeur "timespan" égale au nombre d'heures dans un mois (730). Assurez-vous de bien remplacer my-project
par l'ID de projet que vous avez créé au début du présent atelier de programmation.
./leaderboard querywithtimespan projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard 730
Vous devriez voir une réponse similaire à celle ci-dessous et incluant les joueurs du "Top 10" du mois :
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
À présent, exécutez la commande querywithtimespan
pour obtenir les joueurs du "Top 10 " de la semaine en spécifiant une valeur "timespan" égale au nombre d'heures dans une semaine (168). Assurez-vous de bien remplacer my-project
par l'ID de projet que vous avez créé au début du présent atelier de programmation.
./leaderboard querywithtimespan projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard 168
Vous devriez voir une réponse similaire à celle ci-dessous et incluant les joueurs du "Top 10" de la semaine :
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
Beau travail !
À mesure que vous ajoutez des enregistrements, Cloud Spanner fait évoluer votre base de données en fonction de vos besoins. Quel que soit le volume de votre base de données, le classement de votre jeu peut continuer d'évoluer avec précision grâce à Cloud Spanner et sa technologie Truetime.
Après s'être amusé avec Spanner, il faut maintenant nettoyer notre aire de jeux pour ne gaspiller de préciseuses ressources et donc d'argent. Heureusement, il s'agit d'une étape très facile puisqu'il vous suffit d'accéder à la section Cloud Spanner de Cloud Console et de supprimer l'instance que nous avons créée à l'étape "Configurer une instance Cloud Spanner".
Points abordés :
- Instances, bases de données et schéma de table Google Cloud Spanner pour un classement.
- Comment créer une application de console Go.
- Comment créer une base de données et des tables Spanner à l'aide de la bibliothèque cliente Go.
- Comment charger des données dans une base de données Spanner à l'aide de la bibliothèque cliente Go.
- Comment interroger les résultats du "Top 10" à partir de vos données à l'aide des horodatages de commit Spanner et de la bibliothèque cliente Go.
Prochaines étapes :
- Lisez le Livre blanc de Spanner CAP
- Découvrez la Conception de schémas et les bonnes pratiques relatives aux requêtes.
- Découvrez plus en détail les horodatages de commit de Cloud Spanner
Votre avis nous intéresse !
- Veuillez prendre quelques minutes pour répondre à notre courte enquête.