🔍
【Go, Elasticsearch】ElasticSearchクライアント olivere を使ってみる(CRUD)
本記事は Go Advent Calendar 2021 の12日目の記事です。
はじめに
業務でElasticsearchを利用しているのですが、ライブラリをgo-elasticsearch
からoliver
に変えようという話があり、気になって少し調べたので、アウトプットの為に記事にします。
※Elasticsearchの一番重要な検索機能Searchについては次回の記事にまとめる予定で、今回はCRUDのみです。
前提
- ElasticSearchについて細かくはこちらでは説明しません。詳細は、公式をご覧ください。
- go modを使用します。
- Dockerを使用します。
環境
- go 1.17
- github.com/olivere/elastic/v7 v7.0.29
ソースコード
docker-compose.yml
今回はDockerを利用します。
version: '3'
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:7.4.0
environment:
- discovery.type=single-node
ports:
- 9200:9200
- 9300:9300
main.go
全てのメソッドをmain.goに定義しています。
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/olivere/elastic/v7"
)
type User struct {
Name string `json:"name"`
Address string `json:"address"`
Age int `json:"age"`
Image string `json:"image,omitempty"`
CreatedAt time.Time `json:"created,omitempty"`
Comment string `json:"comment,omitempty"`
}
func main() {
// clientを生成します
client, err := elastic.NewClient(elastic.SetSniff(false))
if err != nil {
log.Fatal(err)
}
// ESのversionが取得出来ます
version, err := client.ElasticsearchVersion("http://127.0.0.1:9200")
if err != nil {
// Handle error
panic(err)
}
fmt.Printf("Elasticsearch version %s\n", version)
// IndexExists 該当のindexが存在するかを確認します
exists, err := client.IndexExists("user").Do(context.Background())
if err != nil {
panic(err)
}
mapping := ""
// もし存在しなかったら新しいindexを作る為の値を用意します
if !exists {
mapping = `{
"settings":{
"number_of_shards":1,
"number_of_replicas":0
},
"mappings":{
"doc":{
"properties":{
"name":{
"type":"keyword"
},
"address":{
"type":"text",
"store": true,
"fielddata": true
},
"age":{
"type":"long"
}
}
}
}
}`
}
// CreateIndex indexを作成します
createIndex(client, mapping)
// put indexに追加します
put(client)
// get indexから取得します
get(client)
// update indexを更新します
update(client)
// updateした後にもう一度getします
get(client)
//delete indexを削除します
delete(client)
}
// get indexから取得します
func get(client *elastic.Client) {
get1, err := client.Get().
Index("user").
Type("doc").
Id("1").
Do(context.Background())
if err != nil {
switch {
case elastic.IsNotFound(err):
panic(fmt.Sprintf("Document not found: %v", err))
case elastic.IsTimeout(err):
panic(fmt.Sprintf("Timeout retrieving document: %v", err))
case elastic.IsConnErr(err):
panic(fmt.Sprintf("Connection problem: %v", err))
default:
fmt.Println("getに失敗しました")
panic(err)
}
}
fmt.Printf("Got document %s in version %d from index %s, type %s\n", get1.Id, get1.Version, get1.Index, get1.Type)
// getした中身を確認する為にUser構造体へ変換します
var user User
err = json.Unmarshal(get1.Source, &user)
if err != nil {
fmt.Println(err)
}
// getしたuserを出力
fmt.Println("get1:", user)
}
// createIndex indexを作成します
func createIndex(client *elastic.Client, mapping string) {
createIndex, err := client.CreateIndex("user").Body(mapping).IncludeTypeName(true).Do(context.Background())
if err != nil {
fmt.Println("createIndexに失敗しました")
panic(err)
}
if !createIndex.Acknowledged {
fmt.Println("Not acknowledged")
}
}
// put indexに追加します
func put(client *elastic.Client) {
user1 := User{Name: "太郎", Address: "大阪府", Age: 20}
put1, err := client.Index().
Index("user").
Type("doc").
Id("1").
BodyJson(user1).
Do(context.Background())
if err != nil {
fmt.Println("putに失敗しました")
panic(err)
}
fmt.Printf("Indexed user %s to index %s, type %s\n", put1.Id, put1.Index, put1.Type)
user2 := `{"name" : "太郎", "image" : "写真.png"}`
put2, err := client.Index().
Index("user").
Type("doc").
Id("2").
BodyString(user2).
Do(context.Background())
if err != nil {
fmt.Println("putに失敗しました")
panic(err)
}
fmt.Printf("Indexed user %s to index %s, type %s\n", put2.Id, put2.Index, put2.Type)
}
// update indexの1つの値を更新します
func update(client *elastic.Client) {
script := elastic.NewScript("ctx._source.age += params.num").Param("num", 1)
update, err := client.Update().Index("user").Type("doc").Id("1").
Script(script).
Upsert(map[string]interface{}{"age": 20}).
Do(context.Background())
if err != nil {
fmt.Println("updateに失敗しました")
panic(err)
}
fmt.Printf("New version of user %q is now %d", update.Id, update.Version)
}
// indexを削除します
func delete(client *elastic.Client) {
deleteIndex, err := client.DeleteIndex("user").Do(context.Background())
if err != nil {
fmt.Println("deleteに失敗しました")
panic(err)
}
if !deleteIndex.Acknowledged {
fmt.Println("Not acknowledged")
}
fmt.Println(deleteIndex)
}
出力結果
Elasticsearch version 7.4.0
Indexed user 1 to index user, type doc
Indexed user 2 to index user, type doc
Got document 1 in version 824635831432 from index user, type doc
get1: {太郎 大阪府 20 0001-01-01 00:00:00 +0000 UTC }
New version of user "1" is now 2Got document 1 in version 824635831736 from index user, type doc
get1: {太郎 大阪府 21 0001-01-01 00:00:00 +0000 UTC }
解説
少しだけ解説します。
CreateIndex
indexを作成します。indexはテーブルのようなものです。
mappingはテーブル設計のようなものです。
mapping = `{
"settings":{
"number_of_shards":1,
"number_of_replicas":0
},
"mappings":{
"doc":{
"properties":{
"name":{
"type":"keyword"
},
"address":{
"type":"text",
"store": true,
"fielddata": true
},
"age":{
"type":"long"
}
}
}
}
}`
func createIndex(client *elastic.Client, mapping string) {
createIndex, err := client.CreateIndex("user").Body(mapping).IncludeTypeName(true).Do(context.Background())
if err != nil {
fmt.Println("createIndexに失敗しました")
panic(err)
}
if !createIndex.Acknowledged {
fmt.Println("Not acknowledged")
}
}
Get
indexから1つのdocument(レコードのようなもの)を取得します。
取得したものを表示する為、構造体へ変換しています。
func get(client *elastic.Client) {
get1, err := client.Get().
Index("user").
Type("doc").
Id("1").
Do(context.Background())
if err != nil {
switch {
case elastic.IsNotFound(err):
panic(fmt.Sprintf("Document not found: %v", err))
case elastic.IsTimeout(err):
panic(fmt.Sprintf("Timeout retrieving document: %v", err))
case elastic.IsConnErr(err):
panic(fmt.Sprintf("Connection problem: %v", err))
default:
fmt.Println("getに失敗しました")
panic(err)
}
}
fmt.Printf("Got document %s in version %d from index %s, type %s\n", get1.Id, get1.Version, get1.Index, get1.Type)
// getした中身を確認する為にUser構造体へ変換します
var user User
err = json.Unmarshal(get1.Source, &user)
if err != nil {
fmt.Println(err)
}
// getしたuserを出力
fmt.Println("get1:", user)
}
Index(Put)
client.Index
を利用する事でindexへput出来ます。
func put(client *elastic.Client) {
user1 := User{Name: "太郎", Address: "大阪府", Age: 20}
put1, err := client.Index().
Index("user").
Type("doc").
Id("1").
BodyJson(user1).
Do(context.Background())
if err != nil {
fmt.Println("putに失敗しました")
panic(err)
}
fmt.Printf("Indexed user %s to index %s, type %s\n", put1.Id, put1.Index, put1.Type)
user2 := `{"name" : "太郎", "image" : "写真.png"}`
put2, err := client.Index().
Index("user").
Type("doc").
Id("2").
BodyString(user2).
Do(context.Background())
if err != nil {
fmt.Println("putに失敗しました")
panic(err)
}
fmt.Printf("Indexed user %s to index %s, type %s\n", put2.Id, put2.Index, put2.Type)
}
Update
client.Update
でdocumentの値の更新が出来ます。
Paramにnum = 1
を渡して、ageに足す事で、Idが1のUserのageが1足されます。
func update(client *elastic.Client) {
script := elastic.NewScript("ctx._source.age += params.num").Param("num", 1)
update, err := client.Update().Index("user").Type("doc").Id("1").
Script(script).
Upsert(map[string]interface{}{"age": 20}).
Do(context.Background())
if err != nil {
fmt.Println("updateに失敗しました")
panic(err)
}
fmt.Printf("New version of user %q is now %d", update.Id, update.Version)
}
DeleteIndex
DeleteIndex
はindex(テーブル)を削除します。
これとは別にdocument(レコード)を削除するDeleteもあります。
func delete(client *elastic.Client) {
deleteIndex, err := client.DeleteIndex("user").Do(context.Background())
if err != nil {
fmt.Println("deleteに失敗しました")
panic(err)
}
if !deleteIndex.Acknowledged {
fmt.Println("Not acknowledged")
}
}
さいごに
- ES難しいですね。もっと仲良くなりたいと思いました。
参考
Discussion
ご存知かもしれませんが、ES v7系はあと一年ほどでEOLになること、olivereのv8対応があまり進んでないことなどから、公式版を継続された方が無難かとは思います。
コメントありがとうございます。
そうなんですね。この記事を書いていた時は社内でElasticSearchを使っていたのですが、諸事情があり使わなくなってしまいまして、情報収集出来ておりませんでした。
もしまたElasticSearchを使う際は、公式版を使用するように社内で進言しようと思います。
良いアドバイスをして頂き、ありがとうございます。