StackTraces
Meilisearch已驗證

Meilisearch rankingScoreThreshold 讓 estimatedTotalHits驟降

@debug_master今天更新閱讀 6 min00

rankingScoreThreshold(0.0–1.0)是停掃語義:搜索結果按相關度降序排,引擎掃到第一個分數低於閾值的文檔就停,其後全部排除。被排除的文檔不計入 estimatedTotalHits、totalHits,也不計入 facet 分佈——計數驟降不是索引壞了,是閾值把低分檔整體切掉了。

Issue & Env(案發現場與環境)

  • 業務場景:給搜索框加「低於 0.5 分的結果不展示」的質量閥門,加上 rankingScoreThreshold: 0.5 後發現:結果數從 2 掉到 1,前端統計的總數和分面篩選器的數字一起縮水,誤以為索引丟數據或 filter 失效。
  • 環境:Meilisearch v1.54+,搜索請求帶 rankingScoreThreshold 參數。
  • 示例索引用 books_rank(6 條書籍文檔,含 title_en、summary、genres 字段),查詢詞 dark forest:
curl -X POST http://localhost:7700/indexes/books_rank/search \
  -H "Authorization: Bearer aSampleMasterKey" \
  -H "Content-Type: application/json" \
  -d '{"q":"dark forest","facets":["genres"]}'

無閾值時兩個分檔都在:《黑暗森林》(title_en 命中 dark + forest,words 2/2)和《影之森》(title_en 只命中 forest,words 1/2):

{
  "hits": ["黑暗森林 9.2", "影之森 8.5"],
  "estimatedTotalHits": 2,
  "facetDistribution": { "genres": { "科幻": 1, "悬疑": 1 } }
}

(返回體為關鍵節選;具體分值以本地實測為準。)

StackTrace(無報錯,計數驟降)

加上閾值再搜(0.5 取在兩檔分數之間;先開 showRankingScore 看分數分佈再選):

curl -X POST http://localhost:7700/indexes/books_rank/search \
  -H "Authorization: Bearer aSampleMasterKey" \
  -H "Content-Type: application/json" \
  -d '{"q":"dark forest","facets":["genres"],"rankingScoreThreshold":0.5}'
{
  "hits": ["黑暗森林 9.2"],
  "estimatedTotalHits": 1,
  "facetDistribution": { "genres": { "科幻": 1 } }
}

《影之森》從 hits 裡消失,estimatedTotalHits 從 2 變 1,facetDistribution 裡「懸疑」整個不見了。返回碼 200,沒有任何報錯——但前端的總數、分面計數全都「變少」了。

Root Cause(根因分析)

停掃是設計,不是過濾

官方 search API 文檔對 rankingScoreThreshold 的描述:在第一個分數低於該值的文檔處停止搜索,其後所有文檔排除。這能成立的前提是結果已按相關度降序排列——詞典序保證了後面的分數只會更低,掃到第一個低分就可以收工。所以它不是「逐條過濾後如實統計」,而是「提前收工,後面的不看了」。

計數與分面隨之縮水

官方文檔同一段明確:被排除的命中不計入 estimatedTotalHits、totalHits 與 facet 分佈。所以:

  • estimatedTotalHits 變小 ≠ 索引數據變少,它統計的是「閾值之上」的命中;
  • 分面篩選器裡某個選項消失,只說明該選項的文檔全在低分檔,不代表分類數據丟失;
  • 想要「全量命中數 + 閾值內命中數」兩個數字,得發兩次請求(一次帶閾值、一次不帶),引擎不會一次給全。

與 page/hitsPerPage 同用會變慢

文檔同時提示:rankingScoreThreshold 與 page/hitsPerPage 一起用時可能降低性能——exhaustive 分頁要求拿到精確總數,停掃優化直接失效,引擎必須給全部命中文檔打分。

The Fix(解決方案)

第一步:先看分數分佈,再定閾值

curl -X POST http://localhost:7700/indexes/books_rank/search \
  -H "Authorization: Bearer aSampleMasterKey" \
  -H "Content-Type: application/json" \
  -d '{"q":"dark forest","showRankingScore":true,"showRankingScoreDetails":true}'

用 _rankingScore(0.0–1.0,1.0 為完美匹配)觀察你的數據在典型查詢下的分數落點,把閾值定在「好結果檔」與「差結果檔」的分界上——不同數據、不同查詢的分數分佈差異很大,別照搬別人的 0.9。

第二步:前端別把 estimatedTotalHits 當全量計數

帶閾值的響應裡,estimatedTotalHits 的語義就是「閾值之上的命中數」。UI 文案寫「約 N 條結果」時要麼接受這個口徑,要麼單獨發一次不帶閾值的請求拿全量數字。

排查建議

  • 計數驟降 + 無報錯 + 只有帶閾值請求復現 → 先懷疑閾值,GET 一遍請求體裡的 rankingScoreThreshold 是不是被上游預設值或 SDK 常量塞進去了。
  • 分面選項消失時,去掉閾值重發一次:數字回來就是閾值切的,不是數據丟了。
  • 需要精確計數(頁碼導航)時用 page/hitsPerPage 體系並接受性能代價;閾值和精確計數本來就是反向優化,二者只取其一。

References(參考來源)

最後更新於 2026-10-31

這篇幫到你了嗎?

剛解決了一個棘手的報錯?花兩分鐘記錄下來,幫助下一個遇到同樣問題的開發者。

貢獻一條解法