AQ Tech Blog

ElasticsearchのIndexマッピング更新について

作成者: ko.shuko|2024年07月24日

はじめに

こんにちは!Web3課のコウです。 現在携わっている案件では、仕様変更や新機能の開発に伴い、既存のElasticsearchのIndexマッピングを更新することが時々あります。

Indexのマッピング更新は、RDBのテーブル構造の変更に似ています。例えば、MySQLでカラム定義を変更したりカラムを追加・削除する際にはALTER TABLE文を使用しますが、既存テーブルのデータ移行は不要です。しかし、ElasticsearchではALTER TABLEのような機能がないため、更新後のマッピングを用いて新しいIndexを作成し、古いIndexからdocumentを新しいIndexに移行する必要があります。

今回の記事では、Index移行の手順を紹介したいと考えています。

事前準備

  • Indexを作成します
curl -XPUT 'http://localhost:9200/students_20240621?pretty' -H 'Content-Type: application/json' -d '{
"mappings": {
"properties": {
"name": {
"type": "keyword"
},
"sex": {
"type": "keyword"
},
"age": {
"type": "keyword"
}
}
}
}'
  • 作成したIndexにエイリアスを追加します
curl -XPOST 'http://localhost:9200/_aliases?pretty'  -H 'Content-Type: application/json' -d '{
"actions": [
{
"add": {
"index": "students_20240621",
"alias": "students"
}
}
]
}'

エイリアスを追加する理由: 検索を行う際にIndexを直接参照することは可能ですが、今回のようにマッピングを更新して新しいIndexを作成する場合、アプリ側でも参照先を全て変更しなければならず、効率的ではありません。エイリアスを使用すると、新しいIndexを作成した際にエイリアスを追加するだけで、アプリ側の参照先の変更は不要になります。

  • Indexにdocumentを追加します
curl -XPOST 'http://localhost:9200/_bluk?pretty'  -H 'Content-Type: application/json' -d '
{"index": {"_index": "students", "_id": "1"}}
{"name": "学生一", "sex": "男", "age": "20"}
{"index": {"_index": "students", "_id": "2"}}
{"name": "学生二", "sex": "女", "age": "21"}
{"index": {"_index": "students", "_id": "3"}}
{"name": "学生三", "sex": "男", "age": "21"}
{"index": {"_index": "students", "_id": "4"}}
{"name": "学生四", "sex": "女", "age": "22"}
{"index": {"_index": "students", "_id": "5"}}
{"name": "学生五", "sex": "男", "age": "21"}
'

実際にやってみよう

①マッピングを更新して、新しいIndexを作成します

  • nameフィールド名をfull_nameに変更
  • ageのtypeはshortに変更
  • departmentフィールドを追加
curl -XPUT 'http://localhost:9200/students_20240622?pretty' -H 'Content-Type: application/json' -d '{
"mappings": {
"properties": {
"full_name": {
"type": "keyword"
},
"sex": {
"type": "keyword"
},
"age": {
"type": "short"
},
"department": {
"type": "keyword"
}
}
}
}'

作成したstudents_20240622Indexのマッピングを確認してみましょう。

curl 'http://localhost:9200/students_20240622/_mapping?pretty' 

結果

{
"students_20240622": {
"mappings": {
"properties": {
"full_name": {
"type": "keyword"
},
"sex": {
"type": "keyword"
},
"age": {
"type": "short"
},
"department": {
"type": "keyword"
}
}
}
}
}

更新したマッピングが反映されました。

②_reindex APIを使用して、documentの移行を行います

curl -XPOST 'http://localhost:9200/_reindex?pretty' -H 'Content-Type: application/json' -d '
{
"source": {
"index": "students_20240621"
},
"dest": {
"index": "students_20240622"
},
"script": {
"source": "ctx._source.full_name = ctx._source.remove(\"name\")"
}
}'

今回field名の変更を行いましたので、scriptを使用して、フィールド名を変更しながら値を移行します。フィールド名を複数で変更する場合、下記のように、;区切りでscript文を作成します。

"script": {
"source": "ctx._source.new_name_1 = ctx._source.remove(\"old_name_1\");ctx._source.new_name_2 = ctx._source.remove(\"old_name_2\")"
}

実行した後のレスポンスは下記となります。created5になっていますので、移行元のdocumentは5件全部移行できたということです。

{
"took": 2020,
"timed_out": false,
"total": 5,
"updated": 0,
"created": 5,
"deleted": 0,
"batches": 1,
"version_conflicts": 0,
"noops": 0,
"retries": {
"bulk": 0,
"search": 0
},
"throttled_millis": 0,
"requests_per_second": -1.0,
"throttled_until_millis": 0,
"failures": []
}

③_aliases APIでエイリアス名を新Indexに移動します

curl -XPOST 'http://localhost:9200/_aliases?pretty'  -H 'Content-Type: application/json' -d '{
"actions": [
{
"remove": {
"index": "students_20240621",
"alias": "students"
}
},
{
"add": {
"index": "students_20240622",
"alias": "students"
}
}
]
}’

上記の_aliases APIを利用して、アプリ側サービス無停止でIndexを更新する効果が実現できます。

注意: _aliases APIのactionsにはremoveremove_indexという2つの似たようなキーワードがあります。removeは指定したIndexのエイリアスを削除しますが、remove_indexはエイリアス名の指定は不要で、Index名の指定だけでIndexを削除します。古いIndexがまだ必要な場合は十分に注意して実行してください。

Indexのエイリアスを確認してみましょう。

curl 'http://localhost:9200/_aliases?pretty'

結果

{
"students_20240621": {
"aliases": {}
},
"students_20240622": {
"aliases": {
"students": {}
}
}
}

エイリアス名studentsは新Indexの方に移動しました。

最後

一度検索クエリを実行してみましょう。

curl 'http://localhost:9200/students/_search?pretty' -H 'Content-Type: application/json' -d '
{
"query": {
"range": {
"age": {
"gt": 21
}
}
}
}'

結果

{
"took": 4,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 1.0,
"hits": [
{
"_index": "students_20240622",
"_id": "4",
"_score": 1.0,
"_source": {
"full_name": "学生四",
"sex": "女",
"age": "22"
}
}
]
}
}

検索の参照先はstudents_20240622になり、nameフィールドもfull_nameに変更されています。 追加したdepartmentフィールドはこれからdocumentの更新処理により、検索結果に反映されます。 今回はdocument更新処理を省略します。

以上、多少参考になったら嬉しいです。 最後までお読みいただき、ありがとうございました!

 

参考 

Elasticsearch Guide _reindex API 
Elasticsearch Guide _aliases API