小技

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

Elasticsearch のドキュメント更新をすっきり理解する

Elasticsearch におけるドキュメントの更新は、ドキュメントの登録の場合と同じく、いくつかやり方がありそうです。 こちらも自分なりに調べた結果をまとめてみます。

Index API の _doc エンドポイント

前回の記事で調べた通り、_doc エンドポイントでは既存のドキュメントに対して、同じドキュメント ID で PUT または POST メソッドでリクエストを行うことで、ドキュメントの更新が可能です。 ここでは、あらかじめ以下のようにドキュメントを登録しておいたものとします。

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

この時、以下のようにリクエストを行うことで、フィールドの値を変更することができます。

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

# レスポンス
{
  "_index": "test-index",
  "_id": "1",
  "_version": 2,
  "result": "updated",
  "_shards": {
    "total": 1,
    "successful": 1,
    "failed": 0
  },
  "_seq_no": 1,
  "_primary_term": 1
}

レスポンスの result フィールドが updated となっていて、更新が行われたことがわかります。POST メソッドを用いた場合も、同様の応答となります。 更新されたドキュメントを実際に取得してみると、フィールドの値が更新時に指定したものになっています。

GET test-index/_doc/1

# レスポンス
{
  "_index": "test-index",
  "_id": "1",
  "_version": 2,
  "_seq_no": 1,
  "_primary_term": 1,
  "found": true,
  "_source": {
    "field1": "bar"
  }
}

では、異なるフィールド名を指定した場合はどうなるか試してみます。

PUT test-index/_doc/1
{
    "field2": "bar"
}

この場合も同様にレスポンスの result フィールドは updated となりますが、更新されたドキュメントを確認してみると、以下のようになっています。

GET test-index/_doc/1

# レスポンス
{
  "_index": "test-index",
  "_id": "1",
  "_version": 3,
  "_seq_no": 2,
  "_primary_term": 1,
  "found": true,
  "_source": {
    "field2": "bar"
  }
}

更新後のドキュメントには field2 が含まれていますが、field1 は含まれていません。つまりここでのドキュメントの更新は、全体の上書き、または置き換えになっています。 更新時に既存のドキュメントのすべてのフィールドとその値を記述し、加えて追加のフィールドと値を記述することで、フィールドを追加することができます。しかし、既存のドキュメントが多数のフィールドを含む場合にはこれは大変な作業になります。 では、既存ドキュメントの一部のみを更新したい場合には、どうするのが良いのでしょうか。

Update API を使ったドキュメントの更新

既存のドキュメントの一部を更新する方法について、公式ドキュメント では以下のように紹介されています。

Update part of a documentedit

The following partial update adds a new field to the existing document:

 POST test/_update/1
 {
   "doc": {
     "name": "new_name"
   }
 }

前節の例に続けて以下のようにすることで、フィールドの追加を行うことができます。

POST test-index/_update/1
{
  "doc": {
    "field3": "baz"
  }
}

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

更新されたドキュメントを見てみると、field3 が追加され、もともとあった field2 も残っていることがわかります。

GET test-index/_doc/1

{
  "_index": "test-index",
  "_id": "1",
  "_version": 4,
  "_seq_no": 3,
  "_primary_term": 1,
  "found": true,
  "_source": {
    "field2": "bar",
    "field3": "baz"
  }
}

もちろん、既存のフィールドの値を変更することもできます。

POST test-index/_update/1
{
  "doc": {
    "field3": "foo"
  }
}

GET test-index/_doc/1

{
  "_index": "test-index",
  "_id": "1",
  "_version": 5,
  "_seq_no": 4,
  "_primary_term": 1,
  "found": true,
  "_source": {
    "field2": "bar",
    "field3": "foo"
  }
}

ここでは、更新時に指定した field3 の値が変更されている一方で、指定していない field2 については元の値のままになっています。

既存ドキュメントのフィールドを削除する方法

既存のドキュメントに対して、フィールドの値の変更やフィールドの追加を行えることがわかりました。 _doc エンドポイントを用いる方法では、更新時に指定した内容でドキュメントの上書きを行うため、必要ないフィールドについては更新時に指定しないことで、結果として削除することができます。 しかし、やはり多数のフィールドを持つドキュメントから少数のフィールドを削除したい場合には、記述する内容が多くなるため、大変な作業になります。 Update API を用いて、指定したフィールドのみを削除する方法はないものでしょうか。

まず思いつくのは、値を null にすることです。

PUT test-index/_doc/1
{
    "field2": null
}

# レスポンス
{
  "_index": "test-index",
  "_id": "1",
  "_version": 6,
  "result": "updated",
  "_shards": {
    "total": 1,
    "successful": 1,
    "failed": 0
  },
  "_seq_no": 5,
  "_primary_term": 1
}

しかしこの方法では、対象のフィールドの値が null に変更されるのみで、フィールドそのものは削除されません。

GET test-index/_doc/1

# レスポンス
{
  "_index": "test-index",
  "_id": "1",
  "_version": 6,
  "_seq_no": 5,
  "_primary_term": 1,
  "found": true,
  "_source": {
    "field2": null,
    "field3": "foo"
  }
}

Update APIでは、doc 要素を指定して更新を行うだけでなく、スクリプト実行を実行することができ、これによりフィールドの削除を行うことができます。 (むしろ公式ドキュメントではスクリプトの実行の方が先に記載されています)

Conversely, this script removes the field new_field:

POST test/_update/1
{
  "script" : "ctx._source.remove('new_field')"
}

前節までの例では、以下のようにして field2 を削除することができます。

POST test-index/_update/1
{
  "script": "ctx._source.remove('field2')"
}

{
  "_index": "test-index",
  "_id": "1",
  "_version": 6,
  "result": "updated",
  "_shards": {
    "total": 1,
    "successful": 1,
    "failed": 0
  },
  "_seq_no": 5,
  "_primary_term": 1
}

GET test-index/_doc/1

{
  "_index": "test-index",
  "_id": "1",
  "_version": 6,
  "_seq_no": 5,
  "_primary_term": 1,
  "found": true,
  "_source": {
    "field3": "foo"
  }
}

参考

Update API | Elasticsearch Guide [8.12] | Elastic