federated search 的
distinct參數做的是跨索引去重,它要求去重屬性名在每個參與查詢的索引的filterableAttributes列表裡都聲明過。任一索引漏聲明,整個 multi-search 請求 400 失敗。這不是 bug 是設計——去重本質是跨索引的 filter 操作,而 filter 參數依賴 filterableAttributes 聲明。
Issue & Env(案發現場與環境)
- 業務場景:三個索引
movies/movie_ratings/movie_people都有title字段。movies 已經 PATCH 過filterableAttributes: ["title"],另外兩個索引漏了。發 federated search 帶federation.distinct: "title"時返回 400。 - 環境:Meilisearch v1.54+,任意 federated multi-search 請求。
- 關鍵區分:這不是
invalid_multi_search_distinct(那個錯誤是 federation.distinct 和 query 級 distinct 同時存在),也不是普通的invalid_search_filter(那個是單索引 filter 語句裡用了未聲明的屬性)。federation.distinct 觸發的是跨索引 filter 校驗,錯誤消息裡的屬性名是 federation 參數裡的值。
StackTrace(錯誤響應)
{
"message": "Attribute \"title\" is not filterable.",
"code": "invalid_search_filter",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_search_filter"
}
注:錯誤碼是
invalid_search_filter(和單索引 filter 未聲明的錯誤碼一樣),但觸發路徑是 federation.distinct 跨索引校驗。具體 message 可能隨 Meilisearch 版本略有差異,但「xxx is not filterable」這個結構是穩定的。
Reproduce(復現程式碼)
# 1. 三个索引都已建,movies 已声明 title 可过滤,movie_ratings 和 movie_people 没声明
curl -X PATCH 'http://localhost:7700/indexes/movies/settings/filterable-attributes' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer aSampleMasterKey' \
-d '["title"]'
# 2. 直接发带 federation.distinct 的请求 —— 400 失败
curl -X POST 'http://localhost:7700/multi-search' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer aSampleMasterKey' \
--data-binary '{
"federation": { "distinct": "title" },
"queries": [
{ "indexUid": "movies", "q": "batman" },
{ "indexUid": "movie_ratings", "q": "batman" },
{ "indexUid": "movie_people", "q": "batman" }
]
}'
{"message":"Attribute \"title\" is not filterable.","code":"invalid_search_filter","type":"invalid_request","link":"https://docs.meilisearch.com/errors#invalid_search_filter"}
Root Cause(根因分析)
federation.distinct 是跨索引的 filter 操作
Meilisearch 的 federated search 合併多索引結果後,要按 federation.distinct 指定的屬性值去重。這個去重動作對每個參與索引來說,等價於在各自索引上做一次「只保留某個屬性值唯一的文檔」——而這個操作依賴該屬性在索引裡聲明為 filterable。
不是「每個索引都要有這個字段」,是「每個索引都要聲明它可過濾」
某個索引的文檔裡哪怕根本沒有 title 這個字段,只要你在 federation.distinct 裡寫了它,Meilisearch 就會去每個參與索引的 filterableAttributes 列表裡查——查不到就報錯。這是一個聲明層面的校驗,不檢查實際數據。
和 invalid_multi_search_distinct 的區別
Meilisearch 還有另一個 multi-search 相關錯誤碼 invalid_multi_search_distinct——它的觸發條件是 federation 級 distinct 和 query 級 distinct 同時存在。而本條 case 的觸發條件是某個索引沒在 filterableAttributes 裡聲明 distinct 屬性,錯誤碼是 invalid_search_filter。兩者別混淆。
The Fix(解決方案)
第一步:給所有參與索引補聲明
curl -X PATCH 'http://localhost:7700/indexes/movie_ratings/settings/filterable-attributes' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer aSampleMasterKey' \
-d '["title"]'
curl -X PATCH 'http://localhost:7700/indexes/movie_people/settings/filterable-attributes' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer aSampleMasterKey' \
-d '["title"]'
等兩個 PATCH 任務都 succeeded 後重發 multi-search 請求。
第二步:驗證 filterableAttributes 已生效
curl 'http://localhost:7700/indexes/movie_ratings/settings/filterable-attributes' \
-H 'Authorization: Bearer aSampleMasterKey'
["title"]
排查建議
- federation.distinct 的屬性名提前拿個 checklist:凡是參與 federated search 的索引,都要在 filterableAttributes 裡聲明它。別假設「這個索引沒這個字段所以不需要聲明」——Meilisearch 不做這個推斷。
- 如果某個索引真的不應該參與去重(比如它的文檔結構里根本沒有 title),那它就不應該出現在 queries 陣列裡,或者換一個所有索引共有的屬性名做 distinct。
- PATCH filterableAttributes 是異步操作,任務 succeeded 之前別重發 multi-search——重發會得到同樣的 400。
附帶提醒:federation.distinct 按屬性值去重還要求字段名跨索引一致。如果一個索引叫 title、另一個叫 movie_title,distinct 用哪個名字都會漏掉另一個——建模時就要統一去重字段的命名。
References(參考來源)
- Federated search — Meilisearch 官方文檔meilisearch-docs
- Multi-search API reference — Meilisearch 官方文檔meilisearch-docs
- Error codes — Meilisearch 官方文檔meilisearch-docs
