如何联合索引查询?

所以给定查询过滤条件 age=18 的过程就是先从term index找到18在term dictionary的大概位置,然后再从term dictionary里精确地找到18这个term,然后得到一个posting list或者一个指向posting list位置的指针。然后再查询 gender=女 的过程也是类似的。最后得出 age=18 AND gender=女 就是把两个 posting list 做一个“与”的合并。

这个理论上的“与”合并的操作可不容易。对于mysql来说,如果你给age和gender两个字段都建立了索引,查询的时候只会选择其中最selective的来用,然后另外一个条件是在遍历行的过程中在内存中计算之后过滤掉。那么要如何才能联合使用两个索引呢?有两种办法:

  • 使用skip list数据结构。同时遍历gender和age的posting list,互相skip;
  • 使用bitset数据结构,对gender和age两个filter分别求出bitset,对两个bitset做AN操作。

PostgreSQL 从 8.4 版本开始支持通过bitmap联合使用两个索引,就是利用了bitset数据结构来做到的。当然一些商业的关系型数据库也支持类似的联合索引的功能。Elasticsearch支持以上两种的联合索引方式,如果查询的filter缓存到了内存中(以bitset的形式),那么合并就是两个bitset的AND。如果查询的filter没有缓存,那么就用skip list的方式去遍历两个on disk的posting list。

利用 Skip List 合并

以上是三个posting list。我们现在需要把它们用AND的关系合并,得出posting list的交集。首先选择最短的posting list,然后从小到大遍历。遍历的过程可以跳过一些元素,比如我们遍历到绿色的13的时候,就可以跳过蓝色的3了,因为3比13要小。

整个过程如下

Next -> 2
Advance(2) -> 13
Advance(13) -> 13
Already on 13
Advance(13) -> 13 MATCH!!!
Next -> 17
Advance(17) -> 22
Advance(22) -> 98
Advance(98) -> 98
Advance(98) -> 98 MATCH!!!

最后得出的交集是[13,98],所需的时间比完整遍历三个posting list要快得多。但是前提是每个list需要指出Advance这个操作,快速移动指向的位置。什么样的list可以这样Advance往前做蛙跳?skip list:

从概念上来说,对于一个很长的posting list,比如:

[1,3,13,101,105,108,255,256,257]

我们可以把这个list分成三个block:

[1,3,13] [101,105,108] [255,256,257]

然后可以构建出skip list的第二层:

[1,101,255]

1,101,255分别指向自己对应的block。这样就可以很快地跨block的移动指向位置了。

Lucene自然会对这个block再次进行压缩。其压缩方式叫做Frame Of Reference编码。示例如下:

考虑到频繁出现的term(所谓low cardinality的值),比如gender里的男或者女。如果有1百万个文档,那么性别为男的posting list里就会有50万个int值。用Frame of Reference编码进行压缩可以极大减少磁盘占用。这个优化对于减少索引尺寸有非常重要的意义。因为这个Frame of Reference的编码是有解压缩成本的。利用skip list,除了跳过了遍历的成本,也跳过了解压缩这些压缩过的block的过程,从而节省了cpu。

利用bitset合并

Bitset是一种很直观的数据结构,对应posting list如:

[1,3,4,7,10]

对应的bitset就是:

[1,0,1,1,0,0,1,0,0,1]

每个文档按照文档id排序对应其中的一个bit。Bitset自身就有压缩的特点,其用一个byte就可以代表8个文档。所以100万个文档只需要12.5万个byte。但是考虑到文档可能有数十亿之多,在内存里保存bitset仍然是很奢侈的事情。而且对于个每一个filter都要消耗一个bitset,比如age=18缓存起来的话是一个bitset,18<=age<25是另外一个filter缓存起来也要一个bitset。

所以秘诀就在于需要有一个数据结构:

  • 可以很压缩地保存上亿个bit代表对应的文档是否匹配filter;
  • 这个压缩的bitset仍然可以很快地进行AND和 OR的逻辑操作。

Lucene使用的这个数据结构叫做 Roaring Bitmap。

其压缩的思路其实很简单。与其保存100个0,占用100个bit。还不如保存0一次,然后声明这个0重复了100遍。

这两种合并使用索引的方式都有其用途。Elasticsearch对其性能有详细的对比(https://www.elastic.co/blog/frame-of-reference-and-roaring-bitmaps)。简单的结论是:因为Frame of Reference编码是如此高效,对于简单的相等条件的过滤缓存成纯内存的bitset还不如需要访问磁盘的skip list的方式要快。

如何减少文档数?

一种常见的压缩存储时间序列的方式是把多个数据点合并成一行。Opentsdb支持海量数据的一个绝招就是定期把很多行数据合并成一行,这个过程叫compaction。类似的vivdcortext使用mysql存储的时候,也把一分钟的很多数据点合并存储到mysql的一行里以减少行数。例如可以把一段时间的很多个数据点打包存储到一个父文档里,变成其嵌套的子文档。示例如下:

{timestamp:12:05:01, idc:sz, value1:10,value2:11}
{timestamp:12:05:02, idc:sz, value1:9,value2:9}
{timestamp:12:05:02, idc:sz, value1:18,value:17}

可以打包成:

{
max_timestamp:12:05:02, min_timestamp: 1205:01, idc:sz,
records: [{timestamp:12:05:01, value1:10,value2:11}
{timestamp:12:05:02, value1:9,value2:9}
{timestamp:12:05:02, value1:18,value:17}
]
}

这样可以把数据点公共的维度字段上移到父文档里,而不用在每个子文档里重复存储,从而减少索引的尺寸。如果我们可以在一个父文档里塞入50个嵌套文档,那么posting list可以变成之前的1/50

总结和思考

Elasticsearch的索引思路:

将磁盘里的东西尽量搬进内存,减少磁盘随机读取次数(同时也利用磁盘顺序读特性)。

对于使用Elasticsearch进行索引时需要注意:

  • 不需要索引的字段,一定要明确定义出来,因为默认是自动建索引的
  • 同样的道理,对于String类型的字段,不需要analysis的也需要明确定义出来,因为默认也是会analysis的
  • 选择有规律的ID很重要,随机性太大的ID(比如java的UUID)不利于查询

关于最后一点,个人认为有多个因素:

其中一个(也许不是最重要的)因素: 上面看到的压缩算法,都是对Posting list里的大量ID进行压缩的,那如果ID是顺序的,或者是有公共前缀等具有一定规律性的ID,压缩比会比较高;

另外一个因素: 可能是最影响查询性能的,应该是最后通过Posting list里的ID到磁盘中查找Document信息的那步,因为Elasticsearch是分Segment存储的,根据ID这个大范围的Term定位到Segment的效率直接影响了最后查询的性能,如果ID是有规律的,可以快速跳过不包含该ID的Segment,从而减少不必要的磁盘读次数,具体可以参考这篇如何选择一个高效的全局ID方案(评论也很精彩)

这篇文章非常棒:https://neway6655.github.io/elasticsearch/2015/09/11/elasticsearch-study-notes.html#section-1

转载于:https://www.cnblogs.com/bonelee/p/6227297.html

lucene底层数据结构——底层filter bitset原理,时间序列数据压缩将同一时间数据压缩为一行...相关推荐

  1. Redis原理以及底层数据结构初探

    什么是Redis? 非关系型的键值对数据库,可以根据键以O(1)的时间复杂度取出或插入关联值 Redis的数据是存在内存中的 键值对中键的类型可以是字符串,整型,浮点型等,且键是唯一的 键值对中的值类 ...

  2. 【Go语言学习】——go 数据结构底层原理

    go 数据结构底层原理 array底层原理 go中的数组是由固定长度的特定类型元素组成的序列,数组的长度是数据类型的组成方式,所以不同长度和不同类型的元素组成的数组是不同的数组类型.数组属于值类型,因 ...

  3. 面试官:MySQL索引底层数据结构原理与性能调优,你能回答多少?

    哈喽!大家好,我是小奇,一位不靠谱的程序员 小奇打算以轻松幽默的对话方式来分享一些技术,如果你觉得通过小奇的文章学到了东西,那就给小奇一个赞吧 文章持续更新,可以微信搜索[小奇JAVA面试]第一时间阅 ...

  4. 【Java基础】HashMap底层数据结构及其原理

    1.简单了解一下HashMap HashMap 就是以 Key-Value 键值对的方式进行数据存储的一种数据结构,它在 JDK 1.7 和 JDK 1.8 中底层数据结构是有些不一样的.简单来说,J ...

  5. MySQL索引底层数据结构原理剖析

    一. 前言 1. 说明 我们平时所说的:聚集索引(主键索引),次要索引,覆盖索引,复合索引,前缀索引,唯一索引在MySQL5.7和 8.0版本默认都是使用B+Tree索引,除此之外还有 Hash索引. ...

  6. 五、redis原理之sort set底层数据结构

    一.redis原理之sort set底层数据结构? SortedSet(zset)有序集合可以看做是在Set集合的的基础上为集合中的每个元素维护了一个顺序值: score,它允许集合中的元素可以按照s ...

  7. 三、redis原理之list底层数据结构

    一.redis原理之list底层数据结构ziplist和quicklist. 快速列表 quicklist[quicklist = 链表+ziplist] 首先在列表元素较少的情况下会使用一块连续的内 ...

  8. 四、redis原理之set底层数据结构

    一.redis原理之set底层数据结构? 其底层有两种实现方式: 1.当value是整数值时,且数据量不大时使用inset来存储, 2.其他情况都是用字典dict来存储 inset的结构: typed ...

  9. 一文讲透 Git 底层数据结构和原理

    简介: 本文将系统分享 Git 底层知识:对象生命周期变化,底层数据结构,数据包文件结构,数据包文件索引,以及详细分析对象查询流程和算法. 状态模型 上图描述了 git 对象的在不同的生命周期中不同的 ...

最新文章

  1. JavaScript数据类型检测总结
  2. 每日一皮:就像我的编程,虽然过程不咋地,结果还不错...
  3. 通过一个实际例子学习SAP UI5的控件绘制和渲染
  4. 跳槽时,不敢要高工资也会对候选人不利
  5. [Reverse] - 百度杯”CTF比赛 2017 二月场-CrackMe-1
  6. 面向对象的特点,封装性,继承性,多态性!
  7. 修复IE下相对定位子元素溢出Bug
  8. python入门教程第三讲_第三讲 使用Template
  9. 安卓apk的编译与反编译
  10. Android中View的绘制过程 onMeasure方法简述 附有自定义View例子
  11. HP M280 鼠标驱动
  12. 阿里巴巴大数据技术专家岗面试题
  13. fn键台式计算机在哪,fn键在哪里
  14. 支付宝沙箱版app登入失败账户不存在问题
  15. 使用IIS Live Smooth Streaming技术搭建流媒体直播系统
  16. 第三周助教工作总结——NWNU李泓毅
  17. 非常喜欢的一期《晓松奇谈》
  18. “只要3分钟,我就能扒光你的隐私!” | 互联网时代,14亿中国人都在裸奔
  19. Silverlight开发MMORPG游戏研讨(4):用一个定时器实现多个不同帧频的动画
  20. HttpClient使用不当,服务挂了!是时候系统学习一下了

热门文章

  1. python执行bat文件_python自动运行cmd,bat文件
  2. 下面属于javascript内部对象的有_【JavaScript 教程】面向对象编程——this 关键字...
  3. 安装linux系统结果,Linux 系统安装[Redhat]
  4. 南开15计算机基础,南开大学计算机基础06-07_B卷
  5. php xml对象解析_php解析xml 的四种简单方法(附实例)
  6. 微信小程序开发第二弹
  7. 【工作经验分享】mysql备份恢复命令
  8. 【408预推免复习】计算机组成原理之指令系统
  9. python【力扣LeetCode算法题库】27-移除元素
  10. python【力扣LeetCode算法题库】914. 卡牌分组(reduce collections.Counter)