---
title: "Elasticsearchのベクトル検索｜dense_vectorの設計とkNN実装、RAG基盤の採用条件"
url: "https://www.issoh.co.jp/tech/details/17035/"
published: 2026-08-27
updated: 2026-08-27
categories: ["データベース"]
publisher: "株式会社一創"
---

# Elasticsearchのベクトル検索｜dense\_vectorの設計とkNN実装、RAG基盤の採用条件

Elasticsearchでベクトル検索を動かすだけなら、フィールドを一つ足してkNNクエリを投げれば済みます。検証データ千件なら半日で着きます。詰まるのはその先です。本番のデータ量でヒープが足りなくなる、8系の記事を写した設定が9系の既定と食い違う、量子化した索引の精度が戻せない。いずれもフィールド定義と探索範囲の値を決めないまま書き始めたことが原因です。9.5系（2026年8月時点）を前提に、決めるべき値と、その値がどこに効くかを順に見ていきます。

## まとめ：ベクトル検索を載せる前に決める6つの値

ベクトル検索の設計とは、次の6点を決める作業です。ここが決まれば残りは公式リファレンスの引き写しで足ります。決めずに動かすと、データ量が増えた段階で索引ごと作り直しになります。

| 決める値     | 指定する場所            | 決め方の基準        |
| -------- | ----------------- | ------------- |
| 次元数      | dims              | 埋め込みモデルの出力に一致 |
| 数値の持ち方   | element\_type     | 既定はfloat・4バイト |
| 距離の測り方   | similarity        | 既定はcosine     |
| 量子化の方式   | index\_options    | 版で既定が変わる      |
| 探索の広さ    | num\_candidates   | 既定はkの1.5倍     |
| 埋め込みの生成元 | semantic\_textか外部 | 言語と更新頻度で決める   |

このうち後から変えられないのが`dims`と`element_type`で、変更にはリインデックスが要ります。`num_candidates`はクエリ側の値なので運用中に振れる。設計の重心は前半3つです。ベクトル検索とセマンティック検索という語の関係は[ベクトル検索とセマンティック検索の違い](https://www.issoh.co.jp/tech/details/9159/)で整理しているため、本記事は製品の設定だけを扱います。

## フィールド設計｜dense\_vectorの四つの値で検索の性質が決まる

ベクトルを入れる器が`dense_vector`です。指定できる値は多くありませんが、それぞれが精度・速度・メモリのどれかに直結します。[Elasticsearchの索引構造とシャード設計](https://www.issoh.co.jp/tech/details/17011/)を前提に、ベクトル固有の項目だけを見ます。

### element\_typeとdims｜4096の上限と埋め込みモデル側の制約

`element_type`は1次元あたりの持ち方です。既定の`float`が4バイト、`bfloat16`が2バイト、`byte`が1バイト、`bit`が1ビット。単純に、選んだ型がそのまま索引サイズの係数になります。

`dims`の上限は4096です。text-embedding-3-largeの3072次元やCohereの1024次元は収まりますが、上限に近い次元数を選ぶ意味は薄いと考えます。公式のチューニング指針は「kNN検索の速度は次元数に線形で比例する」と述べていて、次元を半分にすれば距離計算も半分になるからです。モデル側で切り詰められるなら先に検討します。

### similarityの選び方｜cosineが既定でbitはl2\_norm固定

距離の測り方は`l2_norm`、`dot_product`、`cosine`、`max_inner_product`の4種類です。既定は`cosine`で、`element_type`が`bit`のときだけ`l2_norm`が既定になり、しかも`bit`は`l2_norm`しか受け付けません。

選び方の基準は単純で、埋め込みモデルの提供元が推奨する指標に合わせます。多くの文章埋め込みモデルはコサイン類似度を前提に学習されているため、既定のままで外れません。ベクトルを事前に正規化してあるなら`dot_product`のほうが計算は軽くなります。`max_inner_product`は正規化していないベクトルで内積の大小そのものを見たい場合の選択肢です。

ここで迷って時間を使う価値はありません。モデルカードの記載に従い、評価セットで差が出たときだけ振り直します。dimsやelement\_typeを含むフィールド定義そのものの決め方は[Elasticsearchのマッピング設計](https://www.issoh.co.jp/tech/details/17037/)で扱っています。

### index\_optionsの既定｜9.4系でbbq\_diskへ移った量子化

索引の作り方を決めるのが`index_options`で、指定できる`type`は`hnsw`、`flat`、`int8_hnsw`、`int8_flat`、`int4_hnsw`、`int4_flat`、`bbq_hnsw`、`bbq_flat`、`bbq_disk`です。末尾が`hnsw`なら近似グラフを張り、`flat`なら総当たりになります。

問題は既定が版で三度変わっていることです。9.0は`int8_hnsw`。9.1から9.3は384次元以上で`bbq_hnsw`、384次元未満で`int8_hnsw`。9.4以降とServerlessは、現行のライセンスで使える場合に`float`と`bfloat16`で`bbq_disk`が既定になりました。`byte`と`bit`だけは全版で量子化なしの`hnsw`のままです。

8系の記事を写して`int8_hnsw`を明示した設定例は今も多く流通しています。それ自体は動きますが、同じ次元数のフィールドが索引ごとに違う量子化で作られる状態を生む。方式を揃えるなら明示指定、版の推奨に追従するなら省略と、方針を先に決めておきます。

```
PUT /docs
{
  "mappings": {
    "properties": {
      "content_vector": {
        "type": "dense_vector",
        "dims": 1024,
        "similarity": "cosine",
        "index_options": { "type": "bbq_hnsw" }
      }
    }
  }
}
```

## kNN検索の実装｜kとnum\_candidatesが再現率を決める仕組み

フィールドができたら検索側です。指定する値は3つか4つですが、既定のまま投げると「取りこぼしているのに気づかない」状態になりやすい箇所です。クエリ本体の書き方や結果集合の制御は[ElasticsearchのクエリDSL](https://www.issoh.co.jp/tech/details/17031/)で扱うため、ここではkNN固有の値に絞ります。

### 近似kNNと厳密kNN｜script\_scoreを選んでよいデータ量

Elasticsearchのベクトル検索には二つの経路があります。近似kNNはHNSWまたはDiskBBQのグラフを辿って候補を絞る方式で、本番の既定路線です。厳密kNNは`script_score`でベクトル関数を評価する方式で、公式は「正確な結果を保証するが大規模データにはスケールしない」と明記しています。一致した全ドキュメントを1件ずつ計算するためです。

使い分けの目安はフィルタ後の候補件数です。テナントIDや日付で数千件まで絞った先の検索なら、`script_score`のほうが構成は単純で、グラフのメモリも要りません。数十万件以上を素で走査させる用途では選びません。

### num\_candidatesの既定と上限｜1.5倍と1万件で切れる探索

`k`は各シャードから返す近傍数で、既定は検索リクエストの`size`です。`num_candidates`はシャードごとに集める候補数で、既定は`k`を指定していれば1.5倍、指定していなければ`size`の1.5倍になります。上限は10,000です。

この既定が曲者です。`size`を10にしただけのクエリは、シャードあたり15件しか候補を見ずに近傍を決めています。近似探索は候補を広げるほど正解に近づくため、再現率が要る用途では明示的に引き上げます。実務では`k`の5倍から10倍あたりから始めて、評価セットのHit Rateが頭打ちになる点で止めるのが手早い決め方です。

`k`は`num_candidates`以下でなければエラーになります。上限の10,000は設定で緩められる値ではありません。

### filter付きkNN｜事前フィルタがk件を保証する仕組みと代償

`knn`クエリの`filter`は、近似探索の実行中に適用されます。公式は「k件の一致文書が返ることを保証するために探索中に適用される」と説明し、探索後に絞る後置フィルタは「k件未満になりうる」と対比しています。件数が足りなくなる事故を避けたいなら`filter`に置く、が基本です。

代償は速度です。フィルタで弾かれるノードが多いほどグラフを深く辿る必要が出て、探索コストが上がります。極端に選択率の低い条件、たとえば全体の0.1%しか通らないフィルタをkNNに掛けるくらいなら、その条件で先に絞ってから`script_score`の厳密検索に切り替えたほうが速く終わります。

`similarity`パラメータも併用できます。これは一致とみなす生の類似度の下限で、「近いものが無ければ0件でよい」という要件に効く指定です。件数を埋めた検索結果は、RAGでは雑音をそのままLLMへ渡します。

```
POST /docs/_search
{
  "knn": {
    "field": "content_vector",
    "query_vector": [0.031, -0.114, 0.402],
    "k": 10,
    "num_candidates": 100,
    "filter": { "term": { "tenant_id": "t-0012" } },
    "rescore_vector": { "oversample": 2.0 }
  },
  "size": 10
}
```

### rescore\_vector｜量子化で落ちた精度をoversampleで戻す

量子化は容量と速度のために精度を削る仕組みです。bbqなら1次元あたり1ビットまで潰すため、そのままでは順位が荒くなります。これを戻すのが`rescore_vector`で、`oversample`に指定した倍率だけ多く候補を取り、元のベクトルで計算し直してから上位k件を返します。

指定できる値は1.0以上、または0です。量子化したフィールドにだけ効き、非量子化のフィールドでは無視されます。`num_candidates`との関係は`max(num_candidates, k * oversample)`で、実際に集める候補数は大きいほうが採られます。

9.4以降の既定が`bbq_disk`になったということは、明示指定していない索引はすでに量子化されているということです。「9系にしたら精度が落ちた」という相談の多くは、この再スコアリングを入れていないだけで説明が付きます。まず`oversample`を2.0前後で入れ、それでも足りなければ量子化方式のほうを`int8_hnsw`へ戻す順で調べます。

## 埋め込みの生成経路｜semantic\_textと外部モデルの二択

ベクトルを誰が作るかは、索引設計と同じ重みを持つ判断です。Elasticsearchの中で完結させるか、アプリケーション側で作って投げ込むか。運用の手間と、モデル差し替えのしやすさが逆向きに動きます。

### semantic\_textに寄せる範囲｜チャンク分割まで任せる条件

`semantic_text`はテキスト専用のフィールド型で、索引マッピングの設定、投入時の埋め込み生成、チャンク分割の3つを自動で引き受けます。`dense_vector`を自分で定義して取り込みパイプラインを組む構成に比べ、書く量が目に見えて減ります。

分割の粒度は`chunking_settings`で変えられます。公式の例に挙がるのは`strategy`が`word`、`max_chunk_size`が120、`overlap`が40という値です。日本語では語数基準の分割が段落の切れ目と合わないことがあるため、実データで分割結果を見てから決めます。

寄せてよいのは、文書の更新経路がElasticsearchへの投入に一本化できている場合です。複数のシステムが同じベクトルを共有するなら、生成はアプリ側に置きます。

### 日本語で既定エンドポイントを選ぶ基準｜ELSERが英語限定である理由

`inference_id`を省略すると既定の推論エンドポイントが適用されます。ここに落とし穴がある。`.elser-2-elasticsearch`はELSERを展開するエンドポイントで、公式は「英語テキストに推奨」と明記しています。日本語のコーパスに当てる前提の既定ではありません。

非英語テキスト向けとして案内されているのは`.multilingual-e5-small-elasticsearch`です。日本語の社内文書を対象にするなら、既定に任せず`inference_id`を明示します。公式は、版を上げたときに新しく作った索引だけ別の埋め込みモデルになりうるとも注意しています。省略は「今の推奨に追従する」という選択であって、「何も決めない」ではありません。

検索時だけ別のモデルを使う場合は`search_inference_id`を指定します。省略時は`inference_id`にフォールバックします。

### 外部APIで生成する場合｜inference経由とアプリ側の分岐

外部モデルを使う経路も二つに分かれます。ひとつはInference APIにエンドポイントを登録し、Elasticsearchから呼ばせる形で、Amazon Bedrock、Azure AI Studio、Cohere、Google AI、Mistral、OpenAI、Hugging Faceなどが接続先に並びます。もうひとつはアプリケーション側で埋め込みを作り、配列として投入する形です。

前者は投入処理が短くなる代わり、検索クラスタが外部APIの停止やレート制限の影響を直接受けます。後者は再試行やキャッシュを自前で設計できるぶん、大量の初期ロードで制御が効く。数百万件の初回投入を伴う案件では、投入ジョブ側に流量制御を持たせる構成を採ります。

どちらの経路でも、検索時のクエリベクトルは索引時と同じモデルで作る必要があります。モデルを変えるときは索引とクエリ生成を同時に切り替えます。片側だけ差し替えると、エラーは出ないまま検索結果だけが崩れる。

## BM25との併用｜rrf retrieverで束ねる範囲と委譲する境界

ベクトル検索だけの構成は、型番や社内用語の検索で穴が出ます。Elasticsearchは全文検索エンジンなので、同じ索引でBM25と併走させられるのが強みです。ただし束ね方を誤ると単独より落ちます。

### rrf retrieverの構成｜rank\_window\_sizeが既定10で切れる

Elasticsearchでは`retriever`構文の`rrf`で、通常のクエリとkNNの結果を順位ベースで統合します。定数`rank_constant`の既定は60で、変更できます。

実装で最初に外すのが`rank_window_size`です。既定は10で、各サブクエリの上位10件しかRRFの対象になりません。`size`を10にしていると、統合の材料が10件ずつしか無い状態で並べ替えていることになります。実運用では50から100へ広げます。

スコア統合の方式そのもの、つまりRRFと正規化＋加重平均のどちらを選ぶか、定数をいくつにするかは製品をまたぐ論点です。[BM25とベクトル検索を統合するハイブリッド検索の設計](https://www.issoh.co.jp/tech/details/8014/)に計算式と他製品の実装まで整理してあるため、そちらへ委譲します。

### BM25を併用しない判断｜日本語の解析器を作り込めない場合の帰結

併用は常に得ではありません。前掲の記事に載せた公開実験では、ベクトル単独のHit Rate@10が0.8355だったのに対し、形態素解析だけ入れた既定設定のBM25を足すと0.7819へ下がっています。ランク統合は弱いほうのランキングもそのまま混ぜるからです。

だから判断は明快です。kuromojiやSudachiの解析器、同義語辞書、正規化を誰かが保守できないなら、ハイブリッド化は見送ります。ベクトル単独のまま、フィルタと再スコアリングで精度を詰めるほうが工数に見合う。型番検索の要件が仕様に入っているなら、全文検索側の作り込みを工数として先に積んでおきます。

## メモリ設計｜ベクトルがページキャッシュに載るかで決まる検索速度

ベクトル検索の速度は、索引の作りよりも「ベクトルデータがファイルシステムキャッシュに載っているか」で決まります。ここは設計時に計算できるので、動かしてから驚く必要はありません。

### 見積り式｜量子化の方式別バイト数と100万件・1024次元の例

公式のチューニング指針が方式ごとの必要バイト数を示しています。件数と次元数を入れるだけで、必要なメモリは着工前に出ます。

| 型と量子化        | 必要バイト数             |
| ------------ | ------------------ |
| float（量子化なし） | 件数 \* 次元数 \* 4     |
| float + int8 | 件数 \* (次元数 + 4)    |
| float + int4 | 件数 \* (次元数/2 + 4)  |
| float + bbq  | 件数 \* (次元数/8 + 14) |
| byte         | 件数 \* 次元数          |
| bit          | 件数 \* (次元数/8)      |

公式が挙げる具体例は、100万件・1024次元の非量子化floatをHNSWで持つ場合で、グラフ分と合わせて4.16GBという計算です。同じ条件をbbqにすると1件あたり142バイト、合計142MB前後まで落ちます。量子化の既定が版を追うごとに強い方式へ寄っているのは、この差が効くからです。

この容量はJVMヒープではなくOSのページキャッシュに載せる前提の数字です。ヒープとキャッシュの配分そのものは[Elasticsearchのメモリ配分とシャード設計](https://www.issoh.co.jp/tech/details/17021/)で扱っています。

### \_sourceからの除外｜9.2以降のexclude\_source\_vectors設定

ベクトルは`_source`にも生の配列として入ります。1024次元の配列がJSONで文書ごとに保存されるため、検索応答の組み立てで無視できない負荷になります。

9.2以降で案内されているのが`index.mapping.exclude_source_vectors`という索引設定です。汎用の`excludes`指定と違い、必要なときに自動で復元される仕組みが付きます。合成`_source`を使う手もありますが、まずはこの設定を見ます。

## RAG検索基盤として採用する条件と、専用ベクトルDBへ寄せる場面

Elasticsearchをベクトル検索の基盤に選ぶべきかどうかは、ベクトルの性能比較ではなく、周辺の要件がどれだけ同居するかで決まります。

### 採用条件｜既存の全文検索と同居させてよい三つの前提と確認手順

次の3点が揃うなら、専用のベクトルDBを別に立てるより有利だと判断します。

- すでにElasticsearchが動いており、運用と監視の体制がある
- キーワード一致での検索要件が仕様に含まれている
- テナントや権限、日付での絞り込みが検索条件として必須

3点目が効きます。事前フィルタ付きのkNNを同じクエリで書けることは、権限境界を持つ社内文書検索では設計の単純さに直結する。検索基盤を分けると、フィルタ条件の同期という別問題を抱え込みます。着工前に、フィルタ後の想定件数と前掲の式で出した必要メモリを、既存ノードの空き容量と突き合わせておきます。RAGの検索工程をどこまで自前で持つかの設計は、[RAG構築支援](https://www.issoh.co.jp/service/ai/rag/)で引き受けている範囲です。

### 見送る場面｜ベクトルだけを大量に持つ用途で割に合わなくなる条件

逆に選ばないのは、全文検索の要件が一切なく、億単位のベクトルを持ち、更新頻度が高い用途です。この構成では、Elasticsearchが持つ転置索引や集計の機能が丸ごと遊びます。ノードのメモリは専用エンジンに割り当てたほうが素直に効きます。

もうひとつ見送るのは、埋め込みモデルを頻繁に差し替える計画がある場合です。`dims`と`element_type`の変更にはリインデックスが要るため、モデル選定が固まらない探索段階では、切り替えの軽い構成で試してから移すほうが早く終わります。モデルが決まるまで本番索引は作らない、と決めておきます。

## よくある質問

### dense\_vectorのdimsは後から変更できますか？

できません。`dims`と`element_type`はフィールドの物理的な持ち方を決める値で、変更するには新しいマッピングで索引を作り直し、リインデックスする必要があります。埋め込みモデルの差し替えは次元数の変更を伴うことが多いため、モデル選定が固まってから本番索引を作る順番が安全です。

### 9系にしたらベクトル検索の精度が落ちたのはなぜですか？

量子化の既定が版で変わっているためです。9.0は`int8_hnsw`、9.1から9.3は384次元以上で`bbq_hnsw`、9.4以降はライセンス条件下で`bbq_disk`が既定になります。`index_options`を明示していない索引は、作り直したタイミングで別の方式になります。`knn`クエリに`rescore_vector`の`oversample`を2.0前後で加え、元のベクトルで並べ直してから差を測り直してください。

### num\_candidatesはどのくらいの値にすればよいですか？

既定は`k`の1.5倍、上限は10,000です。既定のままだとシャードあたり十数件しか候補を見ないため、再現率が要る用途では`k`の5倍から10倍あたりを起点にします。値を上げるほど探索は遅くなるため、評価セットのHit Rateが頭打ちになる点で止めます。

### semantic\_textを使えば埋め込みモデルの用意は不要ですか？

`inference_id`を省略すれば既定のエンドポイントが適用されるため、用意なしでも動きます。ただし既定の`.elser-2-elasticsearch`は公式が英語テキスト向けと明記しているモデルです。日本語の文書が対象なら、非英語向けと案内されている`.multilingual-e5-small-elasticsearch`などを`inference_id`に明示してください。

### ベクトル検索とBM25は必ず併用したほうがよいですか？

いいえ。日本語の公開実験では、ベクトル単独のHit Rate@10が0.8355だったのに対し、既定設定のBM25を足した構成は0.7819へ下がっています。ランク統合は弱いほうのランキングも混ぜるためです。解析器や同義語辞書を保守できる体制が無いなら、ベクトル単独で運用するほうが精度は保てます。

## 関連記事

- [Elasticsearchとは？転置インデックスとシャード設計・ライセンス系列で決める採用可否](https://www.issoh.co.jp/tech/details/17011/)：本記事の前提となる索引構造とノード設計です
- [ハイブリッド検索とは？RAGでBM25とベクトル検索を統合する仕組み・RRF・実装コード](https://www.issoh.co.jp/tech/details/8014/)：スコア統合の方式と定数の決め方です
- [ベクトル検索とセマンティック検索の違い｜包含関係・仕組み・使い分けを実装目線で整理](https://www.issoh.co.jp/tech/details/9159/)：用語の関係と何を解く技術かの整理です
- [ElasticsearchのクエリDSL｜queryとfilterの分岐、boolの組み立てと深いページング](https://www.issoh.co.jp/tech/details/17031/)：組み合わせるときのクエリ側の作法です
- [Elasticsearchのチューニング｜メモリ配分とシャード設計・遅いクエリの特定手順](https://www.issoh.co.jp/tech/details/17021/)：ヒープ配分を決めるときの記事です

---

出典: [Elasticsearchのベクトル検索｜dense\_vectorの設計とkNN実装、RAG基盤の採用条件](<https://www.issoh.co.jp/tech/details/17035/>)（株式会社一創）
