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(參考來源)
- Search API reference(rankingScoreThreshold)— Meilisearch 官方文檔meilisearch-docs
- Ranking score — Meilisearch 官方文檔meilisearch-docs
