使用 Hibernate Search 的一个优点是您的宝贵业务数据存储在数据库中:一个可靠的事务性和关系型存储。因此,虽然 Hibernate Search 在常规使用期间会保持索引与数据库同步,但在某些情况下,您可能需要能够从头开始重建索引
- 在引入 Hibernate Search 之前就已经存在数据
- 开发了新功能,索引映射已更改
- 已恢复数据库备份
- 数据库上应用了批量更改
- ...你明白了,这个列表可能非常长
演变,用户驱动
在 Hibernate Search 的先前版本中始终存在执行此操作的 API,但在论坛上关于如何使它更快的问题并不罕见。请注意,重建整个索引基本上意味着您必须从数据库将所有索引过的实体加载到 Java 世界,以便将数据馈送到 Lucene 进行索引。我自己也是用户,并且新的 MassIndexer API 代码是在对几个应用程序的实践经验以及社区的大量参与之后调整的。
快速入门:MassIndexer API
从版本 3.0 开始,文档提供了一种推荐的重新索引例程;此方法仍然可用,但在版本 3.2 中添加了提供更好性能的新 API。无需进行配置更改,只需启动即可
FullTextSession fullTextSession = ...
MassIndexer massIndexer = fullTextSession.createIndexer();
massIndexer.startAndWait();
上面的代码将在所有实体重新索引之前阻塞。如果您不需要等待,请使用异步方法
fullTextSession.createIndexer().start();
选择要重建索引的实体
您不需要为所有索引实体重建索引;假设您只想重新索引DVD。
fullTextSession.createIndexer( Dvd.class ).startAndWait();
这将包括DVD的所有子类,因为Hibernate Search的所有API都是多态的。
索引优化和清理
在Lucene的世界里,更新是通过先删除后添加实现的,在将所有实体添加到索引之前,我们需要从索引中删除它们。这个操作在Hibernate Search中被称为purgeAll。默认情况下,索引在启动时会被清理和优化,并在结束时再次优化;您可以选择不同的策略,但请注意,如果您禁用清理操作,以后可能会发现重复项。清理后的优化是为了节省磁盘空间,如《Hibernate Search in Action》中建议的那样。
fullTextSession.createIndexer()
.purgeAllOnStart( true ) // true by default, highly recommended
.optimizeAfterPurge( true ) // true is default, saves some disk space
.optimizeOnFinish( true ) // true by default
.start();
更快,更快!
MassIndexer对调整非常敏感;一些设置可以在适当调整时使其快几个数量级,而好的值取决于您的特定映射、环境、数据库,甚至您的内容。要找出您需要调整哪些设置,您应该了解一些实现细节。
MassIndexer使用一个包含不同专业线程的管道,因此大多数处理都是并发进行的。以下解释了单个实体类型的处理过程,但实际上当您有多个索引类型时,这是为每个不同实体并行执行的。
- 一个名为identifier-loader的单个线程会遍历该类型的键。这个阶段的线程数始终为1,以便事务可以定义要考虑的键集。因此,第一个提示是使用简单的键,避免使用复杂类型,因为键的加载总是串行的。
- 加载的键被推送到一个id队列中;每个要索引的根类型都有一个这样的队列。
- 在第二阶段,一个名为entity-loader的线程池使用提供的键加载数据批量。您可以调整同时执行此任务的工作线程数(threadsToLoadObjects(int))和每次迭代中获取的实体数(batchSizeToLoadObjects(int))。虽然空闲线程可能被认为是浪费,但这只是小问题,有少量空闲线程做些无用的工作比相反的情况更好。确保您不要通过请求太多数据而给数据库带来压力:设置太大的批量大小或过多的线程也会造成伤害,您将不得不找到最佳平衡点。队列将作为缓冲区来减轻由于不同数据导致的性能波动的影响,因此找到最佳平衡点不是寻找精确值的过程,而是找到合理值的过程。
- entity队列包含需要转换为Lucene文档的实体流,它由entity-loader线程提供,并由document-creator线程消费。
- document-creator线程将实体转换为Lucene文档(应用您的搜索映射和自定义桥接器,将数据转换为文本)。重要的是要理解,在转换过程中,仍然可能会从数据库中加载一些懒加载对象(如图中的步骤7)。因此,这一步可能既便宜也可能昂贵:根据您的领域模型以及您如何映射它,可能会有更多的数据库往返操作或根本不会有。二级缓存交互可能会在这个阶段有所帮助或造成伤害。
- document队列应该是要添加到索引中的文档的恒定流。如果这个队列大部分是空的,这意味着您在生成数据方面的速度比Lucene分析并写入索引的速度慢。如果这个队列大部分是满的,这意味着您在生成文档方面的速度比Lucene将其写入索引的速度快。始终考虑Lucene在写入操作期间会分析文本,所以如果这很慢,这不一定与I/O限制有关,但您可能有昂贵的分析。要找出原因,您需要使用分析器。
- 文档索引线程数量也可配置,因此在进行昂贵分析时,可以有更多的CPU参与处理。
队列是阻塞和有界的,因此为任何阶段设置过多的生产者线程都没有危险:如果队列满了,生产者将会暂停,直到有空间可用。所有线程池都有名称分配,因此如果您连接到分析器或调试器,可以迅速识别不同的阶段。
数据加载调整API
以下设置在3分钟内重建了我的个人参考数据库,而我启用这些设置之前是6小时,或者查看任何Hibernate或Lucene调整之前是2个月。
fullTextSession.createIndexer()
.batchSizeToLoadObjects( 30 )
.threadsForSubsequentFetching( 8 )
.threadsToLoadObjects( 4 )
.threadsForIndexWriter( 3 )
.cacheMode(CacheMode.NORMAL) // defaults to CacheMode.IGNORE
.startAndWait();
缓存
当某些信息来自具有低基数(高缓存命中率)的实体时嵌入到索引中,例如当存在许多一到一关系到性别或国家代码时,启用缓存可能是有意义的,默认情况下缓存是被忽略的。在大多数情况下忽略缓存将产生最佳性能,尤其是如果您使用分布式缓存,这将引入不必要的网络事件。
离线作业
虽然Hibernate Search所做的所有更改通常都绑定到一个事务中,但MassIndexer使用了多个事务,如果在运行时更改数据,则一致性无法保证。索引将只包含在作业开始时存在于数据库中的实体,并且在此时间段内通过其他方式对索引所做的任何更新可能会丢失。虽然数据库中的数据不会出错,但如果在作业忙时进行更改,索引可能不一致。
性能清单
在并行化索引代码之后,还有一些其他瓶颈需要避免
- 检查您的数据库行为,几乎所有的数据库都提供了在大量索引时运行的配置文件工具,可以提供有价值的信息
- 使用连接池并正确设置其大小:当Hibernate Search线程需要竞争数据库连接时,拥有更多的线程并不会有所帮助
- 如果对Hibernate Search不需要的属性设置了EAGER加载,将会加载它们,避免这种情况
- 检查网络延迟
- 调整设置的影响不仅取决于静态信息,如模式、映射选项和Lucene设置,还取决于数据内容:不要假设五分钟的测试将突出您正常的行为,并从真实世界的场景中收集指标。队列将作为各个阶段非恒定性能的缓冲。
- 不要忘记调整IndexWriter。参见参考文档:这个领域没有变化。
- 如果索引CPU使用率不是100%,数据库没有达到其限制,您就知道可以改进设置
进度监控
正在开发一个API来连接您自己的监控实现;当前beta1使用日志记录器定期打印状态,因此您可以通过启用或禁用日志记录器来控制它。让我们知道您想要哪种类型的监控,或者贡献一个!