LRUQueryCache

栏目: R语言 · 发布时间: 4年前

内容简介:LRUQueryCache用来对一个Query查询的结果进行缓存,缓存的内容仅仅是文档号集,由于不会缓存文档的打分(Score),所以只有不需要打分的收集器(Collector)才可以使用LRUQueryCache,比如说TotalHitCountCollector收集器,另外缓存的文档号集使用BitDocIdSet对象进行存储,在BitDocIdSet中实际使用了FixedBitSet对象进行存储。即使使用了不需要打分的收集器,也不一定对所有的查询结果进行缓存,有诸多苛刻的条件,在下文中会详细介绍。LRU

LRUQueryCache用来对一个Query查询的结果进行缓存,缓存的内容仅仅是文档号集,由于不会缓存文档的打分(Score),所以只有不需要打分的收集器(Collector)才可以使用LRUQueryCache,比如说TotalHitCountCollector收集器,另外缓存的文档号集使用BitDocIdSet对象进行存储,在BitDocIdSet中实际使用了FixedBitSet对象进行存储。

即使使用了不需要打分的收集器,也不一定对所有的查询结果进行缓存,有诸多苛刻的条件,在下文中会详细介绍。

LRUQueryCache中缓存的Query结果是有上限限制的,在每次添加一个缓存时,根据两种阈值来判断是否需要将某个已经缓存的数据剔除,使用的算法为LRU:

  • maxCachedQueries:缓存的结果数量,默认值1000
  • maxRamBytesUsed:缓存占用的内存量,默认值32MB或可分配内存的5%中的较小值

当然,我们可以自定义maxCachedQueries跟maxRamBytesUsed的值。

LRUQueryCache流程图

图1: LRUQueryCache

图2: LRUQueryCache 在执行查询时,如果我们使用了一个不需要打分的Collector,那么该Query就可以进入LRUQueryCache的流程之中,比如说TermsCollector、TotalHitCountCollector等等。默认的查询使用的是TopScoreDocCollector,他需要打分,所以无法使用LRUQueryCache的功能。

图3: LRUQueryCache 查询定义的Query对象。

允许缓存?

图4: LRUQueryCache 允许缓存受限于诸多条件,下面一一列出:

Query条件

有些Query不需要缓存:

  • TermQuery:TermQuery作为最常用的Query,源码中给出不需要缓存的理由是这种查询已经足够快(plenty fast)了。
  • MatchAllDocsQuery:此Query是获得IndexReader中的所有文档号,在不使用cache的情况下获取所有结果的逻辑是从0开始遍历到IndexReader中的最大的文档号(因为每一篇文档都是满足要求的),如果缓存这个结果的话,由于使用FixedBitSet存储了cache,在生成缓存的过程中需要编码,并且读取cache还需要解码,查询性能肯定是相比较大的。
  • MatchNoDocsQuery:此Query不会匹配任何文档,所以没有缓存的必要
  • BooleanQuery:BooleanQuery中没有任何子Query是不用缓存的
  • DisjunctionMaxQuery:DisjunctionMaxQuery中没有任何子Query是不用缓存的

满足了Query条件后,会将当前Query(Query对象的HashCode)添加到LRU算法中,并且当前Query为cache中最近最新使用,为了后面执行LRU算法做准备。

索引文件条件

根据当前的索引文件条件决定是否允许缓存,比如说存放DocValues的.dvm、.dvd文件在更新后,那么就不允许缓存。

段(Segment)条件

即使满足Query条件、索引文件条件,还要考虑当前段中的条件,条件跟当前段中包含的文档数量相关:

  • 最坏的情况下缓存所有子IndexReader中所有的文档号需要的内存量(worstCaseRamUsage)、目前最大可使用的内存量(totalRamAvailable:),这个值即上文中的maxRamBytesUsed,当满足(worstCaseRamUsage * 5) < totalRamAvailable时就允许缓存
  • 当前子IndexReader中包含的文档号数量 perReaderDocNum ≥ minSize(默认值10000),并且 perReaderDocNum占所有子IndexReader的文档总量totalDocNum满足 (perReaderDocNum / totalDocNum ≥ minSizeRatio (默认值0.03)), 其中minSize跟minSizeRatio可配

子IndexReader条件

不是所有的IndexReader都适合缓存,比如说facet中读取taxonomy的OrdinalMappingLeafReader,在以后的文章中介绍facet会给出原因。

线程竞争条件

当多个线程使用同一个IndexSearcher对象,那么cache就会成为临界区,当前线程如果访问cache发现已被其他线程占用,源码中的处理方式是不等待锁资源,即不使用LRUQueryCache,原因是在高并发下,查询被阻塞的时间可能跟查询个数成正比,反而降低了查询性能。锁资源被占用的情况有以下几种:

  • 其他线程正在添加一个cache,并且这个cache有可能不是当前线程的Query的cache
  • 其他线程正在读取一个cache,源码中使用的不是读写锁,所以即使当前线程可能也是读操作,也无法访问cache

不存在缓存?

当满足缓存条件后,继续下面的流程 图5: LRUQueryCache 如果存在缓存,那么直接取出缓存就可以退出了,需要重复的是,返回的结果只是文档号集。 图6: LRUQueryCache 如果不存在缓存,那么我们需要增加缓存,但是增加缓存还存在一些额外条件:

允许增加缓存条件

Query条件

这里的Query条件跟上文中的Query条件是一样的,这里还要继续检查一遍当前的Query是否需要缓存,因为如果某个Query使用多线程在多个子IndexReader中并行查询,由于这些线程使用同一个Weight对象,并且在上文中的Query条件检查中会将当前Query添加到LRU算法中,为了避免重复添加造成错误的计数(相同Query的历史查询计数,下文会介绍),所以在上文中的Query条件除了第一个线程,其他线程会跳过这一步骤,故在这里需要检查Query条件。

历史查询条件

在满足Query条件的前提下,并且同时满足历史查询计数打到阈值,才允许增加缓存,不同的Query对象的阈值是不同的,目前Lucene 7.5.0版本中域值根据Query对象有以下几种数值:

  • 2:如果是MultiTermQuery、MultiTermQueryConstantScoreWrapper、TermInSetQuery、PointQuery(点数据类型的所有查询),那么这些Query在历史查询中出现过2次,就允许增加缓存
  • 4:如果是BooleanQuery、DisjunctionMaxQuery,那么这些Query在历史查询中出现过4次,就允许增加缓存
  • 5:其他类型的Query

执行查询,保存结果

当满足允许增加缓存条件后,就可以执行一次常规的查询,获得查询结果后,即文档号集,存放到FixedBitSet,即缓存。

执行LRU?

图7: LRUQueryCache 上文中提到,缓存的个数跟占用内存量是有上限限制的,每当添加一个缓存后,会判断是否需要执行LRU算法来剔除某个旧的缓存或者直接添加新的缓存。在随后的文章中会详细介绍在Lucene中LRU算法的实现,因为这不是LRUQueryCache专有的功能,它属于一个Util类,出于正确分类目的,会另外写一篇文章。

文章中一些细节并没有详细介绍,比如说 为什么有些IndexReader不允许缓存、哪些IndexReader不允许缓存、为什么段文件更新后不允许缓存,在后面的文章中会解释这些问题。

点击下载Markdown文件


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

黑客

黑客

Steven Levy / 赵俐、刁海鹏、田俊静 / 机械工业出版社华章公司 / 2011-10-31 / 69.00元

黑客文化和伦理的奠基之作,计算机专业人士必读。 二十五周年新版,涵盖比尔·盖茨、马克·扎克伯格、理查德·斯托曼、史蒂夫·沃兹尼克等著名黑客的最新资料。 多年前,射击游戏之父、Doom游戏的作者约翰·卡马克由于读到本书,坚定了游戏开发的决心。 谷歌首席信息官本·弗里德也是本书的忠实读者。 探寻黑客文化的本质,体会黑客精神的精髓。一起来看看 《黑客》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具