搜索引擎Lucene(7):索引性能优化
2019-09-21 本文已影响0人
桥头放牛娃
1、性能调优
1.1、索引-搜索时延调优
索引一搜索时延是指从添加、更新或删除索引中的文档开始,直到用户在搜索中看见上述操作结果之间的时间跨度。对于很多应用程序来说,该性能指标是很重要的。但由于 Reader 通常只在被打开时的“特定时间”来展现索引,所以减少索引-搜索时延的唯一方法就是在程序中多次重新开启 Reader 。
所幸的是, Luccne中的近实时搜索功能会将这个转换时间维持在最小值,在实际使用中通常为几十毫秒。在使用Indexwriter 完成索引修改后,你可以通过调用Indexwriter . getReader ( )方法或者调用之前那个IndexReader 的reopen ( )方法来重新打开一个 Reader 。但若这个操作若太频繁则会降低索引吞吐量,因为每次操作时,Indexwriter 都会将缓存中的数据写人磁盘。下面是一些关于减少转换时间的建议:
- 通过使用IndexWriter.setMergedSegmentWarmer使IndexWriter在新合并的段被搜索前激活它。在激活过程中,新的近实时Reader会保持打开状态并使用合并前的段。这在大段合并中尤其重要,它会减少后续针对近实时Reader的新搜索的时延。
- 尝试将IndexWriter类转换成BalanceMergePolicy类,后者可以在各个捐赠模块中获取。该MergePolicy类被设计用来减少段合并操作,因为该操作会消耗大量CPU和I/O资源,从而对搜索性能带来不利的影响。
- 可能的话,将IndexWriter的MaxBufferDocs参数设小点。这样即使不重新打开近实时Reader,系统也会刷新小的段。尽管这会减少净索引率,但它实际上会重新开启Reader的周七保持为最小值。
- 如果只向索引添加文档,那一定要使用addDocument方法而不是updateDocument方法,因为使用updateDocument方法会增加系统开销,即使指定某个项也不会删除任何文档:IndexWriter在创建新的近实时Reader时必须搜索每个被删除的项。
1.2、索引操作吞吐量调优
索引吞吐量主要用来衡量每秒钟编入索引的文档数量,该性能值取决于建立和更新索引的操作时耗。提高查询索引吞吐量的方案:
- 使用多线程。使用IndexWriter内置的多线程;
- 设置触发IndexWriter刷新的事件,将其设置为通过内存使用量来刷新,而不是靠文档计数来刷新。
- 关掉复制文件格式选项(IndexWriter.setUseCompoundFile(false))。在索引期间若使用复杂文件格式会消耗一些处理事件。采用该方案程序会在搜索期间有一个小的性能提升。但这个方案需要多得多的文件描述符用于reader打开他们,因此你可以减小mergeFactor值以避免达到文件描述符数量上限。关闭复杂文件格式选项可以同设置compound=false实现。
- 对Document示例和Field实例进行重用。如果你要处理的文档时高度规则的,那么可以创建单个Document实例,并用它来容纳Field实例。这里只需要根据情况来修改Field值,然后调用同一Documents实例的addDocument方法即可。
- 针对不同的mergeFactor值进行测试。更高的值意味着索引期间更低的段合并开销,但同时也意味着更慢的搜索速度,因为此时的索引通常会包含更多的段。如果该值设置的过高,并同时关闭了compound选项,则程序可能会达到操作系统的文件描述符上限。段合并操作是在索引期间后台程序自动完成的,较高的mergeFactor值能获得更高的索引性能。但若在最后进行索引优化,那么较低的mergeFactor值会带来更快的优化速度,因为在索引操作期间程序会利用并发机制完成段合并操作。故建议对程序分别进行高低多种值的测试,利用计算机的实际性能来告诉你最优值。
- 谨慎使用optimize()方法,最好是使用optimize(maxNumSegments)方法来代替。改方法会将索引所包含的最大段数据优化至maxNumSegments值,这可以大量减少索引优化开销,同时也能较大地提高搜索速度。
- 将文档索引到各个分离的索引中,或者有可能是位于不同计算机上的各个分离索引中,然后调用IndexWriter.addIndexesNoOptimize方法来合并这些索引。不要使用较早的addIndexs方法,它经常额外且不必要地调用Optimize()方法。
- 对文档创建速度和文档语汇单元化速度进行测试,方法是在你的算法中使用ReadTokens任务。该任务会逐个访问文档中每个域,并使用指定的分析器对它们进行语汇单元化操作。
1.3、搜索相关调优
提升性能的方法及步骤:
- 通过调用IndexReader.open(dir)或者IndexReader.open(dir,true)方法以只读的方式使用IndexReader。以只读方式使用IndexReader能得到更好的并发运行效果,因为这种调用方式可以避免一些针对内部数据结构的同步处理。当打开IndexReader时,系统默认的处理方式是只读的。
- 如果应用程序未运行在Windows平台上,那么请使用NioFsDirectory,这能比FsDirectory带来更好的并发运行效果。如果应用程序运行在64位JVM上,可以使用MMapDirectory。
- 减少用户和Lucene之间的每步操作中不必要的时延。例如:要确保供多个线程共同访问的搜索请求队列是先进先出的,以使搜索结果按照搜索请求的排队顺序返回。同时还有确保Lucene返回搜索的结果速度要快。
- 确保时延足够多的线程来充分利用计算机的硬件资源。在吞吐量持续增加的情况下要继续增加线程数,直到吞吐量不再增加位置,但也不能增加太多线程以至于搜索时延越来越恶化。这里我们要找到该临界点。
- 在实际运行程序前对它们进行激活。因为首次使用某个排序时,必须将相关的域写入FieldCache对象中。预处理可以通过为可能参与排序的域进行一次搜索处理来实现。
- 如果使用RAM.FieldCache将所有被保存的域写入RAM的话,推荐使用FieldCache而不是直接使用被保存的域,因为后者必须针对每个文档而被写入磁盘。将域写入FieldCache对象的过程比较消耗系统资源,但该操作只在首次访问时针对每个域进行一次,一旦将域写入FieldCache对象,后续对它的访问就非常快,只有操作系统不针对JVM的内存进行换页操作。
- 减少对mergeFactor的使用,以使索引中保存较少的段。
- 关闭使用索引的复合文件格式。
- 限制使用向量,业务检索他们的过程会很慢。如果必须使用向量的话,只需在搜索中使用它们。可以使用TermVectorMapper类仔细挑选那些需要使用向量的搜索。
- 如果必须加装被存储的域,可以使用FieldSector类将域限制在需要加载的范围内,对较大的域来说,可以使用延迟加载方式,这样就只在需要的时候才能加载域的内容。
- 针对索引周期性运行optimize或者optimize(maxNumSegments)方法进行优化。
- 搜索结果条数要根据需要来设定。
- 只在必要情况下重新打开IndexReader类。
- 调用query.rewrite().toString()方法打印搜索结果。这是lucene实际运行的搜索方式。
- 如果程序要使用FuzzyQuery类,请将最小前缀长度设置成大于0.这样才能在实际运行时增加minimumSimilarity值。