Claude CodeでElasticsearchのマッピング設計をレビューする手順
読み取り専用のサブエージェントに、dynamic strict・keywordとtextの使い分け・フィールド数・doc_values・ignore_aboveの5観点でマッピング案を見直させる手順です。
Elasticsearchのマッピングは、動的マッピングに任せたまま運用に入ると、あとから「文字列がすべてtext型とkeywordの二重持ちになっている」「フィールド数が増え続けて書き込みが失敗する」といった形で手直しが必要になります。Claude Codeには、マッピングJSONとサンプルドキュメントを渡して設計の穴を洗い出させる使い方が向いています。この記事では、読み取り専用のサブエージェントを用意し、dynamic: strict・keywordとtextの使い分け・フィールド数・doc_values・ignore_aboveの5つの観点でレビューさせる手順を説明します。
レビュー役を読み取り専用のサブエージェントにする
マッピングのレビューでは、ファイルを書き換える権限が要りません。見るのはマッピングJSON、サンプルドキュメント、検索や集計のクエリ例です。ツールを絞ったサブエージェントを作っておくと、レビューのたびに同じ観点で動きます。
サブエージェントは.claude/agents/にMarkdownで置きます。フロントマターで必須なのはnameとdescriptionの2つだけです。toolsにRead, Grep, Globだけを挙げれば、書き込み系のツールは持ちません。toolsを省略すると、サブエージェントが使えるツールをすべて継承する点に注意が必要です。
---
name: es-mapping-reviewer
description: Elasticsearchのマッピング案とサンプルドキュメントを見て、型の選び方・フィールド数・doc_valuesの設定を指摘する。マッピングJSONを書いた、または変更したときに使う。
tools: Read, Grep, Glob
model: inherit
---
あなたはElasticsearchのマッピングレビュー担当です。
渡されたマッピングJSONとサンプルドキュメントを読み、次の5つの観点で指摘します。
1. dynamic の設定(最上位と各オブジェクト)
2. 文字列フィールドの型(text / keyword / 両方)
3. フィールド数と入れ子の深さ
4. doc_values と index の無効化候補
5. ignore_above の値と、それより長い値の扱い
指摘ごとに「根拠となるフィールド名」「推測が混じる部分」「確認に必要な情報
(検索・集計・ソートの実際の使い方)」を分けて書いてください。
ファイルは変更しません。model: inheritは、メイン会話と同じモデルを使う指定です。省略したときのモデルは、呼び出し時のパラメータなどを含む決まった順序で解決されます。レビュー品質を揃えたいなら、明示しておく方が追いやすくなります。
CLAUDE.mdに「このプロジェクトのインデックス命名規則」「許容するtextフィールドの条件」といった規約を書いておくと、サブエージェントの初期コンテキストにも読み込まれます。組み込みのExploreとPlanは例外で、CLAUDE.mdを読まずに始まりますが、es-mapping-reviewerのようなカスタムサブエージェントは読み込みます。読み込みを止めたいときだけomitClaudeMd: trueを指定します。
レビューの5つの観点と根拠
サブエージェントのプロンプトに観点だけ書いても、根拠がなければ指摘の確からしさを人が判断できません。ここでは、Elasticsearchのドキュメントに書かれている挙動を観点ごとに示します。
観点1: dynamicはどこまで厳しくするか
dynamicにはtrue(既定)、runtime、false、strictの4つの値があります。違いは新しいフィールドが来たときの扱いです。
未知のフィールドが来たときの動き
false
新しいフィールドは索引されず、検索もできません。マッピングにも追加されませんが、_sourceには残ります。
strict
未知のフィールドを含むドキュメントは、例外が投げられて受け付けられません。追加したいフィールドは、先にマッピングへ明示します。
trueとruntimeは、index.mapping.total_fields.limitに達するまでフィールドを追加し続けます。内側のオブジェクトは親のdynamic設定を引き継ぐので、最上位をstrictにして、任意キーを受けたいオブジェクトだけdynamic: trueに戻す構成が取れます。レビューでは「最上位がtrueのまま」「strictなのに受け口のオブジェクトまで拒否してしまう」の両方を拾わせます。
観点2: 文字列はtextかkeywordか
動的マッピングは、日付に見えない文字列をtext型にして、.keywordのサブフィールドを加えます。この.keywordにはignore_above: 256が付き、256文字を超える値は.keyword側に入りません。
文字列の中身によっては型が変わります。date_detectionは既定で有効なので、"2015/09/02"のように日付の形式に合う文字列はdate型になります。一方、numeric_detectionは既定で無効で、"30"のように数値を文字列で送ってもtextのままです。同じフィールドでも最初に届いた値の形で型が決まるため、サンプルに日付風の文字列や数字の文字列があれば、レビューで確認させます。
型の選び方は、ドキュメントの説明がそのまま判断基準になります。
| 用途 | 向く型 | 注意点 |
|---|---|---|
| 本文・商品説明など人が読む文章 | 向く型text | 注意点並べ替えには使えず、集計にもほとんど使われない |
| ID・メールアドレス・ホスト名・ステータスコード・タグ | 向く型keyword | 注意点全文検索には向かない |
| 検索も集計もしたい文字列 | 向く型text+keywordのマルチフィールド | 注意点フィールド数が増える |
| ISBNや商品IDなどの数字の識別子 | 向く型keywordを検討 | 注意点rangeクエリで検索しないなら |
数字だけの識別子はintegerやlongにしがちですが、数値型はrangeクエリに最適化されています。termクエリで引くだけの識別子なら、keywordの方が適しています。サンプルドキュメントにorder_id: 100234のような値があれば、レビューで必ず確認させたい点です。
観点3: フィールド数は数え方に癖がある
1インデックスのフィールド数は、既定で1000が上限です。超えるとLimit of total fields [X] has been exceededというエラーが返ります。数える対象は葉のフィールドだけではありません。
- オブジェクトマッパー:
host.os.nameならhostとhost.osでそれぞれ1つ - 葉のフィールド:
keywordやtextなど - マルチフィールド:
.keywordのようなサブフィールドも1つずつ - フィールドエイリアスと、マッピング済みのランタイムフィールド
メタデータフィールド(_idや_sourceなど)は数えません。動的マッピングで.keywordが文字列ごとに付くと、その数だけ上限の枠を使います。
キーが際限なく増えるマップ(ユーザーが付ける属性名など)には、flattened型か、index.mapping.total_fields.ignore_dynamic_beyond_limitをtrueにする方法が案内されています。後者は上限を超えた動的フィールドを_ignoredメタデータフィールドに回し、書き込みを失敗させません。入れ子の深さは既定で20、nested型のマッピングは1インデックスあたり既定100までです。ECS風に深い階層のマッピングでは、subobjects: falseでオブジェクトマッパーの数を減らせます。
観点4: doc_valuesとindexを必要な分だけ
doc_valuesは、ソート・集計・スクリプトからの値アクセスに使う、ディスク上の列指向のデータ構造です。ほとんどのフィールド型で既定で有効です。ソートも集計もスクリプトも使わないと分かっているフィールドでは、"doc_values": falseでディスク容量を節約できます。textは逆に、doc_valuesに対応していても既定では無効です。
もう一つの軸がindexです。"index": falseにすると、値は保存されても検索できません。逆に、数値・日付・boolean・ip・geo_point・keywordは、索引せずdoc_valuesだけを持たせる設定でも検索できます。クエリは遅くなるため、ほとんど検索されないゲージやカウンターのようなフィールド向けです。
doc_values: falseを指摘させるときの条件は、ソート・集計・スクリプトのいずれにも使わないことです。サンプルドキュメントを見ただけでは判断できません。
観点5: ignore_aboveの値より長い文字列
ignore_aboveより長い文字列は、索引も保存もされません。ただし_sourceには残ります。配列の場合は要素ごとに判定されます。動的マッピングの.keywordに256文字の壁があるので、長いURLやスタックトレースを.keywordで完全一致検索する設計は、サンプルに長い値が含まれているかで可否が変わります。
サンプルドキュメントから指摘させる
実際にレビューを依頼する場面を想定して、サンプルドキュメントを1つ用意します。ログに近い形で、文字列・数字の識別子・任意キーのマップが混在した例です。
{
"order_id": 100234,
"status": "shipped",
"customer_email": "user@example.com",
"note": "配送先を変更しました。置き配を希望します。",
"tags": ["priority", "gift"],
"attributes": {
"color": "red",
"size_cm": "30",
"custom_x1": "..."
},
"created_at": "2026-10-28T09:00:00Z"
}依頼文には、マッピング案、このドキュメント、想定している検索と集計をまとめて渡します。
@"es-mapping-reviewer (agent)" docs/es/orders-mapping.json と
docs/es/orders-sample.json をレビューして。
検索: status と customer_email の完全一致、note の全文検索
集計: status 別件数、created_at の日次件数
ソート: created_at のみ
attributes は顧客ごとにキーが変わる@でサブエージェントを名前指定すると、そのサブエージェントが確実に実行されます。依頼文に書いた検索・集計・ソートの使い方は、観点4の判断材料になります。
返ってくる指摘は、例えば次のような形になります(Elasticsearchのドキュメントの記述に沿った例で、実際の出力ではありません)。
order_idはrange検索の予定がなければkeywordを検討する(観点2)statusとcustomer_emailはkeywordで足りる。text+.keywordの二重持ちは不要(観点2)noteはtextのみ。ソートも集計もしないのでdoc_valuesの追加は不要(観点4)attributesは顧客ごとにキーが変わるため、flattened型を検討する(観点3)- 最上位は
dynamic: strictにする。attributesのようなサブツリーだけ別設計にする(観点1)
指摘を反映したマッピングの一例は、次のようになります。
{
"mappings": {
"dynamic": "strict",
"properties": {
"order_id": { "type": "keyword" },
"status": { "type": "keyword" },
"customer_email": { "type": "keyword" },
"note": { "type": "text" },
"tags": { "type": "keyword" },
"attributes": { "type": "flattened" },
"created_at": { "type": "date" }
}
}
}created_atはソートと日次集計に使うので、既定のままです。doc_valuesを無効にしたフィールドはありません。この例は検索と集計の使い方がはっきりしているため、削れる候補がなかったという結論になります。
サンプルだけでは決められない指摘を分ける
サンプルドキュメント1件から言えることと、言えないことは分けて受け取ります。サブエージェントのプロンプトに「確認に必要な情報」の欄を入れたのは、このためです。
| 指摘の種類 | サンプルだけで言えるか | 追加で必要な情報 |
|---|---|---|
数字の識別子をkeywordにする | サンプルだけで言えるか条件付き | 追加で必要な情報rangeクエリを使うかどうか |
textの二重持ちをやめる | サンプルだけで言えるか言える | 追加で必要な情報検索・集計の用途 |
doc_values: false | サンプルだけで言えるか言えない | 追加で必要な情報ソート・集計・スクリプトの有無 |
flattenedの採用 | サンプルだけで言えるか条件付き | 追加で必要な情報キーの増え方と、値ごとの型の違い |
ignore_aboveの調整 | サンプルだけで言えるか言えない | 追加で必要な情報実データの値の長さの分布 |
| フィールド数の上限超過 | サンプルだけで言えるか言えない | 追加で必要な情報全フィールドの本数とマルチフィールドの数 |
「言えない」行を指摘として確定させない運用にすると、レビューが断定に傾きません。attributesをどう分けるかのように、複数案があるものは案の並記で終えてもらうのが現実的です。
最後に、変更を試すのは検証用インデックスです。dynamic: strictは、未知のフィールドを含むドキュメントを例外で拒否します。既存の取り込みパイプラインが送っているフィールドがマッピングに無ければ、書き込みが失敗する側に倒れます。レビュー後の案は、検証用インデックスを明示マッピングで作り、実際の取り込みを流してから反映するのが安全です。既存インデックスのマッピングを取得する部分は、Elasticsearch MCPサーバーの使い方で紹介したget_mappingsツールで取得できます。
他のデータストアのレビューとの違い
関係データベースの設計レビューと違い、Elasticsearchではマッピングが検索の挙動そのものを決めます。同じ文字列でも、textとkeywordで使えるクエリが別物になる点が特徴です。PostgreSQLのJSONB列に対するインデックス設計はJSONBのGINインデックス設計、MongoDBの実行計画を数字で読む手順はMongoDBのexplain()とインデックス設計にあります。Redisのキー設計とクエリを任せる方法はRedisプラグインの導入記事が扱っています。
別の文脈でレビューさせて指摘の偏りを減らしたい場合は、敵対的レビューの設計の考え方が参考になります。
まとめ
マッピングのレビューは、読み取り専用のサブエージェントに5つの観点を持たせると、指摘の型が揃います。ただし、doc_valuesの無効化やignore_aboveの調整は、サンプルドキュメントだけでは決まりません。検索・集計・ソートの使い方と、実データの値の長さを一緒に渡してください。指摘が出たら検証用インデックスで試し、取り込みパイプラインがdynamic: strictで落ちないことを確かめてから本番に反映する、という順序を守ると手戻りが少なくなります。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。
- code.claude.com/docs/en/sub-agents
- elastic.co/docs/reference/elasticsearch/mapping-reference/dynamic
- elastic.co/docs/manage-data/data-store/mapping/dynamic-field-mapping
- elastic.co/docs/reference/elasticsearch/mapping-reference/keyword
- elastic.co/docs/reference/elasticsearch/mapping-reference/text
- elastic.co/docs/reference/elasticsearch/mapping-reference/doc-values
- elastic.co/docs/reference/elasticsearch/index-settings/mapping-limit