Lucene 4.x性能问题

时间:2013-09-18 20:47:08

标签: java performance lucene

在过去的几周里,我一直在努力将应用程序从Lucene 3.x升级到Lucene 4.x,以期提高性能。不幸的是,经过完整的迁移过程并且在网上和文档中发现了各种各样的调整后,Lucene 4的运行速度明显慢于Lucene 3(~50%)。在这一点上,我几乎没有想法,并且想知道是否有其他人对如何加快速度有任何建议。我甚至不再寻求超过3.x的大改进了;我很乐意与之匹敌并保留当前版本的Lucene。

<编辑>

为了确认标准迁移更改没有对性能产生负面影响,我将Lucene 4.x版本移植回Lucene 3.6.2并保留了较新的API而不是使用自定义ParallelMultiSearcher和其他已弃用的方法/类。

3.6.2中的表现甚至比以前更快:

  • 旧应用程序(Lucene 3.6.0) - 约5700个请求/分钟
  • 使用新API和一些次要优化(Lucene 4.4.0)更新了应用程序 - 约2900个请求/分钟
  • 移植了新版本的应用程序,但保留了优化和更新的IndexSearcher / etc API(Lucene 3.6.2) - 〜6200个请求/分钟

由于新版Lucene API的优化和使用实际上提高了3.6.2的性能,因此除了Lucene之外,任何问题都没有意义。我只是不知道我还能在我的程序中修改什么来修复它。

< /编辑>

申请信息

  • 我们有一个分为20个分片的索引 - 这在Lucene 3.x和Lucene 4.x中提供了最佳性能

  • 该索引目前包含约1.5亿个文档,所有这些文档都相当简单并且标准化程度很高,因此存在大量重复的标记。只存储一个字段(一个ID) - 其他字段不可检索。

  • 我们有一组固定的相对简单的查询,这些查询填充了用户输入并执行 - 它们由多个BooleanQueries,TermQueries和TermRangeQueries组成。其中一些是嵌套的,但现在只有一个级别。

  • 我们没有对结果做任何进展 - 我们只是获取分数和存储的ID字段

  • 我们正在使用指向tmpfs中索引文件的MMapDirectories。我们使用了useUnmap" hack"因为我们不经常打开新的目录,并从那个

  • 得到了很好的推动
  • 我们为所有查询使用单个IndexSearcher

  • 我们的测试机器有94GB的RAM和64个逻辑核心

一般处理

1)套接字侦听器收到的请求

2)最多生成4个Query对象,并使用规范化的用户输入填充(查询的所有必需输入必须存在或者不能执行)

3)使用Fork / Join框架并行执行查询

  • 使用IndexSearcher w / ExecutorService并行执行每个分片的子查询

4)聚合和其他简单的后处理

其他相关信息

  • 为4.x系统重新创建了索引,但数据是相同的。我们尝试了普通的Lucene42编解码器以及没有使用压缩的扩展编解码器(根据网络上的建议)

  • 在3.x中,我们使用了ParallelMultisearcher的修改版本,在4.x中我们使用带有ExecutorService的IndexSearcher并将所有读者组合在一个MultiReader中

  • 在3.x中,我们使用ThreadPoolExecutor而不是Fork / Join(在我的测试中表现更好的Fork / Join)

4.x热点

方法|自我时间(%)|自我时间(毫秒)|自身时间(以毫秒为单位的CPU)

java.util.concurrent.CountDownLatch.await()| 11.29%| 140887.219 | 0.0< - 这只是来自tcp线程等待实际工作完成 - 你可以忽略它 org.apache.lucene.codecs.lucene41.Lucene41PostingsReader $ BlockDocsEnum。< init>()| 9.74%| 21594.03 | 121594个
org.apache.lucene.codecs.BlockTreeTerReader $ FieldReader $ SegmentTermsEnum $ Frame。< init>()| 9.59%| 119680.956 | 119680个
org.apache.lucene.codecs.lucene41.ForUtil.readBlock()| 6.91%| 86208.621 | 86208个
org.apache.lucene.search.DisjunctionScorer.heapAdjust()| 6.68%| 83332.525 | 83332个
java.util.concurrent.ExecutorCompletionService.take()| 5.29%| 66081.499 | 6153
org.apache.lucene.search.DisjunctionSucorer.afterNext()| 4.93%| 61560.872 | 61560个
org.apache.lucene.search.Tercorer.advance()| 4.53%| 56530.752 | 56530个
java.nio.DirectByteBuffer.get()| 3.96%| 49470.349 | 49470个
org.apache.lucene.codecs.BlockTreeTerReader $ FieldReader $ SegmentTerEnum。< init>()| 2.97%| 37051.644 | 37051个
org.apache.lucene.codecs.BlockTreeTerReader $ FieldReader $ SegmentTerEnum.getFrame()| 2.77%| 34576.54 | 34576个
org.apache.lucene.codecs.MultiLevelSkipListReader.skipTo()| 2.47%| 30767.711 | 30767个
org.apache.lucene.codecs.lucene41.Lucene41PostingsReader.newTertate()| 2.23%| 27782.522 | 27782个
java.net.ServerSocket.accept()| 2.19%| 27380.696 | 0.0
org.apache.lucene.search.DisjunctionSucorer.advance()| 1.82%| 22775.325 | 22775个
org.apache.lucene.search.HitQueue.getSentinelObject()| 1.59%| 19869.871 | 19869个
org.apache.lucene.store.ByteBufferIndexInput.buildSlice()| 1.43%| 17861.148 | 17861个
org.apache.lucene.codecs.BlockTreeTerReader $ FieldReader $ SegmentTerEnum.getArc()| 1.35%| 16813.927 | 16813个
org.apache.lucene.search.DisjunctionSucorer.countMatches()| 1.25%| 15603.283 | 15603个
org.apache.lucene.codecs.lucene41.Lucene41PostingsReader $ BlockDocsEnum.refillDocs()| 1.12%| 13929.646 | 13929个
java.util.concurrent.locks.ReentrantLock.lock()| 1.05%| 13145.631 | 8618
org.apache.lucene.util.PriorityQueue.downHeap()| 1.00%| 12513.406 | 12513个
java.util.TreeMap.get()| 0.89%| 11070.192 | 11070个
org.apache.lucene.codecs.lucene41.Lucene41PostingsReader.docs()| 0.80%| 10026.117 | 10026个
org.apache.lucene.codecs.BlockTreeTerReader $ FieldReader $ SegmentTerEnum $ Frame.decodeMetaData()| 0.62%| 7746.05 | 7746
org.apache.lucene.codecs.BlockTreeTerReader $ FieldReader.iterator()| 0.60%| 7482.395 | 7482
org.apache.lucene.codecs.BlockTreeTerReader $ FieldReader $ SegmentTerEnum.seekExact()| 0.55%| 6863.069 | 6863
org.apache.lucene.store.DataInput.clone()| 0.54%| 6721.357 | 6721
java.nio.DirectByteBufferR.duplicate()| 0.48%| 5930.226 | 5930
org.apache.lucene.util.fst.ByteSequenceOutputs.read()| 0.46%| 5708.354 | 5708
org.apache.lucene.util.fst.FST.findTargetArc()| 0.45%| 5601.63 | 5601
org.apache.lucene.codecs.lucene41.Lucene41PostingsReader.readTermsBlock()| 0.45%| 5567.914 | 5567
org.apache.lucene.store.ByteBufferIndexInput.toString()| 0.39%| 4889.302 | 4889
org.apache.lucene.codecs.lucene41.Lucene41SkipReader。< init>()| 0.33%| 4147.285 | 4147
org.apache.lucene.search.TermQuery $ TermWeight.scorer()| 0.32%| 4045.912 | 4045
org.apache.lucene.codecs.MultiLevelSkipListReader。< init>()| 0.31%| 3890.399 | 3890
org.apache.lucene.codecs.BlockTreeTermsReader $ FieldReader $ SegmentTermsEnum $ Frame.loadBlock()| 0.31%| 3886.194 | 3886


如果您可以使用其他任何可能有用的信息,请告诉我们。

1 个答案:

答案 0 :(得分:2)

对于任何关心或尝试做类似事情的人(在查询中控制并行性),我遇到的问题是IndexSearcher每个分片每个段创建一个任务而不是每个分片的任务 - 我误读了javadoc。

我通过在我的分片上使用forceMerge(1)来解决问题,以限制额外线程的数量。在我的用例中,这不是什么大问题,因为我目前没有使用NRT搜索,但它仍然增加了更新+从属同步过程的不必要的复杂性,所以我正在研究避免forceMerge的方法。

作为一个快速解决方案,我可能只是扩展IndexSearcher并让它为每个读取器生成一个线程而不是每个段的线程,但是在Lucene邮件列表中提出了“虚拟段”的想法。那将是一个更好的长期解决方案。

如果您想查看更多信息,可以在此处关注lucene邮件列表主题: http://www.mail-archive.com/java-user@lucene.apache.org/msg42961.html