Elasticsearch の search template 一覧を取得する
Elsaticsearchでは、Search Templateという機能があります。
クエリの共通部分を事前に登録しておくことで、検索時のクエリ記述量を減らすことができるものです。
www.elastic.co
作成した Search Templateの一覧を取得するための API がなかったので、取得方法をメモ。
Search Templateの登録
POST _scripts/template_for_description_search { "script": { "lang": "mustache", "source": { "query": { "match": { "desctiption": "{{query_string}}" } } } } }
POST _scripts/template_for_title_search { "script": { "lang": "mustache", "source": { "query": { "match": { "title": "{{query_string}}" } } } } }
取得
スクリプトが Cluster State に保存されるのがポイントです。
GET _cluster/state?filter_path=metadata.stored_scripts
{ "metadata" : { "stored_scripts" : { "template_for_description_search" : { "lang" : "mustache", "source" : """{"query":{"match":{"desctiption":"{{query_string}}"}}}""", "options" : { "content_type" : "application/json; charset=UTF-8" } }, "template_for_title_search" : { "lang" : "mustache", "source" : """{"query":{"match":{"title":"{{query_string}}"}}}""", "options" : { "content_type" : "application/json; charset=UTF-8" } } } } }
以上
vimからelasticsearchのAPIを叩く方法メモ
大変長らく放置してしまいましたが、久しぶりに更新。
はじめに
vimで作業をしている時に、
ElasticsearchのAPIを叩きたくなることが良くありますよね。(ないか)
私の場合、「logstashでelasticsearchにデータを投入している傍ら、設定を編集する」といったことがよくあります。もちろんElasticsearchのAPIはKibanaのDevToolsや、curlコマンドなどでも簡単に実行できます。
とはいえ、できることならvimで作業をしながらシームレスに呼び出したい。
作りかけですが、一旦書き残しておきます。
※vim scriptをまともに書く機会がないので、お作法とかが全く分からない。 勉強がてらとりあえず書いてみたという感じなので悪しからず。
こんな感じにしたい↓
目次
1. 前提条件
2. 関数の作成
3. keymap
4. まとめ
1. 前提条件
2. 関数の作成
今回は主に vim-esutil/autoload/esutil/配下の、api.vimとutil.vimにフォーカスします。vim-esutil/plugin配下はキーマップしか書いていないので割愛します。
1. webapi実行部分(ユーティリティー)
ElasticsearchのAPIはplain textで結果が得られる _cat/* 系のものと、jsonで結果が得られるものの2種類に大別できます。
それぞれレスポンスの描画形式を変えたいので、API実行のユーティリティーを2つ用意しています。
※util.vimを一部抜粋
"結果がplain textのもの向け。レスポンスをquick fix windowで開く。 function! esutil#util#get(host, path, params) "ホストやパスを受けとってwebapi-vimの関数を呼ぶだけ let l:res = webapi#http#get(a:host. a:path. a:params) "quick fix windowに追記する for res in split(l:res.content, '\n') caddexpr res endfor copen endfunction "結果がjsonのもの向け。レスポンスをjqで成形し、新規タブで開く。 function! esutil#util#getjson(host, path, params) let l:res = webapi#http#get(a:host. a:path. a:params) tabnew call append(0, l:res.content) :%!jq . endfunction
2. API呼び出し
上記のutil.vimを呼び出すだけです。
それぞれの関数は可変長の引数を受け取って、l:hostなどを決定します。
# ここは、どう考えてももっと良いやり方があるはず。vim scriptわからぬ。
" _cat/nodesの結果を表示する function! esutil#api#catNodes(...) "quick fix listを空にする call setqflist([]) " 環境変数で定義されていればl:hostに設定、引数で指定されればそれを優先 if $ESUTIL_ESHOST != '' let l:host = get(a:, 1, $ESUTIL_ESHOST) " 環境変数・引数のいずれも定義されていなければ、localhost:9200に設定 else let l:host = get(a:, 1, 'localhost:9200') endif call esutil#util#get(l:host, '/_cat/nodes/', '?v') endfunction " _cat/indicesの結果を表示する function! esutil#api#catIndices(...) call setqflist([]) if $ESUTIL_ESHOST != '' let l:host = get(a:, 1, $ESUTIL_ESHOST) else let l:host = get(a:, 1, 'localhost:9200') endif let l:pattern = get(a:, 2, '*') call esutil#util#get(l:host, '/_cat/indices/'. l:pattern, '?v&s=index') endfunction function! esutil#api#search(...) "TODO: implement esutil#api#search() endfunction function! esutil#api#putIndex(...) "TODO: implement esutil#api#putIndex() endfunction
このファイルを保存すれば、vimから呼び出せるようになります。
:call esutil#api#catIndices()
3. keymap
ここまでで、やりたいこと自体は実現できるようになりました。
しかし、毎回 :call esutil#... と入力するのは骨が折れます。
私は.vimrcに下記の設定を入れています。
" Custom Key Mapping let mapleader = "\<Space>" noremap <Leader>escati :call esutil#api#catIndices()<CR> noremap <Leader>escatn :call esutil#api#catNodes()<CR>
この設定により、vimの実行中に" escati"と入力すれば、indexの一覧がquick fix windowで表示されます。めでたしめでたし。
※" escati"はesutilの_cat/indicesを短縮名にしたつもり。
elasticsearch6.xの親子関係を整理してみた
この記事はElastic stack Advent Calendar 2017 の17日目の記事となります。
概要
elasticsearchで、親子関係を持ったデータを扱うには、幾つかの方法があります。
例えば
- 親の情報を冗長に保持して、親子を1対1の関係にする
- object配列として子供を保持する
- nested data typeを用いる
- parent-childの関係を用いる
等があると思います。
elasticsearch6.0から、_typeがindexに対して単一になりました。(ver 7では_typeが無くなる予定)
それに伴って、parent-childの使い方も変わったので、改めて整理しておきます。
目次
- サンプルデータの概要
- 親の情報を冗長に保持して、親子を1対1の関係にする
- object data type
- nested data type
- join data type
- まとめ
サンプルデータの概要
ブログサービスのデータを扱うことを想定します。
(ダミーデータはhttp://www.databasetestdata.com/で生成させてもらいました)
親子関係にある2つのindexがあり、片方はユーザーの情報を表すuser index、
もう片方は、ブログの投稿単位でindexしたblog_post indexです。
ブログは一人のユーザーが複数回投稿しうるので、userとblog_postは1対Nの親子関係にあると言えます。
user情報を示すuser table。
fieldとしては、FullName,Id,Country,registered_time,Emailなどがあります。
1ドキュメント取り出してみると下記のような構造になっています。
{ "_index": "user", "_type": "doc", "_id": "QLwKYGABLuVBi7nej178", "_score": 1, "_source": { "Email": "Felicita@eliane.co.uk", "host": "Macintosh.local", "Country": "Gambia", "registered_time": "12/17/1985", "Id": "81", "@version": "1", "Full Name": "Ms. Judd Tromp", "@timestamp": "2017-12-16T15:56:32.767Z" } }
それに対して、blog_postの内容は下記のようになっています。
{ "_index": "blog_post", "_type": "doc", "_id": "jr09YGABLuVBi7neyjL2", "_score": 1, "_source": { "@version": "1", "Summary": """ facere nobis eum minima perspiciatis sit cum debitis commodi est quia distinctio molestiae ut praesentium autem occaecati nemo et asperiores itaque quis optio harum ratione ex vitae dignissimos consequatur vel aliquam accusantium doloribus optio sint provident praesentium sunt laboriosam ullam quo tempore ratione vel dolorum accusantium suscipit blanditiis distinctio unde dolores commodi recusandae numquam perspiciatis ducimus dicta blanditiis sed sint dolore unde eos distinctio facere veritatis vitae veniam qui harum voluptatem dolorem nam quam """, "Id": 3, "@timestamp": "2017-12-16T16:52:30.135Z", "Title": """ eius a quam sequi nulla unde et laboriosam perferendis ut id accusamus corporis ipsam quisquam vero """, "Created At": "2008-09-12T13:14:46.235Z", "host": "Macintosh.local", "Body": """ quia unde quod distinctio odit commodi odio non qui libero perferendis quos expedita voluptatem id odio ut consequatur minus sit odio est aspernatur vel numquam accusamus laborum eligendi dolore corporis voluptate itaque vel aut repudiandae enim saepe fuga voluptatem sed quis quia voluptatem eos in commodi nisi mollitia occaecati qui sint amet unde iusto enim qui voluptate recusandae dolores quia quam eum at ut et cupiditate odit dolores nulla voluptas """ } }
これらの二つのindexはId fieldで紐付いていて、user:blog_postsが1対Nになっています。
親の情報を冗長に保持して、親子を1対1の関係にする
ドキュメントの構造という観点で、この方法は非常にシンプルになります。
ただし、親ドキュメントの情報が冗長になるので、データサイズの増加や、検索時の結果重複などのデメリットが考えられます。
(今回はuserに比べてblog_postが大きかったので、ほとんどデータサイズは増えませんでした)
つまりデータ構造としては、
親ドキュメント
{"Id" : 10, "user" : "A"}
子ドキュメント
{"Id" : 10, "Title" : "aaa"}, {"Id" : 10, "Title" : "bbb"}, {"Id" : 10, "Title" : "ccc"}
という2つのindexをjoinした結果として、
{"user" : "A", "Id" : 10, "Title" : "aaa"}, {"user" : "A", "Id" : 10, "Title" : "bbb"}, {"user" : "A", "Id" : 10, "Title" : "ccc"}
という1つのindexに統合します。
一部のフィールドを省略していますが、userというfieldが冗長であることがわかります。
(user indexのサイズが大きくなるほどデータサイズなどへの影響が顕著です。)
検索時の結果重複という面でのデメリットは、次のような状況が想像できます。
例えば「Edytheという名前のユーザーが何人いるかを検索したい場合」です。
queryはこんな感じでしょうか
GET join/_search { "query": { "match": { "Full Name": "Edythe" } } }
レスポンスはこうです↓
"hits": { "total": 20, "max_score": 5.1217155, "hits":[...] }
これだけ見ると、Edytheさんは20人登録されているように見えますが、実際は1人しか登録されていません。
Edytheさんのブログ投稿が20件あったため、20件のドキュメントが"Full Name":"Edythe"という条件に当てはまりヒットしてしまいます。
object data type
上記を改善するために思いつく方法として、
ブログ投稿のデータであるblog_postを、配列形式で1つのドキュメントに詰め込む方法があります。
つまり下記のようなデータ構造です。
{ "_index": "object", "_type": "doc", "_id": "jL20YGABLuVBi7neE7yx", "_score": 1, "_source": { "Email": "Kendall@reymundo.me", "@timestamp": "2017-12-16T15:56:32.759Z", "registered_time": "6/24/2012", "host": "Macintosh.local", "@version": "1", "Full Name": "Kale Pouros", "Country": "Sudan", "Id": "19", "posts": [ {blog_post 1},{blog_post 2},{blog_post 3}....{blog_post n} ] } }
ある程度見通しが良くなりました。
検索結果が重複する問題も解決します。
しかし、この構造にも欠点があります。
elasticsearchのobject型は、内部的には、「各object要素の配列」が保存されます。
つまり
{ "posts" : [ { "Id" : "1", "Title" : "AAA" }, { "Id" : "2", "Title" : "BBB" } ] }
というドキュメントがあったとしても....
{ "posts.Id" : [ "1", "2" ], "posts.Title" : [ "AAA", "BBB" ] }
というように保持されているのです。
結果として、このドキュメントは
"Title":"AAA" AND "Id":"2"
という条件で検索してもヒットしてしまいます。
nested data type
上で述べた、object datatypeの欠点を解消するためのものがnested datatypeです。
ユーザーから見ると、object data typeを使っている場合とドキュメント構造は全く同じに見えます。
しかし内部的にnested部分は、別のドキュメントとしてindexされます。
使い方としては、事前にデータ型の定義をしておくだけです。
PUT nested { "mappings": { "doc":{ "properties": { "posts":{ "type": "nested" } } } } }
こうすることで、nestした個々のオブジェクトに対して検索をかけることができます。
見かけ上は100ドキュメントでも、cat APIで確認すると、10100件保持していることがわかります↓
green open object aFeBCUA0SI6xAQAgwFspdQ 5 0 100 0 11.7mb 11.7mb green open nested KMCi8GTQS6eLmofmlRgBcQ 5 0 10100 0 12.4mb 12.4mb
強いてデメリットを挙げるとすれば、nested data typeを使っている場合は、
query, aggregation, sort, highlightにおいて、nested専用のものを使う必要があるというところでしょうか。
(慣れていないと書き方が少し難しい&面倒)
join data type
ここまで挙げてきた方法はいずれも、スキーマが異なる情報を、1つのドキュメントとして統合するものでした。
(nested data typeは内部的には別ドキュメントですが)
それに対して、join data typeは同一index内の異なるドキュメント同士の親子関係を定義するためのものです。
以前は複数の_typeを用いて実現していたparent-childの関係が、
バージョン6以降ではjoin data typeのフィールドを用いて実現するように変更されました。
使い方としては、
事前に親子関係の識別子となるフィールドを定義しておきます。
PUT parent-child { "mappings": { "doc": { "properties": { "user_or_post": { "type": "join", "relations": { "user": "post" } } } } } }
あとはdocument登録の際に、親には "user_or_post":"user"というフィールドを追加し....
{ "_index": "parent-child", "_type": "doc", "_id": "-b3jYGABLuVBi7neE9XR", "_score": 1, "_source": { "@version": "1", "user_or_post": "user", #<= ここ "@timestamp": "2017-12-16T19:53:02.295Z", "host": "Macintosh.local", "Full Name": "Charity Pollich V", "registered_time": "11/22/2011", "Id": "2", "Country": "Svalbard and Jan Mayen", "Email": "Demetrius@estel.com" } }
子供側には
"user_or_post": {"name": "post", "parent": "親のドキュメントID"}
というフィールドを追加します。
{ "user_or_post": { "name": "post", #<= ここ "parent": "98" #<= ここ }, "Body": "xxxxx", "Summary": "xxxxx", "@version": "1", "Title": "xxxxxx", "Id": 98, "@timestamp": "2017-12-16T20:12:12.361Z", "host": "Macintosh.local", "Created At": "1987-12-11T14:58:59.947Z" }
これで親子関係を持つことができます。
検索時には、has_child queryやhas_parent queryを使います。
例えば「1996-08-12T15:28:25.327Z」にブログを投稿したユーザーを調べたければ下記のように検索します。
(要するにchildの条件で絞り込んで、parentのdocumentを返したい)
GET parent-child/_search { "query": { "has_child": { "type": "post", "query": { "term": { "Created At": { "value": "1996-08-12T15:28:25.327Z" } } } } } }
"hits": { "total": 1, "max_score": 1, "hits": [ { "_index": "parent-child", "_type": "doc", "_id": "93", "_score": 1, "_source": { "host": "Macintosh.local", "@timestamp": "2017-12-16T19:57:38.872Z", "registered_time": "5/1/1998", "user_or_post": "user", "Country": "Indonesia", "Email": "Kylie_Kiehn@ernestina.com", "@version": "1", "Full Name": "Dannie Schiller", "Id": "93" } } ] }
無事検索できました。
まとめ
ここまで、親子関係を持つデータの構造について何パターンか整理しました。
各方法とも一長一短あると思うので、状況によって使い分けたいものです。
以上です。読んでいただきありがとうございました。
その他
- 何かツッコミがあればコメントお願いします
- 検索パフォーマンスとかをしっかり検証していないので、データ増やしたり階層増やすとどうなるか、そのうち試してみたい(やってみた人が既にいたら是非教えてください)
X-Pack Watcherのindex actionではまったメモ
概要
elasticsearchのWatcher/Alertingを使っていて、はまったのでメモ。
具体的には "index action" についてです。
特定の条件を満たしたデータをまとめて別indexにbulkします。
やりたいこと
今回使うデータとして、
・anomaly_score
・time
という2種類の値を持ったドキュメントを用意します。
(3件しか登録してませんが、実際には大量のログがリアルタイムで来るイメージ)
ここから、anomaly_scoreが50を超えた物だけ抜き出して、anomaliesという別のindexに入れたい!
というのが今回の目的です。
では早速やってみます。
手順
1. watchを作成、登録する
バージョン6ではKibanaの画面から閾値ベースの定義が登録できそうですが、
今のバージョンではAPIを頑張って叩く必要があります。
今回はKIbanaのConsoleから登録します。
PUT _xpack/watcher/watch/test { "trigger": { "schedule": { "interval": "1d" } }, "input": { "search": { "request": { "indices": [ "test" ], "body": { "query": { "bool": { "must": [ { "range": { "time": { "gte": "now-1d" } } }, { "range": { "anomaly_score": { "gte": 50 } } } ] } } } } } }, "actions": { "index_to_elasticsearch": { "index": { "index": "anomalies", "doc_type": "anomaly" } } } }
...定義をJSONで書くと長いですね。しかし、内容はシンプルです。
ざっくり設定内容を書くと下記のようになっています。
- trigger : 1日ごとに実行
- input : test indexから「直近1日のデータ」かつ「anomaly_scoreが50以上」のドキュメントをとってくる
- actions : inputから受け取ったペイロードをanomalies indexに登録する
2. watchを実行する
実際には登録したwatchは1日待っていれば実行されますが、
流石に待っているのもアレなので、無理矢理APIを叩いて実行します。
POST _xpack/watcher/watch/test/_execute
3. 結果を確認してみる
上手く動いていれば、2件のドキュメント(anomaly_scoreが50以上)がanomalies indexに登録されているはずです。
確認してみると...
しっかり検知・indexingされています!😊
....しかしちょっと待てよ。
確かに2件のドキュメントが登録されているように見えるけれど、
よく見ると単一のドキュメントとして2件分のデータが登録されてしまっています。
2件を見つけてくれたのは嬉しいけど、2件のドキュメントとしてindexingして欲しかったのです...
4.複数ドキュメントとして登録する
公式のドキュメントを読むと、解決策がしっかり書いてありました。
_docというフィールドにオブジェクト配列(ここでは各ドキュメント)を入れておけば
上手く複数ドキュメントとしてbulkしてくれるようです。
実際にwatchを修正してみましょう。
transformという部分を追加しました。
これでドキュメントの配列が_docというフィールドに入ってくれるはず。
5.再度watchを実行
一旦、anomalies indexを削除して、再度watchを実行してみます。
実行に失敗しました...
右の実行結果を読み進めて行くと...
"message": "MapperParsingException[Field [_index] is a metadata field and cannot be added inside a document. Use the index API request parameters.]"
なるほど。登録しようとしているドキュメント内に_indexなどのmeta fieldがあるとエラーが出るようです。
query投げた時に_sourceという部分だけを取得できれば良いのですが、
どうやらURI Searchではできても、Request Body Searchではできないように見えます。←ここが一番自信ないです。これできるなら誰か教えてください。
そうなってくるとwatcherとしてはinputの後で_sourceを取り出さなければ駄目そう。
6. _sourceだけを取り出してみた
"actions": { "index_to_elasticsearch": { "transform": { "script": "for (int i = 0; i < ctx.payload.hits.hits.length; ++i) {ctx.payload.hits.hits[i] = ctx.payload.hits.hits[i]._source } return [ '_doc' : ctx.payload.hits.hits]" }, "index": { "index": "anomalies", "doc_type": "anomaly" } } }
厳密に言うと、各ドキュメントを_sourceで上書きするかたちになっています。
7. 再度watchを実行する
再度実行、今度こそ!
上手く2ドキュメントに分かれて登録されました。
めでたし、めでたし😊
まとめ
- index actionは1つのドキュメントとしてindexingされるのがデフォルト
- _doc にオブジェクト配列を入れると、複数ドキュメントとして扱ってくれる
- ドキュメント内部にmeta fieldがあるとrejectされる
- もっと良い方法があってもおかしくない気もします。(知っている人いたら教えて欲しい)
以上です。
elasticsearchのfieldを一部修正する
概要
elasticesearchに入れたデータを後で修正したくなった時の修正手順。
update by queryを使います。
バージョン情報など
elasticsearch-6.0.0-alpha2
kibana-6.0.0-alpha2
前提
ユーザーのアカウント情報を持つaccountというindexを作成しました。
fieldの内容としては、氏名や電話番号、住所などが入っています。
「ユーザーの増加にともなってuser_idの見直しが必要になった」というシチュエーションを想定します。(先見性の無さ....)
新たなユーザー、yokohama takashiさんが登録されましたが、このユーザーのuser_idが既存のユーザーと被ってしまいました。
もともとuser_idの命名規則が、「firstnameの頭文字」.「lastname」だったので、idが被らないように変更する必要があります。
user_idで検索すると2件ヒットしてしまいます。
この記事では、t.yokohamaのuser_idを、
lastname.firstnameの形式に書き換える手順を書きます。
手順
1. 変更したいドキュメントの条件をqueryで定義する。
今回は取り急ぎ、t.yokohamaというuser_idを持つユーザーのuser_idのみを変更します。
そのためqueryは下記のようになります。
"query": { "term": { "user_id.keyword": { "value": "t.yokohama" } } }
2. update by query
結論から書くと、下記のjsonをConsole上で実行すれば、updateができます。
POST account/_update_by_query { "query": { "term": { "user_id.keyword": { "value": "t.yokohama" } } }, "script": { "inline": "ctx._source.user_id = ctx._source.firstname + '.' + ctx._source.lastname", "lang": "painless" } }
query部分で、対象とするドキュメントを絞り、script部分で処理の内容を書きます。
scriptではpainlessを使っているので、ctx._source.field名でフィールドにアクセスできます。
これを実際に実行すると...
無事書き換わりました
めでたしめでたし
その他メモ
・reindex APIでもscriptが使えるので、部分的に書き換えて別indexに入れることもできる。
追記(10/1)
reindex APIを使った方法も追記して欲しいとのコメントをいただいたので追記します。
下記のようなリクエストを実行すれば同じようなことができます。
(queryの部分を省けば、全ドキュメントを対象にできます。)
POST _reindex { "source": { "index": "account" }, "dest": { "index": "account_dest" }, "script": { "inline": "ctx._source.user_id = ctx._source.firstname + '.' + ctx._source.lastname", "lang": "painless" } }
※この場合、account indexが書き換えられるのでは無く、account_destという別のindexが作成されるので、もう一度reindex APIで元のindexに書き戻すか、aliasを利用してください。
elasticsearchでデータ型を修正するメモ
概要
elasticsearchに入れたデータの型を修正する手順をメモ。
バージョン情報など
elasticsearch-6.0.0-alpha2
kibana-6.0.0-alpha2
前提
・elasticsearchにデータを入れてみたけど、やっぱりデータ型が違っていた。
・データ量がそれほど大きくない
reindexAPIを使った方がスマートな場合も多々ありますが、
今回はKibanaのConsoleからmappingを修正して、Logstashなどでデータを再投入する場合を想定します。
想定するシチュエーション
ユーザーのアカウント情報を示すaccountというindexにデータを突っ込んでみました。
kibanaで可視化するためにindex patternを作成...
確認してみるとageがstringになっています。
平均年齢とかをグラフ化する時とかに困ります。
ageが数字として扱われるようにmappingを修正してみます。
手順
1. 現在のmapping情報を取得する。
GET index名 をKIbanaのConsoleから実行することで、indexの情報を取得できます。
画像を見るとageのフィールドがtext型として定義されていることがわかりますね。
2. mappingの内容をコピーする。
先ほど右画面に出力されたjsonを畳むと、aliases, mappings, settingsなどの項目があるのがわかりますが、
今回はmappingの部分のみをコピーします。
こんな感じでコピーします。
3. mappingを変更する。
今度はConsoleの左画面を使います。
PUT index名 でindexを作成でき、その際にmappingを定義することができます。
これだとindexが作られるだけ。
中にmappingの定義を記述することで、データ型の定義などができます。
先ほどコピーしたmappingの内容を波括弧の中に貼り付けます。
そして、該当箇所のデータ型を書き換えます。
今回はageをintegerに書き換えています。
4. 変更したmappingを適用する。
さて、mapping定義を書き換えたので、実行してみます。
....怒られましたね。
indexが既に存在している場合、PUT index名によってmapping定義を上書きすることはできません。
なので、「indexの削除」→「mappingの定義」→「データの再投入」という手順を踏みます。
(ダウンタイムを極力減らしたい場合は、別名のindexにデータを新しく入れて、aliasを切り替えるという手もありですね。)
indexをDELETEして...
再実行
こんどは上手く行きました☺️
5. データを再投入して型を確認する
データ型の修正もしたところで、データをもう一度入れて、型を確認してみましょう。
index patternsの画面で一度refreshを押して
ageのところを見ると....
しっかりnumberとして認識されていました。
めでたしめでたし。
まとめ
- データ型を修正したい時は、現在のmapping情報をコピーして必要箇所を変更して貼り付けると楽ちん
- もちろんコピーしないで、必要なフィールドの型を定義してもOK(でもコピペなら文法覚えなくてもできる)
- reindexAPIを使ったりする方法もある
- kibanaから参照する場合は、index patternのrefreshをお忘れなく
ElasticsearchのAggregationをpagingする
概要
elasticsearchにはaggregationという便利な機能がありますが、
paginationを提供していません。
aggregateの仕組み上単純な話ではないようです。
そのあたりの詳細は(https://github.com/elastic/elasticsearch/issues/4915)をご覧ください。
今回はaggregateした結果を擬似的にページングします。
(ちょっと反則技が入ります。途中まで読んで憤慨しないでください。。。)
(しかも大きな制約があります。。。)
バージョン情報など
elasticsearch-6.0.0-alpha2
kibana-6.0.0-alpha2
手順
1. データの準備
今回はmetricbeatのデータを使いました。
インストールして起動するだけです。
metricbeatの使い方は本記事では説明しません。
2. データの確認
データが入っていることを確認してみます。
無事indexが作成されていることがわかります。
3. aggregationの適用
aggregationの結果をページングするということで、
まず普通にaggregationを使ってみましょう。
今回、aggregationで行うのは、
pidごとにbucketを分割して、その中の1件ずつをtop_hits aggregationで取得するというものです。
まずはterms aggregationでpid毎のbucketに分割して...
top_hits aggregationで、bucketから1件ずつ取得します。
4. ページングできるか?
ここで問題になってくるのが、
「bucketの数が膨大になった時にページングしたい」ということです。
通常のqueryであれば、fromとsizeを使って実現できますが、下の画像の通り、aggregationには効果がありません。
かといってaggregationの中に記述するとこの通りエラーが。
fromはサポートしていないようです...
5. 解決策
結局のところ、どうしてもページングしたければ別indexにaggregation結果を持つのが妥当だという結論に...
(読んでくれた方の半数がタブを閉じた気がします。)
metricbeatは常に情報が更新されるので、aggregation結果も随時updateする必要があります。
今回はwatcherを使って集計結果をindexingします。
watcherというと、メール通知やslack通知などができる便利なアラートと思われがちですが、
定期実行できる上にscriptが使える(webhookだって使える)というのは割と万能なやつです。
私はわりと好きです。
こんな感じでConsoleから登録できます。(今は一応UIも出ていますね。あまり使いやすくないですが。)
画像だとコピペできないと思うので
一応jsonを貼っておきます。
PUT _xpack/watcher/watch/aggregation { "input": { "search": { "request": { "indices": [ "metricbeat-6.0.0-alpha1-2017.06.*" ], "types": [ "doc" ], "body": { "size": 0, "aggs": { "num": { "terms": { "field": "system.process.pid", "size": 10000 }, "aggs": { "top": { "top_hits": { "size": 1 } } } } } } } } }, "trigger": { "schedule": { "interval": "1m" } }, "actions": { "aggregate": { "transform": { "script": "return ['_doc' : ctx.payload.aggregations.num.buckets]" }, "index": { "index": "aggregated", "doc_type": "aggregated" } } } }
この定義で何をしているかというと、
毎分aggregationを行って、その結果をaggregatedというindexに登録しています。
動いているか確認
aggregationの結果がindexingされていることがわかります。
ページングする
あとはこのindexに対してfromおよびsizeを使えばOKです
こんな感じです。
まとめ
- bucketをページングするのは現状難しい。
- watcherを使ってaggregation結果をindexingしておくと楽ちん。
- ただし、一段目のterms aggregationでsizeを指定するので、term(pid)のパターンが膨大だと不可。
- ブラウザ上で100件ずつページ分けたい時などには使えそう。
- 本当はpid毎にdocument_idを指定しないとだめです。今回は省略です。
- (watchを止め忘れないようにしましょう)
logstashからelasticsearchへデータを送る時に最低限知っておくべき設定
概要
logstashからelasticsearchにデータを送る時の最低限の設定に関するメモ
バージョン情報など
elasticsearch-5.x
logstash-5.x
設定ファイル内容
input{ 省略 } filter{ } output{ elasticsearch{ hosts => "localhost:9200" index => "test_index" document_type => "logs" user => "elastic" password => "changeme" } }
設定項目補足説明
hosts
elasticsearchのホスト名。リモートのelasticsearchにアクセスする場合はそのホスト名を指定する必要がある。
index
elasticsearchに入れる時のindex名を指定する。デフォルトだとlogstash-yyyy-MM-ddのようになる
document_type
elasticsearchに入れる時のtype名を指定する。デフォルトはlogs
user
x-packをインストールしている時のみ必要。
password
x-packをインストールしている時のみ必要。
まとめ
- 要するに何も設定をいじらずにlocalhostで立ち上げる場合はelasticsearch{}とさえ書けば一応問題なくデータが入る
- 詳細は https://www.elastic.co/guide/en/logstash/current/plugins-outputs-elasticsearch.html#plugins-outputs-elasticsearchを参照のこと
elasticsearchにoffice系ファイルやPDFを入れる
概要
elasticsearchにpdfやpptx,xlsxなどのファイルを入れる方法についてメモ。
ingest-attachment-pluginを使います。
バージョン情報など
macOS Sierra 10.12.5
elasticsearch-5.4.1
手順
1.ingest-attachment-pluginをインストールする。
$ bin/elasticsearch-plugin install ingest-attachment -> Downloading ingest-attachment from elastic [=================================================] 100% @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @ WARNING: plugin requires additional permissions @ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ * java.lang.RuntimePermission getClassLoader * java.lang.reflect.ReflectPermission suppressAccessChecks * java.security.SecurityPermission createAccessControlContext * java.security.SecurityPermission insertProvider * java.security.SecurityPermission putProviderProperty.BC See http://docs.oracle.com/javase/8/docs/technotes/guides/security/permissions.html for descriptions of what these permissions allow and the associated risks. Continue with installation? [y/N]y -> Installed ingest-attachment
2. インストールできたか確認。
$ bin/elasticsearch-plugin list analysis-kuromoji ingest-attachment ← ある ingest-geoip x-pack
3. elasticsearchを起動する。
$ bin/elasticsearch
4. ingest-nodeのpipelineを登録する。
ここでは"attachment"という名前のpipelineを登録しています。
"message" fieldにbase64エンコードされたファイル情報が入っていれば、ファイルの内容が抽出されます。
5. logstashを使ってファイルをelasticsearchに入れる。
今回使う設定ファイルは下記の通り。
$ cat attachment.conf input{ stdin{} } output{ elasticsearch{ hosts => ["localhost:9200"] index => "attachment_test" pipeline => "attachment" #ここにpipelineの名前入れる。 } }
Logstashを実行してみましょう。
$ cat ../elasticsearch-5.4.0/base64.pptx| bin/logstash -f attachment.conf
6. kibanaからドキュメントを確認する。
検索してみると、しっかり内容が抽出されていることがわかります。
MacにLogstashをインストールする
バージョン情報など
OS : macOS Sierra 10.12.5
logstash : logstash-5.4.1
メモリ : 4GB
手順
1. logstashのファイルを公式サイトからダウンロードする。
$ wget https://artifacts.elastic.co/downloads/logstash/logstash-5.4.1.tar.gz
2. ダウンロードしたファイルを解凍する。
$ tar zxvf logstash-5.4.1.tar.gz
ディレクトリ構成は下記の通り。
$ tree -L 1 logstash-5.4.1 logstash-5.4.1 ├── CHANGELOG.md ├── CONTRIBUTORS ├── Gemfile ├── Gemfile.jruby-1.9.lock ├── LICENSE ├── NOTICE.TXT ├── bin ├── config ├── data ├── lib ├── logstash-core ├── logstash-core-plugin-api └── vendor
3. 実行する。
$ logstash-5.4.1/bin/logstash Sending Logstash's logs to /Users/SHIN/Elastic/Elastic5.4/logstash-5.4.1/logs which is now configured via log4j2.properties [2017-06-03T00:17:07,073][INFO ][logstash.setting.writabledirectory] Creating directory {:setting=>"path.queue", :path=>"/Users/SHIN/Elastic/Elastic5.4/logstash-5.4.1/data/queue"} ERROR: No configuration file was specified. Perhaps you forgot to provide the '-f yourlogstash.conf' flag? usage: bin/logstash -f CONFIG_PATH [-t] [-r] [] [-w COUNT] [-l LOG] bin/logstash -e CONFIG_STR [-t] [--log.level fatal|error|warn|info|debug|trace] [-w COUNT] [-l LOG] bin/logstash -i SHELL [--log.level fatal|error|warn|info|debug|trace] bin/logstash -V [--log.level fatal|error|warn|info|debug|trace] bin/logstash --help
...これだと怒られます。(知ってた)
通常は "-f" オプションをつけて、logstashのconfファイル(挙動を定義したファイル)を指定します。
4. confファイル(logstash.conf)を作成する。(confファイルについては別エントリで書きます。)
テキストエディタなどで、logstash.confを作成します。
文字コードはUTF-8(BOM無し)にしてください。SJISとかだとエラーが出ると思います。
$ cat logstash.conf input{ stdin{} } filter{} output{ stdout{} }
5. logstash.confファイルを指定して実行する。
$ logstash-5.4.1/bin/logstash -f logstash.conf Sending Logstash's logs to /Users/SHIN/Elastic/Elastic5.4/logstash-5.4.1/logs which is now configured via log4j2.properties [2017-06-03T00:20:54,260][INFO ][logstash.agent ] No persistent UUID file found. Generating new UUID {:uuid=>"d94c4cf3-ef18-404d-9dac-7df53125e1f6", :path=>"/Users/SHIN/Elastic/Elastic5.4/logstash-5.4.1/data/uuid"} [2017-06-03T00:20:54,662][INFO ][logstash.pipeline ] Starting pipeline {"id"=>"main", "pipeline.workers"=>4, "pipeline.batch.size"=>125, "pipeline.batch.delay"=>5, "pipeline.max_inflight"=>500} [2017-06-03T00:20:54,707][INFO ][logstash.pipeline ] Pipeline main started The stdin plugin is now waiting for input: [2017-06-03T00:20:54,867][INFO ][logstash.agent ] Successfully started Logstash API endpoint {:port=>9600} Hello, world!! 2017-06-02T15:22:26.676Z Macintosh.local Hello, world!!
まとめ
- logstashをインストールして実行することができました。
- confファイルは設定が色々複雑なので別エントリで書きます。
================================================