Elasticsearch を利用するにあたり、ドキュメントの登録の仕方を調べようと思ってググってみると、いろいろな記述が見つかります。
どうやらやり方はいくつかありそうなものの、リクエストのメソッドは PUT
と POST
、どちらにするのが良いのか、ドキュメント 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
エンドポイントでは、メソッドとして PUT
と POST
を使うことができます。また、リクエスト URI についても、/<target>/_doc/<_id>
と /<target>/_doc/
のように、ドキュメント ID を含むものと含まないものの2つのパターンがあるようです。
ドキュメント ID を含む URI を指定した場合は、PUT
と POST
どちらのメソッドでもリクエストを行うことができます。指定したドキュメント 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
エンドポイントでも、メソッドとして PUT
と POST
を使うことができます。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_type
を create
とすると、指定したドキュメント 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 Request
や 405 Method Not Allowed
) となるため、やり方は一通りしかないことがわかります。また、リクエストの結果については、ドキュメント ID が新規に自動採番されるため、ドキュメント新規作成のみ、となります。
次に、ドキュメント ID を含む URI へのリクエストについては、それぞれ op_type
がどのようになっているかにより挙動が異なります。op_type
は create
または 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