Elasticsearch 検索と関連性チューニング:マッピング・クエリ・集計・パフォーマンス

数据库

Elasticsearch を本気でチューニングすべき理由

Elasticsearch の「そのまま使える」体験は demo には十分です。しかしドキュメントが数億件、QPS が数千を超えると、チューニングされていない mapping とクエリは一気にクラスタを沈めます。書き込み停滞、クエリのスパイク、ヒープ OOM へ。チューニングの本質は書き込み時に重い計算を済ませ、クエリ時には必要な仕事だけをすることです。

次元 チューニング前 チューニング後
フィールド型 全て textkeyword 検索/集計需要で正確に分割
フィルタ条件 must 内に filter コンテキストへ
シャード数 適当に 5/10 データ量とノードから逆算
バルク書き込み 単発 _doc bulk + 適切なバッチ
関連性 デフォルト BM25 boost / 業務重みを併用

トークン化と Analyzer:検索品質の土台

analyzer は「ユーザーが探す語」と「文書にある語」が一致するかを決めます。中国語は特に正しいトークナイザが必要です。

{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_cn": {
          "tokenizer": "ik_max_word",
          "filter": ["lowercase"]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "title": { "type": "text", "analyzer": "my_cn" },
      "status": { "type": "keyword" }
    }
  }
}

mapping やクエリ DSL を調査するときは、リクエスト本体を JSON フォーマッタ で整形してから見ると、深いネストのオブジェクトでも混乱しません。


text と keyword:検索と集計のトレードオフ

  • text:トークン化される。全文検索に向くが、完全一致や集計には不向き。
  • keyword:トークン化されない。完全一致・ソート・terms 集計に向くが全文検索不可。

一般的な手法は multi-fields で両方を残すことです:

{
  "mappings": {
    "properties": {
      "city": {
        "type": "text",
        "fields": {
          "raw": { "type": "keyword" }
        }
      }
    }
  }
}

検索は city(トークン化)、集計/ソートは city.raw(完全一致)を使います。


bool クエリ:filter と query の雲泥の差

最も誤用されやすい点です。filter コンテキストは関連性スコアを計算せず、結果はキャッシュ可能です。query コンテキストはスコアを計算します。「○/×」条件(ステータス、時間範囲、カテゴリ)は全て filter に入れます。

{
  "query": {
    "bool": {
      "must": [
        { "match": { "title": "手机" } }
      ],
      "filter": [
        { "term": { "status": "on" } },
        { "range": { "price": { "gte": 100, "lte": 5000 } } }
      ]
    }
  }
}

must 内の match はスコアに寄与し、filter 内の term/range は高速でノードレベルキャッシュから再利用されます。


集計:分析をエンジンにプッシュダウン

データをアプリ層に引き戻して集計するのではなく、ES 集計を直接使います:

{
  "size": 0,
  "aggs": {
    "by_category": {
      "terms": { "field": "category.raw", "size": 10 }
    },
    "price_stats": {
      "stats": { "field": "price" }
    }
  }
}

size: 0 はヒット文書を返さず集計結果のみを返すため、帯域を大きく節約します。


インデックス設定:シャードとレプリカ

シャードは多いほど良いわけではありません。目安:

  • 単シャードを 10GB ~ 50GB に収める。
  • シャード数 ≈ 全データ量 / 目標シャードサイズ。
  • レプリカ数 = 読みスループット需要 / 単ノード能力。高可用のため最低 1。
{
  "settings": {
    "number_of_shards": 3,
    "number_of_replicas": 1,
    "refresh_interval": "30s"
  }
}

refresh_interval30s に大きくすると、近リアルタイム更新による書き込み増幅を大幅に下げられます(ログ系に最適)。


バルク書き込みとキャッシュ

単発ではなく bulk

curl -XPOST "localhost:9200/orders/_bulk" -H 'Content-Type: application/json' --data-binary @bulk.jsonl

バッチは 5MB ~ 15MB 程度が目安。大きすぎるとキュー滞留を招きます。

キャッシュを賢く使う

  • Node query cachefilter 結果をノード単位でキャッシュ。
  • Shard request cache:集計 / size:0 リクエストの結果をキャッシュ。
  • Fielddata / doc_values:ソート・集計は列存に依存。text を直接集計しない。

関連性チューニング(BM25)

デフォルト BM25 で十分ですが、業務重みを重ねられます:

{
  "query": {
    "bool": {
      "should": [
        { "match": { "title": { "query": "手机", "boost": 3 } } },
        { "match": { "description": { "query": "手机", "boost": 1 } } }
      ]
    }
  }
}

タイトル一致の重みは説明の 3 倍です。function_score で時間減衰や販売数などの外部因子も導入できます。

API 認証(ES へ書き込む Service Token 等)は JWT デコード ツールで payload の有効期限や権限クレームを即座に確認できます。


よくある質問 FAQ

Q1:なぜ text フィールドで terms 集計がエラーになるのですか?

text はトークン化されるため正確な集計ができません。.raw(keyword)サブフィールドを使ってください。

Q2:filter と must はどちらが速いですか?

filter の方が速く、スコア計算もせずキャッシュ可能です。「硬い条件」は優先して filter へ。

Q3:シャード数はいくつにすべきですか?

全データ量から逆算し、単シャード 10〜50GB に。多すぎるとクラスタのメタデータ負荷が跳ね上がります。

Q4:書き込みが遅いときの調査方法は?

単発書き込み(bulk に切替)、refresh_interval が小さすぎないか、レプリカが多すぎないかを確認。

Q5:中国語検索の品質が悪い場合は?

IK / pinyin などの中国語トークナイザに変え、検索フィールドに該当 analyzer をマッピングで指定します。


おすすめツール

Elasticsearch 開発では、以下の ツール が役立ちます:


Elasticsearch のパフォーマンスは、書き込み前の mapping と analyzer 設計が七分、クエリ時の filter/集計のトレードオフが三分。済ませるべき計算は前もって済ませ、キャッシュすべきものはキャッシュしておけば、数億件のデータでもミリ秒応答を保てます。

ブラウザローカルツールを無料で試す →

#Elasticsearch#搜索#相关性#索引#聚合#全文检索#性能调优