lucene底层数据结构——底层filter bitset原理,时间序列数据压缩将同一时间数据压缩为一行
如何联合索引查询?
所以给定查询过滤条件 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
lucene底层数据结构——底层filter bitset原理,时间序列数据压缩将同一时间数据压缩为一行的更多相关文章
- 聊聊Mysql索引和redis跳表 ---redis的有序集合zset数据结构底层采用了跳表原理 时间复杂度O(logn)(阿里)
redis使用跳表不用B+数的原因是:redis是内存数据库,而B+树纯粹是为了mysql这种IO数据库准备的.B+树的每个节点的数量都是一个mysql分区页的大小(阿里面试) 还有个几个姊妹篇:介绍 ...
- 深入解析Java对象的hashCode和hashCode在HashMap的底层数据结构的应用
转自:http://kakajw.iteye.com/blog/935226 一.java对象的比较 等号(==): 对比对象实例的内存地址(也即对象实例的ID),来判断是否是同一对象实例:又可以说是 ...
- Redis详解(四)------ redis的底层数据结构
上一篇博客我们介绍了 redis的五大数据类型详细用法,但是在 Redis 中,这几种数据类型底层是由什么数据结构构造的呢?本篇博客我们就来详细介绍Redis中五大数据类型的底层实现. 1.演示数据类 ...
- Redis(二)--- Redis的底层数据结构
1.Redis的数据结构 Redis 的底层数据结构包含简单的动态字符串(SDS).链表.字典.压缩列表.整数集合等等:五大数据类型(数据对象)都是由一种或几种数结构构成. 在命令行中可以使用 OBJ ...
- MySQL索引底层数据结构
一.何为索引? 1.索引是帮助数据库高效获取数据的排好序的数据结构. 2.索引存储在文件中. 3.索引建多了会影响增删改效率. (下面这张图为计算机组成原理内容,每查询一次索引节点,都会进行一次磁盘I ...
- Redis 详解 (四) redis的底层数据结构
目录 1.演示数据类型的实现 2.简单动态字符串 3.链表 4.字典 5.跳跃表 6.整数集合 7.压缩列表 8.总结 上一篇博客我们介绍了 redis的五大数据类型详细用法,但是在 Redis 中, ...
- 一文读懂Redis常见对象类型的底层数据结构
Redis是一个基于内存中的数据结构存储系统,可以用作数据库.缓存和消息中间件.Redis支持五种常见对象类型:字符串(String).哈希(Hash).列表(List).集合(Set)以及有序集合( ...
- Redis学习笔记(二)redis 底层数据结构
在上一节提到的图中,我们知道,可以通过 redisObject 对象的 type 和 encoding 属性.可以决定Redis 主要的底层数据结构:SDS.QuickList.ZipList.Has ...
- 深入浅出Redis-redis底层数据结构(上)
1.概述 相信使用过Redis 的各位同学都很清楚,Redis 是一个基于键值对(key-value)的分布式存储系统,与Memcached类似,却优于Memcached的一个高性能的key-valu ...
随机推荐
- OnClientClick的用法
摘自:http://blog.csdn.net/coolpig86/article/details/5439560 OnClientClick用于执行客户端脚本.当我们单击一个按钮时,最先执行的是On ...
- Windows Internals学习笔记(一)概念与工具
参考资料: 1. <Windows Internals> 2. Windows Drive Kit 3. Microsoft Windows SDK 4. WDK下载地址 知识点: 1. ...
- Java中枚举类型简单学习
/* * enum类型不允许继承 * 除了这一点,我们基本上可以将enum看作一个常规的类 * 我们可以添加自己的方法与属性,我们也可以覆盖其中的方法. * 不过一定要在enum实例序列的最后添加分号 ...
- 《转》Ubuntu 12.04常用的快捷键
Ubuntu 12.04常用的快捷键 超级键操作 1.超级键(Win键)–打开dash. www.2cto.com 2.长按超级键– 启动Launcher.并快捷键列表. 3.按住 ...
- iOS - OC RunTime 运行时
1.运行时的使用 向分类中添加属性 // 包含运行时头文件 #import <objc/runtime.h> /* void objc_setAssociatedObject(id obj ...
- iOS - Xib
前言 xib 文件可以被 Xcode 编译成 nib 文件,xib 文件本质上是一个 xml 文件,而 nib 文件就是编译后的二进制文件,该文件将视图等控件对象封装了起来,而在程序运行起来后,这些对 ...
- DBCP、C3P0、Proxool 、 BoneCP开源连接池的比《转》
简介 使用评价 项目主页 DBCP DBCP是一个依赖Jakarta commons-pool对象池机制的数据库连接池.DBCP可以直接的在应用程序用使用 可以设置最大和最小连接,连接等待时 ...
- Mono for Android布局控件属性小结
1. layout_weight 用于给一个线性布局中的诸多视图的重要度赋值. 所有的视图都有一个layout_weight值,默认为零,意思是需要显示 多大的视图就占据多大的屏幕空 间.若赋一个高于 ...
- ora-01033:oracle initialization or shutdown in progress 解决方法
今天研究Oracle遇到了这个问题ora-01033:oracle initialization or shutdown in progress,经过分析研究终于解决了,写下来纪念一下.我的库是ora ...
- linux内核的熵池
也可以看百度科 Linux内核采用熵来描述数据的随机性.熵(entropy)是描述系统混乱无序程度的物理量,一个系统的熵越大则说明该系统的有序性越差,即不确定性越大.在信息学中,熵被用来表征一个符号或 ...