在过去的几周里,我一直致力于将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中我们将IndexSearcher与ExecutorService一起使用,并将所有读者组合在一个MultiReader中
>在3.x中,我们使用ThreadPoolExecutor而不是Fork / Join(在我的测试中表现更好的Fork / Join)
4.x热点
方法|自我时间(%)|自我时间(毫秒)|自我时间(以ms为单位的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
如果您可以使用任何其他可能有帮助的信息,请告诉我们.
最佳答案 对于任何关心或试图做类似事情的人(在查询中控制并行性),我遇到的问题是IndexSearcher每个分片创建一个任务,而不是每个分片的任务 – 我误读了javadoc.
我通过在我的分片上使用forceMerge(1)来解决问题,以限制额外线程的数量.在我的用例中,这不是什么大问题,因为我目前没有使用NRT搜索,但它仍然增加了更新从属同步过程的不必要的复杂性,所以我正在研究避免使用forceMerge的方法.
作为一个快速修复,我可能只是扩展IndexSearcher并让它为每个读取器生成一个线程而不是每个段的线程,但是在Lucene邮件列表中提出了“虚拟段”的想法.这将是一个更好的长期解决方案.
如果您想查看更多信息,可以在此处关注lucene邮件列表主题:
http://www.mail-archive.com/java-user@lucene.apache.org/msg42961.html