如何提高Lucene的搜索速度

  • 确保你使用的是最新版本的Lucene。

  • 使用本地文件系统。
    远程文件系统搜索的速度总是会慢一些。如果你的索引必须放在远程文件系统之上,那么可以考虑把远程文件系统用“只读方式”加载(mount)。某些时候这会改善性能。 

  • 使用更快的硬件,尤其是更快的IO系统。
    固态硬盘(solid-state disk,SSD)非常适合Lucene的搜索。SSD的寻道时间大概是传统磁盘式硬盘的100倍,也就是说常见的寻道时间造成的开销几乎可以忽略掉了。也就是说装有SSD的机器无需太多的内存作为文件缓存,搜索器在允许反应之前需要的预热(warm-up)时间更短。 

  • 调节操作系统。
    Linux可以调节的一个典型的参数是swappiness(参看http://kerneltrap.org/node/3000,译注:可理解为交换系统,调节物理内存和交换分区使用倾向的),这个参数控制了操作系统把内存交换出到IO缓存的倾向性。这个参数在大多数Linux发行版中的默认设置都是一个很大的数(也就是说,倾向于交换分区),这容易造成严重的搜索延迟,特别是在查询频率不高的情况下,搜索一个大的索引时。请尝试把swappiness调低,甚至关闭(把它设置为0)。Windows也有一个选框,在我的电脑->属性->高级->性能设置->内存使用,允许你选择倾向于应用程序还是系统缓存,好像也是起这个作用的。 

  • 使用readOnly=true选项打开IndexReader。
    在多线程共享同一个reader的时候这将起巨大的作用,因为这样会去掉处理线程冲突的代码。 

  • 在非Windows平台,使用NIOFSDirectory取代FSDirectory。
    这样访问这些文件的时候,也会去掉处理线程冲突的代码。不幸的是,由于Sun JRE Windows版本长期存在的bug(http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6265734),NIOFSDirectory在Windows下表现很差。 

  • 使用IndexSearcher的同一个实例。
    在程序的查询和线程之间,使用单一的一个IndexSearcher的实例。 

  • 测量性能的时候,忽视第一条查询。
    对搜索器的第一次查询,要付出初始化缓存的代价(特别是按照字段排序的时候),会干扰你的结论(假设你重用搜索器进行多次查存)。另外一方面,如果你一遍遍的重复同样的查询,结论也会被扭曲,因为操作系统会使用它的缓存加速IO操作。Linux(核心2.6.16或者更新版本)下,你可以用命令 sync ; echo 3 > /proc/sys/vm/drop_caches 来清空缓存。参见http://linux-mm.org/Drop_Caches。 

  • 仅在需要的时候重新打开IndexSearcher。
    只有在需要让最新提交的更新出现在搜索中的时候,才应该重新打开IndexSearcher。注意,重新打开搜索器有它的开销(在大的索引和排序打开的情况下可以被察觉到的),必须将这一开销最小化。可以考虑在面对第一个查询之前,使用预热技术对缓存进行预热。 

  • 搜索前对你的索引进行优化(optimize)。
    优化(optimize)过的索引只有一个段,这样通常比多个段速度快得多,尤其是对于大的索引。如果你的程序不经常更新索引,那么最好是,先构建索引,然后优化,使用优化过的索引进行搜索。如果你的索引更新频率很高,而且更新后就要刷新搜索器的话,那么优化对你的开销就太大了,你可以采用降低合并因子的方法。 

  • 降低合并因子(mergeFactor)。
    小的合并因子意味着更少的段和更快的搜索。然而,这将降低索引的速度,所以,你应该测试这个值,直到找到对于你的程序满足平衡的值。 

  • 限制保存字段和词向量的使用。
    从索引中获取这些信息开销太大。一般来说,你应该只获取用户看前可见的“页”内的条目的这些信息,而不是整个结果集的。针对每个获取的文档,Lucene必须寻道到不同文件的不同位置。尝试首先用docID排序你要获取的文档(译注:有可能提高寻道效率)。 

  • 在你获取一个文档时,使用FieldSelector仔细的选择要载入哪些字段,以及如何载入他们。

  • 不要枚举比所需命中结果更多的结果。
    枚举所有的命中很慢,有两个原因。首先,返回Hits对象的search()方法在你需要超过100个命中的时候,需要内部重新执行搜索。解决方法:用带有HitCollector的search方法代替。其次,命中通常遍布磁盘的每个角落,访问全部需要大量的I/O活动。这很难避免,除非索引小到可以直接放到内存里。如果你不需要完整的文档,而只需要一个(小的)字段,那么你可以用FieldCache类来缓存它,这样就可以告诉访问它了。 

  • 使用模糊查询(fuzzy query)的时候设置最小的前缀长度。
    模糊查询执行耗费CPU的字符串比较──要避免将用户输入的词和所有的索引的词项进行比较,应该只比较前“N”个字符相同的词项。前缀长度是QueryParser和FuzzyQuery都有的参数,默认为0,所有的词项都要比较。 

  • 考虑使用过滤器(filter)。
    把结果限定在索引的一部分时,使用缓存的位域过滤器比使用查询条件高效得多。尤其是在限制条件包含了一个大索引的大量文档时。过滤器通常用于限定一个分类下的结果,但是可以在很多中情况下替代任何查询条件。查询和过滤之间的一个区别在于,查询影响结果评分,过滤不影响。 

  • 找到瓶颈。
    复杂的查询分析或者对结果过度处理,是隐藏的搜索瓶颈的例子。使用VisualVM这样的工具profile程序可以帮助定位这些问题。