Posted at

Kibana+Elasticsearchで文字列の完全一致と部分一致検索の両方を実現する

More than 5 years have passed since last update.

Elasticsearchはデフォルトで文字列を要素解析して保存する。これによって部分一致検索ができるようになるのだが、完全一致検索が難しくなる。


便利なTermsがうまくいかない

KibanaにはTermsという値の上位10件とかを表示してくれるパネルがある。次のようなデータを入れているとする。


example.json

{

"@timestamp": "2013-12-17T10:12:40+09:00",
"remote_addr": "203.0.113.10",
"country_code": "JP",
"request_uri": "/index.php",
"user_agent": "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)"
}

すると、いまアクセスされている国の比率とかを見たければ、country_codeを指定してあげれば次の様なグラフが書けて非常に便利だ。

terms of country

しかし、user_agentを指定するとつぎのようになったりして、うまくいかないことがある。

user_agent fault

これはKibanaが空気を読んで要素解析してしまったせいである。できれば完全一致で見たい。


Mapping Templateを使う

Elasticsearchはスキーマレスでインデックスが増えたら勝手にデータ型とかを判別してくれるが、フィールドのデータ型や、要素解析などを指定することもできる。そのためにはMapping Templateを使う。


test_template.json

{

"template": "*",
"mappings": {
"_default_": {
"_source": { "compress": true },
"properties" : {
"request_uri" : { "type" : "string", "index" : "not_analyzed" }
}
}
}
}

templateフィールドで、どのインデックスにマッチするかを指定する。ここでは"*"としているのですべてのインデックスにマッチする。例えば"access-*"とすれば、access-から始まるインデックスだけに適用することもできる。この例ではrequest_uriフィールドの型をstring型、インデックスの要素解析をnot_analyzedで無効にしている。これによって、request_uriの完全一致ができるようになる。

書けたらテンプレートをElasticsearchに登録する。

$ curl -XPUT elasticsearch.local:9200/test_template/ -d "`cat test_template.json `"


Multi Fieldを使う

前の例では、request_uriの要素解析をnot_analyzedにできたが、逆に部分一致検索をしようとしたらできなくなってしまった。これは困るので部分一致検索も完全一致検索も両方実現したい。そこで、1つのフィールドからデータ型や要素解析の方法が異なる複数のフィールドを生成するMulti Fieldを使う。


multi_field_template.json

{

"template": "*",
"mappings": {
"_default_": {
"_source": { "compress": true },
"properties" : {
"request_uri" : {
"type": "multi_field",
"fields": {
"request_uri": {
"type": "string",
"index" : "analyzed"
},
"full": {
"type": "string",
"index" : "not_analyzed"
}
}
}
}
}
}
}

この例では、Multi Fieldではフィールド名を指定すると、request_uri.fullの用に入れ子になったフィールドができる。また、Multi Fieldのフィールド名を元のフィールド名と同じにしておくと、request_uriで指定できるようになる。これで部分一致検索には元のrequest_uriフィールドを使い、完全一致検索はrequest_uri.fullフィールドを使って実現できるようになった。


Dynamic Templateを使う

Mapping Templateには特定のフィールドを指定しなくても、フィールド名の部分一致や型一致でテンプレートを適用してくれる。これをDynamic Templateという。このDynamic Templateを使って、すべての文字列型フィールドに先ほどのMulti Fieldを適用する。


template_all.json

{

"template": "*",
"mappings": {
"_default_": {
"_source": { "compress": true },
"dynamic_templates": [
{
"string_template" : {
"match" : "*",
"mapping": {
"type": "multi_field",
"fields": {
"{name}": {
"type": "string",
"index" : "analyzed"
},
"full": {
"type": "string",
"index" : "not_analyzed"
}
}
},
"match_mapping_type" : "string"
}
}
],
"properties" : {
"@timestamp" : { "type" : "date", "index" : "not_analyzed" }
}
}
}
}


Termsがうまくいくようになった

こんなかんじでできるようになって便利だ。

131217-0004.png


Mapping Templateを追加したが適用されないとき

Mapping Templateは新しくインデックスができた時に適用されるので、インデックスを作り直すか、新しいインデックスを作るかしないと適用されない。fluent-plugin-elasitcsearchからログを入れているのであれば、日本時間9:00 AMになれば新しくインデックスができて適用されるはずだ。


まとめ

Mapping Templateを使うと特定フィールドのデータ型を指定したりできてとても便利だ。しかし、自分はあまりこの機能を使わないようにしている。なぜならElasticsearchが持つスキーマレスなデータ型というのが失われるからで、できるだけデータを挿入するときにデータ型には気を遣うべきだと思っている。

とはいえ文字列の完全一致は使いたい。なので、文字列はnot_analyzedにしてしまうか、Multi Fieldをうまく使ってよくするのが良いと思う。