PUT /settings/ranking-rules是全量覆盖语义,不是增量追加。只传一条自定义规则(比如["rating:desc"])会把默认 7 条全部冲掉——typo、proximity、attributeRank、wordPosition、exactness 的裁决全部退场,只有words靠隐式优先兜底。不报错,但相关度排序整体失效,结果顺序变成「命中词数相同就按你的字段排」。
Issue & Env(案发现场与环境)
- 业务场景:想让「评分高的书」常驻搜索前排,把
rating:desc写进排名规则。为了「省事」只传了这一个规则。结果发现:拼错词的文档和拼写正确的文档平起平坐、命中标题的文档不再比命中摘要的靠前,整个相关度排序像被拔掉了。 - 环境:Meilisearch v1.54+(默认排名规则为 7 条:
words、typo、proximity、attributeRank、sort、wordPosition、exactness,v1.36 起attribute拆分为attributeRank+wordPosition)。 - 示例索引用
books_rank(6 条书籍文档,含title_en、rating字段)。其中两条关键数据:
{"id": 2, "title": "黑暗森林", "title_en": "The Dark Forest", "rating": 9.2}
{"id": 6, "title": "影之森", "title_en": "The Forest of Shadows", "rating": 8.5}
StackTrace(无报错,静默失效)
第一步先看设置被覆盖成什么样:
curl http://localhost:7700/indexes/books_rank/settings/ranking-rules \
-H "Authorization: Bearer aSampleMasterKey"
["rating:desc"]
返回体里只剩一条规则——不是追加,是整个数组被替换了。没有任何报错,PUT 成功、任务 succeeded。
第二步看搜索行为变化。用 q=forest 搜(两个书名里都有 forest,拼错的场景同理):
curl -X POST http://localhost:7700/indexes/books_rank/search \
-H "Authorization: Bearer aSampleMasterKey" \
-H "Content-Type: application/json" \
-d '{"q":"forest"}'
默认 7 条规则下,attributeRank 和 wordPosition 会裁决:两本书都命中 title_en,但《影之森》的 forest 在标题第 2 个词位、《黑暗森林》在第 3 个词位,wordPosition 让《影之森》排前。覆盖后这个裁决消失了:
默认规则:影之森 8.5 → 黑暗森林 9.2(wordPosition:词位靠前者胜)
覆盖后: 黑暗森林 9.2 → 影之森 8.5(只剩隐式 words 平局 → rating:desc 接管)
顺序整个翻转。相关度维度(词位、字段权重、错字数)全部不再参与排序。
Root Cause(根因分析)
PUT settings 全家都是全量覆盖
Meilisearch 的所有 PUT /settings/* 子路由(包括 PUT /settings 整体)都是替换整个数组/对象的语义。官方 settings API 文档对 ranking-rules 更新端点的描述就是覆写现有列表。想「追加」必须把旧规则原样带上。
冲掉默认规则后,裁决链只剩隐式 words
官方 relevancy 文档写明:即使你删掉 words,Meilisearch 仍会隐式地按 words 优先排序——这是保底,不是完整相关度。被冲掉的其余 5 条各管一摊:
| 被冲掉的规则 | 失效后果 |
|---|---|
typo |
拼错词的文档与拼写正确的文档平起平坐(typo tolerance 仍放行匹配,只是不再按错字数排序) |
proximity |
词距不再影响顺序,「dark forest」相邻命中和相隔十词命中一样 |
attributeRank |
标题命中与摘要命中不再有先后 |
wordPosition |
词位靠前不再加分 |
exactness |
精确整值匹配不再优先于部分匹配 |
words 的隐式优先意味着「命中词数」还在起作用——所以现象是同命中数内部顺序失控,而不是彻底乱序,这也是它容易瞒过测试的原因。
顺带澄清:自定义规则不需要 sortableAttributes
rating:desc 这类自定义排名规则只要字段在全部文档里是数字或字符串即可,不要求先声明 sortableAttributes(那是 sort 搜索参数的要求)。所以坑不在声明缺失,纯在覆盖语义。
The Fix(解决方案)
第一步:全量带上默认 7 条,自定义规则放链尾
curl -X PUT http://localhost:7700/indexes/books_rank/settings/ranking-rules \
-H "Authorization: Bearer aSampleMasterKey" \
-H "Content-Type: application/json" \
--data-binary '["words","typo","proximity","attributeRank","sort","wordPosition","exactness","rating:desc"]'
放链尾是软排序:相关度先裁决,打平时评分高的靠前。要「评分硬压相关度」就放链首,代价是牺牲相关度——位置即权重。
第二步:拿不准就重置为默认
DELETE 该端点会把 rankingRules 恢复成默认 7 条(设置类端点的重置语义):
curl -X DELETE http://localhost:7700/indexes/books_rank/settings/ranking-rules \
-H "Authorization: Bearer aSampleMasterKey"
排查建议
- 改完规则先
GET /settings/ranking-rules看一眼,确认默认 7 条都在——全量覆盖不留神就会少带。 - 设置更新是异步任务且触发全量重建,等任务
succeeded再验证顺序,否则看到的还是旧索引的结果。 - 用
_rankingScoreDetails验证:默认规则下明细里有typo、attributeRank等键;被覆盖后这些键会消失,一眼就能看出裁决链缺了谁。 - v1.36+ 环境注意:
attribute不能与attributeRank/wordPosition混用,带规则时别把新旧两套写在一起。
References(参考来源)
- Built-in ranking rules — Meilisearch 官方文档meilisearch-docs
- Settings API reference — Meilisearch 官方文档meilisearch-docs
