Elasticsearchの操作は、索引の作成から検索、クラスタの状態確認まで、すべてHTTPとJSONで完結します。ところが公式リファレンスのAPI一覧は項目数が多く、実装で毎日触るものと、障害調査のときにしか開かないものが同じ並びで載っています。9.5系(2026年8月時点)を前提に、覚えるべき四系統への分け方、PUTとPOSTで動作が変わる仕組み、APIキーによる認証、そしてアプリ側に書くべきエラー処理までを順に見ていきましょう。
まとめ:REST APIを組み込む前に決めておく6つの値
実装に入る前に決めておかないと、後から書き直しになる箇所が6つあります。
- ドキュメントIDの決定者:アプリ側で採番するか、Elasticsearchへ任せるか
- 上書きの可否:同じIDの再送を許すか、既存があれば失敗させるか
- 認証方式:利用者の資格情報を使うか、APIキーを発行するか
- APIキーの権限範囲と有効期限:読み取りだけか、何日で切るか
- 1リクエストの粒度:件数とバイト数のどちらで上限を決めるか
- 再送の対象:どのステータスコードをリトライしてよいと定めるか
結論を先に言えば、業務データの正本が別のデータベースにある構成では、IDは正本側の主キーをそのまま使い、PUTでの上書き登録に寄せると再送が安全になります。認証は利用者のパスワードではなくAPIキーへ寄せ、期限を明示的に付けてください。再送してよいのは429と一部の5xxだけで、409と404はリトライでは消えません。製品そのものの構造やシャード設計はElasticsearchとは何かを扱った記事で整理しているため、ここではHTTPインタフェースの側だけを扱います。
エンドポイントの体系|四系統に分ければ迷わず目的のAPIへ届く
数百あるエンドポイントも、URLの第1階層かパス中の下線付きの語を見れば、用途は四系統に割れます。この分け方さえ頭に入れば、リファレンスを引く時間が短くなります。
| 系統 | 代表的なパス | 主な用途 |
|---|---|---|
| ドキュメント系 | _doc、_create、_update | 1件ずつの登録と更新 |
| 一括系 | _bulk、_mget | まとめて投入・取得する |
| 検索系 | _search、_count、_query | 検索と集計と件数取得 |
| 索引管理系 | 索引名直下、_mapping | 索引の作成と設定変更 |
| 運用系 | _cluster、_nodes、_cat | 状態確認と調査 |
ドキュメント系|_docと_createと_updateの役割の違い
1件のドキュメントを扱うのは _doc、_create、_update の三つです。登録は _doc、既存があれば失敗させたいときは _create、一部の項目だけ書き換えるなら _update という対応になります。取得は同じ _doc にGETを当て、削除はDELETEを当てます。
PUT /products/_doc/1001
POST /products/_doc
PUT /products/_create/1001
POST /products/_update/1001
GET /products/_doc/1001
DELETE /products/_doc/1001
件数が増えたら1件ずつのAPIをループで回さず、_bulk へ切り替えます。1リクエストへ何件詰めるか、部分失敗をどう拾うかは投入側の設計そのものなので、そちらは別に扱います。用途が違うものは分けてください。NDJSONの組み立て方と部分失敗の拾い方はElasticsearchのデータ投入とbulk APIの部分失敗の扱いで扱っています。まずは _doc と _bulk の境目が「1件か複数件か」ではなく「呼び出し回数を減らしたいかどうか」だと理解しておけば十分です。
検索系|_searchと_query(ES|QL)の呼び分けの基準
検索は _search にPOSTかGETを当て、本文にクエリを載せます。既定で返るのは上位10件だけなので、件数が要るなら size を明示します。from と size で辿れるのは既定で10,000件までという上限があり、それを超えるページングは search_after へ寄せる決まりです。全件を舐める用途なら、PITと組み合わせた search_after が現行の推奨経路になります。
9.x系ではパイプ構文のES|QLも使えます。同期実行は _query、時間のかかる集計は _query/async で、本文の query フィールドへ問い合わせ文字列を入れる形です。表形式で受け取りたい場合は format にcsvやtsvを指定できるため、集計結果をそのまま帳票へ流す用途では扱いやすくなります。クエリ本体の書き方は分量が別物になるので、ここでは呼び出し口だけに絞ります。アプリケーション側へ同じ条件を移植するときは、まず動く形をコンソールで確定させてから写すのが安全です。クエリ本体の構文とスコアの扱いはElasticsearchのクエリDSLとqueryとfilterの分岐で扱っています。
運用系|_catは人が読む用でアプリはJSON APIへ寄せる
_cat 配下は列が揃ったテキストを返すので、ターミナルから状態を眺めるときに便利です。ただし公式リファレンスには、CAT APIはKibanaのコンソールやコマンドラインで人が読むためのものであり、アプリケーションからの利用を意図していないと明記されています。アプリケーション用途には対応するJSON APIを使うよう促す記述も添えられています。
したがって監視や自動処理では、クラスタの状態は _cluster/health、ノードの数値は _nodes/stats といったJSONを返す側を叩いてください。テキスト出力は列の並びや表示単位が版で変わる可能性があり、パース前提のコードは壊れやすい構造になります。調査そのものの進め方はElasticsearchのチューニングと遅いクエリの特定手順にまとめました。手で叩いて確かめる場面ではKibanaの Dev Tools Consoleが速く、curlへの書き出しもそこから行えます。
PUTとPOSTの使い分け|IDを誰が決めるかで機械的に決まる
PUTとPOSTの選択は書き方の好みではありません。ドキュメントIDをURLへ書くかどうかで、内部の op_type が変わり、同じIDを再送したときの結果が変わります。
PUTでのid指定とPOSTでの自動採番|op_typeが変わる仕組み
PUT /index/_doc/id は明示IDが必須で、op_type は既定の index になります。同じIDが既にあれば黙って上書きし、_version が増えるだけです。対して POST /index/_doc はIDを省略する形で、Elasticsearchが一意な識別子を採番し、op_type は create に固定されます。
| 呼び出し | ID | 既存があるとき |
|---|---|---|
| PUT _doc(id あり) | アプリが指定 | 上書きされる |
| POST _doc(id なし) | 自動採番 | 常に新規で増える |
| PUT _create(id あり) | アプリが指定 | 409で失敗する |
この差が表れるのは再送時です。ネットワークが切れて応答を受け取れず同じリクエストを投げ直したとき、PUTでID指定なら結果は1件のままですが、POSTの自動採番だと同じ内容の別ドキュメントが増えます。正本が別のデータベースにあるなら、その主キーをIDへ写して上書き登録に寄せるほうが、後始末が要らない構成になります。
上書きを止める_createと、部分更新に使う_updateの選び方
取り込み履歴のように「二度と上書きしたくない」データでは _create を使います。これは op_type=create を指定した場合と同じ動作で、既存IDがあれば失敗する形です。重複投入をアプリ側の存在確認ではなく、Elasticsearch側の応答で弾けます。
一部の項目だけ書き換えるなら _update です。部分ドキュメントを渡せば既存とマージされ、スクリプトを渡せば加算のような処理も書けます。doc_as_upsert を真にすれば、存在しないときに渡した内容がそのまま登録されます。ただし ingest pipeline との併用は対応していないため、取り込み時の整形が要る経路では使えません。完全に入れ替えたいときは、公式リファレンスも index API を使うよう指示しています。
登録直後に検索へ出したい場合は refresh を付けます。値は即時リフレッシュの true、リフレッシュを待って応答を返す wait_for、何もしない false の三つです。テストコードでは wait_for が扱いやすい一方、本番の書き込み経路で true を常用するとセグメントが増えて性能が落ちます。
同時更新の衝突|if_seq_noとretry_on_conflictの使い分け
同じドキュメントを複数のプロセスが同時に書き換える構成では、楽観的並行制御を使います。読み取り時に得た _seq_no と _primary_term を if_seq_no と if_primary_term として送り、値が食い違えば VersionConflictException とステータス409が返る仕組みです。
{
"error": {
"type": "version_conflict_engine_exception",
"reason": "[1001]: version conflict, required seqNo [5]"
},
"status": 409
}
update APIには retry_on_conflict というパラメータもあり、衝突時にドキュメントを取り直して処理し直す回数を指定できます。既定は0、つまり自動再試行はしません。件数カウンタの加算のように「最新の値へ足せればよい」処理では2〜3を入れると通りやすくなりますが、業務上の整合が絡む更新でこれに頼ると、後勝ちの上書きを黙認する実装になります。どちらを選ぶかは、衝突したときに人が判断すべきかどうかで決めてください。
認証の設計|利用者の資格情報ではなくAPIキーへ寄せる判断基準
8.0以降はセキュリティ機能が既定で有効になっており、認証なしでは叩けません。ここで安易に管理者アカウントのパスワードをアプリの設定ファイルへ書くと、権限が広すぎるうえに、失効させるときサービス全体が止まります。
APIキーの発行手順|_security系のエンドポイントとencodedの使い方
APIキーは _security/api_key へPOSTして発行します。必須なのは name だけで、expiration、role_descriptors、metadata は任意です。発行に必要な権限は manage_own_api_key になります。
POST /_security/api_key
{
"name": "orders-search-app",
"expiration": "90d",
"role_descriptors": {
"read_orders": {
"cluster": [],
"indices": [
{ "names": ["orders-*"], "privileges": ["read"] }
]
}
}
}
応答には id、api_key、そして両者をコロンで繋いでBase64にした encoded が入ります。アプリからは encoded の値をそのまま次のヘッダへ載せるだけです。api_key はこのとき一度しか返らないため、受け取った時点で秘密情報の保管先へ入れてください。
curl -H "Authorization: ApiKey VnVhQ2ZHY0JDZGJrU..." \
--cacert config/certs/http_ca.crt \
"https://es.example.internal:9200/orders-2026/_search?size=20"
権限はrole_descriptorsで絞る|交差の原則と有効期限の決め方
キーの実効権限は、指定した role_descriptors と発行者自身が持つ権限の交差になります。管理者アカウントで発行すれば記述どおりの権限が付き、権限の狭い利用者で発行すればその範囲までしか付きません。裏を返せば、記述を絞らずに管理者で発行したキーは管理者と同等になるため、索引名と操作の両方を明示的に列挙するのが前提です。
もう一点、公式リファレンスは「既定ではAPIキーは失効しない」と明記しています。expiration を書き忘れたキーは、担当者が退任しても、リポジトリへ誤って混入しても、無効化するまで生き続けます。用途ごとに90日や180日といった期限を付け、更新手順を運用へ組み込んでおきましょう。発行済みキーの無効化は同じ _security 配下のAPIから行えます。
TLSと証明書|検証を無効にせずCA証明書を渡す接続設定の書き方
8.0以降の既定構成では自己署名の認証局で証明書が発行されるため、そのままではクライアントが接続先を検証できません。ここで検証を無効にする指定を入れると通ってしまうので、検証環境の設定がそのまま本番へ流れる事故が起きます。正しくは、クラスタが生成した http_ca.crt をクライアント側へ配り、CA証明書として読み込ませる形です。コンテナ構成での証明書の取り出し方はElasticsearchのDocker構築の記事で扱っています。
アプリからの呼び出し設計|四つのステータスコードで分岐を作る順序
本番で問題になるのは、正常系ではなく異常系の書き方です。返ってくるコードを一律に「エラー」と扱うと、再送すべきものを捨て、再送しても無駄なものを何度も投げる実装になります。
| コード | 主な原因 | アプリ側の扱い |
|---|---|---|
| 400 | クエリや型の誤り | 再送しない・修正する |
| 401 / 403 | 認証失敗・権限不足 | 再送しない・鍵を見直す |
| 404 | 索引やIDが無い | 存在前提の設計を直す |
| 409 | 版の衝突 | 取り直して再計算する |
| 413 | 本文サイズ超過 | 分割して送り直す |
| 429 | キューが溢れた | 待ってから再送する |
429は待ってから再送する|指数バックオフが効く唯一の系統と待ち方
429は、書き込みや検索のスレッドプールのキューが埋まって拒否された合図です。原因が一時的な負荷であれば、待って投げ直せば通ります。待ち時間は固定値ではなく、1秒、2秒、4秒のように倍へ伸ばし、そこへ乱数の揺らぎを足してください。複数のワーカーが同じ間隔で再送すると、次の波が同時に来て同じ拒否が繰り返されます。
再送回数には上限を設け、超えたぶんは失敗として記録し、後から流し直せる場所へ退避させます。取り込みが常時ピーク近くで回っていて429が定常的に出るなら、それはリトライで隠す話ではありません。値を伸ばす前に、投入側の粒度とクラスタ側の設定を見直す順序をおすすめします。
409と404はリトライで消えない|設計で潰すための分岐の作り方
409は同じリクエストを投げ直しても同じ結果になります。ドキュメントを取り直し、最新の内容に対して処理をやり直してから送るのが唯一の解き方です。単純な上書きでよいなら楽観的並行制御を外し、加算などの累積処理なら retry_on_conflict を使う、という二択で整理できます。
404にも二種類あります。索引が存在しない index_not_found_exception と、ドキュメントが見つからない場合です。前者はアプリ起動時に索引テンプレートを整えるか、書き込み時に自動作成を許すかという設計上の分岐で、後者はそもそも異常ではありません。この分岐とテンプレートの組み立てはElasticsearchのマッピング設計で扱っています。取得系のAPIで複数索引を指定するときは ignore_unavailable を付けるかどうかも先に決めておきます。索引の作り方と別名の張り替えはバージョンアップと移行の記事で扱った手順が参考になります。
413とタイムアウト|1リクエストの粒度を100mbから逆算する
HTTP本文のサイズ上限は http.max_content_length で決まり、既定値は100mbです。公式のネットワーク設定リファレンスは、これを超える場合の対処として、設定値を上げるのではなく1リクエストあたりの件数を減らすよう促しています。件数ではなくバイト数で上限を持ち、1件あたりの平均サイズから逆算して詰める件数を決めてください。
タイムアウトも二層で考えます。クライアント側の接続・読み取りのタイムアウトと、検索側で指定する時間の制限は別物です。前者だけを短くすると、サーバでは処理が続いているのにクライアントだけ諦める状態になり、負荷が下がりません。重い集計は非同期側のエンドポイントへ寄せる判断も併せて持っておくと、詰まりを分離できます。
互換ヘッダ|メジャー版を跨ぐ移行を1世代だけ吸収する仕組みと限界
メジャー版を上げるとき、アプリの改修とクラスタの更新を同時に行うのは現実的ではありません。そのための逃げ道が互換ヘッダです。次の二つを付けると、9.x系のクラスタが8.x向けのリクエストと応答の形式を受け付けます。
Accept: application/vnd.elasticsearch+json; compatible-with=8
Content-Type: application/vnd.elasticsearch+json; compatible-with=8
Acceptは全リクエストで必要、Content-Typeは本文があるときだけ付ける形です。ただし遡れるのは1メジャー版のみで、公式リファレンスは「前の版と同じ挙動を保証するものではない」と明記し、あくまで移行を滑らかにするための橋渡しであって長期戦略ではないとしています。互換モードが適用されたリクエストは compatible_api のカテゴリでログへ残るため、どの経路が古い形式のまま残っているかを棚卸しできます。期限を切って外す前提で使ってください。
REST APIを直接叩いてよい条件と、クライアントへ寄せる場面の線引き
HTTPで直接叩くか、公式クライアントライブラリを挟むかは、どちらが正しいという話ではなく、扱う範囲の広さで決まります。
採用条件|RESTを直接組み込んでよい三つの前提とその確認手順
次の三つがすべて当てはまるなら、HTTPクライアントで直接組んでも運用が破綻しません。第一に、使うエンドポイントが十数個に収まり、検索と単純な登録が中心であること。第二に、接続先が1クラスタで、ノードの入れ替わりを気にしなくてよいこと。第三に、リトライとタイムアウトの方針を自分たちで書き切る体制があること。この範囲なら、依存関係を増やさない利点のほうが上回ります。
確認は実際の呼び出し一覧を書き出す作業から始めます。設計書ではなくコードを検索して、叩いているパスを列挙してください。想定より多ければ、それは次の条件へ寄せる合図です。
見送る場面|公式クライアントへ寄せたほうが短く終わる条件の線引き
一括投入のヘルパーが要る、複数ノードへの振り分けと死活監視が要る、版差の吸収を自前で書きたくない、といった要件が一つでもあるなら公式クライアントを使います。言語ごとのクライアントには一括投入や再試行の実装が入っており、自前で書いた分は保守対象として残り続けます。クライアント側とサーバ側の対応版の考え方も含めて、実装の詳細は言語別の記事で扱う予定です。
系列そのものを移す判断が絡む場合は、APIの互換性が論点になります。分岐した派生系列との差はElasticsearchとOpenSearchの違いを扱った記事で整理しました。既存システムと検索基盤をどう繋ぐか、認証情報の権限をどこまで絞るかといった設計から実装まで引き受ける体制が要る場合は、API開発・システム連携のサービスで相談を受けています。
よくある質問
curlで叩くときにユーザー名とパスワードを使ってもよいですか?
手元での確認なら差し支えありません。ただしアプリケーションへ埋め込む用途では、権限を絞ったAPIキーへ寄せてください。パスワードは権限が広く、変更したときの影響範囲も読みにくくなります。
PUTとPOSTのどちらで登録するか迷ったときの決め方はありますか?
IDをアプリ側が持っているかどうかで決まります。正本のデータベースに主キーがあるならPUTでID指定、ログのように識別子を持たないデータならPOSTの自動採番です。再送で重複させたくないなら前者を選びます。
_catの結果を監視スクリプトで解析してもよいですか?
推奨されません。公式リファレンスがアプリケーションからの利用を意図していないと明記しており、対応するJSON APIを使うよう促しています。監視には _cluster/health や _nodes/stats を使ってください。
APIキーに有効期限を設定しないと問題がありますか?
既定では失効しない仕様なので、無効化するまで有効なままです。漏えいに気付けない状態が続くため、用途ごとに期限を付け、更新の手順を運用へ組み込んでおくほうが安全でしょう。
バージョンを上げたらリクエストが通らなくなりました。何を見ますか?
まず互換ヘッダで一時的に通るかを確かめ、通るなら古い形式のまま残っている呼び出しを洗い出します。遡れるのは1メジャー版だけなので、恒久策ではなく期限付きの措置として扱ってください。
関連記事
- Elasticsearchとは?転置インデックスとシャード設計:本記事が前提にしている製品側の構造と採用可否の判断です
- Elasticsearchのチューニングと遅いクエリの特定手順:429が定常的に出るときの根本側の調整です
- ElasticsearchとKibanaの連携と権限分離:Dev Tools Consoleでリクエストを検証する手順です
- ElasticsearchのDocker構築とcompose定義:証明書の受け取りと検証環境の立ち上げ方です
- Elasticsearchのバージョンアップと移行の手順:互換ヘッダを外すまでの更新工程です
- ElasticsearchとOpenSearchの違い:派生系列とのAPI互換の差を確認する記事です