列存(Column-Oriented)数据库学习笔记:内存数据压缩

大数据技术已经从几年前的热门概念落地为实用技术,为互联网贡献了重要力量。

随着移动应用发展,数据紧密包裹用户,
数据分析(OLAP)要求日益提高,数据量、查询延时都面临极大挑战。

这种情况下,列存、行列混存数据库技术,在冷门多年后又重新获得了极大关注。

我们一起来看个案例,Mark Litwintschik使用十亿条的士载客记录,
在不同的软硬件平台上测试了的分析查询性能:
http://tech.marksblogg.com/benchmarks.html

可以看到,除了MapD(GPU数据分析平台)遥遥领跑之外,
(跑在8路泰坦、半T内存这种怪兽硬件上)
列存数据库ClickHouse仅凭借单机普通硬件,
就达到了Spark这样专门的大数据平台的百倍性能,非常惊人。

基于此,近期考虑对OLAP架构,尤其是列存数据库做深入的学习探索,
相关笔记就在此记录。

一些资料

VLDB、SIGMOD、ICDE:

数据库技术最重要的期刊会议。

Vertica:

HP的商业列存数据库,以C-Store为原型,在AWS上有托管分析服务售卖。
在最开始的测试案例中,可以看到Vertica虽然没有ClickHouse快,
但也以单机跑出了接近10节点Spark集群的成绩。

Daniel.J.Abadi:

列存数据库C-Store的开发者,在各大期刊上发表过多篇关于列存数据库的Paper。
其09年的“Query Execution in Column-Oriented Database Systems”,
对列存数据库的各方面都做了详细叙述。

何为列存数据库?

我们熟悉的Mysql等数据库,是按行存放数据的:

+--------+-----+
| name   | age |
+--------+-----+
| 孙丽华  |  21 |
| 王永恒  |  23 |
| 张伟朋  |  21 |
+--------+-----+```

这样的数据存储方式,每次增加数据可以在末尾增加一行就行。
(实际比此复杂)

列存数据库则把行列倒转,并把不同列分开存放:

+--------+--------+--------+--------+
| name | 孙丽华 | 王永恒 | 张伟朋 |
+--------+--------+--------+--------+
+--------+--------+--------+--------+
| age | 21 | 23 | 21 |
+--------+--------+--------+--------+


列存数据库在增加数据时IO不连续,需要在每个列后面追加数据,写性能不佳,
因此一直没有成为主流的业务系统(OLTP)的首选数据库。

如前所述,大数据量下的OLAP是个挑战,它的用况特征我们可以列举一下:
    - 数据是批量导入的,导入后不修改
    - 绝大多数是读操作
    - 每个表有很多列,分析操作经常只涉及小部分列

可以看出,OLAP用况很吻合列存数据库,
既避开了列存的细粒度写性能的劣势,
又发挥了读少数列时节省IO、不需要读无用列的优点。

列存将相似数据紧密排列,对内存、CPU和磁盘都很友好,
这篇笔记的主要关注在(单机下的)列存数据在内存和CPU的处理,
磁盘上的存储布局也是一个较大的话题,会放到另一篇笔记。


# 列存数据的SIMD优化

数据的运算在CPU上进行,
CPU的数据吞吐量(Throughput)越大,计算就越快,数据分析也就越快。

CPU性能 = 每周期执行指令数(IPC,Instructions Per Cycle)× 频率
硬件本身决定了IPC上限和频率。

90年代,Intel等CPU厂商设计了MMX、SSE等单指令多数据(SIMD)的指令集,
顾名思义,就是一条指令处理一批数据,提高计算能力。
时至如今,SIMD仍是最重要的优化手段之一。

对于列式存放的数据,在内存里紧密排列、大小相同,
可以很容易地进行SIMD处理,批量地加载进CPU,由一次CPU指令处理完。
这就在不变的硬件条件下,成倍地提高了数据吞吐量。

紧密数组的遍历处理,对CPU流水线很友好,
即使在代码中没有特殊作SIMD处理,
编译器也可以使用loop-pipelining等手段进行优化,达到更高效率。


# 列存数据压缩

在CPU数据吞吐量固定的情况下,数据压缩是提升处理速度有效途径。

常见的压缩算法如lz77、lz78家族,对数据都做了变形处理,
然后在一个较大(k级)的数据窗口中寻找相同的数据达成压缩。

这类压缩的结果与原始数据相去甚远,进行计算之前需要解压,
我们称为通用压缩或者重型压缩(Generic Compression/Heavy Compression)
这类压缩的特点是压缩率高,压缩慢,解压较快,消耗CPU资源较多。

列存数据由于相邻数据相似度非常高,
可以进行针对数据特征的轻量压缩(Lightweight Compression)
并可以直接对压缩处理进行计算。

轻量压缩消耗CPU资源极少,压缩比普遍在1/3-1/4,
理想情况下,可以提升对应倍数的吞吐量。


# 轻量压缩的算法框架

###### 轻量压缩算法有很多种,常用的有:
    - 前缀压缩(Prefix Suppression):
    - 位向量编码(Bit-Vector Encoding)
    - 字典编码(Dictionary Ecoding)
    - 参照系编码(FOR,Frame of Reference Encoding)
    - 差异编码(Defferential Encoding/PFOR-DELTA)

###### 下面简单列一下各个算法的要点。

- 前缀压缩(Prefix Suppression):
  消除相同前缀,常用状况:列中大部分都是小值,但最大值较大

- 位向量编码(Bit-Vector Encoding):
  列中大多数值都相同,比如性别,以1个bit来表达。
  编码后数据还可以再次压缩。

- 字典编码(Dictionary Ecoding):
  适用于唯一值(Distinct Values)数量较少的情况,例如季度、国家
  预提取预存字典的KV对,数据里存K。
  压缩后数据长度为log 2(|D|) bits,D是唯一值数量

- 参照系编码(FOR,Frame of Reference Encoding):
  选择参照值,每个数据只存与参照值的差值,
  压缩后每值占log 2(max − min + 1) bits
  变种FOR: 选择可以小于0的参照值,使的每个值都为正数。

- 差异编码(Defferential Encoding/PFOR-DELTA):
  和参照系编码类似,使用前一个值作为参照值
  对于递增递减的数据性能良好。
  例如时间戳、递增ID、有序数组

- 行程编码(Run-Length Encoding,RLE):
  将一串连续的相同数据转化为特定的格式
  例如:aaabbc,表示为:3个a,2个b,1个c,压缩数据3a2b1c
  RLE是很基础的压缩方式,通用压缩算法中也很多它的影子,
  但通用压缩中经常结合多种算法,例如使用BWT可逆变换提高重复率。
  作为原始的、没有经过数据变形的RLE,更利于在压缩数据上进行计算。

###### 这些算法都可以在同一个算法框架下工作:
    - 找到数据特征,大部分数据都应该符合该特征
    - 对数据逐个进行编码,达成压缩,不同的压缩算法采用不同的编码方式
    - 如果碰到不符合特征的数据,按异常值(Exception Values)处理
    - 异常值的处理方式:编码为“跳出码(Escape Code)+ 原始数据”
    - 跳出码与正常值长度相等,其值为正常值不可能的值。所对应的原始数据可以另外存放

用伪码来表示编码过程:
```c
for i = j = 0; i < n; i++:
  if in[i] < MAXCODE:
    out[i] = CODE(in[i])
  else:
    out[i] = MAXCODE
    exception[j++]) = out[i]```

解码为逆过程:
```c
for i = j = 0; i < n; i++:
  if in[i] < MAXCODE:
    out[i] = DECODE(in[i])
  else:
    out[i] = exception[j++])```

有两个事情值得注意:
1,exception的数据比例对性能的影响。
2,if-else分支引发的指令预测失败,IPC(Instructions Per Cycle)下降的问题。

Marcin Zukowski在引文中测试了不同压缩算法、不同exception比例下的性能,
并且尝试使用两轮循环避免if-else分支,有兴趣可以看看。

在实践中,不同的数据列可以根据特征和需求来决定压缩策略,
工程师们提出过不少策略,以下是其中一种决策树:

该列有排序查询的需求吗
Y => 平均重复行程(Average Run-Length)> 2吗
Y => RLE压缩
N => 差异编码
N => 唯一值(Distinct Values)数量 < 50000吗
Y => 该列经常出现在选择谓词(即SQL where子语句)中吗
Y => 位向量编码
N => 字典编码
N => 是有局部性(某范围值较多)的数字类型吗
Y => 参照系编码
N => 不压缩或者重型压缩

在实际开发中,列数据一般被分成多个等大Block,
每个Block可以有自己的元数据(例如参照值),也可以引用别的Block的,
甚至可以再次对特征采样,选择不同的压缩策略。
可见落地过程中的优化空间还是很大的,需要结合实际数据测量效率。


# 对于压缩数据的计算

轻量压缩的结果与原始数据成一一映射的关系,
几乎所有的算术运算都可以直接在压缩结果上计算。

举例,原始数据[102, 101, 104],使用FOR编码,以100为参考值编码为[2, 1, 4],
那么(if > 101)的运算就被改写为(if > 1),可以直接在编码后数据上计算。

即使我们不使用改写计算的方法来处理数据,
而是使用解压后计算的常规方式,上述的轻量压缩仍旧可以带来巨大好处:

我们知道,RAM对比CPU来说是很慢的。
下表是数据在各个设备上存取数据的大概耗时比例,单位是时钟周期Cycle:

L1-CACHE 4
L2-CACHE 11
L3-CACHE 18
RAM 160```

计算过程:
- 压缩数据从RAM进入CACHE
- 送入CPU解压
- 进行业务运算
- 再压缩,结果留在CACHE
- 写回RAM

可见最耗时的两次IO:RAM-CACHE/CACHE-RAM,传输的都是压缩数据,
该技术被称为RAM-CPU CACHE Compression,
有效减少了RAM-CPU交换,增加了吞吐量。

以上涉及的都是列数据的通用的计算优化,
对于其在关系代数运算(查询)中的优化,
也是一个较大的话题,需要另外的篇幅来叙述。

主要参考资料

以下论文可以在各大学术库下载

- “Query Execution in Column-Oriented Database Systems” Daniel Abadi SIGMOD’09
- “Super-Scalar RAM-CPU Cache Compression” Zukowski, Heman, Nes, Boncz, ICDE’06
- “Weaving relations for cache performance” Ailamaki, DeWitt, Hill, and Skounakis. VLDB’01
- “Integrating Compression and Execution in Column-Oriented Database Systems” Abadi, SIGMOD’06
- “MonetDB/X100: Hyper-Pipelining Query Execution” Peter Boncz, CIDR’05
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容