LabHub

ブログ

Elasticsearch と OpenSearch、Lucene の内部 — Inverted Index、BM25、Sharding、Vector Search、Hybrid RAG まで (2025)

한국어English日本語

"Search is not a feature. It's a philosophy of how humans interact with information." — Doug Cutting (Lucene 作者, 1999)

1999 年、Doug Cutting が Java で Lucene を作ったとき、彼は「誰もが自分のデータに Google 級の検索を持てるべきだ」と信じていた。26 年経った今、Elasticsearch、OpenSearch、Solr はすべて Lucene の上に立つ。ログ解析、商品検索、オートコンプリート、そして 2024 年からは RAG (Retrieval-Augmented Generation) の中核インフラまで。

しかし「Elasticsearch を使う」と「Lucene を理解する」は天と地の差である。本稿は検索の本質から 2025 年の Hybrid Search まで一気通貫の地図である。


1. RDB の LIKE '%keyword%' はなぜダメか

線形スキャンの壁

SELECT * FROM articles WHERE content LIKE '%postgresql%';

Postgres GIN も部分的に解決するが、トークン化/言語解析の制限、スコアリングの困難、オートコンプリートやタイポ補正の生態系の弱さ、分散検索の弱さがある。専用検索エンジンが必要な理由だ。


2. Inverted Index — 検索の数学的心臓

基本アイデア

Document 1: "The quick brown fox"
Document 2: "The lazy brown dog"
Document 3: "Foxes and dogs"

単語 から ドキュメントリスト に反転すると:

brown  -> [1, 2]
dog    -> [2]
dogs   -> [3]
fox    -> [1]
foxes  -> [3]
lazy   -> [2]
quick  -> [1]
the    -> [1, 2]

クエリ "brown dog" は AND 演算で [2]。数十億ドキュメントでも O(logN)O(\log N) に近い速度。

Lucene の実装単位

  1. Term Dictionary — 全 term のソート済み辞書 (FST)
  2. Postings List — term ごとのドキュメントリスト + 頻度/位置
  3. Stored Fields — 原文 (圧縮)
  4. Doc Values — 集計/ソート用のカラムナ格納
  5. Norms — フィールド長の正規化値

FST — 接頭辞共有でメモリ節約

"fox, foxes, foxy" の共通接頭辞を有限状態トランスデューサで圧縮。数千万 term を数 MB に収める。オートコンプリートの中核構造でもある。


3. Segment — Lucene の不変性原則

Immutable Segment

Lucene では一度書かれたファイルは変更されない。これが Lucene を速く安全にする。

Segment の構成ファイル

_0.cfs     — composite file
_0.cfe     — entry point
_0.si      — segment info
_0.fdt/fdx — field data
_0.tim/tip — term dictionary
_0.doc/pos — postings
_0.dvm/dvd — doc values
_0.liv     — live docs

Merge ポリシー

Refresh / Flush / Commit

用語意味タイミング
Refreshメモリバッファを検索可能な Segment にする既定 1 秒ごと
FlushSegment をディスクに fsync自動 (メモリ閾値)
Committranslog 含む完全永続化頻度は低い

「Near Real-Time Search」の秘密: Refresh は 1 秒、Flush は後で。これが Elasticsearch のトレードマーク「1 秒の遅延」だ。

Translog


4. BM25 — TF-IDF を置き換えたスコア

TF-IDF の限界

tf-idf(t,d)=tf(t,d)×logNdf(t)\text{tf-idf}(t, d) = \text{tf}(t, d) \times \log\frac{N}{\text{df}(t)}

長い文書ほど tf が増え、スコアが膨張する。

BM25 の式

score(d,q)=tqIDF(t)f(t,d)(k1+1)f(t,d)+k1(1b+bdavgdl)\text{score}(d, q) = \sum_{t \in q} \text{IDF}(t) \cdot \frac{f(t, d) \cdot (k_1 + 1)}{f(t, d) + k_1 \cdot (1 - b + b \cdot \frac{|d|}{\text{avgdl}})}

重要な変更:

なぜ BM25 が既定か

商品名やクエリログ分析では b=0.3 程度まで下げるのが一般的。


5. Analyzer — トークン化の芸術

3 段パイプライン

  1. Character Filter — HTML 除去、文字置換
  2. Tokenizer — 文を単語に分割
  3. Token Filter — 小文字化、ステミング、Synonym、ストップワード

韓国語の地獄

英語は空白でトークン化しやすい。韓国語は膠着語 + 語尾変化 + 助詞。「검색했다/검색한다/검색은/검색을」すべてが「검색」にマッチしなければならない。nori (韓国語)、kuromoji (日本語)、ik (中国語) が代表的な Analyzer。

nori の例

POST _analyze
{
  "analyzer": "nori",
  "text": "Elasticsearch는 검색엔진입니다"
}

-> "elasticsearch", "는", "검색", "엔진", "입니다"

助詞/語尾を除去: "filter": ["nori_part_of_speech"]

Synonym 展開

"shoe, sneaker, runner"
-> "shoe" クエリで sneaker/runner もマッチ

検索品質の半分は Synonym 辞書にかかっている。構築は地味だが ROI 最高。


6. Shard と Replica — 分散の基本

Primary Shard

Replica Shard

制限と原則

Cluster State と Split Brain


7. Query DSL — JSON の迷宮

主なクエリ

クエリ用途
matchAnalyzer 経由のフルテキストマッチ
termAnalyzer を通さず完全一致 (keyword)
match_phrase順序保持フレーズ
multi_match複数フィールド同時検索
boolAND/OR/NOT 合成
range範囲
function_scoreカスタムスコア
rank_featureブースト用フィールド

bool の 4 節

{
  "bool": {
    "must": [],
    "should": [],
    "filter": [],
    "must_not": []
  }
}

filter を積極的に使う — キャッシュされ高速。スコアリングは match、固定条件は filter

Term vs Match — 最頻出の罠

{"term": {"name": "User Name"}}
{"match": {"name": "User Name"}}

text フィールドには match、keyword フィールドには term

Aggregation

{
  "aggs": {
    "by_category": {
      "terms": {"field": "category"},
      "aggs": {
        "avg_price": {"avg": {"field": "price"}}
      }
    }
  }
}

SQL の GROUP BY に相当。ログ解析/ダッシュボードに必須。


8. Vector Search — 2022 年以降の革命

なぜ Vector か

ES/OpenSearch の kNN

Elasticsearch 8.0 (2022) から native kNN。Lucene 9.0 の HNSW 実装を活用。

{
  "mappings": {
    "properties": {
      "title_vector": {
        "type": "dense_vector",
        "dims": 768,
        "index": true,
        "similarity": "cosine"
      }
    }
  }
}
{
  "knn": {
    "field": "title_vector",
    "query_vector": [0.1, 0.2],
    "k": 10,
    "num_candidates": 100
  }
}

HNSW パラメータ

Quantization


9. Hybrid Search — RAG 時代の答え

BM25 vs Vector

観点BM25Vector
完全一致 (SKU)
意味類似
希少語 (専門用語)
タイポ
多言語

両方が必要だ。

RRF — Reciprocal Rank Fusion

RRF(d)=i1k+ranki(d)\text{RRF}(d) = \sum_i \frac{1}{k + \text{rank}_i(d)}

ES の rrf retriever

{
  "retriever": {
    "rrf": {
      "retrievers": [
        {"standard": {"query": {"match": {"content": "query"}}}},
        {"knn": {"field": "vec", "query_vector": [], "k": 50}}
      ],
      "rank_window_size": 50,
      "rank_constant": 60
    }
  }
}

Cross-Encoder Reranker


10. 2021 年ライセンス戦争 — OpenSearch 誕生

背景

結果

2024-2025 の現状

製品ライセンス主導
Elasticsearch 8Elastic License 2 / SSPLElastic
Elasticsearch 8.14+AGPL 追加 (2024.8)Elastic (回復の試み)
OpenSearch 2.xApache 2.0AWS -> Linux Foundation

選択ガイド


11. 運用の地獄 — よくある障害

JVM Heap

Circuit Breaker

circuit_breaking_exception: Data too large

Hot Shard

Shard 爆発

Snapshot と Restore


12. Ingest パイプライン

Ingest Node Pipeline

{
  "processors": [
    {"set": {"field": "indexed_at", "value": "{{_ingest.timestamp}}"}},
    {"grok": {"field": "message", "patterns": ["%{COMBINEDAPACHELOG}"]}}
  ]
}

13. ES|QL — SQL の帰還 (2024)

Elastic 長年の念願、SQL 風クエリ言語。

FROM logs-*
| WHERE status >= 500
| STATS count = COUNT(*) BY host
| SORT count DESC
| LIMIT 10

OpenSearch も 2024 年に PPL (Piped Processing Language) で類似機能を追加。


14. 検索品質の評価


15. アンチパターン TOP 10

  1. 既定 1-shard/1-replica で数十 TB のインデックス
  2. _id 手動指定による routing 偏り
  3. インデックス/shard が多すぎる
  4. JVM heap 64 GB 設定 (Compressed OOPs 超過)
  5. Deep pagination (from=10000) — scroll/search_after を使う
  6. analyzed text フィールドに term クエリ
  7. クエリごとに cluster health check
  8. 大量削除の頻繁な実行 — _forcemerge 必要
  9. snapshot 無し
  10. Vector のみで BM25 を捨てる — Hybrid がほぼ常勝

16. チェックリスト


終わりに — Lucene の肩の上の巨人

Elasticsearch、OpenSearch、Solr — 名は違えど、すべて Lucene という巨人の肩に立つ。その Lucene は 1999 年、Doug Cutting が「検索を民主化する」ために書いた 1 つの Java ライブラリから始まった。

2025 年の検索は、ログを探し、商品を探し、コンテンツを推薦し、LLM のコンテキストになり、オートコンプリートとタイポ補正を提供する。どこにでもあるが、全員が正しく理解しているわけではない。Inverted Index と Segment、BM25 と Vector Search、Shard と Routing が腑に落ちた瞬間、あなたは検索を使う人から設計する人になる。


"A good search engine doesn't just find what you typed. It finds what you meant." — Peter Norvig

コメント

まだコメントはありません。

ログインするとコメントできます