これはGo Advent Calendar 2020の8日目の記事です。
先日業務の中でElasticSearchを利用する機会があり、elasticがサポートしている公式のGoクライアントをその際にあまり日本語でまとまっていた情報がなかったので、これを機にまとめてみようと思います。
go-elasticsearch
https://github.com/elastic/go-elasticsearch
概要
この公式ライブラリは2019年にリリースされた比較的新しいもので、Elasticの公式のクライアントとして認定され、メンテナンスされています。
https://www.elastic.co/guide/en/elasticsearch/client/index.html
go-elasticsearchクライアントはバージョン6系と7系がありますが、これはそれぞれElasticSearchの6系、7系に対応するものになっているので、使用するElasticSearchのバージョンに合わせて利用するライブラリのバージョンは決定してください。
使い方
Client作成
クライアント作成は2パターンあります。まずNewDefaultClientです。こちらは引数を取らないものですが、 ELASTICSEARCH_URLという環境変数にElasticSearchのエンドポイントURLを入れておくことで自動で設定してくれます。
elasticsearch.NewDefaultClient()
elasticsearch.NewClient(Config)は色々とオプションを追加できるクライアントの作成方法になります。Elastic Cloudなどを利用する場合はアドレスではなく、IDでも接続することができます。この場合はELASTICSEARCH_URLに設定された環境変数は無視されます。
CACertで証明書、RetryOnStatusでリトライするStatusの定義なども盛り込むことが可能です。
cert, _ := ioutil.ReadFile("path/to/ca.crt")
cfg := elasticsearch.Config{
Addresses: []string{
"http://localhost:9200",
"http://localhost:9201",
},
Username: "foo",
Password: "bar",
RetryOnStatus: []int{429, 502, 503, 504},
CACert: cert,
Transport: &http.Transport{
MaxIdleConnsPerHost: 10,
ResponseHeaderTimeout: time.Second,
DialContext: (&net.Dialer{Timeout: time.Second}).DialContext,
TLSClientConfig: &tls.Config{
MinVersion: tls.VersionTLS11,
},
},
}
elasticsearch.NewClient(cfg)
Search
検索サジェストなどで用いるSearchは以下のように使用します。
var buf bytes.Buffer
query := map[string]interface{}{
"query": map[string]interface{}{
"match": map[string]interface{}{
"title": "test",
},
},
}
if err := json.NewEncoder(&buf).Encode(query); err != nil {
log.Fatalf("Error encoding query: %s", err)
}
// Perform the search request.
res, err = es.Search(
es.Search.WithContext(context.Background()),
es.Search.WithIndex("test"),
es.Search.WithBody(&buf),
es.Search.WithTrackTotalHits(true),
es.Search.WithPretty(),
)
if err != nil {
log.Fatalf("Error getting response: %s", err)
}
defer res.Body.Close()
if res.IsError() {
var e map[string]interface{}
if err := json.NewDecoder(res.Body).Decode(&e); err != nil {
log.Fatalf("Error parsing the response body: %s", err)
} else {
// Print the response status and error information.
log.Fatalf("[%s] %s: %s",
res.Status(),
e["error"].(map[string]interface{})["type"],
e["error"].(map[string]interface{})["reason"],
)
}
}
少し複雑ですが、queryに実際のリクエストで投げるJsonの構造体を参考にmap[string]interface()を定義して、検索する文字列を入れます。
HTTPでリクエスト送る際のJsonBodyがこんな感じだと
{
"size": 5,
"query": {
"bool": {
"should": [{
"match": {
"word.autocomplete": {
"query": "え"
}
}
}, {
"match": {
"word.readingform": {
"query": "え",
"fuzziness": "AUTO",
"operator": "and"
}
}
}]
}
},
}'
Goで定義するクエリはこんな感じになります。なかなか複雑ですね・・・。
query := map[string]interface{}{
"query": map[string]interface{}{
"bool": map[string]interface{}{
"should": []map[string]interface{}{
{
"match": map[string]interface{}{
"word.autocomplete": map[string]interface{}{
"query": normalized,
},
},
},
{
"match": map[string]interface{}{
"word.readingform": map[string]interface{}{
"query": normalized,
"fuzziness": "AUTO",
"operator": "and",
},
},
},
},
},
},
}
そしてその後それをjsonにエンコードし、Searchメソッドの引数にWithBody内に入れ、Searchメソッドを叩きます。
if err := json.NewEncoder(&buf).Encode(query); err != nil {
log.Fatalf("Error encoding query: %s", err)
}
// Perform the search request.
res, err = es.Search(
es.Search.WithContext(context.Background()),
es.Search.WithIndex("test"),
es.Search.WithBody(&buf),
es.Search.WithTrackTotalHits(true),
es.Search.WithPretty(),
)
他にも多くの引数があることが見て取れます。withSortなどを用いるとSortなども可能となっています。
Searchメソッドのレスポンスはhttp.Responseのラッパーなようになっています。また、IsError()メソットで500エラーなどの判定をすることが可能です、
Searchなどとは異なり、IndexやCreate,Updateは比較的シンプルに記載することができます。基本的にそれぞれのXXRequestという型がgo-elasticのesapiパッケージに用意されているため、そこにリクエストする値を入れてDoメソッドを叩く形になります。
ここもレスポンスはIsErrorでチェックしてあげてください。
tag := Sample{
ID: id,
Name: name,
}
reqByte, err := json.Marshal(tag)
if err != nil {
return err
}
requestReader := bytes.NewReader(reqByte)
req := esapi.CreateRequest{
Body: requestReader,
Pretty: true,
}
res, err := req.Do(ctx, r.client)
if err != nil {
return xerrors.Errorf("failed to update with elastic search. %w", err)
}
if res.IsError() {
return xerrors.Errorf("failed to update with elastic search. Not ok. %s", res.Status())
}
defer res.Body.Close()
様々なオプションもその型の中で定義することが可能です。試しにUpdateRequest型を見てみましょう。基本的なリクエストのボディをBodyに格納する形になりますがHeaderやPrettyなど様々なオプションの定義ができることがみて取れますね。
type UpdateRequest struct {
Index string
DocumentType string
DocumentID string
Body io.Reader
Fields []string
IfPrimaryTerm *int
IfSeqNo *int
Lang string
Parent string
Refresh string
RetryOnConflict *int
Routing string
Source []string
SourceExcludes []string
SourceIncludes []string
Timeout time.Duration
Version *int
VersionType string
WaitForActiveShards string
Pretty bool
Human bool
ErrorTrace bool
FilterPath []string
Header http.Header
ctx context.Context
}
以上がelasticがサポートするElasticSearchのGoクライアントの紹介になりました。
少しコード量が多くなってしまう場合もありますが、公式がメンテをしてくれることもあり安心して利用のできるライブラリなので使って損はないと思います。