elasticsearchのMapping conflictを解消する

Elasticsearchでfieldのtypeでコンフリクトが発生したときに自分が解消するまでにやった手順を書きます。
今回はコンフリクトを解消する上でElasticsearchのパース機能を利用するため、**パースできないデータは切り捨てます**。たとえば、"100" -> 100 はパースできますが"100MB" -> 100 はパースできません。このやり方で通用する場合は何か参考になるかもしれません。

# 環境
- Windows 10
- Elasticsearch 7.3.0
- Kibana 7.3.0

# 前準備
今回は説明のために意図的にコンフリクトするようなダミーデータを作ります。以下のクエリをKibanaのdev toolsで実行します。

``` 
POST _bulk
{ "index" : { "_index" : "test1", "_id" : "1" } }
{ "name" : "aaa", "num": 100 }
{ "create" : { "_index" : "test1", "_id" : "2" } }
{ "name" : "bbb", "num": 200 }
{ "index" : { "_index" : "test2", "_id" : "1" } }
{ "name" : "aaa", "num": "100" }
{ "create" : { "_index" : "test2", "_id" : "2" } }
{ "name" : "bbb", "num": "200B" }
```

この状態で Management/Index Patterns にて「`test*`」で Create index pattern します。
`test*`を開くと以下の画像のようにMappingがコンフリクトしてると警告が出ると思います。`num`のTypeがconflictになっていることが確認できたら成功(?)です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125635/dfd434fd-32c8-cc42-2d37-dfd050c36a6d.png)

以上で前準備としては終了です。次にコンフリクトを解消する手順です。

# 手順
今回は`num`を`long`にする方向でコンフリクトを解消します。そのため、`test2`のMappingを修正する必要があります。しかし、ElasticsearchにはMappingを修正する機能は存在しないらしくindexを作り直すしかないそうです。そのため`test2`を別のindexにいったんコピーして逃がしておいて、正しいTypeでMappingし直した`test2`に再度コピーするという方法をとります。

## 1. test2の一時保存用Mappingの作成
この時コンフリクトしているfieldのTypeを明示的に指定します。今回はnumのTypeをlongに指定します。

```
PUT tmp-test2
{
  "mappings": {
    "properties": {
      "num": {
        "type": "long"
      }
    }
  }
}
```

## 2. 一時保存先に中身をコピー
_reindexはあるindexの内容を別のindexにコピーするElasticsearchのAPIです。failuresが出ると思いますが、これは`num`が`200B`のdocumentが原因で出ているものです。冒頭で説明した通り今回の方法ではこのようにElasticsearchがパースできない値は切り捨てます。

```
POST _reindex
{
  "source": {
    "index": "test2"
  },
  "dest": {
    "index": "tmp-test2"
  }
}

// RETURN
// {
//   "took": 41,
//   "timed_out": false,
//   "total": 2,
//   "updated": 0,
//   "created": 1,
//   "deleted": 0,
//   "batches": 1,
//   "version_conflicts": 0,
//   "noops": 0,
//   "retries": {
//     "bulk": 0,
//     "search": 0
//   },
//   "throttled_millis": 0,
//   "requests_per_second": -1,
//   "throttled_until_millis": 0,
//   "failures": [
//     {
//       "index": "tmp-test2",
//       "type": "_doc",
//       "id": "2",
//       "cause": {
//         "type": "mapper_parsing_exception",
//         "reason": "failed to parse field [num] of type [long] in document with id '2'. Preview of field's value: '200B'",
//         "caused_by": {
//           "type": "illegal_argument_exception",
//           "reason": "For input string: \"200B\""
//         }
//       },
//       "status": 400
//     }
//   ]
// }

```

## 3. test2のindexを削除
削除するのが怖い場合はsnapshotなどを活用してバックアップをとってください。今回はsnapshotについては書きませんので以下の記事などを参考にしてください。
https://qiita.com/okaru/items/82b1ee5d249c3dc01452

```
DELETE test2
```

## 4. test2のMappingを作成
```
PUT test2
{
  "mappings": {
    "properties": {
      "num": {
        "type": "long"
      }
    }
  }
}
```

## 5. 一時保存先からtest2に中身をコピー
```
POST _reindex
{
  "source": {
    "index": "tmp-test2"
  },
  "dest": {
    "index": "test2"
  }
}
```

## 6. 一時保存先を削除
```
DELETE tmp-test2
```

## 7. test* で Refresh field list
Management/Index Patternにいって`test*`をクリック、右上のリロードっぽいボタンを押して Refresh field list します。

## 8. コンフリクト解消🙌
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125635/298bbe9b-e1d0-4431-10e9-837836f267a6.png)

## さいごに
Discoverで`test*`を見てみましょう。データが3つしかありません。`test2`の_id2のドキュメントはパースすることができなかったため、_reindexでコピーされなかったからです。注意してください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125635/f14d487d-578a-191e-886a-fa84c9856961.png)

間違い・もっと良い方法などありましたらご指摘いただけると嬉しいです。
この記事が誰かの役に立てば幸いです。
質問は[@mn_chorome](https://twitter.com/mn_chorome)まで。

# 参考
- https://discuss.elastic.co/t/how-to-fix-mapping-conflict/55072?source_topic_id=92945
- https://medium.com/@eyaldahari/reindex-elasticsearch-documents-is-easier-than-ever-103f63d411c