做这个 Algolia 官方博客的中文翻译系列,到这一篇已经是第十一期了。前面十期我多半会挑"怎么实现某个功能"这类能直接落地、看完就能去项目里抄作业的主题,但第十一期我特意换了一种口味,选了一篇讲搜索排序机制的官方博客。原因很简单:最近在好几个项目里看到类似的困惑——索引数据没问题、搜索 API 也调通了,但用户总抱怨"搜得到的内容排得太靠后"。很多人第一反应是往索引里塞更多字段、加筛选条件,甚至怀疑这个搜索引擎根本不行。官方博客里那篇《搜索排序机制解析》,恰好把"为什么某些结果会排在前面"这件事讲清楚了。这篇文章会先把原文的核心思路重新梳理成中文,再补上原文没有展开、但你在真实项目里几乎一定会碰到的细节和坑。
1. 为什么第十一篇要选"排序机制"这篇博客
1.1 官方博客想解决的那种"搜索不好用"
你别看 Algolia 一直标榜自己快,其实官方工程团队在博客里反复强调一件事:快只是基础,搜索体验里真正难的是"让对的内容出现在对的位置"。如果你只是把数据同步进索引,然后原地起飞,大概率得到的是一个"什么都搜得到、但什么都不太对"的结果。
这篇官博文章核心想解决的问题,是纠正一个常见的错误认知:结果排序不是一个模糊的"综合评分"问题,而是一条透明、可解释、可调试的排序管线。很多人觉得排序是"玄学",因为搜索引擎内部可能有一个黑盒模型在打分,你只能靠猜。Algolia 的做法恰恰相反——它把排序拆成一组互相独立的判断条件,一条一条往下执行,每条只回答一个具体的问题,比如"这次拼写有没有错""查询词命中了几个""多个词在文本里离得近不近"。
这对我做翻译的人来说很有意思。因为英文原文里反复出现"deterministic"(确定性)这个词,它想表达的是:搜索结果不该依赖某个神秘的数学公式,而应该能被任何一个人逐条推演出来。你一旦理解这条管线,后面所有调优动作都会变得非常具体:不再是"提高相关性"这种空话,而是"我要调整哪一条判断条件、把它换成什么规则"。
1.2 翻译时最费功夫的一段:相关性不是一个数
原文有一句话我记得很清楚:相关性不是由某个单一分数决定的,而是由记录之间的"逐个比较"决定的。这句话翻译起来容易,但理解起来很容易走偏。我最初也习惯性地把搜索排序想象成一个打分系统:每条记录算出 86 分、92 分,然后按分数倒序排列。
官方文章里的模型完全不是这样。它更像我们相亲时候的筛选流程:先看预算是否达标,预算内再看有没有共同话题,共同话题一样再看居住地远近——每一步都是一个独立的、只有"通过/不通过"或简单大小比较的关卡,而不是把所有因素换算成分数相加。
这个设计对外行人来说有点反直觉,但对工程者来说极其合理。我用一个生活化的类比来解释:假设你在选手机,A 款便宜但内存小,B 款贵但内存大。如果用"综合评分",你可能把价格和内存都折算成百分制再加权,最后得到一个 87 比 81 的结论。但用"关卡制",规则就是:先看预算,预算内留下;再看内存,内存大的胜出。A 和 B 根本不会走到比内存这一步,因为 A 在预算关就被淘汰了。
Algolia 的排序机制正是这种"关卡制"——不匹配查询的记录在匹配阶段就直接出局了,根本不会进入后面的排序环节。剩余记录再沿着固定顺序的排序条件互相比较。这个思路带来的好处和原文里面也有几个:速度快(不需要为每条候选计算复杂分数)、可解释(任何一条结果的位置都能说清依据)、易调试(改一个条件就能观察到影响)。我现在觉得,理解这个模型比记住任何 API 参数都重要。
2. 原文拆解:Algolia 的排序管线到底怎么工作
2.1 匹配阶段与排序阶段的分工
原文把整个过程划分成两个阶段,这是全文的骨架。第一阶段叫匹配(matching):搜索引擎在索引里找出所有可能与查询词相关的记录,主要靠倒排索引、前缀匹配、拼写容错这些能力。第二阶段叫排序(ranking):对第一轮留下来的记录,按照预设的排序条件逐条比较,排出先后顺序。
很多人(包括刚开始用 Algolia 的我)容易把这两个阶段混在一起,以为匹配阶段有个"相关性打分",分数高的排在前面。实际上,匹配阶段更接近一个"漏斗":记录要么命中、要么不命中,不命中就立刻离开候选池,后面无论你再怎么调整 customRanking 之类的东西,它都不会被捞回来。
为什么要区分得这么清楚?因为这直接影响调优策略。如果你的问题是"完全搜不到某条记录",那多半在匹配阶段就出局了,这时候去折腾排序配置(Ranking)毫无意义,应该去调可搜索属性(Searchable Attributes)、词干化、同义词规则这类匹配层参数。如果问题是"能搜到,但排太靠后",那才轮到排序阶段上场。原文里用了很大篇幅强调这个先后关系,我自己翻译完之后最大的收获就是:排查问题之前,先确定问题发生在哪个阶段。
举个例子,一条商品记录可能长这样:
{ "title": "RGB 机械键盘", "description": "红轴版本,适合长时间输入,带白色背光", "category": "外设", "sales_last_30d": 1200, "stock": 46 }这里title、description、category如果是可搜索属性,那么用户搜"机械键盘"时,记录就靠title里的"机械键盘"进入候选池。如果用户搜的是"茶轴键盘",而 description 里写了"红轴版本",没有"茶轴"字样,那它可能在匹配阶段就过滤掉了——除非你配置同义词,把"红轴/青轴/茶轴"关联起来,或者放开部分匹配。
2.2 默认排序条件逐条翻译
原文花了不少篇幅解释 Algolia 默认的排序条件顺序,我在翻译的时候做了一张表,它可能比原文的纯文字描述更容易一眼看懂:
| 顺序 | 排序条件 | 它实际回答的问题 |
|---|---|---|
| 1 | typo | 拼写错误更少的结果,排在拼写错误更多的结果前面 |
| 2 | words | 命中查询词数量更多的结果,放在命中更少的结果前面 |
| 3 | proximity | 多个查询词在文本里离得更近的结果,排在离得更远的结果前面 |
| 4 | attribute | 查询词命中了优先级更高的属性时,排名更靠前 |
| 5 | exact | 完全精确匹配的结果,排在部分匹配的结果前面 |
| 6 | custom | 按业务自定义字段排序,如销量、上架时间等 |
这六条就是官方默认的排序管线,翻译成大白话就是:先看谁错得少,再看谁词更全,然后看谁词更近,接着看谁命中的字段更重要,然后看谁匹配得更精确,最后才轮到业务指标。
我第一次看到这个顺序时很惊讶:custom竟然排在最后。这意味着如果你只在索引里填了商品数据,不对排序做任何配置,那么"销量 1200 的商品"永远排在"销量只有 20 的商品"后面——前提是前面的文本相关性条件能够分出胜负。后来我理解了,这个设计其实很符合"搜索"的本质:搜索不是"卖货",而是"找东西"。用户输入一个查询词,他表达的信息需求优先级天然高于你的运营指标。销量再高,如果文本相关性差一截,默认引擎仍然认为"找得准"更重要。
当然,在实际业务里,这个默认顺序几乎不可能原样上线,这就是后面章节要讨论的。
2.3 一次真实查询的排序推演
原文后面配了一个很强的示例,我也是靠它才彻底搞懂"逐条比较"的含义。假设用户输入red keyboard,候选记录简化成两条:
- 记录 A:
title: "red mechanical keyboard",description: "red keycaps included" - 记录 B:
title: "mechanical keyboard",description: "keyboard with red backlight"
第一关 typo:两条记录的词都没有拼写错误,平手。
第二关 words:两条记录都完整命中了red和keyboard两个词,平手。
第三关 proximity:记录 A 的red和keyboard之间隔着一个mechanical,距离为一个词;记录 B 的red出现在 description 里,而keyboard出现在 title 里,同一个属性内没有直接相邻关系,距离更远。所以这一关记录 A 胜出。
第四关 attribute:如果title的搜索权重高于description,记录 B 在 title 里命中了keyboard,在 description 里命中red;记录 A 在 title 里同时命中了两个词。按属性优先级,记录 A 也占优。
第五关 exact:查询词red keyboard在记录 A 的 title 里并不是一个连续短语,中间夹了别的词;记录 B 的 description 里red backlight也不是精确短语。两条都算不上严格精确匹配。到这里其实已经不需要比了,记录 A 在第三关已经胜出。
这个推演过程告诉我们:Algolia 的排序不是先算分再比较,而是一关一关地淘汰。前面的条件一旦分出胜负,后面的条件根本不会执行。这也是为什么排序速度快、结果稳定的重要原因。
3. 原文没明说但你必须知道的:排序和业务的拉扯
3.1 默认排序顺序为什么不能直接上线
官方博客这篇文章的语气很工程化,它只解释了"默认怎么排",没有太强调"默认往往不满足业务诉求"。我自己在实际项目里的体感是:文本相关性优先的默认排序,在内容型网站还好,在电商和商业化场景里基本撑不住。
原因很简单:文本相关性不在乎库存、销量、毛利率、运营位。一个滞销了三年的老款,只要标题精准匹配了用户搜索词,照样能压过当季爆款排到第一。用户可能只是搜索"机械键盘",他没有表达任何价格偏好,但你的业务目标是卖出更多高毛利商品,两者天然打架。
解决途径是引入customRanking(自定义排序),把业务指标插到管线里。比如设定customRanking为sales_last_30d降序,那么在文本相关性难分高下的时候,销量就会成为决定性因素。注意,这个机制不是"替换掉文本相关性",而是"在所有文本相关性条件之后,再按业务指标微调"——官方术语里的定位非常准确:不伤害文本相关性前提下的业务排序。
这里给一个建议:如果要做电商,优先把sales_last_30d、stock、margin、is_promoted这类字段都索引进去,并且想清楚到底是谁参与排序。我见过有人把所有数值字段都塞进 customRanking,结果排序优先级互相冲突,反而乱套。本质上你要回答的是:当两条记录在文本上一样相关时,我们希望用什么业务规则来决定胜负?只选一两个指标,最多不要超过三个。
3.2 把销量放进排序之后的事故现场
光说理论不够,我讲一个真实翻过车的情况。某个电商项目,运营要求"销量优先",我照做,把sales_last_30d配置成了 customRanking。上线后效果确实好——搜"键盘"这种泛词的时候,爆款都排到了前面。但是很快有人报了一个诡异 Bug:搜索某品牌名加"红轴"时,排第一的不是"红轴"款,而是该品牌销量最高的普通款。
问题出在哪?我拆开查询推演了一遍:用户输入的是"某品牌 红轴",两条记录都完整命中了这两个词,typo、words 平手。proximity上,普通款在其他位置也是"某品牌 + 红轴"相邻?不,实际上我在字段设计里把品牌和型号分开存储,导致普通款的 description 里没有"红轴"两个字,匹配量少。真正让它胜出的是:它整体文本相关性和红轴款持平,但销量高出太多,在 customRanking 这一关直接逆袭。
从用户角度这非常荒谬——我明确搜了红轴,你却给我一个非红轴的爆款。问题根源是:customRanking排在整个管线最后,只在文本相关性完全打平的情况下才生效;但文本相关性"打平"的判断并没有那么精细,两个词命中、词序接近、字段权重相同,就已经平手了,它不会理解"用户意图是红轴"这个语义。想解决这种问题,光靠排序配置是做不到的。我最后的处理是用查询规则(Query Rules)把"品牌 + 红轴"这类组合词强制关联到对应商品的过滤器,同时给爆款降权,才把体验拉回来。
这个事故让我学到的一个道理:排序只是最后一公里,前面的字段设计、同义词、规则配置,其实一直在决定排序的条件是否合理。
3.3 给运营干预留一个"走后门"的口子
业务方不会满足于纯算法排序。今天想置顶某个活动商品,明天想屏蔽某条的曝光,后天想让特定搜索词下展示一个专题。如果每次都用 customRanking 来做,你会发现指标被改得乱七八糟。
官方博客里跟我实际经验最匹配的部分,是它对"确定性规则"的强调:所有排序条件最终都是确定性判断,你可以预判任何一条记录的结果。这给运营干预提供了非常优雅的落地方式——不要改全局排序,而是针对特定查询词设置例外规则。
实操时我的做法是:给运营提一套"高级配置"入口,里面是几个固定的规则模板。比如"当查询词完全等于 A 时,把记录 X 固定在第一位,并把记录 Y 降到最后一位""当查询词包含 B 时,强制追加一个筛选条件"。这些规则翻译到底层,其实就是把某条记录置顶(pinned)、给记录加负向权重、或者临时改查询条件。它共享同一条排序管线,不破坏默认排序逻辑,只是在前面的关卡里人为地插了一个"插队"节点。
这个方法比在代码里硬写逻辑要稳得多,因为它能被搜索控制台的调试工具清晰地看到,也能随时回滚,不会出现"为什么线上结果突然变了"的悬案。
4. 中文语境下的落地:我实际调优时踩过的坑
4.1 中文分词:不要直接照搬英文的字段设计
原博客是用英文场景写的,里面大量例子都基于英文空格分词。翻译成中文发布之后,我接到的读者反馈里,最集中的问题就是中文分词带来的排序异常。
英文天然有空格,单词边界清楚;中文没有空格,搜索引擎要把一段连续汉字切成有意义的词。Algolia 对中文有自己的分段处理策略,但分词结果和你预想的不一定一致。比如商品标题"机械键盘红轴静音版",你希望它被切分成"机械键盘 / 红轴 / 静音版",但实际切分可能把"键盘红"或"红轴静音"当成别的组合,导致用户搜"机械键盘"时匹配权重被稀释。
我的实践建议是:在建立索引的字段设计阶段,就让每个属性承载语义单一的片段。宁可多用几个字段,比如brand、series、model、color、switch_type,也不要全部堆在title一个字段里。字段拆得越细,attribute这个排序条件越能发挥作用——用户搜"红轴"时,它能明确知道是switch_type字段命中,而不是 title 里某个汉字组合恰好命中。
如果你已经有一堆老数据、不方便改字段结构,另一个补救措施是同义词配置。把产品库里常见的口语说法、简称、别名,统一做成同义词表。注意,同义词不是越多越好,配置太激进会导致搜索"太松",用户搜一个精确型号时,反而召回了一堆不相关的近似词。好的做法是先收集搜索日志里的高频失败词,再针对性建表。
4.2 typo tolerance 在中文场景里要特别小心
默认的 typo 容错(拼写容错)在英文里很好用:用户把keyboard打成keybord,少一个字母,编辑距离很小,依然能命中。但中文的"错误"不太一样。中文输入错误很多来自拼音输入法的同音字,比如把"青轴"打成"轻轴",把"机械"打成"机戒"。这两个词的编辑距离和英文拼写错误完全不同:汉字之间不是一个字母一个字母的差异,而是整个字被替换。
如果完全依赖默认 typo 容错,你会发现"同音字错误"经常救不回来;如果你为了救同音字把拼写容错阈值调得过大,又会引入大量不相关匹配。我的处理方式通常是:不要大幅提高 typo 容错的强度,而是把中文常见误输词维护进同义词规则。从产品效果看,这比提高编辑距离阈值更可控、更好解释。
还有一个容易被忽略的点:typo tolerance 影响的是匹配阶段,而中文的搜索词往往很短,一两个字的变化就可能让命中记录集合完全不一样。所以排查"为什么搜不到"时,先看控制台返回的 matched words,判断是不是 typo 容错把某个词"容忍"掉了,导致它没有被当作核心关键词来匹配。
4.3 用调试工具看每一条命中的"判决理由"
我见过太多人调排序靠猜,改一个配置,看整体结果"顺眼不顺眼",然后反复试。官方博客那篇文章最实用的部分,反而是它提到的调试思路:每一轮排序都有一份判决记录,你要学会把它展开来看。
在 Algolia 控制台的查询调试器里,每一条返回记录都会给出详细的匹配信息:命中了哪些词、分别在哪个属性命中、rankingInfo里记录了每个排序条件的结果。我调优时基本流程是:输入一个真实查询,看排前三的记录,逐一检查它们"赢在哪一关、输在哪一关"。如果排第一的记录是因为exact赢的,而业务上你更希望attribute权重生效,那就去调属性顺序;如果一条记录在proximity输掉了,那不是排序问题,是文本内容本身不同,你可能需要通过分词字段设计去改变它。
这个方法的好处是不靠感觉。我经常在排查时发现,真正的问题根本不在排序阶段,而是某个字段值本身脏了、有错别字、或者类型不对导致没有被匹配。调试器能帮你把这类问题从排序问题里剥离出来。
4.4 从搜索日志反推优化方案
前面的方法偏"从查询到调试器",但实际产品里优先级最高的工作应该是:用搜索日志找出最痛的那批查询,再去做针对性优化。搜索分析数据会告诉你:哪些查询返回了 0 条结果、哪些查询点击率高但转化低、哪些查询下用户反复翻页。
我在一个内容站里做过这样的优化:日志显示大量用户搜"入门教程",但列表页排序把最新文章放在最前面,旧的经典入门文章沉到底部。记录本身是命中的,但排序规则明显没匹配用户意图。后来我把分类字段和标签字段纳入排序权重调整,并且针对"入门""教程"这类词写了一条查询规则,让标签为"新手"的内容优先。最终该查询的点击率提升非常明显。
排错类优化和体验类优化要做区分:无结果查询优先修匹配,有结果但没人点优先修排序,有结果也有人点但转化差,那已经不属于搜索问题,而是内容着陆页的问题。这个优先级判断帮我省了很多时间,也推荐给所有做站内搜索的人。
5. 如果从头做一遍相关性调优,我建议按这个顺序来
5.1 把业务目标翻译成排序条件
动手改任何配置之前,先坐下来回答一个问题:用户搜完,我们希望谁赢?这不是技术问题,而是业务问题。把目标明确之后,翻译成配置才快。
| 业务目标 | 推荐手段 | 注意点 |
|---|---|---|
| 卖得好的优先 | customRanking 使用销售额字段 | 注意泛词场景下的误伤 |
| 新品优先 | customRanking 使用上架时间 | 避免新但不相关的产品霸屏 |
| 品牌词必须精准 | 精确匹配 + 提升 exact 条件权重 | 小心同品牌其他型号混入 |
| 强运营活动 | Query Rules 置顶/降权 | 规则要可回滚、可解释 |
| 内容生态丰富度 | 字段权重 + 分类过滤 | 避免头部内容垄断全部展示 |
这张表不是固定公式,但它能帮你很快把一个模糊诉求拆成具体配置。没有业务目标就去调排序,就像没有终点去跑马拉松,最后一定被需求方反复推翻。
5.2 整理一组真实查询样本,建立基线
我强烈建议在上线大改之前,先建一个"查询金样本集"。操作方法是:从搜索日志里抽出 30 到 50 条高频、有代表性的真实查询,覆盖导航型查询(搜品牌名)、交易型查询(搜商品属性)、调查型查询(搜比较类内容)三类。然后定义你期望的结果顺序,哪怕手动标注其中前三条都行。
有了这组样本和标注,每次配置改动后都跑一遍,对比理想排序和实际排序之间的差距。指标可以很简单:满意结果出现在前三位的比例、无结果率、以及主观判断的"明显错排"个数。这套方法不需要写复杂代码,用控制台就能手工完成,但效果远远好过"我觉得好像变好了"。
5.3 一次只改一个变量,并记录每一次实验
调优时最容易犯的错误是同时改多个参数。今天又改了同义词,又调了属性权重,又加了规则,结果搜索效果变好了,但你完全说不清是哪个改动在起作用;变差了也一样,难以回滚。
我的习惯是给每一轮实验写一个非常轻的记录,格式就三行:改了什么、预期效果、实测结果。比如"修改:把销量加入 customRanking;预期:泛词查询下爆款整体前置,品牌词下不受影响;实测:品牌词下出现爆款逆袭非目标款,需要加规则修正"。这个习惯对我帮助非常大,尤其是当你需要向团队成员解释一个排序问题时,这份记录基本就是完整的排查日志。
5.4 别忘了空结果和兜底体验
排序优化通常关注的是"有结果时怎么排",但用户感知最强的往往是"没有结果时怎么办"。搜索日志里的无结果查询,是性价比最高的优化对象。你可以先看这些查询是不是因为配置太严格导致漏匹配;如果确实没有内容,那就做"推荐兜底"——展示热门内容、同类商品、或者提示用户改一个更短的关键词。
官方博客里对这类兜底讨论不多,很可能是因为它默认你会用搜索分析发现这个问题。我自己每次上线搜索项目,都会设一个规则:不允许任何真实查询直接面对空白结果页。这比任何 fine-tune 排序参数都更能立竿见影地提升用户满意度。兜底方案可以是简单的推荐列表,也可以是 query suggestions(查询建议),关键是让页面有东西可看,给用户下一步动作的路径。
5.5 把'搜索调试器'当作日常工具,而不是应急工具
最后一个建议听上去最小,但实际影响最大。很多开发者只在项目新上线或者用户投诉时才打开调试器,平时完全不看。我建议把"每个搜索词都去调试器里点开排名详情"变成一个例行动作。每周花二十分钟,随机抽几个真实查询,展开前五条结果的rankingInfo,问问自己:这个结果如果我是用户,会觉得合理吗?如果觉得不合理,是哪条排序条件导致的?
这个动作让我发现过很多隐藏问题——比如某个字段因为数据导入异常全是空值,导致搜索时大量结果只靠 description 匹配;又比如两个属性权重差距太大,导致所有查询都偏向某一个属性命中的内容,整个搜索结果的多样性变差。这些问题在日志和统计里不一定显眼,但在调试器里一眼就能看到。说白了,排序机制本身是确定性的,你只要愿意去读它的判断过程,几乎所有结果都能找到合理的原因;真正需要警惕的是那些"说不出为什么排前面"的记录,它们往往就是隐藏问题的入口。翻译完这一篇,我最大的收获不是记住了那几个排序条件,而是学会了像搜索引擎一样思考——每次看到一个结果,先问"它在哪一关赢的"。
我觉得这个思路很值得推荐给你。如果你正在用 Algolia 做站内搜索,又总觉得结果"差点意思",不妨从今天开始:打开调试器,输入一条你业务里最典型(或最赚钱)的查询,把前五条结果的排序依据全部看一遍。你可能会发现,问题根本不在排序,而在字段设计、数据质量或者匹配阶段。找到真正的问题,比急着调参重要得多。