4

在过去的几周里,我一直致力于将应用程序从 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 requests/min

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

</编辑>

应用信息

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

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

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

  • 我们没有对结果进行任何高级操作 - 我们只是获取分数和存储的 ID 字段

  • 我们使用 MMapDirectories 指向 tmpfs 中的索引文件。我们使用了 useUnmap “hack”,因为我们不经常打开新目录并从中获得了不错的提升

  • 我们对所有查询使用单个 IndexSearcher

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

一般处理

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

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

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 热点

方法 | 自拍时间 (%) | 自拍时间(毫秒)| 自用时间(以毫秒为单位的 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
章 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
章 1.00% | 12513.406 | 第12513
章 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
章 0.60% | 7482.395 | 第7482
章 0.55% | 6863.069 | 第6863
章 0.54% | 6721.357 | 第6721
章 0.48% | 5930.226 | 第5930
章 0.46% | 5708.354 | 第5708
章 0.45% | 5601.63 | 5601
org.apache.lucene.codecs.lucene41.Lucene41PostingsReader.readTermsBlock() | 0.45% | 5567.914 | 第5567
章 0.39% | 4889.302 | 第4889
章 0.33% | 4147.285 | 第4147
章 0.32% | 4045.912 | 4045
org.apache.lucene.codecs.MultiLevelSkipListReader.<init>() | 0.31% | 3890.399 | 第3890
章 0.31% | 3886.194 | 3886


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

4

1 回答 1

2

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

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

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

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

于 2013-10-01T21:51:42.910 回答