ElasticsearchのIndexマッピング更新について
        目次
はじめに
こんにちは!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\")"
}
実行した後のレスポンスは下記となります。createdは5になっていますので、移行元の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にはremoveとremove_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
アジアクエスト株式会社では一緒に働いていただける方を募集しています。
興味のある方は以下のURLを御覧ください。