Elasticsearch REST APIを使用したドキュメントのインデックス作成と更新

REST APIを使用してElasticsearchでコアな作成、読み取り、更新、削除(CRUD)操作を習得します。このガイドでは、新しいドキュメントのインデックス作成(ID指定あり/なし)や、既存レコードの部分更新に必要な正確なHTTPリクエスト、エンドポイント、JSONペイロードについて詳しく説明します。アトミック更新、スクリプトによる変更、効率的な一括データ取り込みのための実用的なcurl例を学びます。

Elasticsearch REST APIを使用したドキュメントのインデックス作成と更新

Elasticsearch REST APIを使用したドキュメントのインデックス作成と更新は、最初は簡単に見えます。JSONをエンドポイントに送信し、JSONを受け取るだけです。本番環境で重要な部分はもっと具体的です。新しいドキュメントを作成していますか、それとも古いものを置き換えていますか?ElasticsearchにIDを生成させますか、それともソースシステムからのIDが必要ですか?部分更新を行っていますか、それとも保持したいフィールドを誤って上書きしていませんか?

以下の例では、localhost:9200productsというインデックス、およびプレーンなcurlを使用しています。実際のクラスターでは、HTTPS、認証、および証明書オプションも必要になる場合があります。

curl -u elastic:password --cacert http_ca.crt https://es.example.com:9200/

自動IDでドキュメントを作成する

ElasticsearchにドキュメントIDを割り当てさせる場合は、POST /{index}/_docを使用します。

curl -X POST "localhost:9200/products/_doc" \
  -H "Content-Type: application/json" \
  -d '{
    "sku": "mouse-x1",
    "name": "Wireless Mouse X1",
    "price": 25.99,
    "in_stock": true,
    "updated_at": "2026-05-24T09:30:00Z"
  }'

成功した応答には、_idresultcreatedとして含まれます。

{
  "_index": "products",
  "_id": "wI4aQZkB8xExample",
  "_version": 1,
  "result": "created"
}

自動IDは、イベントデータ、ログ、クリックレコード、追記専用コンテンツに適しています。同じ実世界のアイテムが後で再度到着する可能性がある場合には理想的ではありません。同じ製品更新が自動IDで2回インデックスされると、2つのドキュメントが作成されます。

独自のIDでインデックスを作成する

ソースシステムにすでに安定した識別子がある場合は、PUT /{index}/_doc/{id}を使用します。

curl -X PUT "localhost:9200/products/_doc/sku-mouse-x1" \
  -H "Content-Type: application/json" \
  -d '{
    "sku": "mouse-x1",
    "name": "Wireless Mouse X1",
    "price": 25.99,
    "in_stock": true,
    "updated_at": "2026-05-24T09:30:00Z"
  }'

IDが存在しない場合、Elasticsearchはそれを作成します。すでに存在する場合、Elasticsearchはドキュメント全体を置き換えます。この置き換え動作は単純な同期ジョブには便利ですが、送信し忘れたフィールドを削除する可能性もあります。

たとえば、既存のドキュメントにcategorybranddescriptionがあり、次のPUTpriceのみを送信した場合、保存された_sourceはその新しいリクエストのフィールドのみになります。完全な目的のドキュメントを送信する場合は、PUTを使用します。

存在しない場合のみ作成する

重複作成がバグになる場合は、作成操作を使用します。

curl -X PUT "localhost:9200/products/_create/sku-mouse-x1" \
  -H "Content-Type: application/json" \
  -d '{
    "sku": "mouse-x1",
    "name": "Wireless Mouse X1",
    "price": 25.99
  }'

IDがすでに存在する場合、Elasticsearchはドキュメントを上書きする代わりにバージョン競合を返します。これは、メッセージの再試行が既存のレコードを変更してはならない取り込みジョブに便利です。

フィールドを部分的に更新する

一部のフィールドのみを変更したい場合は、docオブジェクトを指定してPOST /{index}/_update/{id}を使用します。

curl -X POST "localhost:9200/products/_update/sku-mouse-x1" \
  -H "Content-Type: application/json" \
  -d '{
    "doc": {
      "price": 22.49,
      "in_stock": false,
      "updated_at": "2026-05-24T10:15:00Z"
    }
  }'

Elasticsearchは内部で既存のドキュメントを取得し、提供されたフィールドをマージして、変更されたソースを再度インデックスします。これはAPIユーザーの観点からの部分更新であり、ディスク上のインプレース変更ではありません。

ドキュメントがまだ存在しない可能性がある場合は、doc_as_upsertを使用します。

curl -X POST "localhost:9200/products/_update/sku-keyboard-k90" \
  -H "Content-Type: application/json" \
  -d '{
    "doc": {
      "sku": "keyboard-k90",
      "name": "Mechanical Keyboard K90",
      "price": 129.99,
      "in_stock": true
    },
    "doc_as_upsert": true
  }'

これにより、ドキュメントが存在しない場合はdocの内容で作成されます。アップストリームフィードが「現在の状態」レコードを送信し、ElasticsearchがそのIDを初めて見るかどうか気にしない場合に使用します。

スクリプトによる更新

スクリプトは、新しい値が古い値に依存する場合に便利です。一般的な例はカウンターのインクリメントです。

curl -X POST "localhost:9200/products/_update/sku-mouse-x1" \
  -H "Content-Type: application/json" \
  -d '{
    "script": {
      "source": "ctx._source.views = (ctx._source.views ?: 0) + params.count",
      "params": {
        "count": 1
      }
    }
  }'

スクリプトは小さく予測可能に保ちます。これらは強力ですが、プレーンなdoc更新よりも誤用しやすいです。大量のカウンターについては、書き込みレート、リフレッシュの必要性、およびElasticsearchを信頼できる情報源とすべきかどうかを慎重に検討します。

一括インデックス作成と更新

数個以上のドキュメントの場合は、_bulkを使用します。リクエスト本文は改行区切りのJSONであり、最後の改行が重要です。

cat bulk-products.ndjson
{"index":{"_index":"products","_id":"sku-mouse-x1"}}
{"sku":"mouse-x1","name":"Wireless Mouse X1","price":25.99,"in_stock":true}
{"update":{"_index":"products","_id":"sku-keyboard-k90"}}
{"doc":{"price":119.99,"in_stock":true},"doc_as_upsert":true}
{"delete":{"_index":"products","_id":"sku-old-cable"}}

次のように送信します。

curl -X POST "localhost:9200/_bulk" \
  -H "Content-Type: application/x-ndjson" \
  --data-binary @bulk-products.ndjson

プレーンな-dではなく--data-binaryを使用して、curlが改行を保持するようにします。次に応答を検査します。一括リクエストは、個々のアイテムが失敗してもHTTP 200を返す可能性があります。

curl -s -X POST "localhost:9200/_bulk" \
  -H "Content-Type: application/x-ndjson" \
  --data-binary @bulk-products.ndjson | jq '.errors, .items[] | select(.index.error or .update.error or .delete.error)'

リフレッシュと可視性

インデックスされたドキュメントは、すぐに検索可能になるとは限りません。Elasticsearchはデフォルトで定期的にインデックスをリフレッシュします。IDでドキュメントを読み取る必要がある場合、GET /products/_doc/sku-mouse-x1はすぐにそれを見つけることができます。テストを続行する前に検索に表示させる必要がある場合は、refresh=wait_forを使用します。

curl -X PUT "localhost:9200/products/_doc/sku-mouse-x1?refresh=wait_for" \
  -H "Content-Type: application/json" \
  -d '{"sku":"mouse-x1","name":"Wireless Mouse X1","price":25.99}'

コストを測定せずに、すべての本番書き込みに強制リフレッシュを追加しないでください。インデックススループットを低下させる可能性があります。

実用的な経験則

追記専用データで重複イベントが許容されるか、他の場所で処理される場合は、POST /_docを使用します。リクエストに完全な現在のドキュメントが含まれている場合は、PUT /_doc/{id}を使用します。特定のフィールドのみを変更したい場合は、_updateを使用します。上書きがデータの問題を隠してしまう場合は、_createを使用します。作業が数個以上のドキュメントの場合は、_bulkを使用します。

ほとんどのElasticsearchインデックス作成バグは、間違った書き込み形状を選択することから発生します。エンドポイントは、ソースデータ、再試行動作、およびElasticsearchが完全なレコードを保存しているのか、別のシステムのレコードの検索可能なコピーを保存しているのかに一致する必要があります。

書き込みリクエストを非難する前にマッピングを確認する

書き込みが成功しても、検索結果が間違って見えることがあります。これは多くの場合、インデックス作成エンドポイントの問題ではなく、マッピングの問題です。

マッピングを確認します。

curl -s "localhost:9200/products/_mapping?pretty"

priceが初期のテストドキュメントで文字列として"25.99"を送信したために最初にテキストとしてインデックスされた場合、範囲クエリの動作が悪くなる可能性があります。skuが完全一致と集計を必要とする場合、通常はkeywordフィールドまたはキーワードサブフィールドを持つ必要があります。タイムスタンプが異なる形式で到着する場合、一部のドキュメントはインデックス作成中に拒否され、他のドキュメントは成功する可能性があります。

予測可能なシステムの場合、最初の書き込みの前にインデックスマッピングを作成します。

curl -X PUT "localhost:9200/products" \
  -H "Content-Type: application/json" \
  -d '{
    "mappings": {
      "properties": {
        "sku": { "type": "keyword" },
        "name": { "type": "text" },
        "price": { "type": "double" },
        "in_stock": { "type": "boolean" },
        "updated_at": { "type": "date" }
      }
    }
  }'

動的マッピングは探索には便利です。本番取り込みでは、明示的なマッピングにより、最初の悪いドキュメントがインデックスを形成するのを防ぎます。

競合と再試行を意図的に処理する

同時更新は競合する可能性があります。2つのワーカーがほぼ同時に同じドキュメントを更新する場合、一方が古いバージョンに基づいている可能性があります。単純な更新再試行の場合、Elasticsearchはretry_on_conflictをサポートしています。

curl -X POST "localhost:9200/products/_update/sku-mouse-x1?retry_on_conflict=3" \
  -H "Content-Type: application/json" \
  -d '{
    "doc": {
      "price": 21.99
    }
  }'

これはリスクの低い更新には便利ですが、明確な所有権の代わりにはなりません。あるシステムが製品名を所有し、別のシステムが在庫を所有する場合、明確に定義されたパイプラインを通じて異なるフィールドを更新することを検討します。イベントの順序が重要な場合は、ソースタイムスタンプを含め、取り込みレイヤーまたは注意深いスクリプトで古い更新を拒否します。

デバッグ中の書き込み後の読み取り

インデックスフローをテストするときは、すぐにIDでドキュメントを取得します。

curl -s "localhost:9200/products/_doc/sku-mouse-x1?pretty"

次に検索します。

curl -s "localhost:9200/products/_search?pretty" \
  -H "Content-Type: application/json" \
  -d '{
    "query": {
      "term": {
        "sku": "mouse-x1"
      }
    }
  }'

IDによるGETは機能するが検索は機能しない場合は、リフレッシュ、マッピング、アナライザー、またはクエリタイプを考えます。両方とも失敗する場合は、インデックスエラー、間違ったインデックス、間違ったID、認証、またはルーティングを考えます。

アプリケーションの場合は、ターゲットインデックス、ドキュメントID、応答ステータス、およびアイテムレベルの一括エラーをログに記録します。すべての完全なドキュメントを永久にログに記録する必要はありませんが、ロールアウト中はこれらのフィールドが時間を節約します。