Elasticsearch 6.2.4 で join datatype を使って親子関係（旧 Parent-Child ）のデータをインデクシングする

Elasticsearch で親子関係のデータをインデクシングして検索します。
内容としては以下のドキュメントをまとめたものになります。
[join datatype](https://www.elastic.co/guide/en/elasticsearch/reference/current/parent-join.html)

手元の環境は以下になります。

* Ubuntu 16.04 LTS
* Elasticsearch 6.2.4
* ICU Analysis Plugin、Japanese (kuromoji) Analysis Plugin

Elasticsearch 6系 から、インデックスにはひとつのタイプ（ _type で指定していたもの）となりました。
別々の _type で同じフィールドを設定した場合、最終的に Lucene のインデックスで処理する際に非効率になっていたようです。
そのため、親子関係のデータについても実現方法が変更になっています。

最初にインデックスの設定とマッピングを定義します。( jq コマンドで整形してます。)
今回は goods データに対して review データが複数存在するような状態を作ってみます。

```
$ curl -X PUT 'localhost:9200/shop' -H 'Content-Type: application/json' -d'
{
    "settings": {
        "index": {
            "number_of_shards" : 1,
            "number_of_replicas" : 0,
            "refresh_interval" : "-1",
            "analysis": {
                "tokenizer": {
                    "shop_tokenizer": {
                        "type": "kuromoji_tokenizer",
                        "mode": "search",
                        "discard_punctuation": "true",
                        "user_dictionary": "userdict_ja.txt"
                    }
                },
                "analyzer": {
                    "my_analyzer": {
                        "type": "custom",
                        "tokenizer": "shop_tokenizer",
                        "char_filter": [
                            "icu_normalizer",
                            "kuromoji_iteration_mark"
                        ],
                        "filter": [
                            "kuromoji_baseform",
                            "kuromoji_part_of_speech"
                        ]
                    }
                }
            }
        }
    },
    "mappings": {
        "_doc": {
            "_all": {"enabled" : false},
            "properties": {
                "type": {"type": "keyword"},
                "name": {"type": "text", "analyzer":"my_analyzer"},
                "text": {"type": "text", "analyzer":"my_analyzer"},
                "user": {"type": "keyword"},
                "my_join_field": {
                    "type": "join",
                    "relations": {
                        "goods": "review"
                    }
                }
            }
        }
    }
}
' | jq
```

ポイントは以下になります。

* type フィールド（ _type ではありません）に goods や review を指定することでデータ種類の判別ができるようします。
* join 用のフィールドを作成し、relations で親子関係を設定します。（配列を指定することで複数の子供を表現したり、複数の relation を設定することも可能なようです。）

次に、以下のようなデータで json ファイルを作成します。

```
{"index": {"_index": "shop", "_type": "_doc","_id": "goods_id.1"}}
{"type": "goods","name": "三岳","text": "もののけ姫のモデルにもなった、鹿児島県屋久島の焼酎です。屋久島は水が美味しく、その水を使って作られています。ロックで飲むことをオススメします。","my_join_field": "goods"}
{"index": {"_index": "shop", "_type": "_doc","_id": "review_id.1", "routing": "goods_id.1"}}
{"type": "review","user": "nettle0010","text": "何度も購入させていただいてます。以前はレア物でなかなか飲めませんでしたが、こちらで購入できるようになって晩酌で楽しんでます。","my_join_field": {"name": "review", "parent": "goods_id.1"}}
{"index": {"_index": "shop", "_type": "_doc","_id": "review_id.2", "routing": "goods_id.1"}}
{"type": "review","user": "udfhsudadb","text": "注文してから２〜３日で届きました。ありがとうございます！","my_join_field": {"name": "review", "parent": "goods_id.1"}}
{"index": {"_index": "shop", "_type": "_doc","_id": "goods_id.2"}}
{"type": "goods","name": "屋久の島","text": "もののけ姫のモデルにもなった、鹿児島県屋久島の焼酎です。三岳と違ってあまりメジャーではありませんが、なかなか美味しいです。ロックがオススメです。","my_join_field": "goods"}
{"index": {"_index": "shop", "_type": "_doc","_id": "review_id.3", "routing": "goods_id.2"}}
{"type": "review","user": "okodshywegfej","text": "昨年購入しました。両親からも喜ばれています。","my_join_field": {"name": "review", "parent": "goods_id.2"}}
```

ポイントは以下になります。

* 子供のデータ（今回は review ）には routing で親のデータ（今回は goods ）の _id を設定します。
* 親と子供のそれぞれのデータで join 用のフィールド（今回は my_join_field ）を設定します。
* 子供のデータの join 用のフィールドで親のデータの _id を指定します。

ここで json ファイルを使ってインデクシングします。

```
$ curl -H "Content-type: application/x-ndjson" -X POST http://localhost:9200/_bulk?refresh=false --data-binary @request_bulk.json | jq
$ curl -X POST 'localhost:9200/shop/_refresh' | jq
```

登録したドキュメントを検索してみます。
まずは Parent Id Query を使って親の _id から子供のデータを取得してみます。

```
$ curl -X POST 'localhost:9200/shop/_search' -H 'Content-Type: application/json' -d'
{
    "query": {
        "parent_id": {
            "type": "review",
            "id": "goods_id.1"
         }
    }
}
' | jq
```

すると、以下のような結果が返ってきます。

```
{
  "took": 8,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 2,
    "max_score": 0.5389965,
    "hits": [
      {
        "_index": "shop",
        "_type": "_doc",
        "_id": "review_id.1",
        "_score": 0.5389965,
        "_routing": "goods_id.1",
        "_source": {
          "type": "review",
          "user": "nettle0010",
          "text": "何度も購入させていただいてます。以前はレア物でなかなか飲めませんでしたが、こちらで購入できるようになって晩酌で楽しんでます。",
          "my_join_field": {
            "name": "review",
            "parent": "goods_id.1"
          }
        }
      },
      {
        "_index": "shop",
        "_type": "_doc",
        "_id": "review_id.2",
        "_score": 0.5389965,
        "_routing": "goods_id.1",
        "_source": {
          "type": "review",
          "user": "udfhsudadb",
          "text": "注文してから２〜３日で届きました。ありがとうございます！",
          "my_join_field": {
            "name": "review",
            "parent": "goods_id.1"
          }
        }
      }
    ]
  }
}
```

今度は Has Child Query を使って子供のデータから親のデータを取得してみます。
inner_hits を設定することで子供の情報も付けて返してくれます。

```
$ curl -X POST 'localhost:9200/shop/_search' -H 'Content-Type: application/json' -d'
{
    "query": {
        "has_child": {
            "type": "review",
            "query": {
                "match": {
                    "text": "購入"
                 }
            },
            "inner_hits": {}
         }
    }
}
' | jq
```

この場合は以下のような結果が返ってきます。

```
{
  "took": 4,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 2,
    "max_score": 1,
    "hits": [
      {
        "_index": "shop",
        "_type": "_doc",
        "_id": "goods_id.1",
        "_score": 1,
        "_source": {
          "type": "goods",
          "name": "三岳",
          "text": "もののけ姫のモデルにもなった、鹿児島県屋久島の焼酎です。屋久島は水が美味しく、その水を使って作られています。ロックで飲むことをオススメします。",
          "my_join_field": "goods"
        },
        "inner_hits": {
          "review": {
            "hits": {
              "total": 1,
              "max_score": 1.1297731,
              "hits": [
                {
                  "_index": "shop",
                  "_type": "_doc",
                  "_id": "review_id.1",
                  "_score": 1.1297731,
                  "_routing": "goods_id.1",
                  "_source": {
                    "type": "review",
                    "user": "nettle0010",
                    "text": "何度も購入させていただいてます。以前はレア物でなかなか飲めませんでしたが、こちらで購入できるようになって晩酌で楽しんでます。",
                    "my_join_field": {
                      "name": "review",
                      "parent": "goods_id.1"
                    }
                  }
                }
              ]
            }
          }
        }
      },
      {
        "_index": "shop",
        "_type": "_doc",
        "_id": "goods_id.2",
        "_score": 1,
        "_source": {
          "type": "goods",
          "name": "屋久の島",
          "text": "もののけ姫のモデルにもなった、鹿児島県屋久島の焼酎です。三岳と違ってあまりメジャーではありませんが、なかなか美味しいです。ロックがオススメです。",
          "my_join_field": "goods"
        },
        "inner_hits": {
          "review": {
            "hits": {
              "total": 1,
              "max_score": 1.112344,
              "hits": [
                {
                  "_index": "shop",
                  "_type": "_doc",
                  "_id": "review_id.3",
                  "_score": 1.112344,
                  "_routing": "goods_id.2",
                  "_source": {
                    "type": "review",
                    "user": "okodshywegfej",
                    "text": "昨年購入しました。両親からも喜ばれています。",
                    "my_join_field": {
                      "name": "review",
                      "parent": "goods_id.2"
                    }
                  }
                }
              ]
            }
          }
        }
      }
    ]
  }
}
```

なお、Has Child Query などは使い方によってはパフォーマンスが悪くなるので、例えばひとつの製品データに対して **非常に多くの** 発注データがあるような状態でなければ効果がないとのことです。

### 参考になった記事
[Removal of mapping types](https://www.elastic.co/guide/en/elasticsearch/reference/6.2/removal-of-types.html)
[_parent field](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-parent-field.html)
[Parent Id Query](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-parent-id-query.html)
[Has Child Query](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-has-child-query.html)
[Inner hits](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-inner-hits.html)