首页 > 代码库 > RCFile 和 ORCFile
RCFile 和 ORCFile
RCFile
之前听说 RCFile 在读取数据时可以跳过不需要的列,不需要将一整行读入然后选择所需字段,所以在 Hive 中执行 select a, b from tableA where c = 1
这样的操作就相对比较高效。为了满足好奇心,找了一下关于 RCFile 的论文(RCFile: A Fast and Space-efficient Data Placement Structure in MapReduce-based Warehouse System)看了一下,这里记录一下当作笔记。
首先大数据的查询和处理有以下几个需求:
- 数据的快速加载
- 数据的快速查询处理
- 高效的存储,减少存储空间的使用
- 能很好的适应动态的查询模式
在传统的数据库系统中,主要有三种数据存储方式:
水平的行存储结构: 行存储模式就是把一整行存在一起,包含所有的列,这是最常见的模式。这种结构能很好的适应动态的查询,比如
select a from tableA
和select a, b, c, d, e, f, g from tableA
这样两个查询其实查询的开销差不多,都需要把所有的行读进来过一遍,拿出需要的列。而且这种情况下,属于同一行的数据都在同一个 HDFS 块上,重建一行数据的成本比较低。但是这样做有两个主要的弱点:a)当一行中有很多列,而我们只需要其中很少的几列时,我们也不得不把一行中所有的列读进来,然后从中取出一些列。这样大大降低了查询执行的效率。b)基于多个列做压缩时,由于不同的列数据类型和取值范围不同,压缩比不会太高。垂直的列存储结构: 列存储是将每列单独存储或者将某几个列作为列组存在一起。列存储在执行查询时可以避免读取不必要的列。而且一般同列的数据类型一致,取值范围相对多列混合更小,在这种情况下压缩数据能达到比较高的压缩比。但是这种结构在重建出行时比较费劲,尤其适当一行的多个列不在一个 HDFS 块上的时候。 比如我们从第一个 DataNode 上拿到 column A,从第二个 DataNode 上拿到了 column B,又从第三个 DataNode 上拿到了 column C,当要把 A,B,C 拼成一行时,就需要把这三个列放到一起重建出行,需要比较大的网络开销和运算开销。
混合的 PAX 存储结构: PAX 结构是将行存储和列存储混合使用的一种结构,主要是传统数据库中提高 CPU 缓存利用率的一种方法,并不能直接用到 HDFS 中。但是 RCFile 也是继承自它的思想,先按行存再按列存。
RCFile 的设计和实现
数据的布局: 首先根据 HDFS 的结构,一个表可以由多个 HDFS 块构成。在每个 HDFS 块中,RCFile 以 row group 为基本单位组织数据,一个表多所有 row group 大小一致,一个 HDFS 块中可以包含多个 row group。每个 row group 包含三个部分,第一部分是 sync marker,用来区分一个 HDFS 块中两个连续多 row group。第二部分是 row group 的 metadata header,记录每个 row group 中有多少行数据,每个列数据有多少字节,以及每列一行数据的字节数。第三部分就是 row group 中的实际数据,这里是按列存储的。
数据的压缩: 在 metadata header 部分用 RLE (Run Length Encoding) 方法压缩,因为对于记录每个列数据中一行数据的字节数是一样的,这些数字重复连续出现,因此用这种方法压缩比比较高。在压缩实际数据时,每列单独压缩。
数据的追加写: RCFile 会在内存里维护每个列的数据,叫 column holder,当一条记录加入时,首先会被打散成多个列,人后追加到每列对应的 column holder,同时更新 metadata header 部分。可以通过记录数或者缓冲区大小控制内存中 column holder 的大小。当记录数或缓冲大小超过限制,就会先压缩 metadata header,再压缩 column holder,然后写到 HDFS。
数据的读取和惰性解压(lazy decompression): RCFile 在处理一个 row group 时,只会读取 metadata header 和需要的列,合理利用列存储在 I/O 方面的优势。而且即使在查询中出现的列技术读进内存也不一定会被解压缩,只有但确定该列数据需要用时才会去解压,也就是惰性解压(lazy decompression)。例如对于
select a from tableA where b = 1
,会先解压 b 列,如果对于整个 row group 中的 b 列,值都不为 1,那么就没必要对这个 row group 对 a 列去解压,因为整个 row group 都跳过了。row group 的大小: row group 太小肯定是不能充分发挥列存储的优势,但是太大也会有问题。首先,论文中实验指出,当 row group 超过某一阈值时,很难再获得更高当压缩比。其次,row group 太大会降低 lazy decompression 带来的益处,还是拿
select a from tableA where b = 1
来说,如果一个 row group 里有一行b = 1
,我们还是要解压 a 列,从而根据 metadata header 中的信息找到b = 1
的那行的 a 列的值,如果此时我们把 row group 设小一点,假设就设成 1,这样对于b <> 1
的行都不需要解压 a 列。最后论文中给出一个一般性的建议,建议将 row group 设成 4MB。
ORC File
然后是 ORC File(Optimized Row Columnar file),对RCFile做了一些优化,克服 RCFile 的一些限制,主要参考这篇文档。
和RCFile格式相比,ORC File格式有以下优点:
- 每个task只输出单个文件,这样可以减少NameNode的负载;
- 支持各种复杂的数据类型,比如: datetime, decimal, 以及一些复杂类型(struct, list, map, and union);
- 在文件中存储了一些轻量级的索引数据;
- 基于数据类型的块模式压缩:
- a、integer类型的列用行程长度编码(run-length encoding)
- String类型的列用字典编码(dictionary encoding);
- 用多个互相独立的RecordReaders并行读相同的文件;
- 无需扫描markers就可以分割文件;
- 绑定读写所需要的内存;
- metadata的存储是用 Protocol Buffers的,所以它支持添加和删除一些列。
文件结构
ORC File包含一组组的行数据,称为stripes,除此之外,ORC File的file footer还包含一些额外的辅助信息。在ORC File文件的最后,有一个被称为postscript的区,它主要是用来存储压缩参数及压缩页脚的大小。
在默认情况下,一个stripe的大小为250MB。大尺寸的stripes使得从HDFS读数据更高效。
在file footer里面包含了该ORC File文件中stripes的信息,每个stripe中有多少行,以及每列的数据类型。当然,它里面还包含了列级别的一些聚合的结果,比如:count, min, max, and sum。这里贴一下文档上的图:
Stripe结构
每个Stripe都包含index data、row data以及stripe footer,Stripe footer包含流位置的目录,Row data在表扫描的时候会用到。
Index data包含每列的最大和最小值以及每列所在的行。行索引里面提供了偏移量,它可以跳到正确的压缩块位置。
通过行索引,可以在stripe中快速读取的过程中可以跳过很多行,尽管这个stripe的大小很大。在默认情况下,最大可以跳过10000行。
因为可以通过过滤预测跳过很多行,因而可以在表的 secondary keys 进行排序,从而可以大幅减少执行时间。比如你的表的主分区是交易日期,那么你可以对次分区(state、zip code以及last name)进行排序。
RCFile 和 ORCFile