前段时间在玩Lucene的时候发现 字段若想排序,则必须写入是索引使用NumericDocValuesField类型,否则不能进行排序操作。然后特地调研了下这个DocValue。不知道自己理解的是否正确,所以想在这里把我的想法和疑惑写一下,欢迎大家拍砖指正。
首先 只有倒排索引进行排序效率是很低的,我理解如下:
假设我要根据 content搜索,然后根据time字段排序,因为倒排索引是一个field对应一个docId的,所以query阶段我只能取回docId,没有更多字段的值,所以需要有一个地方获取time的值。倒排索引如下:
term docId
--------------------
10 2
13 3
14 1, 5, 8
16 7,9,10
--------------------
这样的结构是没法直接获取到time字段的,假设现在没有doc_value 以及fieldcache(doc_value出来之前貌似都是通过这个结构做排序) 所以只有到time所在的倒排索引进行遍历,看看我这些docId的time值都对应是多少,所以这一步是要通过docId获取fieldvalue的,遍历倒排索引效率会很低,因此必须要一个正向关系来做这件事儿。
那么这个结构在低版本的时候是通过FieldCache 来获取的,FieldCache简单抽象的理解就是一个大数组,数组的下标表示docId,数组中的值表示这个field的value(注意 还是一个field一个数组)。上面的步骤当我需要按照time排序时,假设过滤出100个docID,就可以拿他们去数组中直接取到time值然后排序,获取排序字段的时间复杂度瞬间变成O(n) (n为doc个数),效率比遍历倒排索引可好多了吧。
抽象的结构如下:
time:
--------------------------------------------------------------
| 2019 | 2018 | 2017 | 2019 | 2016 | 2015 | 2016 | 2018 |
--------------------------------------------------------------
0 1 2 3 4 5 6 7
同理如果是聚合类需求,例如查看年龄大于24岁人的职业分布情况,假设职业是一个枚举并且建索引了,过滤出24岁人后,依然需要一个地方把满足条件的人的职业信息或取出来,通过上述结构可以解决这个问题
但是这个东西比较吃内存(doc越多,数组越长),所以后来出来了一个doc_value 替代了fieldCache,这个东西是放在磁盘上的,能够承载的空间也就比较大了。
我对doc_value的理解描述完了,接下来就说我的疑问。
是不是所有排序/聚合场景都必须走doc_value?
问题的根源在于排序/聚合的数据需要一次回取操作(类似mysql的一次二级索引回取数据),如果过滤和排序/聚合用的是同一个字段是不是就不需要额外走doc_value了? 比如查询2019-07-18日的日志数据,按照时间排序; 查询职业为程序员、产品经理、运营、设计、hrbp的同学分别有多少人。case1中 取到docId的时候可以跟time做一个映射,然后根据time排序docId即可;case2中 因为一个值对应一个docId列表很容易就能计算出这个值下面有多少docId。 我的结论:在过滤和排序/聚合使用相同字段时,无需走doc_value
首先 只有倒排索引进行排序效率是很低的,我理解如下:
假设我要根据 content搜索,然后根据time字段排序,因为倒排索引是一个field对应一个docId的,所以query阶段我只能取回docId,没有更多字段的值,所以需要有一个地方获取time的值。倒排索引如下:
term docId
--------------------
10 2
13 3
14 1, 5, 8
16 7,9,10
--------------------
这样的结构是没法直接获取到time字段的,假设现在没有doc_value 以及fieldcache(doc_value出来之前貌似都是通过这个结构做排序) 所以只有到time所在的倒排索引进行遍历,看看我这些docId的time值都对应是多少,所以这一步是要通过docId获取fieldvalue的,遍历倒排索引效率会很低,因此必须要一个正向关系来做这件事儿。
那么这个结构在低版本的时候是通过FieldCache 来获取的,FieldCache简单抽象的理解就是一个大数组,数组的下标表示docId,数组中的值表示这个field的value(注意 还是一个field一个数组)。上面的步骤当我需要按照time排序时,假设过滤出100个docID,就可以拿他们去数组中直接取到time值然后排序,获取排序字段的时间复杂度瞬间变成O(n) (n为doc个数),效率比遍历倒排索引可好多了吧。
抽象的结构如下:
time:
--------------------------------------------------------------
| 2019 | 2018 | 2017 | 2019 | 2016 | 2015 | 2016 | 2018 |
--------------------------------------------------------------
0 1 2 3 4 5 6 7
同理如果是聚合类需求,例如查看年龄大于24岁人的职业分布情况,假设职业是一个枚举并且建索引了,过滤出24岁人后,依然需要一个地方把满足条件的人的职业信息或取出来,通过上述结构可以解决这个问题
但是这个东西比较吃内存(doc越多,数组越长),所以后来出来了一个doc_value 替代了fieldCache,这个东西是放在磁盘上的,能够承载的空间也就比较大了。
我对doc_value的理解描述完了,接下来就说我的疑问。
是不是所有排序/聚合场景都必须走doc_value?
问题的根源在于排序/聚合的数据需要一次回取操作(类似mysql的一次二级索引回取数据),如果过滤和排序/聚合用的是同一个字段是不是就不需要额外走doc_value了? 比如查询2019-07-18日的日志数据,按照时间排序; 查询职业为程序员、产品经理、运营、设计、hrbp的同学分别有多少人。case1中 取到docId的时候可以跟time做一个映射,然后根据time排序docId即可;case2中 因为一个值对应一个docId列表很容易就能计算出这个值下面有多少docId。 我的结论:在过滤和排序/聚合使用相同字段时,无需走doc_value
3 个回复
laoyang360 - 《一本书讲透Elasticsearch》作者,Elastic认证工程师 [死磕Elasitcsearch]知识星球地址:http://t.cn/RmwM3N9;微信公众号:铭毅天下; 博客:https://elastic.blog.csdn.net
赞同来自: code4j
2,区别于倒排索引,当进行聚合或者排序,倒排拿不到这些值,所以正排doc value是存储这些值的,存储方式:列式存储,落地磁盘。
Ombres
赞同来自:
1. 先将原始query改写为一个或者多个原子query
2. 获取所有原子query的查询结果,这里获取的是posting list(docidset)
3. 然后所有posting list进行合并,并计算评分,排序,这一步是由Scorer完成,Collector收集结果
倒排表主要用于获取posting list,也就是第2步中,第三步拿到的只是posting list,并没有真正获取数据
排序发生在第3步,这时候去计算评分或者不计算分数按照排序字段进行排序
整个过程中,倒排索引用于获取posting list,DocValues用于排序
code4j - coder github: https://github.com/rpgmakervx
赞同来自: