小技

日々の仕事の中で調べたり発見したりしたことを書き留めていくブログ

Elasticsearch へのドキュメント登録をすっきり理解する

Elasticsearch を利用するにあたり、ドキュメントの登録の仕方を調べようと思ってググってみると、いろいろな記述が見つかります。 どうやらやり方はいくつかありそうなものの、リクエストのメソッドは PUTPOST、どちらにするのが良いのか、ドキュメント ID の指定は必要なのか等、すっきりしないことが多々ありました。 ここでは、自分なりに調べた結果をまとめてみます。

_doc_create

Elasticsearch へのドキュメントの登録について、多くの記事では _doc エンドポイントに関する記述があります。一方、Elasticsearch の Index API には、_create エンドポイントというのもあるようです。 Elastic の公式ドキュメントには、以下の記述があります。

Request
PUT /<target>/_doc/<_id>
POST /<target>/_doc/
PUT /<target>/_create/<_id>
POST /<target>/_create/<_id>

Prerequisites
(略)
- To add or overwrite a document using the PUT /<target>/_doc/<_id> request format, you must have the create, index, or write index privilege.
- To add a document using the POST /<target>/_doc/, PUT /<target>/_create/<_id>, or POST /<target>/_create/<_id> request formats, you must have the create_doc, create, index, or write index privilege.

<target> の部分には対象のインデックス名を指定します。上記の記述を見る限り、PUT /<target>/_doc/<_id> という形式でリクエストを行うことで、ドキュメントの追加、または上書きができるようです。 一方、その他の形式に関しては、ドキュメントの追加については記述がありますが、上書きについては記述がありません。

_doc エンドポイントによるドキュメント登録

_doc エンドポイントでは、メソッドとして PUTPOST を使うことができます。また、リクエスURI についても、/<target>/_doc/<_id>/<target>/_doc/ のように、ドキュメント ID を含むものと含まないものの2つのパターンがあるようです。

ドキュメント ID を含む URI を指定した場合は、PUTPOST どちらのメソッドでもリクエストを行うことができます。指定したドキュメント ID がまだ存在しない場合は、リクエストボディとして送信したドキュメントが、指定したドキュメント ID で登録されることとなります。 レスポンスコードはいずれのメソッドの場合も 201 Created となり、レスポンスボディの result フィールドは created となります。

PUT test-index/_doc/1
{
  "field1": "foo"
}

# レスポンスコード 201 Created
{
  "_index": "test-index",
  "_id": "1",
  "_version": 1,
  "result": "created",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "_seq_no": 0,
  "_primary_term": 1
}
POST /test_index/_doc/2
{
  "field1": "foo"
}

# レスポンスコード 201 Created
{
  "_index": "test-index",
  "_id": "2",
  "_version": 1,
  "result": "created",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "_seq_no": 1,
  "_primary_term": 1
}

ドキュメント ID を含まない URI に対しては、POST メソッドでのみリクエストを行うことができます。この場合、ドキュメント ID は Elasticsearch により自動採番されます。 一方、ドキュメント ID を含まない URI に対して PUT メソッドでリクエストを行った場合は、下記のようにエラーとなってしまいます。

PUT /test_index/_doc/
{
  "field1": "foo"
}

# レスポンスコード 405 Not Allowed
{
  "error": "Incorrect HTTP method for uri [/test-index/_doc/] and method [PUT], allowed: [POST]",
  "status": 405
}
POST test-index/_doc/
{
  "field1": "foo"
}

# レスポンスコード 201 Created
{
  "_index": "test-index",
  "_id": "s8jQ44wBuSrg6d_5yvgh",
  "_version": 1,
  "result": "created",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "_seq_no": 2,
  "_primary_term": 1
}

こうした挙動は、Elasticsearch の API の実装というより、元々の PUT メソッドの用途に起因するようです。

(Wikipediaより)

PUT
指定したURIにリソースを保存する。URIが指し示すリソースが存在しない場合は、サーバはそのURIにリソースを作成する。画像のアップロードなどが代表的。

PUT メソッドでは、リソース (ここではドキュメント) の URI を指定して保存を行うので、ドキュメント ID を含む URI を指定する必要があるものと考えられます。実際のところ、対象のドキュメントを取得する場合は、同じくドキュメント ID を含む URI を指定して GET メソッドによりリクエストを行います。

GET test-index/_doc/1

# レスポンスコード 200 OK
{
  "_index": "test-index",
  "_id": "1",
  "_version": 1,
  "_seq_no": 0,
  "_primary_term": 1,
  "found": true,
  "_source": {
    "field1": "foo"
  }
}

_doc エンドポイントによるドキュメントの上書き

ドキュメント ID を含む URI を指定して PUT または POST メソッドでリクエストを行う際、指定したドキュメント ID が既に存在している場合は、ドキュメントの上書きが行われます。 この場合はいずれのメソッドでもレスポンスコードは 200 OK となり、result フィールドは updated となります。また、_version フィールドの値が 1 ずつ増加していることがわかります。

PUT test-index/_doc/1
{
  "field1": "foo"
}

# レスポンスコード 200 OK
{
  "_index": "test-index",
  "_id": "1",
  "_version": 2,
  "result": "updated",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "_seq_no": 3,
  "_primary_term": 1
}
POST /test_index/_doc/1
{
  "field1": "foo"
}

# レスポンスコード 200 OK
{
  "_index": "test-index",
  "_id": "1",
  "_version": 3,
  "result": "updated",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "_seq_no": 4,
  "_primary_term": 1
}

ドキュメント ID を含まない URI を指定する場合は、ドキュメント ID は自動採番されるため、指定したドキュメント ID が既に存在しているということはあり得ません。このため、ドキュメントの上書きも発生し得ないことになります。 ドキュメント ID を含まない URI を指定して POST メソッドで複数回リクエストを行うと、毎回新たなドキュメント ID でドキュメントが新規作成されます。

POST test-index/_doc/
{
  "field1": "foo"
}

# レスポンスコード 201 Created
{
  "_index": "test-index",
  "_id": "tMjW44wBuSrg6d_5QviD",
  "_version": 1,
  "result": "created",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "_seq_no": 5,
  "_primary_term": 1
}
POST test-index/_doc/
{
  "field1": "foo"
}

# レスポンスコード 201 Created
{
  "_index": "test-index",
  "_id": "tcjW44wBuSrg6d_5kPiU",
  "_version": 1,
  "result": "created",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "_seq_no": 6,
  "_primary_term": 1
}

_create エンドポイントによるドキュメント登録

_create エンドポイントでも、メソッドとして PUTPOST を使うことができます。URI については、ドキュメント ID を含むものを指定する必要があります。指定したドキュメント ID が存在しない場合は、ドキュメントの新規作成が行われます。

PUT test-index/_create/3
{
  "field1": "foo"
}

# レスポンスコード 201 Created
{
  "_index": "test-index",
  "_id": "3",
  "_version": 1,
  "result": "created",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "_seq_no": 7,
  "_primary_term": 1
}
POST /test_index/_create/4
{
  "field1": "foo"
}

# レスポンスコード 201 Created
{
  "_index": "test-index",
  "_id": "4",
  "_version": 1,
  "result": "created",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "_seq_no": 8,
  "_primary_term": 1
}

_create エンドポイントでは、ドキュメントの上書きは行われません。URI の中で指定したドキュメント ID が既に存在する場合は、上書きは行われず、下記のようにエラーとなります。

PUT test-index/_create/1
{
  "field1": "foo"
}

# レスポンスコード 409 Conflict
{
  "error": {
    "root_cause": [
      {
        "type": "version_conflict_engine_exception",
        "reason": "[1]: version conflict, document already exists (current version [3])",
        "index_uuid": "TgaTsAzNTnq9BXGM12n8dQ",
        "shard": "0",
        "index": "test-index"
      }
    ],
    "type": "version_conflict_engine_exception",
    "reason": "[1]: version conflict, document already exists (current version [3])",
    "index_uuid": "TgaTsAzNTnq9BXGM12n8dQ",
    "shard": "0",
    "index": "test-index"
  },
  "status": 409
}
POST /test_index/_create/1
{
  "field1": "foo"
}

# レスポンスコード 409 Conflict
{
  "error": {
    "root_cause": [
      {
        "type": "version_conflict_engine_exception",
        "reason": "[1]: version conflict, document already exists (current version [3])",
        "index_uuid": "TgaTsAzNTnq9BXGM12n8dQ",
        "shard": "0",
        "index": "test-index"
      }
    ],
    "type": "version_conflict_engine_exception",
    "reason": "[1]: version conflict, document already exists (current version [3])",
    "index_uuid": "TgaTsAzNTnq9BXGM12n8dQ",
    "shard": "0",
    "index": "test-index"
  },
  "status": 409
}

また、ドキュメント ID を含まない URI を指定した場合は、いずれのメソッドを用いた場合でもエラーとなります。ただし、エラーの内容はそれぞれ異なります。 PUT メソッドでリクエストを行った場合は、メソッドが利用できないとのメッセージがレスポンスとして返されます。一方で、POST メソッドは許可されているようなので、こちらは実行できそうなものの、実際にリクエストを行ってみると Bad Request エラーとなってしまいます。

PUT test-index/_create/
{
  "field1": "foo"
}

# レスポンスコード 405 Method Not Allowed
{
  "error": "Incorrect HTTP method for uri [/test-index/_create/] and method [PUT], allowed: [POST]",
  "status": 405
}
POST test-index/_create/
{
  "field1": "foo"
}

# レスポンスコード 400 Bad Request
{
  "error": "no handler found for uri [/test-index/_create/] and method [POST]"
}

op_type パラメータの利用

ElasticSearch の Index API においてドキュメント登録時の挙動に関係する要素としては、op_type パラメータというものもあります。

(Elastic の公式ドキュメントより)

op_type (Optional, enum) Set to create to only index the document if it does not already exist (put if absent). If a document with the specified _id already exists, the indexing operation will fail. Same as using the <index>/_create endpoint. Valid values: index, create. If document id is specified, it defaults to index. Otherwise, it defaults to create.

このパラメータの値は index または create とすることができ、create とした場合は作成のみを行うことができます。リクエスURI の中でドキュメント ID を指定した場合は、パラメータのデフォルト値は index であり、そうでない場合は create となるようです。ドキュメント ID を指定しない場合は、メソッドが利用できないなどの理由でエラーとなる場合を除いては、自動採番されたドキュメント ID とともにドキュメントが新規作成されます。このため、op_type が create または index どちらの値であっても、挙動に違いはないものと考えられます。

実際の挙動を確認すると、_doc エンドポイントへのリクエストで op_typecreate とすると、指定したドキュメント ID が既存のものであった場合には以下のようにエラーとなります。

PUT /test-index/_doc/1?op_type=create
{
  "field1": "foo"
}

# レスポンスコード 409 Conflict
{
  "error": {
    "root_cause": [
      {
        "type": "version_conflict_engine_exception",
        "reason": "[1]: version conflict, document already exists (current version [3])",
        "index_uuid": "TgaTsAzNTnq9BXGM12n8dQ",
        "shard": "0",
        "index": "test-index"
      }
    ],
    "type": "version_conflict_engine_exception",
    "reason": "[1]: version conflict, document already exists (current version [3])",
    "index_uuid": "TgaTsAzNTnq9BXGM12n8dQ",
    "shard": "0",
    "index": "test-index"
  },
  "status": 409
}
POST test-index/_doc/1?op_type=create
{
  "field1": "foo"
}

# レスポンスコード 409 Conflict
{
  "error": {
    "root_cause": [
      {
        "type": "version_conflict_engine_exception",
        "reason": "[1]: version conflict, document already exists (current version [3])",
        "index_uuid": "TgaTsAzNTnq9BXGM12n8dQ",
        "shard": "0",
        "index": "test-index"
      }
    ],
    "type": "version_conflict_engine_exception",
    "reason": "[1]: version conflict, document already exists (current version [3])",
    "index_uuid": "TgaTsAzNTnq9BXGM12n8dQ",
    "shard": "0",
    "index": "test-index"
  },
  "status": 409
}

_create エンドポイントについても op_type パラメータの指定は可能ですが、create 以外の値は利用できなさそうです。

PUT /test-index/_create/1?op_type=index
{
  "field1": "foo"
}

{
  "error": {
    "root_cause": [
      {
        "type": "illegal_argument_exception",
        "reason": "opType must be 'create', found: [index]"
      }
    ],
    "type": "illegal_argument_exception",
    "reason": "opType must be 'create', found: [index]"
  },
  "status": 400
}
POST /test-index/_create/1?op_type=index
{
  "field1": "foo"
}

{
  "error": {
    "root_cause": [
      {
        "type": "illegal_argument_exception",
        "reason": "opType must be 'create', found: [index]"
      }
    ],
    "type": "illegal_argument_exception",
    "reason": "opType must be 'create', found: [index]"
  },
  "status": 400
}

まとめ

以上をまとめると、op_type を明示的に指定しない場合、_doc エンドポイントへのリクエストとその結果は以下のようになります。

リクエストメソッドURIドキュメント ID
新規既存
PUT/<target>/_doc/<_id>新規作成(201 Created)上書き(200 OK)
POST/<target>/_doc/<_id>新規作成(201 Created)上書き(200 OK)
PUT/<target>/_doc/エラー(405 Method Not Allowed)-
POST/<target>/_doc/新規作成(201 Created)
* ドキュメント ID は自動採番
-

同様に _create エンドポイントについては以下のとおりです。

リクエストメソッドURIドキュメント ID
新規既存
PUT/<target>/_create/<_id>新規作成(201 Created)エラー(409 Conflict)
POST/<target>/_create/<_id>新規作成(201 Created)エラー(409 Conflict)
PUT/<target>/_create/エラー(405 Method Not Allowed)-
POST/<target>/_create/エラー(400 Bad Request)-

まず、ドキュメント ID を含まない URI に対しては、_doc エンドポイントに POST メソッドでリクエストを行う以外の方法ではエラー (400 Bad Request405 Method Not Allowed) となるため、やり方は一通りしかないことがわかります。また、リクエストの結果については、ドキュメント ID が新規に自動採番されるため、ドキュメント新規作成のみ、となります。

次に、ドキュメント ID を含む URI へのリクエストについては、それぞれ op_type がどのようになっているかにより挙動が異なります。op_typecreate または index の値をとることができ、create とした場合はドキュメントの新規作成のみ、index とした場合は、ドキュメントの新規作成、または上書きのいずれかが行われます。

_create エンドポイントでは op_type の値を index にしようとするとエラー(400 Bad Request)となるため、op_type の値は常に create となっているものと考えられます。このため、ドキュメントの新規作成のみを行うことができ、URI の中で既存のドキュメント ID を指定してリクエストを行うとエラー(409 Conflict)となります。

_doc エンドポイントでは、URI の中でドキュメント ID を指定する場合は op_type はデフォルトで index となるため、指定したドキュメント ID が既に存在するかどうかに応じて、ドキュメントの新規作成、または上書きのいずれかが行われます。一方、_doc エンドポイントでもリクエスト時に op_type の値を create とすることができ、この場合は既存のドキュメント ID を指定して上書きが発生するような状況ではエラー (409 Conflict) が発生します。

結局どうするのが良いのか

以下は私見となりますが、利用する場面に応じて以下のように使い分けするのが良さそうです。

ドキュメントの登録時にドキュメント ID にはこだわらない場合

POST /<target>/_doc/

_doc エンドポイントに POST メソッドでリクエストを行う必要があり、そのほかの方法ではエラーとなります。 ドキュメント ID を指定しなくても自動採番されるため、指定が必要な情報が最も少なく、スムーズかと思います。

ドキュメントの登録時にドキュメント ID を明示的に指定したい場合

PUT /<target>/_doc/<_id>

POST /<target>/_doc/<_id>

いずれの方法でも違いはなさそうなので、ドキュメント ID を指定しない場合と同様、POST メソッドのみ使うのでもよいように思えます。

ドキュメントの登録時にドキュメント ID を明示的に指定したい、かつ新規作成のみを行いたい場合

PUT /<target>/_create/<_id>

POST /<target>/_create/<_id>

PUT /<target>/_doc/<_id>?op_type=create

POST /<target>/_doc/<_id>?op_type=create

指定したドキュメント ID が既に存在する場合はエラー(409 Conflict)応答を受け取ることができます。 こちらもいずれの方法でも違いはなさそうですが、_create エンドポイントと op_type の指定はやりやすい方を選べばよいかと思います。

参考

Index API | Elasticsearch Guide [8.11] | Elastic

Hypertext Transfer Protocol - Wikipedia

PUT と POST どっち使う?5分で使い分けを確実に覚えよう! #API - Qiita