lucene MoreLikeThis性能分析

最近使用lucene的MoreLikeThis实现一个小型的推荐系统。语料由短文本构成,本身也还算比较中小等规模:7000w左右(亿级别)的数据量,3G大小的文件。对需要的Field建完索引后的索引文件大小在4G左右。本文只是结合自己的实践列出一些注意事项,以做为参考。

一、MoreLikeThis实现原理

根据输入文本和要检索的Field,进行like判断时,整个过程如下:

  1. 提取出terms:分词,判断:是否大于最大出现数,是否停留词,统计tf => 返回<word,int>频率表
  2. 根据tf-idf分值对term进行从大到小排序: 遍历所有fields,找到最大文档频率df的值和对应field,判断一些条件,计算tf/idf值,插入到优先级队列 => FreqQ<word,topField,score,idf,docFreq,tf>
  3. 生成Query:使用TermQuery,设置相应boost值和Occuer.SHOULD生成BooleanQuery查询.
  4. 进行search.

这里说一下第3步的boost生成策略:

首先优先级队列中score(即tf/idf值)分值最高的,为bestScore. myScore为队列中的当前score值,按照以下的公式线性衰减,myScore越小,衰减的越厉害:

boost_value = boostFactor * myScore / bestScore

得到boost后,我们就可以根据原生lucene的打分公式在search后进行相关性排序:

(q为对应的查询条件,d为对应文档)

MoreLikeThis使用的是DefaultSmilarity. (对应coord, queryNorm等等,由它与BooleanQuery来决定)

二、性能分析

lucene的首次查询可忽略。简单地写个循环,文件中读取10000条不同的测试数据,存在一个map数组中。分别对其进行检索。

对相应比较的接口进行比较:

1.使用普通的接口MultiFieldQueryParse.

2.使用MoreLikeThis接口(不使用DuplicateFilter)

3.使用MoreLikeThis接口(使用DuplicateFilter)

方式3耗时相当严重(秒级别),如果真的需要去重,建议使用hashmap去重好了。这里的接口比较中,只比较方式1和方式2.

JAVA_OPTS为:-Xmx8096m -Xms8096m -server

(8g的jvm内存空间已经足够,经测试,开得更大不会在性能性获得提升。初始化时一次性分配好。)

使用mmap加载索引,使用的环境及版本:

lucene4.7.2 + jdk1.6.0-64+4core+32Gmem+linux修改版

这里使用了3个文档数,里面含有短文本,做为搜索输入,规模分别为:100条,1000条,1w条。此处的先对比下MostLike接口,相关参数如下:

  • topN: 20
  • MinTermFreq: 0
  • MinDocFreq: 0
  • MinWordLen: 1
  • MaxQueryTerm: [1-4]

此处对最关键的MaxQueryTerm做了一个简单的性能测试,不同值下一个横向对比:

文档数 MoreLike
(1,20)
平均时间 MoreLike
(2,20)
平均时间 MoreLike
(3,20)
平均时间 MoreLike
(4,20)
平均时间
100 728 7ms 1214 12ms 2398 24ms 3083 31ms
1000 3114 3ms 9651 10ms 36116 36ms 62432 62ms
10000 17558 1.7ms 80584 8ms 305365 31ms 565437 57ms

MaxQueryTerm的长短会直接影响你对结果判断的好坏。因此,需要对该值做一个权衡。在不影响结果效果的前提下,当然追求速度越快越好,这会直接影响你线上的请求处理并发量。

为了再进行一次对比,这里列出了 MultiFieldParser的性能测试,包含了些简单逻辑处理单个字的词,停留词等,这里使用第一个为MUST,其余都为SHOULD(你也可以设计一定的规则,去自动设定搜索词的occur),其它参数:

topN: 20

文档数 common(20) 平均处理时间
100 292 3ms
1000 989 1ms
10000 3602 0.3ms

性能方面是远远好与MoreLikeThis。但是效果方面肯定会打一些折扣。

切换到新的版本与环境:lucene-4.9.1 + jdk7,

文档数 MostLike
(1,20)
平均时间 MostLike
(2,20)
平均时间 MostLike
(3,20)
平均时间 MostLike
(4,20)
平均时间
100 755 7ms 1265 12ms 2459 24ms 3152 31ms
1000 3245 3ms 10135 10ms 36463 36ms 62607 62ms
10000 18289 1.8ms 82256 8ms 305795 31ms 564701 56ms

性能没有本质的提升。

接着再说一下多线程版本,还是jdk6的环境。性能测试表如下(此处算法有做了调整,只做横向对比,不与上面对比),分别处理100/1000/10000条的消息时的性能表现:

序号 线程数 100语料
(ms)
100处理量(ms) 1000语料
(ms)
1000处理量(ms) 10000语料
(ms)
10000处理量(ms)
1 1 4821 48 94900 94 782241 78
2 2 3237 32 54074 54 447454 45
3 4 2361 23 39928 40 250838 25
4 8 2310 23 37501 37 247236 25
5 16 2405 24 30930 31 261238 26
6 32 2264 23 30731 31 269937 27

相应的图为:

lucene MoreLikeThis性能分析_第1张图片

单cpu-4核的机器,4个线程的时候差不多是性能最好的时候。

长耗时查询

最后一个问题是长耗时查询,产生的原因也很简单。虽然MoreLikeThis会根据tf/idf取出前n个词来进行搜索,但是如果本身搜索的词就小于 n个,并且这些词还长着一张“大众脸”的话。。。duang,你一搜出来的totalHits就可能是上千万的结果集,然后根据分值做一下排序,那也会耗去不少时间。直观的感觉就是限制搜索集的数目,当满足一定量时就不再继续搜索下去。

方式一:

上面的条件中,有一个MinWordLen,如果为1的话,那么类似于单个字的倒排索引,它的文档列表会相当长(很可能几百万几千万)。将它设置为2的话,不算是查询效果以及查询性能上都有较大的提升。

4线程,MinWordLen=2的示例:

1000: 总耗时:16351ms,   平均:16ms

10000:  总耗时:392398ms, 平均:39ms

当你去跑上面的文档时,虽然降了不少,效果也提升不少。但是还是有一部分还是有些耗时(理论上问题仍存在,只是出现的机率下降了不少)。下面引出方式二。

方式二:

MoreLikeThis中还存在着一个参数,maxDocFreq。缺省值为32位的1. 我们可以设置成50w。该参数可以忽略过出现超过50w的查询。这个时候,你的查询性能将大大获得提升。完全可以满足实时查询的要求。

方式三:

由源码级别可知:TopScoreDocCollector的collect(). BooleanScorer2的score. 这两者,有能力的话自行修改代码. (通过继承等手段实现略难),就不推荐使用了。

排序公式部分:来自《annotated lucene》.

你可能感兴趣的:(技术,Lucene,性能分析)