OpenSearchにBulkで登録しようとしたらエラーになった

OpenSearchのマッピングも無事完了し、データ投入をしようとした時にエラーになってハマったのでその覚書です。

# 前提
実行環境はOpenSearch DashbordsのDev Toolsです。
HTTPなリクエストを投げても結果は同じになると思います。

# 準備編
インデックスを作ってマッピング定義をします。
わかりやすさのために本質と関係のない箇所は簡略化しています。

```
PUT blog
{
    "settings": {
        "analysis": {
            "analyzer": {
                "ja_analyzer": {
                    "type": "custom",
                    "char_filter":[
                        "icu_normalizer"
                    ],
                    "tokenizer": "kuromoji_tokenizer",
                    "filter": [
                        "kuromoji_baseform",
                        "kuromoji_part_of_speech",
                        "ja_stop",
                        "kuromoji_number",
                        "kuromoji_stemmer"
                    ]
                }
            }
        }
    },
    "mappings": {
        "properties": {
            "title": {
                "type": "text",
                "fielddata": true,
                "analyzer": "ja_analyzer",
                "search_analyzer": "ja_analyzer"
            },
            "thumbnail": {
                "properties": {
                    "image": {
                        "type": "keyword"
                    },
                    "alt": {
                        "type": "keyword"
                    }
                }
            },
            "content": {
              "type": "text",
              "fielddata": true,
              "analyzer": "ja_analyzer",
              "search_analyzer": "ja_analyzer"
            },
            "category": {
                "type": "byte"
            }
        }
    }
}
```

# データ投入
さて、いよいよ本題のデータ投入です。
データ投入するにはデータを１件ずつ投入していく方法と、まとめて投入する方法があります。
１件ずつであればこんな感じ。
```
POST blog/_doc/1
{
    "title": "記事のタイトル１",
    "thumbnail.image": "/images/category1.png",
    "thumbnail.alt": "カテゴリ１の記事",
    "content": "記事の本文１",
    "category": 1
}
```

まとめてならBulkを使ってこんな感じ。
対象のインデックス指定とデータを交互に。
```
POST blog/_bulk
{
    "index": {
        "_index": "blog",
        "_id": "1"
    }
}
{
    "title": "記事のタイトル１",
    "thumbnail.image": "/images/category1.png",
    "thumbnail.alt": "カテゴリ１の記事",
    "content": "記事の本文１",
    "category": 1
}
{
    "index": {
        "_index": "blog",
        "_id": "2"
    }
}
{
    "title": "記事のタイトル２",
    "thumbnail.image": "/images/category1.png",
    "thumbnail.alt": "カテゴリ１の記事",
    "content": "記事の本文２",
    "category": 1
}
```

で登録できると思ってました。

思っていたんですが、Bulkは流すとエラーが起きてしまいました。
> {
  "error" : {
    "root_cause" : [
      {
        "type" : "json_e_o_f_exception",
        "reason" : "Unexpected end-of-input: expected close marker for Object (start marker at [Source: (byte[])\"{\"; line: 1, column: 1])\n at [Source: (byte[])\"{\"; line: 1, column: 2]"
      }
    ],
    "type" : "json_e_o_f_exception",
    "reason" : "Unexpected end-of-input: expected close marker for Object (start marker at [Source: (byte[])\"{\"; line: 1, column: 1])\n at [Source: (byte[])\"{\"; line: 1, column: 2]"
  },
  "status" : 400
}

なんだこれ。。。
`json_e_o_f_exception`ってありますが、よく見るとreasonに`Unexpected end-of-input: expected close marker for Object`とあるので、JSONのフォーマット間違っているようです。
目視する限り間違っていないように見えます。
ここは自分の目を疑って、parseしてみます。
http://json.parser.online.fr/
こちらにペタっと貼り付けて、頭とお尻を`[` `]`で囲って配列にします。
結果は、問題なし。

改行コードがLFになっていたのでこれがダメなのか？と思ってCRLFに変換してみても状況変わらず。
出力されたメッセージには`[Source: (byte[])\"{\"; line: 1, column: 1])`とあり、1行目が問題だと言われているので、１行目から改行外してみます。

```
POST blog/_bulk
{"index": {"_index": "blog","_id": "1"}}
{
    "title": "記事のタイトル１",
    "thumbnail.image": "/images/category1.png",
    "thumbnail.alt": "カテゴリ１の記事",
    "content": "記事の本文１",
    "category": 1
}
{
    "index": {
        "_index": "blog",
        "_id": "2"
    }
}
{
    "title": "記事のタイトル２",
    "thumbnail.image": "/images/category1.png",
    "thumbnail.alt": "カテゴリ１の記事",
    "content": "記事の本文２",
    "category": 1
}
```

別のエラーが返ってきました。
> {
  "error" : {
    "root_cause" : [
      {
        "type" : "illegal_argument_exception",
        "reason" : "Malformed action/metadata line [3], expected START_OBJECT but found [VALUE_STRING]"
      }
    ],
    "type" : "illegal_argument_exception",
    "reason" : "Malformed action/metadata line [3], expected START_OBJECT but found [VALUE_STRING]"
  },
  "status" : 400
}

今度は3行目のフォーマット違反だと。
オブジェクトが始まらないといけないのに文字列が始まってるよ。。。ここも改行コードが悪さしてるの？
ということで、こちらも削ってみます。

```
POST blog/_bulk
{"index": {"_index": "blog","_id": "1"}}
{"title": "記事のタイトル１","thumbnail.image": "/images/category1.png","thumbnail.alt": "カテゴリ１の記事","content": "記事の本文１","category": 1
}
{
    "index": {
        "_index": "blog",
        "_id": "2"
    }
}
{
    "title": "記事のタイトル２",
    "thumbnail.image": "/images/category1.png",
    "thumbnail.alt": "カテゴリ１の記事",
    "content": "記事の本文２",
    "category": 1
}
```

また違うエラーになりました。

> {
  "error" : {
    "root_cause" : [
      {
        "type" : "json_parse_exception",
        "reason" : "Unexpected close marker '}': expected ']' (for root starting at [Source: (byte[])\"}\"; line: 1, column: 0])\n at [Source: (byte[])\"}\"; line: 1, column: 2]"
      }
    ],
    "type" : "json_parse_exception",
    "reason" : "Unexpected close marker '}': expected ']' (for root starting at [Source: (byte[])\"}\"; line: 1, column: 0])\n at [Source: (byte[])\"}\"; line: 1, column: 2]"
  },
  "status" : 400
}

今度はオブジェクトの終了がないと。
データを定義しているオブジェクトの終了だけ次の行にいるので、ここも改行外してみます。

```
POST blog/_bulk
{"index": {"_index": "blog","_id": "1"}}
{"title": "記事のタイトル１","thumbnail.image": "/images/category1.png","thumbnail.alt": "カテゴリ１の記事","content": "記事の本文１","category": 1}
{
    "index": {
        "_index": "blog",
        "_id": "2"
    }
}
{
    "title": "記事のタイトル２",
    "thumbnail.image": "/images/category1.png",
    "thumbnail.alt": "カテゴリ１の記事",
    "content": "記事の本文２",
    "category": 1
}
```

また最初のエラーに戻りました。
エラーの行数まで同じ。
なんでやねん！とツッコまざるを得ない。。。
ですが、おそらく２つ目のデータの塊（インデックス指定とデータのセット）が１つ目の修正前と同じ状態なのでこちらも改行を取っ払えばよさそう。

```
POST blog/_bulk
{"index": {"_index": "blog","_id": "1"}}
{"title": "記事のタイトル１","thumbnail.image": "/images/category1.png","thumbnail.alt": "カテゴリ１の記事","content": "記事の本文１","category": 1}
{"index": {"_index": "blog","_id": "2"}}
{"title": "記事のタイトル２","thumbnail.image": "/images/category1.png","thumbnail.alt": "カテゴリ１の記事","content": "記事の本文２","category": 1}
```

やっと、無事登録できました。
長かった。

# 結論/解決策
Bulkでデータ投入する際にはJSONだからと自由に整形せず、
・インデックスの指定
・データの指定
この２つをそれぞれ１行にまとめて繰り返しましょう。

ドキュメントのどこかに書いてあるのかもしれませんが、完全見落とし。。。
しかし、JSONだから改行読み飛ばして処理してくれよー