2026年5月22日
2026年5月22日
Elasticsearchのインデックスとマッピング設定(型の選び方・日本語解析・再インデックスとエイリアス)
Elasticsearch のマッピングは、インデックスに入れるフィールドの型と解析方法を決める「スキーマ」です。自動マッピングに任せたままでも動きますが、一度決まったフィールドの型は後から変えられないため、検索や集計に使う項目は最初に明示的に定義しておくのが安全です。この記事では、WordPress の投稿を検索用に入れる例を使って、型の選び方、日本語の解析、変更が必要になったときの再インデックスとエイリアスの切り替えまでを説明します。
コード例は Kibana の Dev Tools(コンソール)の書式です。curl で送る場合は、メソッドとパスを URL に置き換え、本文を -d で渡してください。
インデックスとマッピングの関係
インデックスは、同じ種類のドキュメント(JSON)を入れておく入れ物です。リレーショナルデータベースのテーブルに近いものですが、7.x 以前にあった「マッピングタイプ」は 8.0 で廃止され、現在は 1 インデックスに 1 つのマッピングという形になっています(2026年10月現在)。
マッピングには主に次の内容が入ります。
- 各フィールドの型(
text・keyword・date・longなど) - 文字列フィールドをどう単語に分けるか(アナライザー)
- 定義にないフィールドが来たときの扱い(
dynamic)
今のマッピングは次のように確認できます。
GET wp-posts/_mapping
GET wp-posts/_mapping/field/title
自動マッピングに任せたときに起きること
マッピングを定義せずにドキュメントを入れると、Elasticsearch が値から型を推測します。文字列は、全文検索用の text と、完全一致・集計用の keyword(.keyword というサブフィールド、既定で 256 文字を超える値は索引しない)の両方で登録されます。
これは便利な反面、次のような問題につながります。
- 本文のような長い文字列にも
keywordが作られ、ディスクを余分に使う - 数字だけの ID が最初に数値として入ると
longになり、後から英字を含む値を入れるとエラーになる - 日付の書式が推測と違うと
textとして登録される - ログのように項目名が増え続けるデータでは、フィールド数が上限(
index.mapping.total_fields.limit、既定 1000)に達して書き込みが失敗する
検索や集計の対象は自分で定義し、想定外の項目は拒否するか無視する設定にしておくと、こうした事故を避けられます。
型の選び方
よく使う型と使い分けの考え方です。
- text: 本文やタイトルなど、単語単位で検索したい文字列。アナライザーで分割されるため、そのままでは並べ替えや集計に使えません。
- keyword: カテゴリー名、タグ、ステータス、URL など、値全体で一致・集計・並べ替えをしたい文字列。
- long / integer / float / scaled_float: 数値。範囲検索や平均の計算をしないコードや ID は、数値に見えても
keywordのほうが扱いやすい場合があります。 - date: 日時。
formatを指定しない場合は ISO 8601 形式とミリ秒のエポック値を受け付けます。 - boolean / ip / geo_point: 真偽値、IP アドレス、緯度経度。
- object / nested: 入れ子のデータ。配列の中のオブジェクトを「要素ごとの組み合わせ」で検索したい場合だけ
nestedを使います。nestedは内部的に別ドキュメントとして扱われるため、多用すると重くなります。
タイトルのように「全文検索もしたいし、完全一致で並べ替えもしたい」項目は、fields でサブフィールドを持たせます(マルチフィールド)。
日本語の解析を設定する
標準のアナライザー(standard)は、日本語の文を単語ではなく文字に近い単位で分けるため、検索結果が不自然になりがちです。日本語の本文を扱うなら、形態素解析のプラグイン analysis-kuromoji を使うのが一般的です。全ノードに入れてから再起動します。
sudo /usr/share/elasticsearch/bin/elasticsearch-plugin install analysis-kuromoji
sudo systemctl restart elasticsearch
プラグインは Elasticsearch 本体と同じ版のものが入ります。本体を更新するときはプラグインも入れ直しが必要になるため、更新手順に含めておきます。
マッピングを明示してインデックスを作る
WordPress の投稿を入れる想定の例です。インデックス名に -v1 のような版番号を付けておくと、後で作り直すときに楽になります(理由は後述)。
PUT wp-posts-v1
{
"settings": {
"number_of_shards": 1,
"number_of_replicas": 1,
"analysis": {
"analyzer": {
"ja_text": {
"type": "custom",
"tokenizer": "kuromoji_tokenizer",
"filter": [
"kuromoji_baseform",
"kuromoji_part_of_speech",
"cjk_width",
"ja_stop",
"kuromoji_stemmer",
"lowercase"
]
}
}
}
},
"mappings": {
"dynamic": "strict",
"properties": {
"post_id": { "type": "keyword" },
"title": {
"type": "text",
"analyzer": "ja_text",
"fields": { "raw": { "type": "keyword", "ignore_above": 256 } }
},
"content": { "type": "text", "analyzer": "ja_text" },
"categories": { "type": "keyword" },
"tags": { "type": "keyword" },
"author": { "type": "keyword" },
"published_at": { "type": "date" },
"comment_count":{ "type": "integer" },
"permalink": { "type": "keyword", "index": false }
}
}
}
ポイントは次のとおりです。
"dynamic": "strict"にすると、定義にないフィールドを含むドキュメントはエラーで拒否されます。取り込み側の不具合にすぐ気づけます。拒否まではしたくない場合はfalse(保存はするが索引しない)を使います。permalinkは表示に使うだけなので"index": falseにして検索対象から外しています。- 1台構成ではレプリカの置き場所がなく、
number_of_replicasが 1 だとクラスターが黄色になります。1台で試すときは 0 にします。
作成後は _analyze API で、日本語がどう分割されるかを確かめます。思った単語で検索に当たらないときも、まずここを見ます。
GET wp-posts-v1/_analyze
{
"analyzer": "ja_text",
"text": "ワードプレスのサイト内検索を高速化する"
}
後から変えられること、変えられないこと
既存のインデックスに対して、新しいフィールドの追加はいつでもできます。
PUT wp-posts-v1/_mapping
{
"properties": {
"excerpt": { "type": "text", "analyzer": "ja_text" }
}
}
一方で、既存フィールドの型の変更(keyword を text にする、など)や、使っているアナライザーの差し替えはできません。すでに索引されたデータの構造が変わってしまうためです。この場合は新しいインデックスを作り、データを移し替えます。
再インデックスとエイリアスで無停止に切り替える
アプリケーションからは実際のインデックス名ではなく、エイリアス(別名)を参照させておきます。こうすると、裏でインデックスを作り直しても接続先を変えずに済みます。
POST _aliases
{
"actions": [
{ "add": { "index": "wp-posts-v1", "alias": "wp-posts" } }
]
}
マッピングを変えたくなったら、wp-posts-v2 を新しい定義で作成し、_reindex でデータをコピーしてから、エイリアスを付け替えます。actions に書いた削除と追加は一度にまとめて反映されるため、切り替えの瞬間に検索先がなくなることはありません。
POST _reindex
{
"source": { "index": "wp-posts-v1" },
"dest": { "index": "wp-posts-v2" }
}
POST _aliases
{
"actions": [
{ "remove": { "index": "wp-posts-v1", "alias": "wp-posts" } },
{ "add": { "index": "wp-posts-v2", "alias": "wp-posts" } }
]
}
つまずきやすい点は次の 2 つです。
- 再インデックス中も元のインデックスに書き込みが続くと、その差分は新しい側に入りません。書き込みを一時的に止めるか、切り替え後に元データ(WordPress の DB など)から差分を入れ直します。
- 件数が多いと時間がかかります。
?wait_for_completion=falseを付けるとタスクとして裏で実行され、GET _tasks/<タスクID>で進み具合を確認できます。
古いインデックスは、件数(GET _cat/indices?v)と検索結果を新旧で比べてから削除します。
ログのように毎日増えるインデックスはテンプレートで
日付ごとに nginx-logs-2026.10.18 のようなインデックスを作る運用では、毎回マッピングを書くわけにはいきません。インデックステンプレートを登録しておくと、名前のパターンに合うインデックスが作られたときに設定とマッピングが自動で適用されます。現在の方式は _index_template(コンポーザブルテンプレート)で、旧来の _template は非推奨です。
PUT _index_template/nginx-logs
{
"index_patterns": ["nginx-logs-*"],
"priority": 200,
"template": {
"settings": { "number_of_shards": 1 },
"mappings": {
"dynamic": false,
"properties": {
"@timestamp": { "type": "date" },
"client_ip": { "type": "ip" },
"status": { "type": "short" },
"url_path": { "type": "keyword" },
"request_time": { "type": "float" },
"user_agent": { "type": "text", "fields": { "raw": { "type": "keyword", "ignore_above": 512 } } }
}
}
}
}
Elasticsearch には logs-*-* などのパターンに対する組み込みテンプレートがあり、同じパターンを使うと意図しないほうが適用されることがあります。独自の名前のパターンにするか、priority で優先度を明示します。テンプレートはインデックス作成時にだけ効くため、既存のインデックスには反映されない点にも注意します。
WordPress の検索連携プラグインを使う場合
ElasticPress などの連携プラグインは、インデックスとマッピングをプラグイン自身が作成・管理します。Elasticsearch 側で直接マッピングを書き換えると、プラグインの同期や再インデックスで上書きされたり、検索が壊れたりします。項目の追加や解析方法の変更は、プラグインが用意しているフィルターや設定から行い、変更後はプラグインの機能で再同期します。対応する Elasticsearch の版や設定方法はプラグインの版によって変わるため、公式ドキュメントで確認してください。
本番のインデックスを作り直す前には、スナップショットを取るか、元データから再構築できることを確かめておきます。
まとめ
- マッピングはフィールドの型と解析方法の定義。既存フィールドの型は後から変えられない
- 自動マッピングに任せず、検索・集計に使う項目は明示し、
dynamicで想定外の項目の扱いを決める - 全文検索は
text、一致・集計・並べ替えはkeyword。両方要るならマルチフィールド - 日本語は
analysis-kuromojiを使い、_analyzeで分割結果を確かめる - 型を変えるときは新しいインデックスへ
_reindexし、エイリアスを一括で付け替える - 日付ごとのインデックスは
_index_templateで設定を自動適用する