給索引 PUT 了
dictionary、separatorTokens、nonSeparatorTokens、localizedAttributes這類分詞設置,接口返回 200,但緊接著搜索行為一點沒變。原因是這些都是索引側設置:PUT 只是把變更排進隊列,返回一個taskUid,真正幹活的是異步的settingsUpdate任務,而且它會重索引該索引的全部文檔。任務還沒succeeded就去搜,用的還是舊設定。輪詢到任務成功再搜才行。
Issue & Env(案發現場與環境)
- 觸發場景:按文檔給
dictionary或separatorTokens發了 PUT,命令行看到 200 和taskUid,以為設置「已經生效」,馬上重搜,結果和設置前一模一樣。 - 環境:Meilisearch v1.54.3,本機實跑復現,索引
books_zh。 - 關鍵區分:PUT 返回 200 不代表設置生效,只代表任務入隊。這些設置不是查詢時讀取的即時參數,而是要重建倒排索引才能體現。
- 直覺誤導:
taskUid這種一點點返回的值很容易被當成「操作完成」,實際上它對應的是一個還沒跑完的後臺任務。
StackTrace(堆疊日誌)
PUT dictionary 的返回,只有 taskUid 和 enqueued,沒有生效結果:
{"taskUid":2,"indexUid":"books_zh","status":"enqueued","type":"settingsUpdate","enqueuedAt":"2026-10-05T01:52:44.547909Z"}
輪詢到 GET /tasks/2 才看到它做完,details 裡帶著實際寫入的值:
{"uid":2,"batchUid":2,"indexUid":"books_zh","status":"succeeded","type":"settingsUpdate","canceledBy":null,"details":{"dictionary":["未来科技"]},"error":null,"duration":"PT0.024570S","enqueuedAt":"2026-10-05T01:52:44.547909Z","startedAt":"2026-10-05T01:52:44.556926Z","finishedAt":"2026-10-05T01:52:44.581496Z"}
separatorTokens 同理,任務號是 5:
{"taskUid":5,"indexUid":"books_zh","status":"enqueued","type":"settingsUpdate","enqueuedAt":"2026-10-05T01:52:53.876945Z"}
在這個任務跑完之前,行為完全不改。
Reproduce(復現程式碼)
用 red|green|blue 這段數據最能看清「設置沒生效」和「生效後」的差別:
# 1. 先放一条关键字里带 | 的文档
curl -s -X POST http://localhost:7700/indexes/books_zh/documents \
-H 'Authorization: Bearer aSampleMasterKey' \
-H 'Content-Type: application/json' \
-d '[{"id":7,"title":"色彩手册","keywords":"red|green|blue"}]'
# 2. 设置前搜 green —— | 不是默认分隔符,整段是一个 token,命中 0
curl -s -X POST http://localhost:7700/indexes/books_zh/search \
-H 'Authorization: Bearer aSampleMasterKey' \
-H 'Content-Type: application/json' \
-d '{"q":"green"}'
# 返回:{"hits":[],"query":"green","processingTimeMs":10,"limit":20,"offset":0,"estimatedTotalHits":0,...}
# 3. PUT separatorTokens,把 | 声明为分隔符
curl -s -X PUT http://localhost:7700/indexes/books_zh/settings/separator-tokens \
-H 'Authorization: Bearer aSampleMasterKey' \
-H 'Content-Type: application/json' \
-d '["|"]'
# 返回:{"taskUid":5,"indexUid":"books_zh","status":"enqueued","type":"settingsUpdate",...}
# 4. 任务没跑完就搜,还是 0 命中;轮询到 succeeded 后再搜
curl -s http://localhost:7700/tasks/5 \
-H 'Authorization: Bearer aSampleMasterKey'
# 返回:{"uid":5,...,"status":"succeeded","type":"settingsUpdate","details":{"separatorTokens":["|"]},...}
curl -s -X POST http://localhost:7700/indexes/books_zh/search \
-H 'Authorization: Bearer aSampleMasterKey' \
-H 'Content-Type: application/json' \
-d '{"q":"green"}'
# 任务成功后返回:{"hits":[{"id":7,"title":"色彩手册","keywords":"red|green|blue","_matchesPosition":{"keywords":[{"start":4,"length":5}]}}],"estimatedTotalHits":1,...}
Root Cause(根因分析)
分詞設置是索引側設置,不是查詢參數
dictionary、separatorTokens、nonSeparatorTokens、localizedAttributes 定義的是「文檔入庫時怎麼切詞」。文檔的 token 在索引階段就寫進了倒排索引,查詢時只按已經建好的 token 去匹配。所以要改變切詞行為,必須把已有文檔按新設置重新切一遍、重建索引——這一步不可能在搜索請求裡即時完成。
PUT 只是入隊,任務會重索引全部文檔
Meilisearch 把這類寫操作放進異步隊列。官方異步操作文檔原文:
Many operations in Meilisearch are processed asynchronously. These API requests are not handled immediately—instead, Meilisearch places them in a queue and processes them in the order they were received.
PUT /settings/... 返回的 taskUid 只表示「已入隊」,type 是 settingsUpdate。這個任務執行時會重索引該索引的全部文檔(本機 books_zh 只有 7 條,幾十毫秒跑完;數據量大時會明顯更久)。在它 succeeded 之前,倒排索引還是舊的,搜索自然沒變化。
各設置的表現差異
separatorTokens/nonSeparatorTokens:改變 token 邊界,直接決定一個詞能不能被切出來。red|green|blue預設整段一個 token,加了|後green才成為獨立 token。dictionary:把某個字符串鎖成一個整詞,改變的是分詞粒度和後續排名。本機把「未來科技」加進 dictionary 後,命中條數不變(仍是 2 條),但_matchesPosition從兩個 6 字節 token 變成一個 12 字節整詞——它影響的是切分與排名,不是「從搜不到變搜到」。localizedAttributes:聲明字段語言,同樣會觸發重索引。
The Fix(解決方案)
第一步:拿到 taskUid 後輪詢任務
# PUT 返回 taskUid=5
curl -s http://localhost:7700/tasks/5 \
-H 'Authorization: Bearer aSampleMasterKey'
第二步:等 status 變成 succeeded 再搜索
TASK_UID=5
while true; do
STATUS=$(curl -s "http://localhost:7700/tasks/${TASK_UID}" \
-H 'Authorization: Bearer aSampleMasterKey' | jq -r '.status')
if [ "$STATUS" = "succeeded" ]; then
echo "settings applied"
break
elif [ "$STATUS" = "failed" ]; then
echo "settings update failed"
curl -s "http://localhost:7700/tasks/${TASK_UID}" \
-H 'Authorization: Bearer aSampleMasterKey' | jq '.error'
break
fi
sleep 1
done
第三步:核對設置確實回讀了
任務成功後 GET 一下確認寫入的值(注意回讀時順序可能被規範化,比如 ["+","#"] 回讀成 ["#","+"]):
curl -s http://localhost:7700/indexes/books_zh/settings/separator-tokens \
-H 'Authorization: Bearer aSampleMasterKey'
注意事項
- 設置更新會重索引該索引全部文檔,大索引上耗時長,別在流量高峰做。
- 多個設置可以一次
PATCH /settings提交,但仍然是異步任務,同樣要等。 - 用 SDK 時優先用其內置的
wait_for_task一類方法,別自己搶跑。 - 如果任務
failed,看error字段;比如索引側 locale 寫錯會以invalid_settings_localized_attributes失敗。
References(參考來源)
- Asynchronous operations — Meilisearch 官方文檔meilisearch-docs
- Settings — Meilisearch 官方文檔meilisearch-docs
- Tokenization — Meilisearch 官方文檔meilisearch-docs
