背景
晚上,有同事咨询solr排序框架的事情。谈到混合排序的实现。依然不放过我昨天的建议:solr从1.*到4.*在排序框架上已经非常非常具有适应性、定制性、稳定性、先进性。没有必要整一个新的名词或者名称出来。不过,可以扩展solr的排序易用性和优化排序性能。
陷阱
讨论的对象: 因子A*文本得分 因子B*函数得分=文档得分。
目标:通过函数得分和文本得分的比例来调和最终命中的结果。solr直接提供的_val_ 是得分相加,并且没有显示“因子”的概念。
其实这里有陷阱。首先solr文本得分的归一化,和函数得分的归一化不一样,并且文本是语言方面的关联信息,而函数通常是业务方面的关联信息。实际测试发现,函数得分往往屏蔽了文本得分的影响。导致结果呈现另外一个极端,业务效果明显,而文本效果很糟糕。其实一个语言的就够复杂了,又融和一个业务的,并且要他们组合起来,把两种不同业务的东西通过单纯的数字组合起来。这不是拍脑袋的事情了。
其次,函数得分计算过程,往往与命中的文档的域值相关,这就涉及IO了。从而增加了排序阶段的IO、CPU开销,当然cache能缓解部分压力,但是,依然存在cache击穿的风险。也就意味着,很容易在命中大的结果集时候,注定要发生超时。超时的排序,等于失败的排序,这样的排序没法上线。
第三,得分计算算法复杂度是线性的,但是,实际中,时间开销与命中文档规模、函数涉及的域多少是紧密相关的。假如命中10w,那么就多出了10w的函数得分计算开销。这就极大的影响了查询性能。并且,可能拖垮整个系统,导致整体响应时间变慢。进一步深入细说下,lucene索引结构和文本得分。
在lucene搜索里面,文档得分是一档一分,也就是 document just。而不是全局得分corpus just。也就是说知道了doc 就知道了得分,而无需在命中结果集之后,再根据结果集的信息来计算得分。这样就可以做到得分计算,基于当前文档的“自封闭性”。而lucene索引结构,针对文本得分做了优化处理,tf itf 值在获取doc id的时候就已经知晓,无需额外的IO。这就是为何doc 和freq 联合编码的原因所在,获取了docid 同时获取了freq。而docfreq 通过term直接获取了的。
解法
那么这个文本与函数的融合,该如何处理?
回到问题的上一层,文本和函数的融合是出于什么,或者说需求是什么。
实践业务沟通中,原来需求是这样的:在文本搜索的时候,发现类似的文本太多了,我希望业务价值好的文档在文本得分接近的时候,尽可能的靠前点。这样既保持了文本的良好视觉、语义效果,又把业务期望凸显的文档展现了。两种方法可以解决:
方法1:offline计算好得分,这样online开销降低,排序的时候sortby(score,desc,func desc);
方法2:两阶段排序 在文本相关性收集结果后,对topN执行函数排序。这样,可以实现文本相关度的筛选后,比较好的文档进一步参入函数排序。确保了结果近似最优,同时降低了函数排序部分IO开销,使得需求和实现的性能得到平衡。