GridDBのGo言語 Client入門

はじめに

GridDBは、Googleのプログラミング言語Go 用のデータベースコネクタをリリースしました。 Python Ruby のように、 SWIG ライブラリを使用して構築されています。このコネクタは、Go 1.9以上のバージョンをサポートしており、CentOSバージョン6および7で動作します。
Go言語 は、Google によって開発されたオープンソースのプログラミング言語です。並列処理と並行処理のために、 goroutines と呼ばれる軽量スレッドを使用して最適化されています。 これにより、Go言語を、GridDBのような高速メモリ内の指向データベースと組み合わせた高性能プログラミング言語にすることができます。

このブログでは、Go client構築のためのGridDB用のデータベースコネクタの設定とテストに関する一般的な手順を説明します。

クライアントへのインストール&セットアップ

まずはGo APIのGithubリポジトリをクローン作成してください。

$ git clone https://github.com/griddb/go_client

システムにGo clientを構築するには、 GridDB C clientを構築し、 インストールする必要があります。 C clientの設定とテストの方法については、このブログ記事を参照してください。

  • 注意: make してGo clientをシステムに構築する前に、Go言語 バージョン1.9以上を使用していることを確認してください。 Go言語バージョン1.9の入手方法と設定方法については、このページを参照してください。

Githubからソースコードを入手したら、Go ClientのGithub pageに従って進めてください。

Go clientの構築時に起こりうる問題

場合によっては、Go clientパッケージで make を発行するときに次のようなエラーが発生することがあります。

go install github.com/griddb/go_client
can't load package: package github.com/griddb/go_client: 
  cannot find package "github.com/griddb/go_client"

これは、 GOPATH または GODEBUG 環境変数の問題が原因である可能性があります。 修正するには、GridDB Go clientプロジェクトディレクトリをクリーンアップしてから、適切な値に設定します。

$ make clean
$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:<Path to GridDB c_client bin/ directory>
$ export GOPATH=$GOPATH:<Path to go_client directory>
$ export GODEBUG=cgocheck=0
$ make

NOTE: 予期しない実行時エラーを防ぐため、常に GODEBUG cgocheck = 0 にしてください。

Go言語を使ってGridDBに接続する

Go言語プログラムでGridDBに接続する場合は、 import statement ( "github.com/griddb/go_client" )を使用してGo APIをインポートします。 Go言語のGridDBパッケージの名前は griddb_go です。

そこから、GridDBライブラリから StoreFactoryInstance を取得し、それをFactoryインスタンスの.GetStore()メソッドを使ってGridDBクラスタに接続することができます。 このメソッドに与えるパラメータは、map[string]interface{}{} オブジェクト(文字列キーを持つ HashMapまたはPython dictionaryに相当)です。キーは、GridDBに接続するための設定フィールドです。
ポートは整数として指定する必要がありますが、ホスト名やクラスタ名などのその他のデータベース設定値はすべて文字列にすることができます。

package main
import (
	"github.com/griddb/go_client"
	"fmt"
	"os"
	"strconv"
)

func main(){
	factory := griddb_go.StoreFactoryGetInstance() //Get a GridStoreFactory instance

        //The 'notificationPort' value to connect to GridDB must be an integer
        //when using the StoreFactory.GetStore() method to connect to GridDB

        //Read notification-port from command-line
        //Parse port reading to integer
	port, err := strconv.Atoi(os.Args[2]) 

	if err != nil {
		fmt.Println(err)
		os.Exit(2)
	}        

        //Connect to GridDB cluster
        //Note that port must be an integer
        gridstore := factory.GetStore(map[string]interface{}{
                "host": os.Args[1], // "239.0.0.1"
                "port": os.Args[2], // 31999
                "cluster_name": os.Args[3], //"defaultCluster"
                "user": os.Args[4], // "admin"
                "password": os.Args[5], // "admin"
                //"notification_member": os.Args[6],
                //"notification_provide": os.Args[7],
        })

        //Close access to GridDB
        gridstore.Close(0)
        //Note that you must specify an integer flag when using the .Close() function call
}

スキーマとコンテナの作成

コンテナと行のスキーマは、Go言語のインターフェースを使って作成できます。

コンテナの列タイプをマップするには、インターフェイスの 2次元配列を使用します。 そこからStoreFactory.CreateContainerInfo()メソッドを使用してコンテナスキーマ( ContainerInfo Type )を作成できます。 これらのスキーマを使用して挿入すると、コンテナを作成できます。

はインターフェース配列( [] interface {} )としてモデル化できます。 行を作成するには、インターフェイス配列を作成し、各インデックスの値をコンテナに格納する予定の対応する型にマップさせます。
たとえば、コンテナ内で2番目の列に 'count' という名前の整数が格納されている場合、配列row := []interface{}{"A_1",3,true} index 1 の値3 'count' 列にマップされます。

//Container schema for our timeseries container
//The first column is always the row-key
row_schema := [][]interface{}{
		{"timestamp",griddb_go.TYPE_TIMESTAMP},
		{"water_temperature",griddb_go.TYPE_FLOAT},
		{"turbidity",griddb_go.TYPE_FLOAT},
		{"depth",griddb_go.TYPE_FLOAT},
		{"wave_height",griddb_go.TYPE_FLOAT},
		{"wave_period",griddb_go.TYPE_FLOAT},
		{"battery_life",griddb_go.TYPE_FLOAT},
		{"beach_name",griddb_go.TYPE_STRING}}

timeseries_name := "water_monitor_ts_2" //Name for our container
//Create a container info to use to insert containers into GridDB
containerInfo, err := griddb_go.CreateContainerInfo(timeseries_name,
                        row_schema,
                        griddb_go.CONTAINER_TIME_SERIES,
                        true)

timeseries := gridstore.PutContainer(containerInfo,false)

//Use an interface array to create and insert rows
row := []interface{}{time.Now().UTC(),16.2,1.26,1.514,0.147,4.0,11.7,"Calumet Beach"}
// You can also initialize an empty row with ( row := make([]interface{},8) )

//Insert row into Timeseries container
err = timeseries.Put(row)

行キーを使用しコンテナの .Get()メソッドを呼び出すことで、簡単にを取得することもできます。 前述のように、行は単なる一般的なGo言語 インターフェースの配列であり、列の行インデックス を参照するだけで、検索する列の値にアクセスすることができます。。

たとえば、コンテナのスキーマの 4番目の列であるfloat型の '経度' 列のコンテナを作成するとしましょう。 経度列にアクセスするためにこのコンテナから行を取得するには、index 4 にアクセスするだけです。

また、行キーでもコンテナ内の行を削除することもできます。

 //Schema of our collection container
schema, err := griddb_go.CreateContainerInfo(collection_name,[][]interface{}{
		{"sid",griddb_go.TYPE_STRING},
		{"name",griddb_go.TYPE_STRING},
		{"type",griddb_go.TYPE_STRING},
		{"latitude",griddb_go.TYPE_DOUBLE},
		{"longitude",griddb_go.TYPE_DOUBLE},
		{"installation",griddb_go.TYPE_TIMESTAMP}},
		griddb_go.CONTAINER_COLLECTION,
		true)
       
collection, err := gridstore.PutContainer(schema,false)
// (snip)

//Find a row in the collection container with row-key ('sid') "W_1003"
retrieved, err := collection.Get("W_1003")
if len(retrieved) > 0 {
                //Access and display longitude column of row (is column 4 in row)
                fmt.Printf("Longitude value of row with row-key %s is %f\n","W_1003",retrieved[4]
}

//We can also remove the "W_1003" row by calling .remove()
collection.Remove("W_1003")

//Attempt to access row again, this should return an empty row (an empty array) if the removal was successful
attempt, err := collection.Get("W_1003")
if len(attempt) == 0 {
                fmt.Printf("Row with key %s removed successfully\n","W_1003")
}

データのクエリと取得

コンテナにデータが格納され、GridDBに挿入されると、データのクエリとフェッチが可能になります。 PythonやJava APIと同様に、コンテナに発行したいTQLクエリを使って Queryオブジェクト(または griddb_go.Query 型)を構築するだけです。 これが完了したら、クエリの結果を取得して griddb_go.RowSet オブジェクトに格納します。

  • :更新する特定の行を選択する場合は、更新しようとしているコンテナのコミットモード false に設定してください。

griddb_go.Container.Put()またはgriddb_go.RowSet.Update()メソッドを使用して行の値を更新し終えたら、変更をCommit()します。

標準TQLクエリ

//Set commit-mode to false so you can update the rows
collection.SetAutoCommit(false)

//Search for a row with row-key that ends in '2' and has a column value of 'weather' in the type column
query,err := collection.Query("select * WHERE CHAR_LENGTH(sid) = 6 AND sid LIKE 'W%2' AND LOWER(type) = 'weather'")

//Fetch the rows and make them available for update
rowSet,err := query.Fetch(true)

for rowSet.HasNext(){
        //Retrieve and iterate through the rowset row-by-row
	rrow, err := rowSet.NextRow()
	if err != nil{
		fmt.Println("Error retrieving row")	
	}

        //Update the fourth column value of the row
	rrow[4] = -88.0

        //Save updated row back to the rowSet
	rowSet.Update(rrow)
}

//Commit and save the updated RowSet back to the container
collection.Commit()

集計TQL

また、 TQL を使用して集約クエリを作成し発行することもできます。 コンテナからgriddb_go.AggregationResultを実行してフェッチする方法は、Python APIによく似ています。 rowSet.NextRow()を使わなくても、.NextAggregation()に変更するだけでクエリから取得した RowSet を取得することができます。 その AggregationResult オブジェクトでは、必要な番号の型を取得するために.Get()を使用します。

column := "turbidity"
aggQuery, err := timeseries.Query(fmt.Sprintf("SELECT TIME_AVG(%s)",column)) //Get the time-weighted average of all turbidity values in our container
if err != nil {
	fmt.Printf("Failed to issue query to timeseries container %s\n",container_name)
	fmt.Println(err)
	os.Exit(2)
}

aggRowSet, err := aggQuery.Fetch(false) //Issue query and fetch results
if err != nil {
	fmt.Printf("Error retrieving query results from timeseries container %s\n",container_name)
	fmt.Println(err)
	os.Exit(2)
}


for aggRowSet.HasNext(){
	aggregation, err := aggRowSet.NextAggregation() //Get the aggregation result from the rowset.

	if err != nil {
		fmt.Println("Failed to get aggregation result!")
	} else {
		avg, err := aggregation.Get(griddb_go.TYPE_DOUBLE) //Get the aggregation result's numerical value 
		if err != nil {
			fmt.Printf("Error parsing aggreagtion result\n")
		}

		fmt.Printf("The time average for the %s column in the %s container is %f\n",column,container_name,avg)
	}
}

タイムスタンプとバイナリデータの処理

タイムスタンプデータ

GridDBのGo言語clientは、タイムスタンプ操作のための他のAPI client(Python、C)と同じ機能を提供します。 GridDBでは時刻または日付の値をGridDBコンテナに挿入するために、その値をGo言語で Time.time 型にキャストする必要があることに注意してください。

つまり、タイムスタンプ値を純粋な数値として挿入することはできません。 タイムスタンプ値が行キー列にあり、データがコンテナに挿入されない場合、container.Put(row)の呼び出しができません。

もう一つの注意点は、GridDBタイムスタンプがミリ秒精度を持つことです。

GridDBタイムスタンプ番号をタイプ time.Time に変換するには、タイムスタンプ番号をナノ秒に変換し、 time.Unix ()を使用してtime型に変換してください。

import (
     "time"
     "github.com/griddb/go_client"
)

// (snip)

//A way to express the current time as a time Type in Golang 
now := time.Now().UTC() //This has less precision than GridDB TimestampUtils

// GridDB method of getting the current time as a number
now_ts :=  time.Unix(griddb_go.TimestampUtilsCurrent() * 1000000)// The number is then converted into a UTC time

// Converting GridDB to Time.time values allows for precision and lowers the chance that 
// a row will inadvertently replace another row with the same row-key.
row := []interface{}{now_ts.UTC(),true,5.6}
err = timeseries.Put(row) 

バイナリまたはBlobデータ

GridDBでは、バイナリまたは BLOB データは、 bytearray として表すことができます。 画像データや大きなバッファを読み込まなければならない場合は、空のバイト配列または [] byte を割り当ててください。

parseBlob の下の関数は、画像データを bytearray に読み込みます。

func parseBlob(filename string) (blob []byte){
	file, err := os.Open(filename) //Open image file
	if err != nil {
		fmt.Printf("Error opening file %s\n",filename)
		fmt.Println(err)
		os.Exit(1)
	}

	defer file.Close()

	fileInfo, _ := file.Stat() //Get meta-information related to the image file
	var size int64 = fileInfo.Size() //Obtain file size
	
	outputStream := make([]byte,size) //Allocate a byte array that is the size of the image's data
	buffer := bufio.NewReader(file) //Create a file reader for reading byte data from image file
	_, err = buffer.Read(outputStream) //Read image file's data into output byte array

	return outputStream
}

これにより、 parseBlob のデータを BLOB 列の値に使用することができるようになります。

image_blob := parseBlob("liveimage1.jpg") //Get bytearray for image file's data
now := time.Now().UTC() //Obtain current time

//Create a Row containing our BLOB data (byte array) and insert it into Timeseries container
first_row := []interface{}{now,"Bright Sky",500,"liveimage1.jpg",image_blob)
err = timeseries.Put(first_row)

blob :=  []byte{65, 66, 67, 68, 69, 70, 71, 72, 73, 74} //Create Blob as a simple byte array
third_row := []interface{}{time.Now().UTC(),"Generic",10,"NULL",blob} 

//Create and insert GridDB row containing smaller byte array
err = timeseries.Put(third_row)

結論

Go言語を使用してGridDBクラスタを接続、管理するのは非常に簡単であることがお分かりいただけたでしょうか。 GridDB Go clientが提供するさまざまなユーティリティをすべて使用することで、GridDBを使ったデータの保存や高性能Goアプリケーションの開発は、とても面白いものとなります。

参照

  • CentOS 7.3オペレーティングシステム上で、Go言語 version 1.9.2 linux/amd64を使用しました。
  • GridDBデータベースは、GridDB Community Edition 3.0.1 を使用しました。
  • GridDB Go clientのソースコードについては、official Github repositoryをご参照ください。
  • このリファレンスページ に、Go clientからアクセスできるGridDBのすべてのオブジェクトとリソースが載っています。
  • GridDBのGo libraryが構築された後は、griddb_goパッケージで使用されるすべてのメソッドと属性は src/griddb_go.goファイルに保存されます。
  • Go言語のより詳しい使い方については、official documentationをご参照ください。

ソースコード

このブログで使用されているすべてのソースコードは、以下からダウンロードできます。

ブログの内容について疑問や質問がある場合は Q&A サイトである Stack Overflow に質問を投稿しましょう。 GridDB 開発者やエンジニアから速やかな回答が得られるようにするためにも "griddb" タグをつけることをお忘れなく。 https://stackoverflow.com/questions/ask?tags=griddb

Leave a Reply

Your email address will not be published. Required fields are marked *