Go で Cloud Spanner を使ってみる

目標

このチュートリアルでは、Go 用の Cloud Spanner クライアント ライブラリを使用するステップを詳しく説明します。

  • Cloud Spanner のインスタンスとデータベースを作成します。
  • データベースのデータに対し、書き込み、読み取り、SQL クエリの実行を行います。
  • データベース スキーマを更新します。
  • 読み取り / 書き込みトランザクションを使用してデータを更新します。
  • セカンダリ インデックスをデータベースに追加します。
  • インデックスを使用して、データの読み込みと SQL クエリの実行を行います。
  • 読み取り専用トランザクションを使用してデータを取得します。

料金

このチュートリアルで使用する Cloud Spanner は、Google Cloud の有料コンポーネントです。Cloud Spanner を使用する料金については、料金をご覧ください。

始める前に

設定に示されている手順を完了します。この手順では、デフォルトの Google Cloud プロジェクトの作成と設定、課金の有効化、Cloud Spanner API の有効化、Cloud Spanner API の使用に必要な認証情報を取得するための OAuth 2.0 の設定について説明しています。

特に、ローカルの開発環境に認証情報を設定するために、必ず gcloud auth application-default login を実行してください。

ローカルの Go 環境を準備する

  1. まだインストールしていない場合は開発マシンに Go(ダウンロード)をインストールします。

  2. GOPATH 環境変数をまだ構成していない場合は、インストールをテストするの説明に沿って構成します。

  3. サンプルをマシンにダウンロードします。

    go get -u github.com/GoogleCloudPlatform/golang-samples/spanner/...
    
  4. Cloud Spanner のサンプルコードが含まれるディレクトリに移動します。

    cd $GOPATH/src/github.com/GoogleCloudPlatform/golang-samples/spanner/spanner_snippets
    
  5. 環境変数 GCLOUD_PROJECT に Google Cloud プロジェクト ID を設定します。

    export GCLOUD_PROJECT=[MY_PROJECT_ID]
    

インスタンスの作成

Cloud Spanner を最初に使用するときは、インスタンスを作成する必要があります。インスタンスとは、Cloud Spanner データベースによって使用されるリソースの割り当てのことです。インスタンスを作成するときは、インスタンス構成を選択してデータの格納場所を指定し、さらに使用するノード数も選択して、インスタンスの配信リソースおよびストレージ リソースの量を決定します。

次のコマンドを実行して、1 ノードの us-central1 リージョンに Cloud Spanner インスタンスを作成します。

gcloud spanner instances create test-instance --config=regional-us-central1 \
    --description="Test Instance" --nodes=1

これにより、次の特性を持つインスタンスが作成されます。

  • インスタンス ID test-instance
  • 表示名 Test Instance
  • インスタンス構成 regional-us-central1(リージョン構成ではデータが単一のリージョンに保存され、マルチリージョン構成ではデータが複数のリージョンに分散されます。詳しくは、インスタンスをご覧ください。)
  • ノード数 1(node_count はインスタンスのデータベースで使用可能な配信リソースとストレージ リソースの量に対応します。詳しくは、ノード数をご覧ください。)

以下のように表示されます。

Creating instance...done.

サンプル ファイルの確認

サンプル レポジトリには、Cloud Spanner の Go での使用方法を示すサンプルが含まれます。

snippet.go ファイルを見ると、Cloud Spanner の使用方法を確認できます。このファイルのコードでは、新しいデータベースを作成して使用する方法が示されています。データで使用しているサンプル スキーマは、スキーマとデータモデルのページにあります。

データベースの作成

コマンドラインで次のコマンドを実行して、test-instance というインスタンスに example-db というデータベースを作成します。

go run snippet.go createdatabase projects/$GCLOUD_PROJECT/instances/test-instance/databases/example-db

以下のように表示されます。

Created database [example-db]

これで Cloud Spanner データベースが作成されました。データベースを作成したコードは次のとおりです。

import (
	"context"
	"fmt"
	"io"
	"regexp"

	database "cloud.google.com/go/spanner/admin/database/apiv1"
	adminpb "google.golang.org/genproto/googleapis/spanner/admin/database/v1"
)

func createDatabase(w io.Writer, db string) error {
	matches := regexp.MustCompile("^(.*)/databases/(.*)$").FindStringSubmatch(db)
	if matches == nil || len(matches) != 3 {
		return fmt.Errorf("Invalid database id %s", db)
	}

	ctx := context.Background()
	adminClient, err := database.NewDatabaseAdminClient(ctx)
	if err != nil {
		return err
	}
	defer adminClient.Close()

	op, err := adminClient.CreateDatabase(ctx, &adminpb.CreateDatabaseRequest{
		Parent:          matches[1],
		CreateStatement: "CREATE DATABASE `" + matches[2] + "`",
		ExtraStatements: []string{
			`CREATE TABLE Singers (
				SingerId   INT64 NOT NULL,
				FirstName  STRING(1024),
				LastName   STRING(1024),
				SingerInfo BYTES(MAX)
			) PRIMARY KEY (SingerId)`,
			`CREATE TABLE Albums (
				SingerId     INT64 NOT NULL,
				AlbumId      INT64 NOT NULL,
				AlbumTitle   STRING(MAX)
			) PRIMARY KEY (SingerId, AlbumId),
			INTERLEAVE IN PARENT Singers ON DELETE CASCADE`,
		},
	})
	if err != nil {
		return err
	}
	if _, err := op.Wait(ctx); err != nil {
		return err
	}
	fmt.Fprintf(w, "Created database [%s]\n", db)
	return nil
}

コードでは、基本的な音楽アプリケーション用の 2 つのテーブル SingersAlbums も定義されています。これらのテーブルはこのページ全体で使用されています。まだスキーマ例を見ていない場合は確認してください。

次のステップでは、データベースにデータを書き込みます。

データベース クライアントの作成

読み取りまたは書き込みを行うには、その前に Client を作成する必要があります。


import (
	"context"
	"io"

	"cloud.google.com/go/spanner"
	database "cloud.google.com/go/spanner/admin/database/apiv1"
)

func createClients(w io.Writer, db string) error {
	ctx := context.Background()

	adminClient, err := database.NewDatabaseAdminClient(ctx)
	if err != nil {
		return err
	}

	dataClient, err := spanner.NewClient(ctx, db)
	if err != nil {
		return err
	}

	_ = adminClient
	_ = dataClient

	return nil
}

Client はデータベース接続と考えることができます。Cloud Spanner とのすべてのやり取りは Client を経由する必要があります。通常はアプリケーション開始時に Client を作成し、読み取り、書き込み、トランザクションの実行に Client を再利用します。各クライアントは Cloud Spanner のリソースを使用します。

同じアプリで複数のクライアントを作成する場合は、Client.Close() を呼び出し、必要がなくなったらすぐにクライアントのリソース(ネットワーク接続を含む)をクリーンアップする必要があります。

詳細については、Client のリファレンスをご覧ください。

上記のコードでは、データベースの作成に使用される DatabaseAdminClient の作成方法も示されています。

DML でのデータの書き込み

読み取り / 書き込みトランザクションでデータ操作言語(DML)を使用してデータを挿入できます。

Update() メソッドを使用して DML ステートメントを実行します。


import (
	"context"
	"fmt"
	"io"

	"cloud.google.com/go/spanner"
)

func writeUsingDML(w io.Writer, db string) error {
	ctx := context.Background()
	client, err := spanner.NewClient(ctx, db)
	if err != nil {
		return err
	}
	defer client.Close()

	_, err = client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
		stmt := spanner.Statement{
			SQL: `INSERT Singers (SingerId, FirstName, LastName) VALUES
				(12, 'Melissa', 'Garcia'),
				(13, 'Russell', 'Morales'),
				(14, 'Jacqueline', 'Long'),
				(15, 'Dylan', 'Shaw')`,
		}
		rowCount, err := txn.Update(ctx, stmt)
		if err != nil {
			return err
		}
		fmt.Fprintf(w, "%d record(s) inserted.\n", rowCount)
		return err
	})
	return err
}

dmlwrite 引数を使用してサンプルを実行します。

go run snippet.go dmlwrite projects/$GCLOUD_PROJECT/instances/test-instance/databases/example-db

以下のように表示されます。

4 record(s) inserted.

ミューテーションを使用してデータを書き込む

ミューテーションを使ってデータを挿入することもできます。

Mutation はミューテーション オペレーションのコンテナです。Mutation は挿入、更新、削除など、Cloud Spanner データベース内のさまざまな行やテーブルに、Cloud Spanner によってアトミックに適用される一連の操作を表します。

Mutation.InsertOrUpdate() を使用して INSERT_OR_UPDATE ミューテーションを作成できます。これによって新しい行が追加されます。行がすでに存在している場合は列値が更新されます。あるいは、Mutation.Insert() メソッドを使用して、新しい行を追加する INSERT ミューテーションを作成します。

Client.Apply() を使用してデータベースにミューテーションをアトミックに適用します。

次のコードは、ミューテーションを使用してデータを書き込む方法を示しています。


import (
	"context"
	"io"

	"cloud.google.com/go/spanner"
)

func write(w io.Writer, db string) error {
	ctx := context.Background()
	client, err := spanner.NewClient(ctx, db)
	if err != nil {
		return err
	}
	defer client.Close()

	singerColumns := []string{"SingerId", "FirstName", "LastName"}
	albumColumns := []string{"SingerId", "AlbumId", "AlbumTitle"}
	m := []*spanner.Mutation{
		spanner.InsertOrUpdate("Singers", singerColumns, []interface{}{1, "Marc", "Richards"}),
		spanner.InsertOrUpdate("Singers", singerColumns, []interface{}{2, "Catalina", "Smith"}),
		spanner.InsertOrUpdate("Singers", singerColumns, []interface{}{3, "Alice", "Trentor"}),
		spanner.InsertOrUpdate("Singers", singerColumns, []interface{}{4, "Lea", "Martin"}),
		spanner.InsertOrUpdate("Singers", singerColumns, []interface{}{5, "David", "Lomond"}),
		spanner.InsertOrUpdate("Albums", albumColumns, []interface{}{1, 1, "Total Junk"}),
		spanner.InsertOrUpdate("Albums", albumColumns, []interface{}{1, 2, "Go, Go, Go"}),
		spanner.InsertOrUpdate("Albums", albumColumns, []interface{}{2, 1, "Green"}),
		spanner.InsertOrUpdate("Albums", albumColumns, []interface{}{2, 2, "Forever Hold Your Peace"}),
		spanner.InsertOrUpdate("Albums", albumColumns, []interface{}{2, 3, "Terrified"}),
	}
	_, err = client.Apply(ctx, m)
	return err
}

write 引数を使用してサンプルを実行します。

go run snippet.go write projects/$GCLOUD_PROJECT/instances/test-instance/databases/example-db

コマンドが正常に実行されることを確認します。

SQL を使用したデータのクエリ

Cloud Spanner では、データの読み取り用にネイティブ SQL インターフェースがサポートされています。このインターフェースにアクセスするには、コマンドラインで gcloud コマンドライン ツールを使用するか、プログラムで Go 用の Cloud Spanner クライアント ライブラリを使用します。

コマンドラインから

Albums テーブルのすべての列から値を読み取るには、次の SQL ステートメントを実行します。

gcloud spanner databases execute-sql example-db --instance=test-instance \
    --sql='SELECT SingerId, AlbumId, AlbumTitle FROM Albums'

結果は次のようになります。

SingerId AlbumId AlbumTitle
1        1       Total Junk
1        2       Go, Go, Go
2        1       Green
2        2       Forever Hold Your Peace
2        3       Terrified

Go 用の Cloud Spanner クライアント ライブラリの使用

コマンドラインで SQL ステートメントを実行するだけでなく、プログラムで Go 用の Cloud Spanner クライアント ライブラリを使用して同じ SQL ステートメントを発行することもできます。

SQL クエリの実行に使用されるメソッドとタイプを次に示します。

  • Client.Single(): このメソッドは、Cloud Spanner テーブルの 1 つ以上の行から 1 つ以上の列の値を読み取るために使用します。Client.Single は、読み取りまたは SQL ステートメントを実行するために使用される ReadOnlyTransaction を返します。
  • ReadOnlyTransaction.Query(): このメソッドは、データベースに対してクエリを実行するために使用します。
  • Statement タイプ: SQL 文字列を作成するために使用します。
  • Row タイプ: SQL ステートメントまたは読み取り呼び出しから返されるデータにアクセスするために使用します。

クエリを発行してデータにアクセスする方法を次に示します。


import (
	"context"
	"fmt"
	"io"

	"cloud.google.com/go/spanner"
	"google.golang.org/api/iterator"
)

func query(w io.Writer, db string) error {
	ctx := context.Background()
	client, err := spanner.NewClient(ctx, db)
	if err != nil {
		return err
	}
	defer client.Close()

	stmt := spanner.Statement{SQL: `SELECT SingerId, AlbumId, AlbumTitle FROM Albums`}
	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 singerID, albumID int64
		var albumTitle string
		if err := row.Columns(&singerID, &albumID, &albumTitle); err != nil {
			return err
		}
		fmt.Fprintf(w, "%d %d %s\n", singerID, albumID, albumTitle)
	}
}

query 引数を使用してサンプルを実行します。

go run snippet.go query projects/$GCLOUD_PROJECT/instances/test-instance/databases/example-db

次のような結果が表示されます。

1 1 Total Junk
1 2 Go, Go, Go
2 1 Green
2 2 Forever Hold Your Peace
2 3 Terrified

SQL パラメータを使用したクエリ

サポートされている SQL 型を使用して、SQL ステートメントにカスタム値を追加できます。

ここでは、WHERE 句でパラメータとして @lastName を使用し、LastName に特定の値を含むレコードを取得します。


import (
	"context"
	"fmt"
	"io"

	"cloud.google.com/go/spanner"
	"google.golang.org/api/iterator"
)

func queryWithParameter(w io.Writer, db string) error {
	ctx := context.Background()
	client, err := spanner.NewClient(ctx, db)
	if err != nil {
		return err
	}
	defer client.Close()

	stmt := spanner.Statement{
		SQL: `SELECT SingerId, FirstName, LastName FROM Singers
			WHERE LastName = @lastName`,
		Params: map[string]interface{}{
			"lastName": "Garcia",
		},
	}
	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 singerID int64
		var firstName, lastName string
		if err := row.Columns(&singerID, &firstName, &lastName); err != nil {
			return err
		}
		fmt.Fprintf(w, "%d %s %s\n", singerID, firstName, lastName)
	}
}

querywithparameter 引数を使用してサンプルを実行します。

go run snippet.go querywithparameter projects/$GCLOUD_PROJECT/instances/test-instance/databases/example-db

次のような出力が表示されます。

12 Melissa Garcia

読み取り API を使用したデータの読み込み

Cloud Spanner の SQL インターフェースに加えて、Cloud Spanner は読み取りインターフェースもサポートしています。

ReadOnlyTransaction.Read() を使用して、データベースから行を読み取ります。読み取るキーおよびキー範囲のコレクションを定義するには、KeySet を使用します。

データを読み取る方法を次に示します。


import (
	"context"
	"fmt"
	"io"

	"cloud.google.com/go/spanner"
	"google.golang.org/api/iterator"
)

func read(w io.Writer, db string) error {
	ctx := context.Background()
	client, err := spanner.NewClient(ctx, db)
	if err != nil {
		return err
	}
	defer client.Close()

	iter := client.Single().Read(ctx, "Albums", spanner.AllKeys(),
		[]string{"SingerId", "AlbumId", "AlbumTitle"})
	defer iter.Stop()
	for {
		row, err := iter.Next()
		if err == iterator.Done {
			return nil
		}
		if err != nil {
			return err
		}
		var singerID, albumID int64
		var albumTitle string
		if err := row.Columns(&singerID, &albumID, &albumTitle); err != nil {
			return err
		}
		fmt.Fprintf(w, "%d %d %s\n", singerID, albumID, albumTitle)
	}
}

read 引数を使用してサンプルを実行します。

go run snippet.go read projects/$GCLOUD_PROJECT/instances/test-instance/databases/example-db

次のような出力が表示されます。

1 1 Total Junk
1 2 Go, Go, Go
2 1 Green
2 2 Forever Hold Your Peace
2 3 Terrified

データベース スキーマの更新

MarketingBudget という列を新たに Albums テーブルに追加する必要があるとします。既存のテーブルに新しい列を追加するには、データベース スキーマの更新が必要です。Cloud Spanner は、トラフィック提供中のデータベースへのスキーマの更新をサポートしています。スキーマの更新では、データベースをオフラインにする必要がなく、テーブル全体や列がロックされないため、スキーマの更新中もデータベースへのデータの書き込みを続けることができます。サポートされるスキーマの更新とスキーマ変更のパフォーマンスの詳細については、スキーマの更新をご覧ください。

列の追加

列を追加するには、コマンドラインで gcloud コマンドライン ツールを使用するか、プログラムで Go 用の Cloud Spanner クライアント ライブラリを使用します。

コマンドラインから

テーブルに新しい列を追加するには、次の ALTER TABLE コマンドを使用します。

gcloud spanner databases ddl update example-db --instance=test-instance \
    --ddl='ALTER TABLE Albums ADD COLUMN MarketingBudget INT64'

以下のように表示されます。

Schema updating...done.

Go 用の Cloud Spanner クライアント ライブラリの使用

スキーマを変更するには、DatabaseAdminClient.UpdateDatabaseDdl() を使用します。


import (
	"context"
	"fmt"
	"io"

	database "cloud.google.com/go/spanner/admin/database/apiv1"
	adminpb "google.golang.org/genproto/googleapis/spanner/admin/database/v1"
)

func addNewColumn(w io.Writer, db string) error {
	ctx := context.Background()
	adminClient, err := database.NewDatabaseAdminClient(ctx)
	if err != nil {
		return err
	}
	defer adminClient.Close()

	op, err := adminClient.UpdateDatabaseDdl(ctx, &adminpb.UpdateDatabaseDdlRequest{
		Database: db,
		Statements: []string{
			"ALTER TABLE Albums ADD COLUMN MarketingBudget INT64",
		},
	})
	if err != nil {
		return err
	}
	if err := op.Wait(ctx); err != nil {
		return err
	}
	fmt.Fprintf(w, "Added MarketingBudget column\n")
	return nil
}

addnewcolumn 引数を使用してサンプルを実行します。

go run snippet.go addnewcolumn projects/$GCLOUD_PROJECT/instances/test-instance/databases/example-db

以下のように表示されます。

Added MarketingBudget column.

新しい列へのデータの書き込み

次のコードは、新しい列にデータを書き込みます。MarketingBudget の値を、キーが Albums(1, 1) の行は 100000 に、キーが Albums(2, 2) の行は 500000 に設定します。

import (
	"context"
	"io"

	"cloud.google.com/go/spanner"
)

func update(w io.Writer, db string) error {
	ctx := context.Background()

	client, err := spanner.NewClient(ctx, db)
	if err != nil {
		return err
	}
	defer client.Close()

	cols := []string{"SingerId", "AlbumId", "MarketingBudget"}
	_, err = client.Apply(ctx, []*spanner.Mutation{
		spanner.Update("Albums", cols, []interface{}{1, 1, 100000}),
		spanner.Update("Albums", cols, []interface{}{2, 2, 500000}),
	})
	return err
}

update 引数を使用してサンプルを実行します。

go run snippet.go update projects/$GCLOUD_PROJECT/instances/test-instance/databases/example-db

SQL クエリまたは読み取り呼び出しを実行して、書き込んだばかりの値を取得することもできます。

クエリを実行するコードを次に示します。


import (
	"context"
	"fmt"
	"io"
	"strconv"

	"cloud.google.com/go/spanner"
	"google.golang.org/api/iterator"
)

func queryNewColumn(w io.Writer, db string) error {
	ctx := context.Background()
	client, err := spanner.NewClient(ctx, db)
	if err != nil {
		return err
	}
	defer client.Close()

	stmt := spanner.Statement{SQL: `SELECT SingerId, AlbumId, MarketingBudget FROM Albums`}
	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 singerID, albumID int64
		var marketingBudget spanner.NullInt64
		if err := row.ColumnByName("SingerId", &singerID); err != nil {
			return err
		}
		if err := row.ColumnByName("AlbumId", &albumID); err != nil {
			return err
		}
		if err := row.ColumnByName("MarketingBudget", &marketingBudget); err != nil {
			return err
		}
		budget := "NULL"
		if marketingBudget.Valid {
			budget = strconv.FormatInt(marketingBudget.Int64, 10)
		}
		fmt.Fprintf(w, "%d %d %s\n", singerID, albumID, budget)
	}
}

このクエリを実行するには、querynewcolumn 引数を使用してサンプルを実行します。

go run snippet.go querynewcolumn projects/$GCLOUD_PROJECT/instances/test-instance/databases/example-db

以下のように表示されます。

1 1 100000
1 2 NULL
2 1 NULL
2 2 500000
2 3 NULL

データの更新

読み取り / 書き込みトランザクションで DML を使用してデータを更新できます。

Update() メソッドを使用して DML ステートメントを実行します。


import (
	"context"
	"fmt"
	"io"

	"cloud.google.com/go/spanner"
)

func writeWithTransactionUsingDML(w io.Writer, db string) error {
	ctx := context.Background()
	client, err := spanner.NewClient(ctx, db)
	if err != nil {
		return err
	}
	defer client.Close()

	_, err = client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
		// getBudget returns the budget for a record with a given albumId and singerId.
		getBudget := func(albumID, singerID int64) (int64, error) {
			key := spanner.Key{albumID, singerID}
			row, err := txn.ReadRow(ctx, "Albums", key, []string{"MarketingBudget"})
			if err != nil {
				return 0, err
			}
			var budget int64
			if err := row.Column(0, &budget); err != nil {
				return 0, err
			}
			return budget, nil
		}
		// updateBudget updates the budget for a record with a given albumId and singerId.
		updateBudget := func(singerID, albumID, albumBudget int64) error {
			stmt := spanner.Statement{
				SQL: `UPDATE Albums
					SET MarketingBudget = @AlbumBudget
					WHERE SingerId = @SingerId and AlbumId = @AlbumId`,
				Params: map[string]interface{}{
					"SingerId":    singerID,
					"AlbumId":     albumID,
					"AlbumBudget": albumBudget,
				},
			}
			_, err := txn.Update(ctx, stmt)
			return err
		}

		// Transfer the marketing budget from one album to another. By keeping the actions
		// in a single transaction, it ensures the movement is atomic.
		const transferAmt = 200000
		album2Budget, err := getBudget(2, 2)
		if err != nil {
			return err
		}
		// The transaction will only be committed if this condition still holds at the time
		// of commit. Otherwise it will be aborted and the callable will be rerun by the
		// client library.
		if album2Budget >= transferAmt {
			album1Budget, err := getBudget(1, 1)
			if err != nil {
				return err
			}
			if err = updateBudget(1, 1, album1Budget+transferAmt); err != nil {
				return err
			}
			if err = updateBudget(2, 2, album2Budget-transferAmt); err != nil {
				return err
			}
			fmt.Fprintf(w, "Moved %d from Album2's MarketingBudget to Album1's.", transferAmt)
		}
		return nil
	})
	return err
}

dmlwritetxn 引数を使用してサンプルを実行します。

go run snippet.go dmlwritetxn projects/$GCLOUD_PROJECT/instances/test-instance/databases/example-db

以下のように表示されます。

Moved 200000 from Album2's MarketingBudget to Album1's.

セカンダリ インデックスの使用

Albums から AlbumTitle の値が特定の範囲内にある行すべてを取得すると仮定します。SQL ステートメントまたは読み取り呼び出しを使用して AlbumTitle 列からすべての値を読み取り、基準を満たしていない行を破棄することもできますが、このようなテーブル全体のスキャンは割高です(特に、行数が多いテーブルの場合)。代わりに、テーブルにセカンダリ インデックスを作成することにより、主キー以外の列を検索するときの行の取得速度を上げることができます。

既存のテーブルにセカンダリ インデックスを追加するには、スキーマの更新が必要です。他のスキーマの更新と同様に、Cloud Spanner ではデータベースがトラフィックを提供している間にインデックスを追加できます。Cloud Spanner では、インデックスに既存のデータが自動的にバックフィルされます。バックフィルには数分かかることがありますが、このプロセスの間に、データベースをオフラインにしたり、インデックス化対象のテーブルへの書き込みを控えたりする必要はありません。詳細については、インデックスのバックフィリングをご覧ください。

セカンダリ インデックスを追加すると、インデックス効果で実行速度が上がりそうな SQL クエリに対してそのセカンダリ インデックスが自動的に使用されるようになります。読み取りインターフェースを使用する場合は、使用するインデックスを指定する必要があります。

セカンダリ インデックスの追加

インデックスを追加するには、コマンドラインで gcloud コマンドライン ツールを使用するか、プログラムで Go 用の Cloud Spanner クライアント ライブラリを使用します。

コマンドラインから

データベースにインデックスを追加するには、次の CREATE INDEX コマンドを使用します。

gcloud spanner databases ddl update example-db --instance=test-instance \
    --ddl='CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)'

以下のように表示されます。

Schema updating...done.

Go 用の Cloud Spanner クライアント ライブラリの使用

インデックスを追加するには、UpdateDatabaseDdl() を使用します。


import (
	"context"
	"fmt"
	"io"

	database "cloud.google.com/go/spanner/admin/database/apiv1"
	adminpb "google.golang.org/genproto/googleapis/spanner/admin/database/v1"
)

func addIndex(w io.Writer, db string) error {
	ctx := context.Background()
	adminClient, err := database.NewDatabaseAdminClient(ctx)
	if err != nil {
		return err
	}
	defer adminClient.Close()

	op, err := adminClient.UpdateDatabaseDdl(ctx, &adminpb.UpdateDatabaseDdlRequest{
		Database: db,
		Statements: []string{
			"CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)",
		},
	})
	if err != nil {
		return err
	}
	if err := op.Wait(ctx); err != nil {
		return err
	}
	fmt.Fprintf(w, "Added index\n")
	return nil
}

インデックスの追加には数分かかる場合があります。インデックスが追加されると、次のように表示されます。

Added index

インデックスを使用して読み取りを行う

SQL クエリの場合、Cloud Spanner により適切なインデックスが自動的に使用されます。読み取りインターフェースでは、リクエストでインデックスを指定する必要があります。

読み取りインターフェースでインデックスを使用するには、ReadOnlyTransaction.ReadUsingIndex() を使用し、インデックスを指定してデータベースから 0 行以上の値を読み取ります。

次のコードでは、インデックス AlbumsByAlbumTitle から AlbumId および AlbumTitle 列をすべて取得しています。


import (
	"context"
	"fmt"
	"io"

	"cloud.google.com/go/spanner"
	"google.golang.org/api/iterator"
)

func readUsingIndex(w io.Writer, db string) error {
	ctx := context.Background()
	client, err := spanner.NewClient(ctx, db)
	if err != nil {
		return err
	}
	defer client.Close()

	iter := client.Single().ReadUsingIndex(ctx, "Albums", "AlbumsByAlbumTitle", spanner.AllKeys(),
		[]string{"AlbumId", "AlbumTitle"})
	defer iter.Stop()
	for {
		row, err := iter.Next()
		if err == iterator.Done {
			return nil
		}
		if err != nil {
			return err
		}
		var albumID int64
		var albumTitle string
		if err := row.Columns(&albumID, &albumTitle); err != nil {
			return err
		}
		fmt.Fprintf(w, "%d %s\n", albumID, albumTitle)
	}
}

readindex 引数を使用してサンプルを実行します。

go run snippet.go readindex projects/$GCLOUD_PROJECT/instances/test-instance/databases/example-db

以下のように表示されます。

2 Forever Hold Your Peace
2 Go, Go, Go
1 Green
3 Terrified
1 Total Junk

STORING 句を使用してインデックスを追加する

上記の読み取り例では、MarketingBudget 列の読み取りが含まれていませんでした。これは、Cloud Spanner の読み取りインターフェースが、インデックスとデータテーブルを結合してインデックスに格納されていない値を検索する機能をサポートしていないためです。

MarketingBudget のコピーをインデックスに格納する AlbumsByAlbumTitle の代替定義を作成します。

コマンドラインから

gcloud spanner databases ddl update example-db --instance=test-instance \
    --ddl='CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) STORING (MarketingBudget)'

インデックスの追加には数分かかる場合があります。インデックスの追加が済むと、次のように表示されます。

Schema updating...done.

Go 用の Cloud Spanner クライアント ライブラリの使用

UpdateDatabaseDdl() を使用し、STORING 句を指定してインデックスを追加します。


import (
	"context"
	"fmt"
	"io"

	database "cloud.google.com/go/spanner/admin/database/apiv1"
	adminpb "google.golang.org/genproto/googleapis/spanner/admin/database/v1"
)

func addStoringIndex(w io.Writer, db string) error {
	ctx := context.Background()
	adminClient, err := database.NewDatabaseAdminClient(ctx)
	if err != nil {
		return err
	}
	defer adminClient.Close()

	op, err := adminClient.UpdateDatabaseDdl(ctx, &adminpb.UpdateDatabaseDdlRequest{
		Database: db,
		Statements: []string{
			"CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) STORING (MarketingBudget)",
		},
	})
	if err != nil {
		return err
	}
	if err := op.Wait(ctx); err != nil {
		return err
	}
	fmt.Fprintf(w, "Added storing index\n")
	return nil
}

addstoringindex 引数を使用してサンプルを実行します。

go run snippet.go addstoringindex projects/$GCLOUD_PROJECT/instances/test-instance/databases/example-db

インデックスの追加には数分かかる場合があります。インデックスが追加されると、次のように表示されます。

Added storing index

これで、インデックス AlbumsByAlbumTitle2 から AlbumIdAlbumTitleMarketingBudget 列をすべて取得する読み取りを実行できるようになりました。


import (
	"context"
	"fmt"
	"io"
	"strconv"

	"cloud.google.com/go/spanner"
	"google.golang.org/api/iterator"
)

func readStoringIndex(w io.Writer, db string) error {
	ctx := context.Background()
	client, err := spanner.NewClient(ctx, db)
	if err != nil {
		return err
	}
	defer client.Close()

	iter := client.Single().ReadUsingIndex(ctx, "Albums", "AlbumsByAlbumTitle2", spanner.AllKeys(),
		[]string{"AlbumId", "AlbumTitle", "MarketingBudget"})
	defer iter.Stop()
	for {
		row, err := iter.Next()
		if err == iterator.Done {
			return nil
		}
		if err != nil {
			return err
		}
		var albumID int64
		var marketingBudget spanner.NullInt64
		var albumTitle string
		if err := row.Columns(&albumID, &albumTitle, &marketingBudget); err != nil {
			return err
		}
		budget := "NULL"
		if marketingBudget.Valid {
			budget = strconv.FormatInt(marketingBudget.Int64, 10)
		}
		fmt.Fprintf(w, "%d %s %s\n", albumID, albumTitle, budget)
	}
}

readstoringindex 引数を使用してサンプルを実行します。

go run snippet.go readstoringindex projects/$GCLOUD_PROJECT/instances/test-instance/databases/example-db

次のような出力が表示されます。

2 Forever Hold Your Peace 300000
2 Go, Go, Go NULL
1 Green NULL
3 Terrified NULL
1 Total Junk 300000

読み取り専用トランザクションを使用したデータの取得

同じタイムスタンプで複数の読み取りを実行する場合について考えます。読み取り専用トランザクションはトランザクションの commit 履歴で整合性のあるプレフィックスを監視しているため、アプリケーションは常に整合性のあるデータを取得できます。 読み取り専用トランザクションを実行するには、ReadOnlyTransaction タイプを使用します。Client.ReadOnlyTransaction() を使用して ReadOnlyTransaction を取得します。

同じ読み取り専用トランザクションでクエリと読み取りを実行する方法を次に示します。


import (
	"context"
	"fmt"
	"io"

	"cloud.google.com/go/spanner"
	"google.golang.org/api/iterator"
)

func readOnlyTransaction(w io.Writer, db string) error {
	ctx := context.Background()
	client, err := spanner.NewClient(ctx, db)
	if err != nil {
		return err
	}
	defer client.Close()

	ro := client.ReadOnlyTransaction()
	defer ro.Close()
	stmt := spanner.Statement{SQL: `SELECT SingerId, AlbumId, AlbumTitle FROM Albums`}
	iter := ro.Query(ctx, stmt)
	defer iter.Stop()
	for {
		row, err := iter.Next()
		if err == iterator.Done {
			break
		}
		if err != nil {
			return err
		}
		var singerID int64
		var albumID int64
		var albumTitle string
		if err := row.Columns(&singerID, &albumID, &albumTitle); err != nil {
			return err
		}
		fmt.Fprintf(w, "%d %d %s\n", singerID, albumID, albumTitle)
	}

	iter = ro.Read(ctx, "Albums", spanner.AllKeys(), []string{"SingerId", "AlbumId", "AlbumTitle"})
	defer iter.Stop()
	for {
		row, err := iter.Next()
		if err == iterator.Done {
			return nil
		}
		if err != nil {
			return err
		}
		var singerID int64
		var albumID int64
		var albumTitle string
		if err := row.Columns(&singerID, &albumID, &albumTitle); err != nil {
			return err
		}
		fmt.Fprintf(w, "%d %d %s\n", singerID, albumID, albumTitle)
	}
}

readonlytransaction 引数を使用してサンプルを実行します。

go run snippet.go readonlytransaction projects/$GCLOUD_PROJECT/instances/test-instance/databases/example-db

次のような出力が表示されます。

2 2 Forever Hold Your Peace
1 2 Go, Go, Go
2 1 Green
2 3 Terrified
1 1 Total Junk
1 1 Total Junk
1 2 Go, Go, Go
2 1 Green
2 2 Forever Hold Your Peace
2 3 Terrified

クリーンアップ

このチュートリアルで使用したリソースについて Cloud 請求先アカウントに課金されないようにするため、作成したデータベースとインスタンスを削除します。

データベースの削除

インスタンスを削除すると、それに含まれるすべてのデータベースが自動的に削除されます。このステップでは、インスタンスを削除しないでデータベースを削除する方法を示します(インスタンスの料金は引き続き発生します)。

コマンドラインから

gcloud spanner databases delete example-db --instance=test-instance

Cloud Console を使用する

  1. Google Cloud Console で、Cloud Spanner の [インスタンス] ページに移動します。

    インスタンス ページに移動

  2. インスタンスをクリックします。

  3. 削除するデータベースをクリックします。

  4. [データベースの詳細] ページで [削除] をクリックします。

  5. データベースを削除することを確認し、[削除] をクリックします。

インスタンスの削除

インスタンスを削除すると、そのインスタンスで作成されたすべてのデータベースが自動的に削除されます。

コマンドラインから

gcloud spanner instances delete test-instance

Cloud Console を使用する

  1. Google Cloud Console で、Cloud Spanner の [インスタンス] ページに移動します。

    インスタンス ページに移動

  2. インスタンスをクリックします。

  3. [削除] をクリックします。

  4. インスタンスを削除することを確認し、[削除] をクリックします。

次のステップ

  • 仮想マシン インスタンスで Cloud Spanner にアクセスする。Cloud Spanner データベースにアクセスできる仮想マシン インスタンスを作成します。
  • 認証の開始で、承認と認証情報について学習する。
  • Cloud Spanner の概念を学習します。