Elasticsearchはデフォルトで文字列を要素解析して保存する。これによって部分一致検索ができるようになるのだが、完全一致検索が難しくなる。
便利なTermsがうまくいかない
KibanaにはTermsという値の上位10件とかを表示してくれるパネルがある。次のようなデータを入れているとする。
{
"@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
を指定してあげれば次の様なグラフが書けて非常に便利だ。
しかし、user_agent
を指定するとつぎのようになったりして、うまくいかないことがある。
これはKibanaが空気を読んで要素解析してしまったせいである。できれば完全一致で見たい。
Mapping Templateを使う
Elasticsearchはスキーマレスでインデックスが増えたら勝手にデータ型とかを判別してくれるが、フィールドのデータ型や、要素解析などを指定することもできる。そのためにはMapping Templateを使う。
{
"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
を使う。
{
"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": "*",
"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がうまくいくようになった
こんなかんじでできるようになって便利だ。
Mapping Templateを追加したが適用されないとき
Mapping Templateは新しくインデックスができた時に適用されるので、インデックスを作り直すか、新しいインデックスを作るかしないと適用されない。fluent-plugin-elasitcsearchからログを入れているのであれば、日本時間9:00 AMになれば新しくインデックスができて適用されるはずだ。
まとめ
Mapping Templateを使うと特定フィールドのデータ型を指定したりできてとても便利だ。しかし、自分はあまりこの機能を使わないようにしている。なぜならElasticsearchが持つスキーマレスなデータ型というのが失われるからで、できるだけデータを挿入するときにデータ型には気を遣うべきだと思っている。
とはいえ文字列の完全一致は使いたい。なので、文字列はnot_analyzed
にしてしまうか、Multi Fieldをうまく使ってよくするのが良いと思う。