[关闭]
@Wishes 2021-04-02T02:27:57.000000Z 字数 1647 阅读 218

Druid是一款面向大数据的实时查询引擎,采用列式存储可以很好的提升查询的性能。
影响查询的性能的一个重要的因素就是schema的设计,druid有三种列,如下图所示:

Timestamp列是每行数据必须要带的时间列,metric列是指标列,用于在查询的时候需要做聚合操作(如sum()函数),所以纯数值类型比较合适。

时间列和指标列的存储相对而言是比较简单的,只需要将每列的内容用LZ4压缩算法压缩然后存起来就可以了,在查询的时候,将对应列的内容进行解压缩,然后做聚合操作。

维度列(Dimensions columns)相比上面的两种列会复杂一些,因为维度列要支持过滤和分组。
维度列有4中类型:string, Long, Double, Float。

Long, Double, Float 数字类型数据格式

维度列和指标列的数字类型存储完全一致,只简单存储了列的值并压缩,官方文档中也说明了数字类型是没有索引的,所以维度列如果需要过滤,最好就不要用数值类型。

A dictionary that maps values (which are always treated as strings) to integer IDs,
A list of the column’s values, encoded using the dictionary in 1, and
For each distinct value in the column, a bitmap that indicates which rows contain that value.

string 类型数据格式

string是druid最核心的类型,为了支持大量数据下的过滤操作,string类型用了倒排索引。所以string类型在存储的时候,需要存索引和值两部分数据。
假设数据中,某一列的值如下:

  1. R0 = "Justin Bieber"
  2. R1 = "Justin Bieber"
  3. R2 = "Ke$ha"
  4. R3 = "Ke$ha"

我们就能得到value -> rowId 列表的倒排索引结构:

  1. "Justin Bieber": [0, 1]
  2. "Ke$ha": [2, 3]

这一列的值存储结构:

  1. [Justin Bieber, Justin Bieber, Ke$ha, Ke$ha]

想象一下,如果数据的行数不是4行,而是4亿行,那么倒排索引的rowId列表和列值会变得很大,druid为了优化这种情况,分别用了:
1. 列值的存储,增加了一个对列值的编码字典如下:

  1. Justin Bieber=0
  2. Ke$ha=1

这样列值中就不需要存原始的字符串,而变成了:

  1. [0, 0, 1, 1]

列值的存储空间瞬间减小。
2. rowId列表用bitmap代替整数列表, rowId列表变成了:

  1. "Justin Bieber": [1, 1, 0, 0]
  2. "Ke$ha": [0, 0, 1, 1]

druid 默认的bitmap是roaring bitmap, rowId列表如果比较稀疏会被很好压缩, 如果不稀疏,那会比直接存整数节省很多内存。

还有一种特殊的情况就是多值列,假设数据中,某一列的值如下:

  1. R0 = "Justin Bieber"
  2. R1 = "Justin Bieber"
  3. R2 = ["Ke$ha", "Justin Bieber"]
  4. R3 = "Ke$ha"

根据上述的内容,列值的编码字典保持不变,倒排索引结构如下:

  1. "Justin Bieber": [1, 1, 1, 0]
  2. "Ke$ha": [0, 0, 1, 1]

这一列的值存储结构如下:

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

所以,string类型的数据要存3部分内容:
1. 列值和对应的编码的字典
2. 转换成编码后的列值
3. 倒排列表

string类型的意义就是加速查询,假设查询Justin Bieber所在的行,直接从倒排索引中就能找到对应的bitmap。
但是仔细思考的话,会发现string类型的列值存的是列值的编码,而不是列值本身,这样虽然节省了存储空间,但是如果需要返回这一列的值,性能就会有损耗,因为需要把列值的编码还原成列值本身。
就整体而言,在schema设计的时候,如果某一列涉及到过滤操作,最好将该列设计成维度列中的字符串类型是很必要的。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注