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

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](https://qiita-image-store.s3.amazonaws.com/0/5218/ab9f5e88-d62d-f1b4-7f80-3ce90305403e.png)

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

![user_agent fault](https://qiita-image-store.s3.amazonaws.com/0/5218/827c9a36-76e4-5384-080e-fb36d117ab64.png)

これは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](https://qiita-image-store.s3.amazonaws.com/0/5218/e5a645b3-7baa-73f9-0617-8a64581d9576.png)

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

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

## まとめ

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

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