Elasticsearch 7.16からの新機能！障害時のログ傾向を迅速に掴めるか？Categorize Text Aggregationを試してみた

この記事は[NTTテクノクロスアドベントカレンダー2021](https://qiita.com/advent-calendar/2021/ntt-tx) 25日目の記事です。

## はじめに
こんにちは。[NTTテクノクロス](https://www.ntt-tx.co.jp/)の山下です。

[2021/12/08にElasticsearch 7.16.0がリリース](https://www.elastic.co/jp/blog/whats-new-elasticsearch-kibana-cloud-7-16-0)され、Experimentalな新機能として[Categorize Text Aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/7.16/search-aggregations-bucket-categorize-text-aggregation.html)が追加されました。
アプリログのような定型的な文字が出力されるドキュメントに対して、似たようなパターンのドキュメント(行)がどれぐらいあるか？を集計してくれる機能のようです。[^1]

[^1]: 元々Elastic Stackをご存知の人向けに説明すると、Machine LearningのAnomaly Detectionで使われていた機能を単体で使えるようにしたもののようです。

この機能を事前に実行しておけば、障害時などに大量に同じ内容のログが出力されるケースなどに対応する際に、ログ傾向を把握した上で詳細なログを確認できそうです。

というわけで、この記事ではCategorize Text Aggregationを実際に使ってみて、どんな感じの機能なのかまとめてみます。

::: note alert 
なお、一応ドキュメントと見比べてはいますが、試した中から把握したことをまとめているため、必ずしも正しい内容とは限りません。間違いを見つけたら編集リクエストお願いします🙇‍♂️🙇‍♂️🙇‍♂️
:::

## Elasticsearchとは
[Elasticsearch](https://www.elastic.co/jp/elasticsearch/)はスキーマレスなデータベースを持った高速な全文検索エンジンです。世界のデータベースランキングである[DB-Engines Ranking](https://db-engines.com/en/ranking)で常に10位以内をキープしているなど、全文検索用途ではデファクトスタンダードになっている人気プロダクトです。

![elastic-stack.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264679/88ee2a2c-b6e9-a786-e798-44733dd7b444.png)

ちなみに、Elasticsearchは[Elastic Stack](https://www.elastic.co/jp/elastic-stack/)と呼ばれるプラットフォームの1プロダクトでもあり、今回も関連プロダクトである[Kibana](https://www.elastic.co/jp/kibana/)を使ってElasticsearchへのデータの投入やAPIリクエストを試していきます。

## Categorize Text Aggregationを実際に試してみる

### Elastic Stackの起動

[deviantony / docker-elk](https://github.com/deviantony/docker-elk)を使ってElastic Stackの環境を構築します。Docker実行環境さえあれば特定バージョンの環境構築ができるので、Elastic Stackで何か試すときには定番ですね。

- 動作確認時の各種ツール・ミドルウェアのバージョン
  - Git
      - 2.33.0
  - Docker
      - 20.10.8
  - docker-compose
      - 1.29.2
  - Elastic Stack
      - 7.16.2

```bash
git clone https://github.com/deviantony/docker-elk.git
cd docker-elk
git checkout 691ffd8764fe22bc363836219b3ee2e089c2c12e
docker-compose up -d
```

これでElasticsearch + Kibanaの環境が起動しました。

### データの投入

まず、カテゴライズしたい対象のログファイルをダウンロードしましょう。

今回は[logpai / loghub](https://github.com/logpai/loghub)で公開されているApacheのアクセスログを使用することにします。
以下のURLから直接ダウンロードできます。

https://raw.githubusercontent.com/logpai/loghub/master/Apache/Apache_2k.log

続いて、ダウンロードしたApacheアクセスログをKibana経由でElasticsearchに投入していきましょう。
[Kibanaの対応ブラウザ(Google Chrome / Firefox / Safari / Chromium Edge)](](https://www.elastic.co/jp/support/matrix#matrix_browsers))で http://localhost:5601 を開きます。

![localhost_5601_login_next=%2F.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264679/f7a3dcad-1d84-8cfc-14e5-7e7e79e6bb99.png)

以下の情報を入力し`Log in`をクリックします。

- Username
  - elastic
- Password
  - changeme

![localhost_5601_app_home (33).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264679/ae04de66-21e1-0f93-c05f-a03de0eae9ed.png)

画面上部の初期通知に記載されているとおり、業務上使用する場合は、使用情報をElasticに送信したくないケースもあるでしょう。その場合は、`disable usage data here.`をクリックして、テレメトリーデータの送信をしないように設定しておくのが良いです。しかし、今回は本筋ではないため説明を割愛します。画面上部の通知にある`Dismiss`をクリックします。

左上のハンバーガーボタンをクリックします。

![localhost_5601_app_home (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264679/1cf0bc0c-df5d-1397-6330-3b1dde79fd45.png)

`Analytics` => `Machine Learning`をクリックします。

上部のタブから`Data Visualizer`をクリックします。

![localhost_5601_app_home (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264679/a8c77547-8325-fb51-27e3-39b1d31c1f66.png)

`Select file`をクリックします。

![localhost_5601_app_home (3).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264679/ad92b922-86a9-f602-2c2f-3859ff666696.png)

`Select or drag and drop a file`をクリックし、ダウンロードした`Apache_2k.log`を選択しましょう。ドラッグ&ドロップで運んでも構いません。

![localhost_5601_app_ml_filedatavisualizer (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264679/4efae9df-b588-e71f-0c72-67479757a85f.png)

すると、ログファイルの内容をKibanaが解析し、Elasticsearchに取り込める形式に変換してくれます。

画面左下の`Import`をクリックします。

![localhost_5601_app_ml_filedatavisualizer (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264679/357c1adb-4762-41d0-b994-b219890e49fb.png)

Index名として`apache-2k`と入力し、`Import`をクリックします。

![localhost_5601_app_ml_filedatavisualizer (3).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264679/3424e521-296a-9622-7a35-b95245249633.png)

ファイルの取り込み処理が実行されます。プログレスバーが完了まで進んだら画面左下の`View index in Discover`をクリックします。

![localhost_5601_app_ml_filedatavisualizer (4).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264679/736a3c60-e71b-cc4e-b45e-18a1c3995068.png)

各行のログ内容が表示されました。
この画面はKibanaの機能の中でもDiscoverと呼ばれる画面で、
この画面の時点でこのログについて以下のことが分かります。

- 右上の日付期間から、2005年12月4日04:47:44.00から2005年12月5日19:15:57.00までのログが含まれていることが分かる
- 画面左上のヒット件数から、2000行あることが分かる
- 画面中央上部の件数の時系列ヒストグラムから、全くログ出力がない期間といくつかのスパイクが見られ、流量が一定ではないことが分かる

今回メインとなる確認ではないですが、こういったことが一目でわかるのがElasticsearch + Kibanaの良いところですね。
何はともあれ、これでElasticsearchにログを取り込むことができました。

### シンプルなCategorize Text Aggregationを試してみる

次はいよいよ新機能を試してみましょう。
ElasticsearchのAPIを叩くのはKibanaのDev ToolsにあるConsole画面から試すのがおすすめです。

左上のハンバーガーボタンをクリックします。

![localhost_5601_app_ml_filedatavisualizer (6).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264679/ac9f84b4-9fc6-e766-08bd-55d9b1170ac1.png)

`Management` => `Dev Tools`をクリックします。

![localhost_5601_app_ml_filedatavisualizer (7).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264679/85ffeba3-48ca-b6df-0281-531058b7714c.png)

これがConsole画面です。
左側にElasticsearch APIのエンドポイントや送信するボディを記述し、`Ctrl(Cmd) + Enter`するか、再生ボタンをクリックすると、APIが実行され、レスポンスが画面右側に表示されます。

以降、この画面で実行するための形式でリクエストを書いて試していきます。

まずは最も基本的な使い方を試していきます。ログのメッセージ部分が入っている`message`フィールドに対して、単純なCategorize Text Aggregationを試してみます。

```json
POST apache-2k/_search?filter_path=aggregations
{
  "aggs": {
    "categories": {
      "categorize_text": {
        "field": "message"
      }
    }
  }
}
```

他のデータについて試すときには、インデックス名(`apache-2k`)とフィールド名(`message`)の指定に注意してください。それ以外は基本的にこの使い方のままで実行できるはずです。
実行後のレスポンスは以下の通りになりました。

```json
{
  "aggregations" : {
    "categories" : {
      "buckets" : [
        {
          "doc_count" : 836,
          "key" : "jk2_init Found child in scoreboard slot"
        },
        {
          "doc_count" : 569,
          "key" : "workerEnv.init ok /etc/httpd/conf/workers2.properties"
        },
        {
          "doc_count" : 539,
          "key" : "mod_jk child workerEnv in error state"
        },
        {
          "doc_count" : 32,
          "key" : "Directory index forbidden by rule /var/www/html/"
        },
        {
          "doc_count" : 12,
          "key" : "jk2_init Can t find child in scoreboard"
        },
        {
          "doc_count" : 12,
          "key" : "mod_jk child init"
        }
      ]
    }
  }
}
```

`aggregations.categories.buckets`の中に配列形式で、複数のドキュメントに登場した文字列パターン(`key`)と登場したドキュメント件数(`doc_count`)が返却されます。

今回はシンプルなログファイルということもあって、2000件のドキュメントが6種類のカテゴリに分類されました。全ての`doc_count`を合計すると、ログ件数の2000と一致するので、全てのドキュメントがどれかのカテゴリに分類されているようです。[^2]

[^2]: デフォルトでは上位10位までしかカテゴリは分類されないようなので、より多くのカテゴリを分類したいときには`size`パラメータで調整しましょう。

最も多いカテゴリは`jk2_init Found child in scoreboard slot`という文字列で、836件あるようです。実際にログの最初の10行を抜粋してみましょう。

```log
[Sun Dec 04 04:47:44 2005] [notice] workerEnv.init() ok /etc/httpd/conf/workers2.properties
[Sun Dec 04 04:47:44 2005] [error] mod_jk child workerEnv in error state 6
[Sun Dec 04 04:51:08 2005] [notice] jk2_init() Found child 6725 in scoreboard slot 10
[Sun Dec 04 04:51:09 2005] [notice] jk2_init() Found child 6726 in scoreboard slot 8
[Sun Dec 04 04:51:09 2005] [notice] jk2_init() Found child 6728 in scoreboard slot 6
[Sun Dec 04 04:51:14 2005] [notice] workerEnv.init() ok /etc/httpd/conf/workers2.properties
[Sun Dec 04 04:51:14 2005] [notice] workerEnv.init() ok /etc/httpd/conf/workers2.properties
[Sun Dec 04 04:51:14 2005] [notice] workerEnv.init() ok /etc/httpd/conf/workers2.properties
[Sun Dec 04 04:51:18 2005] [error] mod_jk child workerEnv in error state 6
[Sun Dec 04 04:51:18 2005] [error] mod_jk child workerEnv in error state 6
```

3行目〜5行目までが該当の文字列パターンが登場するログです。
カテゴリ集計後の文字列と比較すると、集計後の文字列(`key`)には共通部分のみが抽出されており、行ごとに異なる文字列は除去されています。共通部分のみに着目してログの種類別件数を確認できるのは便利ですね！

## 基本的なパラメータ
先ほど使用した`field`以外のパラメータについても確認しておきましょう。まずは挙動を簡単に理解できるパラメータを試してみます。

::: note info
その他の応用的なパラメータに関しては本編に記載するとボリューミーだったので、Appendixに試した結果をまとめました。興味があれば読んでみてください。
:::

### categorization_filters
カテゴライズする際に指定した単語を除去してカテゴライズできます。正規表現も使用できます。
なお、後ほど紹介する`categorization_analyzer`とは共用できません。[^3]

[^3]: `categorization_analyzer`の中でも`filter`を設定可能でバッティングしないようにするためでしょう。

#### リクエストとレスポンス
`mod_jk child workerEnv in error state`と`mod_jk child init`から`child`以外の単語を除去してカテゴライズする例です。

```json
POST apache-2k/_search?filter_path=aggregations
{
  "aggs": {
    "categories": {
      "categorize_text": {
        "field": "message",
        "categorization_filters": ["\\w+\\_\\w+", "workerEnv", "in", "error", "state", "it"]
      }
    }
  }
}
```

```json
{
  "aggregations" : {
    "categories" : {
      "buckets" : [
        {
          "doc_count" : 836,
          "key" : "Found child scoreboard slot"
        },
        {
          "doc_count" : 569,
          "key" : "/etc/httpd/conf/workers2.properties"
        },
        {
          "doc_count" : 551,
          "key" : "child"
        },
        {
          "doc_count" : 32,
          "key" : "Directory dex forbidden by rule /var/www/html/"
        },
        {
          "doc_count" : 12,
          "key" : "Can t child scoreboard"
        }
      ]
    }
  }
}
```

### size
レスポンスとして返ってくるカテゴリ数の上限を指定できます。
オーダー順を指定しなければ、件数が多い方順に上位N件が返却されます。
デフォルトの値は`10`です。

#### リクエストとレスポンス
件数上位のカテゴリ2件のみを返却する例です。

```json
POST apache-2k/_search?filter_path=aggregations
{
  "aggs": {
    "categories": {
      "categorize_text": {
        "field": "message",
        "size" : 2
      }
    }
  }
}
```

```json
{
  "aggregations" : {
    "categories" : {
      "buckets" : [
        {
          "doc_count" : 836,
          "key" : "jk2_init Found child in scoreboard slot"
        },
        {
          "doc_count" : 569,
          "key" : "workerEnv.init ok /etc/httpd/conf/workers2.properties"
        }
      ]
    }
  }
}
```

### min_doc_count
カテゴリとして集計する際に何件以上ある`key`についてのみ集計するか指定できます。
指定した数より小さい件数のカテゴリは結果が返却されません。

#### リクエストとレスポンス
```json
POST apache-2k/_search?filter_path=aggregations
{
  "aggs": {
    "categories": {
      "categorize_text": {
        "field": "message",
        "min_doc_count" : 100
      }
    }
  }
}
```

```json
{
  "aggregations" : {
    "categories" : {
      "buckets" : [
        {
          "doc_count" : 836,
          "key" : "jk2_init Found child in scoreboard slot"
        },
        {
          "doc_count" : 569,
          "key" : "workerEnv.init ok /etc/httpd/conf/workers2.properties"
        },
        {
          "doc_count" : 539,
          "key" : "mod_jk child workerEnv in error state"
        }
      ]
    }
  }
}
```

## おわりに
Categorize Text Aggregation、一瞬で生ログの傾向を把握できて便利そうです。まだKibanaのVisualizeでこのAggregationに対応したものはないと思いますが、自由にElasticsearchへのクエリが書ける[Vega Visualization](https://www.elastic.co/guide/en/kibana/current/vega.html)と組み合わせれば何とか可視化にも使えそうです。

ただし、公式ドキュメントによれば、このCategorize Text Aggregationは大量のドキュメントに対して実行すると膨大な時間とメモリを使用するため、[Async search](https://www.elastic.co/guide/en/elasticsearch/reference/7.16/async-search.html)にしたり、[Sampler aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/7.16/search-aggregations-bucket-sampler-aggregation.html)や[Diversified sampler aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/7.16/search-aggregations-bucket-diversified-sampler-aggregation.html)することが推奨されています。

これで[NTTテクノクロスAdvent Calendar 2021](https://qiita.com/advent-calendar/2021/ntt-tx)は終わりとなります。
たまたまこの記事にアクセスされた方はぜひカレンダーの記事一覧に飛んで他にも興味ある記事がないかどうか確認してみてください！

## Appendix

### 応用的なパラメータ
以下のパラメータについては、本格的にaggregationの設定をカスタマイズしていく際には重要なパラメータになりそうなのですが、本編の内容にするには重めの内容だったので、Appendixとしてまとめておきます。

#### categorization_analyzer
::: note warn
このパラメータに関してはElasticsearchのanalyzerなどについての知識が必要となるため、analyzerを知らない方は読み飛ばすことを推奨します。
:::

[aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/7.16/search-aggregations.html)を実行する前に適用するanalyzerを指定できます。
指定しない場合はCategorize Text Aggregationが持っているデフォルトの[analyzer](https://www.elastic.co/guide/en/elasticsearch/reference/7.16/analyzer.html)が使われるようです。

##### リクエストとレスポンス
Standard Analyzerを使用する例です。
なお、この後のパラメータ紹介はこれを設定した前提で説明していきます。

```json
POST apache-2k/_search?filter_path=aggregations
{
  "aggs": {
    "categories": {
      "categorize_text": {
        "field": "message",
        "categorization_analyzer": "standard"
      }
    }
  }
}
```

```json
{
  "aggregations" : {
    "categories" : {
      "buckets" : [
        {
          "doc_count" : 781,
          "key" : "jk2_init found child * in scoreboard slot *"
        },
        {
          "doc_count" : 569,
          "key" : "workerenv.init ok etc httpd conf workers2 properties"
        },
        {
          "doc_count" : 539,
          "key" : "mod_jk child workerenv in error state *"
        },
        {
          "doc_count" : 32,
          "key" : "directory index forbidden by rule var www html"
        },
        {
          "doc_count" : 12,
          "key" : "jk2_init can't find child * in scoreboard"
        },
        {
          "doc_count" : 12,
          "key" : "mod_jk child init 1 2"
        },
        {
          "doc_count" : 2,
          "key" : "jk2_init found child 6733 in scoreboard slot *"
        },
        {
          "doc_count" : 2,
          "key" : "jk2_init found child 6740 in scoreboard slot 7"
        },
        {
          "doc_count" : 2,
          "key" : "jk2_init found child 6750 in scoreboard slot *"
        },
        {
          "doc_count" : 2,
          "key" : "jk2_init found child 6751 in scoreboard slot *"
        }
      ]
    }
  }
}
```

`*`が登場している意味については後述します。

##### Categorize Text Aggregationが使うデフォルトのanalyzerについて
デフォルトのanalyzerについては自信はないものの、以下のような[GET _ml/info](https://www.elastic.co/guide/en/elasticsearch/reference/7.16/get-ml-info.html)で取得できる`categorization_analyzer`を使っていそうです。試しに適用してみたところ、同じ結果が得られました。

```json
{
  "char_filter" : [
    "first_line_with_letters"
  ],
  "tokenizer" : "ml_standard",
  "filter" : [
    {
      "type" : "stop",
      "stopwords" : [
        "Monday",
        "Tuesday",
        "Wednesday",
        "Thursday",
        "Friday",
        "Saturday",
        "Sunday",
        "Mon",
        "Tue",
        "Wed",
        "Thu",
        "Fri",
        "Sat",
        "Sun",
        "January",
        "February",
        "March",
        "April",
        "May",
        "June",
        "July",
        "August",
        "September",
        "October",
        "November",
        "December",
        "Jan",
        "Feb",
        "Mar",
        "Apr",
        "May",
        "Jun",
        "Jul",
        "Aug",
        "Sep",
        "Oct",
        "Nov",
        "Dec",
        "GMT",
        "UTC"
      ]
    }
  ]
}
```
[Token Filter](https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-tokenfilters.html)についてはシンプルな[Stop token filter](https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-stop-tokenfilter.html)ですね。曜日に該当する単語を除去しています。

[Character Filter](https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-charfilters.html)の`first_line_with_letters`と、[Tokenizer](https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-tokenizers.html)の`ml_standard`についてはドキュメントに挙動が記載されていませんが、ソースコードのJavaDocを読めば挙動が理解できそうです。リンクを貼っておきます。

- [FirstLineWithLettersCharFilter.java](https://github.com/elastic/elasticsearch/blob/v7.16.2/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/job/categorization/FirstLineWithLettersCharFilter.java)
- [MlStandardTokenizer.java](https://github.com/elastic/elasticsearch/blob/v7.16.2/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/job/categorization/MlStandardTokenizer.java)

#### max_matched_tokens
指定した位置以降のトークンで異なるパターンのトークンがある場合、カテゴリとしてマージするかを指定できます。
これだけだと意味不明なので、いくつかパターンが異なる実例を見ていきます。

##### 8トークン目以降のトークンをマージするリクエストとレスポンス
`similarity_threshold`はデフォルトの`50`、`max_unique_tokens`もデフォルトの`50`とします。

```json
POST apache-2k/_search?filter_path=aggregations
{
  "aggs": {
    "categories": {
      "categorize_text": {
        "field": "message",
        "categorization_analyzer": "standard",
        "similarity_threshold": 50,
        "max_matched_tokens": 8,
        "max_matched_tokens": 50
      }
    }
  }
}
```

```json
{
  "aggregations" : {
    "categories" : {
      "buckets" : [
        {
          "doc_count" : 617,
          "key" : "jk2_init found child * in scoreboard slot *"
        },
        {
          "doc_count" : 569,
          "key" : "workerenv.init ok etc httpd conf workers2 properties"
        },
        {
          "doc_count" : 369,
          "key" : "mod_jk child workerenv in error state 6"
        },
        {
          "doc_count" : 101,
          "key" : "mod_jk child workerenv in error state 7"
        },
        {
          "doc_count" : 44,
          "key" : "mod_jk child workerenv in error state 8"
        },
        {
          "doc_count" : 32,
          "key" : "directory index forbidden by rule var www html"
        },
        {
          "doc_count" : 20,
          "key" : "mod_jk child workerenv in error state 9"
        },
        {
          "doc_count" : 11,
          "key" : "mod_jk child init 1 2"
        },
        {
          "doc_count" : 5,
          "key" : "mod_jk child workerenv in error state 10"
        },
        {
          "doc_count" : 2,
          "key" : "jk2_init found child 6733 in scoreboard slot *"
        }
      ]
    }
  }
}
```

`mod_jk child workerenv in error state [number]`という文字列について、それぞれの6~10までの数字別の内容がカテゴリとして返却されます。`[number]`は半角スペースで区切られた7トークン目の文字列となるため、8トークン目以降をマージする設定のこの時点ではカテゴリとしてマージされていません。

また、`jk2_init found child [number] in scoreboard slot *`という文字列についても、`[number]`に相当する箇所は4トークン目となるため、カテゴリとしてマージされていません。

##### 7トークン目以降のトークンをマージするリクエストとレスポンス
```json
POST apache-2k/_search?filter_path=aggregations
{
  "aggs": {
    "categories": {
      "categorize_text": {
        "field": "message",
        "categorization_analyzer": "standard",
        "similarity_threshold": 50,
        "max_matched_tokens": 7
      }
    }
  }
}
```

```json
{
  "aggregations" : {
    "categories" : {
      "buckets" : [
        {
          "doc_count" : 634,
          "key" : "jk2_init found child * in scoreboard slot *"
        },
        {
          "doc_count" : 569,
          "key" : "workerenv.init ok etc httpd conf workers2 properties"
        },
        {
          "doc_count" : 539,
          "key" : "mod_jk child workerenv in error state *"
        },
        {
          "doc_count" : 32,
          "key" : "directory index forbidden by rule var www html"
        },
        {
          "doc_count" : 11,
          "key" : "mod_jk child init 1 2"
        },
        {
          "doc_count" : 2,
          "key" : "jk2_init found child 6733 in scoreboard slot *"
        },
        {
          "doc_count" : 2,
          "key" : "jk2_init found child 6741 in scoreboard slot *"
        },
        {
          "doc_count" : 1,
          "key" : "jk2_init can't find child 2085 in scoreboard"
        },
        {
          "doc_count" : 1,
          "key" : "jk2_init found child 8744 in scoreboard slot 8"
        },
        {
          "doc_count" : 1,
          "key" : "jk2_init found child 8766 in scoreboard slot 12"
        }
      ]
    }
  }
}
```

`mod_jk child workerenv in error state [number]`という文字列について、それぞれの6~10までの数字別の内容がマージされた`mod_jk child workerenv in error state *`というカテゴリにマージされました。パラメータで指定した7トークン目以降の異なるパターンがマージされた形です。

##### 4トークン目以降のトークンをマージするリクエストとレスポンス
```json
POST apache-2k/_search?filter_path=aggregations
{
  "aggs": {
    "categories": {
      "categorize_text": {
        "field": "message",
        "categorization_analyzer": "standard",
        "similarity_threshold": 50,
        "max_matched_tokens": 4,
        "max_unique_tokens": 50
      }
    }
  }
}
```

```json
{
  "aggregations" : {
    "categories" : {
      "buckets" : [
        {
          "doc_count" : 836,
          "key" : "jk2_init found child * in scoreboard slot *"
        },
        {
          "doc_count" : 569,
          "key" : "workerenv.init ok etc httpd conf workers2 properties"
        },
        {
          "doc_count" : 539,
          "key" : "mod_jk child workerenv in error state *"
        },
        {
          "doc_count" : 32,
          "key" : "directory index forbidden by rule var www html"
        },
        {
          "doc_count" : 12,
          "key" : "jk2_init can't find child * in scoreboard"
        },
        {
          "doc_count" : 12,
          "key" : "mod_jk child init 1 2"
        }
      ]
    }
  }
}
```

`jk2_init found child [number] in scoreboard slot *`というメッセージについて、マージされた`k2_init found child * in scoreboard slot *`というカテゴリが返却されています。`[number]`に相当する箇所は4トークン目であるため、今回の指定である4トークン目以降での異なるパターンがマージされました。

#### similarity_threshold
トークン全体で見た時の何%以上のトークンが一致した時に`*`としてカテゴリにマージして表示するか設定できるパラメータです。
デフォルトの値は`50`です。

##### 100%トークンが一致した文字列をカテゴライズするリクエストとレスポンス
トークンが完全一致した文字列のみをカテゴライズする例です。似たような文字列でもマージされません。

```json
POST apache-2k/_search?filter_path=aggregations
{
  "aggs": {
    "categories": {
      "categorize_text": {
        "field": "message",
        "categorization_analyzer": "standard",
        "similarity_threshold": 100,
        "max_matched_tokens": 5,
        "max_unique_tokens": 50
      }
    }
  }
}
```

```json
{
  "aggregations" : {
    "categories" : {
      "buckets" : [
        {
          "doc_count" : 569,
          "key" : "workerenv.init ok etc httpd conf workers2 properties"
        },
        {
          "doc_count" : 369,
          "key" : "mod_jk child workerenv in error state 6"
        },
        {
          "doc_count" : 101,
          "key" : "mod_jk child workerenv in error state 7"
        },
        {
          "doc_count" : 44,
          "key" : "mod_jk child workerenv in error state 8"
        },
        {
          "doc_count" : 32,
          "key" : "directory index forbidden by rule var www html"
        },
        {
          "doc_count" : 20,
          "key" : "mod_jk child workerenv in error state 9"
        },
        {
          "doc_count" : 11,
          "key" : "mod_jk child init 1 2"
        },
        {
          "doc_count" : 5,
          "key" : "mod_jk child workerenv in error state 10"
        },
        {
          "doc_count" : 1,
          "key" : "jk2_init found child 1338 in scoreboard slot 7"
        },
        {
          "doc_count" : 1,
          "key" : "jk2_init found child 312 in scoreboard slot 10"
        }
      ]
    }
  }
}
```

ここの結果の中で、`mod_jk child workerenv in error state [number]`という`key`について考えてみます。
似たような`key`を眺めると、`mod_jk`、`child`、`workerenv`、`in`、`error`、`state`の6つは共通したトークンで、`[number]`だけが異なっています。異なるトークンの割合は`6 / 7 = 85.7(%)`です。この値の前後を設定することで挙動を確認してみましょう。

##### 86%以上トークンが一致した文字列をカテゴライズするリクエストとレスポンス
86%以上トークンが一致した文字列のみをカテゴライズする例です。85.7%は含まれないため、`mod_jk child workerenv in error state [number]`はマージされません。

```json
POST apache-2k/_search?filter_path=aggregations
{
  "aggs": {
    "categories": {
      "categorize_text": {
        "field": "message",
        "categorization_analyzer": "standard",
        "similarity_threshold": 86,
        "max_matched_tokens": 7,
        "max_unique_tokens": 50
      }
    }
  }
}
```

```json
{
  "aggregations" : {
    "categories" : {
      "buckets" : [
        {
          "doc_count" : 590,
          "key" : "jk2_init found child * in scoreboard slot *"
        },
        {
          "doc_count" : 569,
          "key" : "workerenv.init ok etc httpd conf workers2 properties"
        },
        {
          "doc_count" : 369,
          "key" : "mod_jk child workerenv in error state 6"
        },
        {
          "doc_count" : 101,
          "key" : "mod_jk child workerenv in error state 7"
        },
        {
          "doc_count" : 44,
          "key" : "mod_jk child workerenv in error state 8"
        },
        {
          "doc_count" : 32,
          "key" : "directory index forbidden by rule var www html"
        },
        {
          "doc_count" : 20,
          "key" : "mod_jk child workerenv in error state 9"
        },
        {
          "doc_count" : 11,
          "key" : "mod_jk child init 1 2"
        },
        {
          "doc_count" : 5,
          "key" : "mod_jk child workerenv in error state 10"
        },
        {
          "doc_count" : 2,
          "key" : "jk2_init found child 6741 in scoreboard slot *"
        }
      ]
    }
  }
}
```

##### 85%以上トークンが一致した文字列をカテゴライズするリクエストとレスポンス
85%以上トークンが一致した文字列のみをカテゴライズする例です。85.7%が含まれるため、`mod_jk child workerenv in error state [number]`もマージされます。

```json
POST apache-2k/_search?filter_path=aggregations
{
  "aggs": {
    "categories": {
      "categorize_text": {
        "field": "message",
        "categorization_analyzer": "standard",
        "similarity_threshold": 85,
        "max_matched_tokens": 7,
        "max_unique_tokens": 50
      }
    }
  }
}
```

```json
{
  "aggregations" : {
    "categories" : {
      "buckets" : [
        {
          "doc_count" : 627,
          "key" : "jk2_init found child * in scoreboard slot *"
        },
        {
          "doc_count" : 569,
          "key" : "workerenv.init ok etc httpd conf workers2 properties"
        },
        {
          "doc_count" : 539,
          "key" : "mod_jk child workerenv in error state *"
        },
        {
          "doc_count" : 32,
          "key" : "directory index forbidden by rule var www html"
        },
        {
          "doc_count" : 11,
          "key" : "mod_jk child init 1 2"
        },
        {
          "doc_count" : 2,
          "key" : "jk2_init found child 6733 in scoreboard slot *"
        },
        {
          "doc_count" : 2,
          "key" : "jk2_init found child 6741 in scoreboard slot *"
        },
        {
          "doc_count" : 1,
          "key" : "jk2_init found child 32390 in scoreboard slot 8"
        },
        {
          "doc_count" : 1,
          "key" : "jk2_init found child 32445 in scoreboard slot 10"
        },
        {
          "doc_count" : 1,
          "key" : "jk2_init found child 8766 in scoreboard slot 12"
        }
      ]
    }
  }
}
```

#### max_unique_tokens
`max_matched_tokens`で指定した位置のトークンより前のトークンについて複数候補あるトークンを何種類`key`として許容するかを指定できます。トークンは指定した種類に収まるように`*`にマージされます。

##### 1種類(`*`のみ)しか許容しない場合のリクエストとレスポンス
```json
POST apache-2k/_search?filter_path=aggregations
{
  "aggs": {
    "categories": {
      "categorize_text": {
        "field": "message",
        "categorization_analyzer": "standard",
        "similarity_threshold": 50,
        "max_matched_tokens": 5,
        "max_unique_tokens": 1
      }
    }
  }
}
```

```json
{
  "aggregations" : {
    "categories" : {
      "buckets" : [
        {
          "doc_count" : 836,
          "key" : "jk2_init found child * in scoreboard slot *"
        },
        {
          "doc_count" : 569,
          "key" : "workerenv.init ok etc httpd conf workers2 properties"
        },
        {
          "doc_count" : 539,
          "key" : "mod_jk child workerenv in error state *"
        },
        {
          "doc_count" : 32,
          "key" : "directory index forbidden by rule var www html"
        },
        {
          "doc_count" : 12,
          "key" : "jk2_init can't find child * in scoreboard"
        },
        {
          "doc_count" : 12,
          "key" : "mod_jk child init 1 2"
        }
      ]
    }
  }
}
```

`jk2_init found child * in scoreboard slot *`として、`child`の後のトークンがマージされています。
これは、`max_matched_tokens`で指定した5番目のトークンより前の4番目のトークンです。今回は`max_unique_tokens`で、1種類しか許容しないように設定したので、全てのトークンが`*`にマージされました。


##### 2種類(`*`ともう1つのトークン)許容する場合のリクエストとレスポンス
```json
POST apache-2k/_search?filter_path=aggregations
{
  "aggs": {
    "categories": {
      "categorize_text": {
        "field": "message",
        "categorization_analyzer": "standard",
        "similarity_threshold": 50,
        "max_matched_tokens": 5,
        "max_unique_tokens": 2
      }
    }
  }
}
```

```json
{
  "aggregations" : {
    "categories" : {
      "buckets" : [
        {
          "doc_count" : 834,
          "key" : "jk2_init found child * in scoreboard slot *"
        },
        {
          "doc_count" : 569,
          "key" : "workerenv.init ok etc httpd conf workers2 properties"
        },
        {
          "doc_count" : 539,
          "key" : "mod_jk child workerenv in error state *"
        },
        {
          "doc_count" : 32,
          "key" : "directory index forbidden by rule var www html"
        },
        {
          "doc_count" : 12,
          "key" : "jk2_init can't find child * in scoreboard"
        },
        {
          "doc_count" : 12,
          "key" : "mod_jk child init 1 2"
        },
        {
          "doc_count" : 2,
          "key" : "jk2_init found child 6741 in scoreboard slot *"
        }
      ]
    }
  }
}
```

`jk2_init found child * in scoreboard slot *`として、`child`の後のトークンがマージされていますが、`jk2_init found child 6741 in scoreboard slot *`という似た`key`のカテゴリも存在しています。
今回は`max_unique_tokens`で、2種類許容するように設定したので、この2つの似た`key`が許容されました。

### その他のパラメータ
その他、インデックスを複数シャードに分割している際に使えるパラメータとして`shard_size`や`shard_min_doc_count`などもあります。
今回はデフォルトのプライマリシャード数1で試していたので紹介するのみに留めます。