Elasticsearch 搜尋與相關性調優:映射、查詢、聚合與效能
Elasticsearch 為什麼值得認真調優
Elasticsearch 的「開箱即用」往往只適合 demo。一旦資料量上億、QPS 上千,未調優的 mapping 和查詢會迅速拖垮叢集:寫入阻塞、查詢毛刺、堆記憶體溢出。調優的本質是在寫入時把該算的算完,在查詢時只做必要的工作。
| 維度 | 未調優 | 調優後 |
|---|---|---|
| 欄位類型 | 全 text 或 keyword |
按檢索/聚合需求精確拆分 |
| 過濾條件 | 寫在 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 cache:
filter結果依節點快取。 - 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 開發中,以下 工具庫 能幫上忙:
- JSON 格式化 — 整理 mapping 與 DSL 請求體
- SQL 格式化 — 對照 SQL 思路設計 ES 查詢
- Base64 編碼 — 處理文件 ID / 二進位載荷
- JWT 解碼 — 查看寫入鑑權 Token 的聲明
Elasticsearch 的效能,七分在寫入前的 mapping 與 analyzer 設計,三分在查詢時的 filter/聚合取捨。把「該算的提前算、該快取的快取住」,叢集才能在億級資料下依然毫秒回應。
本站提供瀏覽器本地工具,免註冊即可試用 →