Elasticsearch 搜尋與相關性調優:映射、查詢、聚合與效能

数据库

Elasticsearch 為什麼值得認真調優

Elasticsearch 的「開箱即用」往往只適合 demo。一旦資料量上億、QPS 上千,未調優的 mapping 和查詢會迅速拖垮叢集:寫入阻塞、查詢毛刺、堆記憶體溢出。調優的本質是在寫入時把該算的算完,在查詢時只做必要的工作

維度 未調優 調優後
欄位類型 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 vs 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_interval 調大到 30s 能顯著降低近即時刷新帶來的寫入放大(適合日誌類場景)。


批次寫入與快取

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 的請求結果快取。
  • Fielddata / doc_values:排序聚合依賴列存,避免對 text 直接聚合。

相關性調優(BM25)

預設 BM25 已不錯,但可疊加業務權重:

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

標題命中權重是描述的 3 倍。function_score 還能引入時間衰減、銷量等外部因子。

介面鑑權(如寫入 ES 的 Service Token)可用 JWT 解碼 工具快速查看 payload 中的過期時間與權限聲明。


常見問題 FAQ

Q1:為什麼 terms 聚合對 text 欄位報錯?

text 欄位被分詞,無法做精確聚合;改用 .raw(keyword)子欄位。

Q2:filter 和 must 哪個更快?

filter 更快且不參與算分、可快取,凡是「硬性條件」優先放 filter

Q3:分片數設多少合適?

按總資料量反推,單分片 10~50GB,避免過多導致叢集元資料開銷飆升。

Q4:寫入慢怎麼排查?

先看是否單條寫入(應改 bulk)、refresh_interval 是否過小、副本是否過多。

Q5:中文搜尋效果差怎麼辦?

換用 IK / pinyin 等中文分詞器,並在 mapping 裡為檢索欄位指定對應 analyzer。


工具推薦

在 Elasticsearch 開發中,以下 工具庫 能幫上忙:


Elasticsearch 的效能,七分在寫入前的 mapping 與 analyzer 設計,三分在查詢時的 filter/聚合取捨。把「該算的提前算、該快取的快取住」,叢集才能在億級資料下依然毫秒回應。

本站提供瀏覽器本地工具,免註冊即可試用 →

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