Go
golang
GoogleAppEngine
GoogleCloudPlatform
CloudSpanner

GAE/Go で Google Cloud Spanner を操作する(前編)

こんにちは。今日はネコの日(2017 年 2 月 22 日)ですね :cat:

昨年(2016)の 10 月ぐらいから Go 言語を勉強し始めた @wezardnet です。新しい言語はまだまだ慣れませんw
さて、今回は Go 言語の学習がてら先日ローンチされた Google Cloud Spanner を触ってみようと思います。具体的には Go で Cloud Spanner のインスタンスを作成/削除したり、データベースを作ったりをしてみたので、忘れないように書いておこうと思います。タイトルに「(前編)」と入れましたが、後編があるかどうかは未定です :sweat_smile:

1. Cloud Spanner ってなに?

ネットなどのニュース記事を読むと、要はスケールアウトするリレーショナルデータベースって感じでしょうか。グーグルのコアサービスでも使われているそうです。現在はベータ版で Google Cloud Platform(GCP) のプロジェクトを作成すれば誰でも使えるようになっています。

2. Cloud Spanner を使うための準備

まずは Google Developer Console の API Manager で Cloud Spanner API を有効にします。

API Library.png

次に Cloud Spanner API 用に Service Account を作成し JSON キーファイルを入手します。

ServiceAccount.png

以上で準備は完了です :v:

3. コードを書く

それでは実際にコードを書いていきます。インスタンス操作とデータベース操作に分けて解説します。コードは見づらくなるので一部省略してます。

3.1 インスタンスの作成と削除

必要なパッケージをインポートします。

Import
package spanner

import (
    "cloud.google.com/go/spanner"
    "cloud.google.com/go/spanner/admin/database/apiv1"
    "cloud.google.com/go/spanner/admin/instance/apiv1"

    "golang.org/x/oauth2"
    "golang.org/x/oauth2/google"

    "google.golang.org/api/option"
    "google.golang.org/appengine"
    "google.golang.org/appengine/log"

    admindb "google.golang.org/genproto/googleapis/spanner/admin/database/v1"
    adminis "google.golang.org/genproto/googleapis/spanner/admin/instance/v1"
)

JSON キーファイルを読み込んでアクセストークンを取得し Admin Client を作成します。

CreateAdminClient
ctx := appengine.NewContext(r)

// Service Account でアクセスするための JSON ファイルを読み込む
data, err := ioutil.ReadFile("{JSON Key File}")
if err != nil {
    log.Errorf(ctx, "err = %v", err.Error())
    return
}

conf, err := google.JWTConfigFromJSON(data, "https://www.googleapis.com/auth/spanner.admin", "https://www.googleapis.com/auth/spanner.data")
if err != nil {
    log.Errorf(ctx, "err = %v", err.Error())
    return
}
option := option.WithTokenSource(conf.TokenSource(ctx))

adminClient, err := instance.NewInstanceAdminClient(ctx, option)
if err != nil {
    log.Errorf(ctx, "Error NewInstanceAdminClient: %v", err)
    return
}

「JSON キーファイルを読み込んで...」という部分は App Engine のサービスアカウントを使うと次のように 1 行で済みます(ご指摘ありがとうございます!) :smiley:

// App Engine Service Account のトークンをセットする
option := option.WithTokenSource(google.AppEngineTokenSource(ctx, "https://www.googleapis.com/auth/spanner.admin", "https://www.googleapis.com/auth/spanner.data"))

Cloud Spanner のインスタンスを作成するコードは次のようになります。この例ではリージョンは「asia-east1」、ノード数は「1」としています。

CreateInstance
op, err := adminClient.CreateInstance(ctx, &adminis.CreateInstanceRequest{
    Parent:     "projects/{Youre Project ID}", 
    InstanceId: "{Youre Instance ID}", 
    Instance:   &adminis.Instance{
        Config:      "projects/{Youre Project ID}/instanceConfigs/regional-asia-east1", 
        DisplayName: "{Youre Instance Display Name}", 
        Name:        "projects/{Youre Project ID}/instances/{Youre Instance ID}", 
        NodeCount:   1, 
    }, 
})
if err != nil {
    log.Errorf(ctx, "Error Create Instace: %v", err)
    return
}
if _, err := op.Wait(ctx); err == nil {
    // インスタンス作成が成功
}

Cloud Spanner はインスタンスを上げてるだけでも課金されるので不要になったら削除しないと大変です。今回の記事を書くために試行しただけで自身のプロジェクトの課金状況を見たら $10.8 課金されてました :worried:

DeleteInstance
adminClient.DeleteInstance(ctx, &adminis.DeleteInstanceRequest{
    Name: "projects/{Youre Project ID}/instances/{Youre Instance ID}", 
})

3.2 データベースとスキーマの作成

データベースの作成もインスタンス作成とほぼ同じ実装になります。まずは JSON キーファイルを読み込んでアクセストークンを取得し Admin Client を作成します。

CreateAdminClient
ctx := appengine.NewContext(r)

// Service Account でアクセスするための JSON ファイルを読み込む
data, err := ioutil.ReadFile("{JSON Key File}")
if err != nil {
    log.Errorf(ctx, "err = %v", err.Error())
    return
}

conf, err := google.JWTConfigFromJSON(data, "https://www.googleapis.com/auth/spanner.admin", "https://www.googleapis.com/auth/spanner.data")
if err != nil {
    log.Errorf(ctx, "err = %v", err.Error())
    return
}
option := option.WithTokenSource(conf.TokenSource(ctx))

adminClient, err := database.NewDatabaseAdminClient(ctx, option)
if err != nil {
    log.Errorf(ctx, "Error NewDatabaseAdminClient: %v", err)
    return
}

今回はサンプルのため、次のような昔ながらの得意先マスタを作成してみることにします。

論理名 物理名 データ型 Not Null
得意先コード TCODE number(10) Yes
フリガナ FURIGANA varchar2(50)
得意先名 TNAME varchar2(50)
郵便番号 YUBIN varchar2(8)
住所1 ADD1 varchar2(50)
住所2 ADD2 varchar2(50)
電話番号 TEL char(12)
FAX番号 FAX char(12)
担当者名 TANTOU varchar2(20)
値引率 NEBIKI number(19,3)
メールアドレス MAILADDR varchar2(50)

データ型は Cloud Spanner で用意されているモノに置き換えます。データ型については こちら を参照してください。

CreateDatabase
op, err := adminClient.CreateDatabase(ctx, &admindb.CreateDatabaseRequest{
    Parent:          "projects/{Youre Project ID}", 
    CreateStatement: "CREATE DATABASE {Youre Database Name}", 
    ExtraStatements: []string{
        `CREATE TABLE TOKUI (
            TCODE     INT64 NOT NULL, 
            FURIGANA  STRING(50), 
            TNAME     STRING(50), 
            YUBIN     STRING(8), 
            ADD1      STRING(50), 
            ADD2      STRING(50), 
            TEL       STRING(12), 
            FAX       STRING(12), 
            TANTOU    STRING(20), 
            NEBIKI    FLOAT64, 
            MAILADDR  STRING(50), 
        ) PRIMARY KEY (TCODE)`, 
    }, 
})
if err != nil {
    log.Errorf(ctx, "Error Create Database: %v", err)
    return
}
if _, err := op.Wait(ctx); err == nil {
    // データベース & スキーマ作成が成功
}

4. 管理コンソールで確認する

実際にインスタンスやデータベースが作られているかどうか Google Developer Console の Cloud Spanner で確認してみます。

Database.png

いい感じに出来てますね :smiley:

5. おわりに

実際に実行してみると、インスタンスはサクッと作られますね。
今回は Cloud Spanner の導入部分だけを試しただけで、その本領域を知るに至っていません。次回(モチベがあれば)は RDBMS のデータを移行できるかどうか、トランザクションはどうなのか、SQL のような扱いが可能か、といった点を調査してみたいと思います。。。