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

  • 231
    Like
  • 1
    Comment
More than 1 year has 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をうまく使ってよくするのが良いと思う。