Cloud Spanner: Membuat papan peringkat game dengan Go

Google Cloud Spanner adalah layanan database relasional skalabel dan terkelola sepenuhnya secara global yang menyediakan transaksi ACID dan semantik SQL tanpa merusak performa dan ketersediaan yang tinggi.

Di lab ini, Anda akan mempelajari cara menyiapkan instance Cloud Spanner. Anda akan menjalani langkah-langkah untuk membuat database dan skema yang dapat digunakan untuk papan peringkat game. Anda akan mulai dengan membuat tabel Pemain untuk menyimpan informasi pemain dan tabel Skor untuk menyimpan skor pemain.

Selanjutnya, Anda akan mengisi tabel dengan data contoh. Kemudian Anda akan mengakhiri lab dengan menjalankan kueri contoh Sepuluh Teratas dan menghapus instance untuk mengosongkan resource.

Yang akan Anda pelajari

  • Cara menyiapkan instance Cloud Spanner.
  • Cara membuat database dan tabel.
  • Cara menggunakan kolom stempel waktu commit.
  • Cara memuat data ke tabel database Cloud Spanner dengan stempel waktu.
  • Cara membuat kueri database Cloud Spanner.
  • Cara menghapus instance Cloud Spanner.

Yang akan Anda butuhkan

Bagaimana Anda akan menggunakan tutorial ini?

Hanya membacanya Membacanya dan menyelesaikan latihan

Bagaimana penilaian Anda terhadap pengalaman dengan Google Cloud Platform?

Pemula Menengah Mahir

Penyiapan lingkungan mandiri

Jika belum memiliki Akun Google (Gmail atau Google Apps), Anda harus membuatnya. Login ke Google Cloud Platform console (console.cloud.google.com) dan buat project baru.

Jika Anda sudah memiliki project, klik menu pull-down pilihan project di kiri atas konsol:

6c9406d9b014760.png

dan klik tombol 'PROJECT BARU' dalam dialog yang dihasilkan untuk membuat project baru:

f708315ae07353d0.png

Jika belum memiliki project, Anda akan melihat dialog seperti ini untuk membuat project pertama:

870a3cbd6541ee86.png

Dialog pembuatan project berikutnya memungkinkan Anda memasukkan detail project baru:

6a92c57d3250a4b3.png

Ingat project ID yang merupakan nama unik di semua project Google Cloud (maaf, nama di atas telah digunakan dan tidak akan berfungsi untuk Anda!) Project ID tersebut selanjutnya akan dirujuk di codelab ini sebagai PROJECT_ID.

Selanjutnya, jika Anda belum melakukannya, Anda harus mengaktifkan penagihan di Developers Console untuk menggunakan resource Google Cloud dan mengaktifkan Cloud Spanner API.

15d0ef27a8fbab27.png

Menjalankan melalui codelab ini tidak akan menghabiskan biaya lebih dari beberapa dolar, tetapi bisa lebih jika Anda memutuskan untuk menggunakan lebih banyak resource atau jika Anda membiarkannya berjalan (lihat bagian "pembersihan" di akhir dokumen ini). Harga Google Cloud Spanner didokumentasikan di sini.

Pengguna baru Google Cloud Platform memenuhi syarat untuk mendapatkan uji coba gratis senilai $300, yang menjadikan codelab ini sepenuhnya gratis.

Penyiapan Google Cloud Shell

Meskipun Google Cloud dan Spanner dapat dioperasikan dari jarak jauh menggunakan laptop Anda, dalam codelab ini, kita akan menggunakan Google Cloud Shell, lingkungan command line yang berjalan di Cloud.

Mesin virtual berbasis Debian ini memuat semua alat pengembangan yang akan Anda perlukan. Layanan ini menawarkan direktori beranda tetap sebesar 5 GB dan beroperasi di Google Cloud, sehingga sangat meningkatkan performa dan autentikasi jaringan. Ini berarti bahwa semua yang Anda perlukan untuk codelab ini adalah browser (ya, ini berfungsi di Chromebook).

  1. Untuk mengaktifkan Cloud Shell dari Cloud Console, cukup klik Aktifkan Cloud Shell gcLMt5IuEcJJNnMId-Bcz3sxCd0rZn7IzT_r95C8UZeqML68Y1efBG_B0VRp7hc7qiZTLAF-TXD7SsOadxn8uadgHhaLeASnVS3ZHK39eOlKJOgj9SJua_oeGhMxRrbOg3qigddS2A (hanya perlu beberapa saat untuk melakukan provisioning dan terhubung ke lingkungan).

JjEuRXGg0AYYIY6QZ8d-66gx_Mtc-_jDE9ijmbXLJSAXFvJt-qUpNtsBsYjNpv2W6BQSrDc1D-ARINNQ-1EkwUhz-iUK-FUCZhJ-NtjvIEx9pIkE-246DomWuCfiGHK78DgoeWkHRw

Screen Shot 2017-06-14 at 10.13.43 PM.png

Setelah terhubung ke Cloud Shell, Anda akan melihat bahwa Anda sudah diautentikasi dan project sudah ditetapkan ke PROJECT_ID.

gcloud auth list

Output perintah

Credentialed accounts:
 - <myaccount>@<mydomain>.com (active)
gcloud config list project

Output perintah

[core]
project = <PROJECT_ID>

Jika, untuk beberapa alasan, project belum disetel, cukup jalankan perintah berikut:

gcloud config set project <PROJECT_ID>

Mencari PROJECT_ID Anda? Periksa ID yang Anda gunakan di langkah-langkah penyiapan atau cari di dasbor Cloud Console:

158fNPfwSxsFqz9YbtJVZes8viTS3d1bV4CVhij3XPxuzVFOtTObnwsphlm6lYGmgdMFwBJtc-FaLrZU7XHAg_ZYoCrgombMRR3h-eolLPcvO351c5iBv506B3ZwghZoiRg6cz23Qw

Cloud Shell juga menetapkan beberapa variabel lingkungan secara default, yang mungkin berguna saat Anda menjalankan perintah di masa mendatang.

echo $GOOGLE_CLOUD_PROJECT

Output perintah

<PROJECT_ID>
  1. Terakhir, tetapkan zona dan konfigurasi project default.
gcloud config set compute/zone us-central1-f

Anda dapat memilih berbagai zona yang berbeda. Untuk informasi selengkapnya, lihat Region & Zona.

Ringkasan

Pada langkah ini, Anda menyiapkan lingkungan.

Berikutnya

Selanjutnya, Anda akan menyiapkan Instance Cloud Spanner.

Pada langkah ini kita menyiapkan Instance Cloud Spanner untuk codelab ini. Telusuri entri Spanner 1a6580bd3d3e6783.png di kiri atas Menu Tiga Garis 3129589f7bc9e5ce.png atau telusuri Spanner dengan menekan "/" dan ketik "Spanner"

36e52f8df8e13b99.png

Selanjutnya, klik 95269e75bc8c3e4d.png dan isi formulir dengan memasukkan nama instance cloudspanner-leaderboard untuk instance Anda, memilih konfigurasi (pilih instance regional), dan menetapkan jumlah node, untuk codelab ini kita hanya perlu 1 node. Agar instance produksi dan untuk memenuhi syarat untuk SLA Cloud Spanner, Anda harus menjalankan 3 node atau lebih di instance Cloud Spanner.

Terakhir, namun tidak kalah penting, klik "Buat" dan dalam beberapa detik Anda sudah memiliki instance Cloud Spanner.

dceb68e9ed3801e8.png

Pada langkah berikutnya, kita akan menggunakan library klien Go untuk membuat database dan skema dalam instance baru.

Pada langkah ini, kita akan membuat contoh database dan skema.

Mari menggunakan library klien Go untuk membuat dua tabel; tabel Pemain untuk info pemain dan tabel Skor untuk menyimpan skor pemain. Untuk melakukannya, kita akan memandu langkah-langkah pembuatan aplikasi konsol Go di Cloud Shell.

Pertama-tama, clone kode sampel untuk codelab ini dari GitHub dengan mengetik perintah berikut di Cloud Shell:

go get -u github.com/GoogleCloudPlatform/golang-samples/spanner/...

Kemudian, ubah direktori ke direktori "papan peringkat" tempat Anda akan membuat aplikasi.

cd gopath/src/github.com/GoogleCloudPlatform/golang-samples/spanner/spanner_leaderboard

Semua kode yang diperlukan untuk codelab ini terletak di direktori golang-samples/spanner/spanner_leaderboard/ yang ada sebagai aplikasi Go yang dapat dijalankan dengan nama leaderboard untuk berfungsi sebagai referensi saat Anda melanjutkan codelab. Kita akan membuat direktori baru dan mem-build salinan aplikasi Papan Peringkat secara bertahap.

Buat direktori baru bernama "codelab" untuk aplikasi dan ubah direktori menjadi papan peringkat dengan perintah berikut:

mkdir codelab && cd $_

Sekarang mari kita perbarui pembuatan aplikasi Go dasar bernama "Papan Peringkat" yang menggunakan library klien Spanner untuk membuat papan peringkat yang terdiri dari dua tabel; Pemain dan Skor. Anda dapat melakukannya langsung di Cloud Shell Editor:

Buka Cloud Shell Editor, dengan mengklik ikon yang disorot di bawah:

73cf70e05f653ca.png

Buat file bernama "leaderboard.go" dalam folder ~/gopath/src/github.com/GoogleCloudPlatform/golang-samples/spanner/codelab.

  • Pertama, pastikan bahwa Anda telah memilih folder "codelab" di daftar folder Cloud Shell Editor.
  • Kemudian pilih "File Baru" di bagian menu "File" pada Cloud Shell Editor.
  • Masukkan "leaderboard.go" sebagai nama untuk file baru.

Ini adalah file utama aplikasi yang akan berisi kode aplikasi dan referensi untuk menyertakan dependensi.

Untuk membuat database leaderboard serta tabel Players dan Scores, salin (Ctrl + P) dan tempel (Ctrl + V) kode Go berikut ke file 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)
        }
}

Simpan perubahan yang Anda buat ke file leaderboard.go dengan memilih "Simpan" di bagian menu "File" pada Cloud Shell Editor.

Anda dapat menggunakan file leaderboard.go di direktori golang-samples/spanner/spanner_leaderboard untuk melihat contoh tampilan file leaderboard.go setelah menambahkan kode untuk mengaktifkan perintah createdatabase.

Untuk membuat aplikasi Anda di Cloud Shell, jalankan "go build" dari direktori codelab tempat file leaderboard.go Anda berada:

go build leaderboard.go

Setelah aplikasi Anda berhasil dibuat, jalankan aplikasi yang dihasilkan di Cloud Shell dengan memasukkan perintah berikut:

./leaderboard

Anda akan melihat output seperti berikut:

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.

Dari respons ini, kita dapat melihat bahwa ini adalah aplikasi Leaderboard yang saat ini memiliki satu kemungkinan perintah: createdatabase. Kita dapat melihat bahwa argumen yang diharapkan dari perintah createdatabase adalah string yang berisi ID Instance dan ID Database tertentu.

Sekarang jalankan perintah berikut. Pastikan Anda mengganti my-project dengan Project ID yang Anda buat di awal codelab ini.

./leaderboard createdatabase projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard

Setelah beberapa detik, Anda akan melihat respons seperti berikut:

Created database [projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard]

Di bagian Cloud Spanner pada Cloud Console, Anda akan melihat database dan tabel baru muncul di menu sebelah kiri.

ba9008bb84cb90b0.png

Pada langkah berikutnya, kita akan mengupdate aplikasi untuk memuat beberapa data ke database baru Anda.

Sekarang kita memiliki database yang disebut leaderboard yang berisi dua tabel; Players dan Scores Sekarang mari kita gunakan library klien Go untuk mengisi tabel Players dengan pemain dan tabel Scores dengan skor acak untuk setiap pemain.

Jika belum terbuka, buka Cloud Shell Editor dengan mengklik ikon yang ditandai di bawah:

ef49fcbaaed19024.png

Selanjutnya, edit file leaderboard.go di Cloud Shell Editor untuk menambahkan perintah insertplayers yang dapat digunakan untuk menyisipkan 100 pemain ke dalam tabel Players. Kita juga akan menambahkan perintah insertscores yang dapat digunakan untuk memasukkan 4 skor acak dalam tabel Scores untuk setiap pemain dalam tabel Players.

Pertama-tama perbarui bagian imports di bagian atas file leaderboard.go, menggantikan apa yang saat ini ada, sehingga setelah selesai akan terlihat seperti berikut:

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

Kemudian tambahkan tipe perintah baru bersama daftar perintah di bagian atas file, tepat di bawah baris yang dimulai dengan "type adminCommand ..." sehingga bila Anda sudah selesai akan terlihat seperti berikut:

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

Selanjutnya, tambahkan fungsi insertPlayers dan insertScores berikut di bawah fungsi createdatabase() yang ada:

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
}

Kemudian, untuk membuat perintah insert berfungsi, tambahkan kode berikut ke fungsi "run" Aplikasi Anda di bawah pernyataan penanganan createdatabase, menggantikan pernyataan 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

Setelah selesai, fungsi run akan terlihat seperti berikut:

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
}

Langkah terakhir untuk menyelesaikan penambahan fungsi "insert" ke aplikasi Anda adalah menambahkan teks bantuan untuk perintah "insertplayers" dan "insertscores" ke fungsi flag.Usage(). Tambahkan teks bantuan berikut ke fungsi flag.Usage() untuk menyertakan teks bantuan untuk perintah insert:

Tambahkan kedua perintah ke daftar kemungkinan perintah:

Command can be one of: createdatabase, insertplayers, insertscores

Dan tambahkan teks bantuan tambahan ini di bawah teks bantuan untuk perintah 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.

Simpan perubahan yang Anda buat ke file leaderboard.go dengan memilih "Simpan" di bagian menu "File" pada Cloud Shell Editor.

Anda dapat menggunakan file leaderboard.go di direktori golang-samples/spanner/spanner_leaderboard untuk melihat contoh tampilan file leaderboard.go setelah menambahkan kode untuk mengaktifkan perintah insertplayers dan insertscores.

Sekarang, mari kita buat dan jalankan aplikasi untuk mengonfirmasi bahwa perintah insertplayers dan insertscores baru disertakan dalam daftar kemungkinan perintah aplikasi. Jalankan perintah berikut untuk membuild aplikasi:

go build leaderboard.go

Jalankan aplikasi yang dihasilkan dalam Cloud Shell dengan memasukkan perintah berikut:

./leaderboard

Anda akan melihat perintah insertplayers dan insertscores kini disertakan dalam output default aplikasi:

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.

Sekarang mari kita jalankan perintah insertplayers dengan nilai argumen yang sama dengan yang kita gunakan saat memanggil perintah createdatabase. Pastikan Anda mengganti my-project dengan Project ID yang Anda buat di awal codelab ini.

./leaderboard insertplayers projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard

Setelah beberapa detik, Anda akan melihat respons seperti berikut:

Inserted players

Sekarang mari kita gunakan library klien Go untuk mengisi tabel Scores dengan empat skor acak beserta stempel waktu untuk setiap pemain di tabel Players.

Kolom Timestamp pada tabel Scores ditetapkan sebagai kolom "stempel waktu commit" melalui pernyataan SQL berikut yang dijalankan saat sebelumnya menjalankan perintah 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

Perhatikan atribut OPTIONS(allow_commit_timestamp=true). Ini membuat Timestamp menjadi kolom "stempel waktu commit" dan mengaktifkannya untuk diisi secara otomatis dengan stempel waktu transaksi yang tepat untuk operasi INSERT dan UPDATE pada baris tabel tertentu.

Anda juga dapat menyisipkan nilai stempel waktu Anda sendiri ke dalam kolom "stempel waktu commit" selama Anda menyisipkan stempel waktu dengan nilai yang sudah berlalu, yang akan kita lakukan untuk tujuan codelab ini.

Sekarang, jalankan perintah insertscores dengan nilai argumen yang sama dengan yang digunakan saat memanggil perintah insertplayers. Pastikan Anda mengganti my-project dengan Project ID yang Anda buat di awal codelab ini.

./leaderboard insertscores projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard

Setelah beberapa detik, Anda akan melihat respons seperti berikut:

Inserted scores

Menjalankan fungsi insertScores menggunakan cuplikan kode berikut untuk menyisipkan stempel waktu yang dibuat secara acak dengan waktu tanggal yang terjadi di masa lalu:

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

Untuk mengisi kolom Timestamp secara otomatis dengan stempel waktu tepat saat transaksi "Insert" terjadi, Anda dapat menyisipkan konstanta Go spanner.CommitTimestamp seperti dalam cuplikan kode berikut:

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

Setelah kita menyelesaikan pemuatan data, verifikasi nilai yang baru saja ditulis ke tabel baru di bagian Cloud Spanner pada Cloud Console. Pertama-tama, pilih database leaderboard, lalu pilih tabel Players. Klik tab Data. Anda akan melihat bahwa Anda memiliki data di kolom PlayerId dan PlayerName tabel.

7bc2c96293c31c49.png

Selanjutnya, pastikan tabel Skor juga memiliki data dengan mengklik tabel Scores dan memilih tab Data. Anda akan melihat bahwa Anda memiliki data di kolom PlayerId, Timestamp, dan Score tabel.

d8a4ee4f13244c19.png

Bagus! Mari mengupdate aplikasi untuk menjalankan beberapa kueri yang dapat kita gunakan untuk membuat papan peringkat game.

Setelah menyiapkan database dan memuat informasi ke dalam tabel, mari kita buat papan peringkat menggunakan data ini. Untuk melakukannya, kita harus menjawab empat pertanyaan berikut:

  1. Pemain mana yang masuk peringkat "Sepuluh Teratas" sepanjang waktu?
  2. Pemain mana yang masuk peringkat "Sepuluh Teratas" tahun ini?
  3. Pemain mana yang masuk peringkat "Sepuluh Teratas" bulan ini?
  4. Pemain mana yang masuk peringkat "Sepuluh Teratas" minggu ini?

Mari mengupdate aplikasi untuk menjalankan kueri SQL yang akan menjawab pertanyaan ini.

Kita akan menambahkan perintah query dan perintah queryWithTimespan yang akan memberikan cara untuk menjalankan kueri guna menjawab pertanyaan yang akan menghasilkan informasi yang diperlukan untuk papan peringkat.

Edit file leaderboard.go di Cloud Shell Editor untuk mengupdate aplikasi guna menambahkan perintah query dan perintah queryWithTimespan. Kita juga akan menambahkan fungsi bantuan formatWithCommas untuk memformat skor dengan koma.

Pertama-tama perbarui bagian imports di bagian atas file leaderboard.go, menggantikan apa yang saat ini ada, sehingga setelah selesai akan terlihat seperti berikut:

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

Selanjutnya, tambahkan dua fungsi berikut dan fungsi bentuan di bawah metode insertScores yang ada:

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, &timestamp); 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, &timestamp); 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()
}

Selanjutnya, di bagian atas file leaderboard.go tambahkan "query" sebagai satu opsi perintah dalam variabel commands, tepat di bawah opsi "insertscores": insertScores sehingga variabel commands terlihat seperti ini:

var (
        commands = map[string]command{
                "insertplayers": insertPlayers,
                "insertscores":  insertScores,
                "query":         query,
        }
)

Selanjutnya, tambahkan "queryWithTimespan" sebagai opsi perintah dalam fungsi run, di bawah bagian perintah "createdatabase" dan di atas bagian penanganan perintah "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
        }

Setelah selesai, fungsi run akan terlihat seperti berikut:

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
}

Kemudian, untuk membuat perintah queryWithTimespan berfungsi, update blok kode flag.Parse() di metode "utama" aplikasi Anda agar terlihat seperti berikut:

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

Langkah terakhir untuk menyelesaikan penambahan fungsi "query" ke aplikasi Anda adalah menambahkan teks bantuan untuk perintah "query" dan "querywithtimespan" ke fungsi flag.Usage(). Tambahkan baris kode berikut ke fungsi flag.Usage() untuk menyertakan teks bantuan untuk perintah kueri:

Tambahkan kedua perintah "query" ke daftar perintah yang memungkinkan:

Command can be one of: createdatabase, insertplayers, insertscores, query, querywithtimespan

Dan tambahkan teks bantuan tambahan berikut di bawah teks bantuan untuk perintah 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.

Simpan perubahan yang Anda buat ke file leaderboard.go dengan memilih "Simpan" di bagian menu "File" pada Cloud Shell Editor.

Anda dapat menggunakan file leaderboard.go di direktori golang-samples/spanner/spanner_leaderboard untuk melihat contoh tampilan file leaderboard.go setelah menambahkan kode untuk mengaktifkan perintah query dan querywithtimespan.

Sekarang, mari kita buat dan jalankan aplikasi untuk mengonfirmasi bahwa perintah query dan querywithtimespan baru disertakan dalam daftar kemungkinan perintah aplikasi.

Jalankan perintah berikut di Cloud Shell untuk membuild aplikasi:

go build leaderboard.go

Jalankan aplikasi yang dihasilkan dalam Cloud Shell dengan memasukkan perintah berikut:

./leaderboard

Anda akan melihat perintah query dan querywithtimespan kini disertakan dalam output default aplikasi sebagai opsi perintah baru:

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.

Anda dapat melihat dari respons bahwa kita dapat menggunakan perintah query untuk mendapatkan daftar pemain "Sepuluh Teratas" sepanjang waktu. Kita juga dapat melihat bahwa perintah querywithtimespan memungkinkan kita menentukan rentang waktu dalam jumlah jam yang bisa digunakan untuk memfilter data berdasarkan nilainya di kolom Timestamp pada tabel Scores.

Mari jalankan perintah query menggunakan nilai argumen yang sama dengan yang digunakan saat menjalankan perintah create. Pastikan Anda mengganti my-project dengan Project ID yang Anda buat di awal codelab ini.

./leaderboard query  projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard

Anda akan melihat respons yang menyertakan pemain "Sepuluh Teratas" sepanjang masa seperti berikut:

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

Sekarang mari kita jalankan perintah querywithtimespan dengan argumen yang diperlukan untuk membuat kueri pemain "Sepuluh Teratas" tahun ini dengan menentukan "timespan" yang sama dengan jumlah jam dalam setahun, yakni 8.760. Pastikan Anda mengganti my-project dengan Project ID yang Anda buat di awal codelab ini.

./leaderboard querywithtimespan projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard 8760

Anda akan melihat respons yang menyertakan pemain "Sepuluh Teratas" tahun ini seperti berikut:

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

Sekarang, mari kita jalankan perintah querywithtimespan untuk membuat kueri pemain "Sepuluh Teratas" bulan ini dengan menentukan "timespan" yang sama dengan jumlah jam dalam sebulan, yaitu 730. Pastikan Anda mengganti my-project dengan Project ID yang Anda buat di awal codelab ini.

./leaderboard querywithtimespan projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard 730

Anda akan melihat respons yang menyertakan pemain "Sepuluh Teratas" bulan ini seperti berikut:

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

Sekarang mari kita jalankan perintah querywithtimespan untuk membuat kueri pemain "Sepuluh Teratas" minggu ini dengan menetapkan "timespan" yang sama dengan jumlah jam dalam seminggu, yaitu 168. Pastikan Anda mengganti my-project dengan Project ID yang Anda buat di awal codelab ini.

./leaderboard querywithtimespan  projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard 168

Anda akan melihat respons yang menyertakan pemain "Sepuluh Teratas" minggu ini seperti berikut:

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

Bagus!

Kini setelah Anda menambahkan data, Cloud Spanner akan mengubah skala database Anda menjadi sebesar apa pun kebutuhan Anda. Berapa pun pertumbuhan database Anda, papan peringkat game dapat terus diskalakan dengan akurat menggunakan Cloud Spanner dan teknologi Truetime-nya.

Setelah asyik bermain dengan Spanner, kita perlu membersihkan tempat bermain kita, menghemat resource dan uang yang berharga. Untungnya ini adalah langkah yang mudah. Cukup buka bagian Cloud Spanner dari Cloud Console dan hapus instance yang kita buat di langkah codelab bernama "Siapkan Instance Cloud Spanner".

Yang telah kita bahas:

  • Instance, Database, dan Skema Tabel Google Cloud Spanner untuk papan peringkat
  • Cara membuat aplikasi konsol Go
  • Cara membuat Database Spanner dan Tabel menggunakan library klien Go
  • Cara memuat data ke Database Spanner menggunakan library klien Go
  • Cara membuat kueri hasil "Sepuluh Teratas" dari data Anda menggunakan stempel waktu commit Spanner dan library klien Go

Langkah Berikutnya:

Kirimkan masukan Anda

  • Luangkan waktu Anda untuk menyelesaikan survei singkat kami