lucene MoreLikeThis性能分析

标签: 技术 lucene 性能分析 | 发表时间:2015-03-19 20:47 | 作者:junGle
出处:http://www.d0evi1.com

最近使用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

相应的图为:

单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 morelikethis 性能分析] 推荐:

lucene MoreLikeThis性能分析

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

lucene排序

- - 开源软件 - ITeye博客
排序是对于全文检索来言是一个必不可少的功能,在实际运用中,排序功能能在某些时候给我们带来很大的方便,比如在淘宝,京东等一些电商网站我们可能通过排序来快速找到价格最便宜的商品,或者通过排序来找到评论数最高或卖的最好的商品,再比如在Iteye里的博客栏里,每天都会以降序的方式,来显示出最新发出的几篇博客,有了排序,我们就能在某些时候很方便快速的得到某些有效信息,所以说排序功能,无处不在 ^_^.

[原]Lucene系列-facet

- - 文武天下
facet:面、切面、方面. 个人理解就是维度,在满足query的前提下,观察结果在各维度上的分布(一个维度下各子类的数目). 如jd上搜“手机”,得到4009个商品. 其中品牌、网络、价格就是商品的维度(facet),点击某个品牌或者网络,获取更细分的结果. 点击品牌小米,获得小米手机的结果,显示27个.

[原]Lucene系列-FieldCache

- - 文武天下
域缓存,加载所有文档中某个特定域的值到内存,便于随机存取该域值. 当用户需要访问各文档中某个域的值时,IndexSearcher.doc(docId)获得Document的所有域值,但访问速度比较慢,而且只能获得Stored域的值. FieldCache能获得域值数组,根据docId random access域值.

Lucene 使用教程

- - 行业应用 - ITeye博客
1 lucene简介 . 1.1 什么是lucene . Lucene是一个全文搜索框架,而不是应用产品. 因此它并不像 http://www.baidu.com/ 或者google Desktop那么拿来就能用,它只是提供了一种工具让你能实现这些产品. 1.2 lucene能做什么 . 要回答这个问题,先要了解lucene的本质.

Lucene 4.x 之 IndexReader

- - zzm
在Lucene 3.x时代,《Lucene In Action》是一本相当不错的参考书,书中详细介绍了Lucene各种高级使用技术,对于开发者来说非常实用. 但是近期Lucene升级到了4.x版本,在性能等各方面有了很大的提高,值得在新项目中使用. 然而Lucene 4.x中的API相比3.x来说有了很大的改变,《Lucene In Action》中的很多内容都已经过时了,并且由于4.x推出的时间不长,还没有比较好的文档来对用法进行说明,这个系列文章就是想记录下自己使用Lucene 4.x的经验体会,供大家参考使用.

文章: 集成Lucene和HBase

- - InfoQ cn
在所有先进的应用程序中,不管是购物站点还是社交网络乃至风景名胜站点,搜索都扮演着关键的角色. Lucene搜索程序库事实上已经成为实现搜索引擎的标准. 苹果、IBM、Attlassian(Jira)、Wolfram以及很多大家喜欢的公司【1】都使用了这种技术. 因此,大家对任何能够提升Lucene的可伸缩性和性能的实现都很感兴趣.

Solr\Lucene优劣势分析

- - 淘宝网综合业务平台团队博客
最早lucene2.4以及以前,追溯到2008年前后,lucene刚刚引起大家的关注,到后来Nutch. 、solr的出现,lucene变得更加热. Nutch、Solr的发展,极大推动了lucene的升级. 对于一些接触过搜索,使用过lucene、solr的人来说,一般都会感觉lucene、solr很牛逼.