参考如下:
http://www.colabug.com/thread-1063815-1-1.html
http://www.cnblogs.com/cxzdy/p/5910760.html
http://www.lai18.com/content/24596520.html
http://blog.csdn.net/liuzhoulong/article/details/52048105
一、定义
ORC File,(Optimized Row Columnar)File,是对RCFile的优化。ORC文件格式是从Hive-0.11版本开始的.
二、目的:
1、大幅度降低磁盘占用
2、大幅提高执行效率
三、和RCFile格式相比,ORC File格式有以下优点:
(1)、每个task只输出单个文件,这样可以减少NameNode的负载;
(2)、支持各种复杂的数据类型,比如: datetime, decimal, 以及一些复杂类型(struct, list, map, and union);
(3)、在文件中存储了一些轻量级的索引数据;
(4)、基于数据类型的块模式压缩:
a、integer类型的列用行程长度编码(run-length encoding);
b、String类型的列用字典编码(dictionary encoding);
(5)、用多个互相独立的RecordReaders并行读相同的文件;
(6)、无需扫描markers就可以分割文件;
(7)、绑定读写所需要的内存;
(8)、metadata的存储是用 Protocol Buffers的,所以它支持添加和删除一些列。
- 1、ORC中的特定的序列化与反序列化操作可以使ORC file writer根据数据类型进行写出。
- 2、提供了多种RCFile中没有的indexes,这些indexes可以使ORC的reader很快的读到需要的数据,并且跳过无用数据,这使得ORC文件中的数据可以很快的得到访问。
- 3、由于ORC file writer可以根据数据类型进行写出,所以ORC可以支持复杂的数据结构(比如Map等)。
- 4、除了上面三个理论上就具有的优势之外,ORC的具体实现上还有一些其他的优势,比如ORC的stripe默认大小更大,为ORC writer提供了一个memory manager来管理内存使用情况。
四、ORC File文件结构
ORC File包含一组组的行数据,称为stripes。在默认情况下,一个stripe的大小为250MB。大尺寸的stripes使得从HDFS读数据更高效。
ORC File的file footer还包含一些额外的辅助信息。包含了该ORC File文件中stripes的信息,每个stripe中有多少行,以及每列的数据类型。当然,它里面还包含了列级别的一些聚合的结果,比如:count, min, max, and sum。
在ORC File文件的最后,有一个被称为postscript的区,它主要是用来存储压缩参数及压缩页脚的大小。
五、stripe结构
每个Stripe都包含index data、row data以及stripe footer。
Stripe footer包含流位置的目录;
Row data在表扫描的时候会用到。
Index data包含每列的最大和最小值以及每列所在的行。行索引里面提供了偏移量,它可以跳到正确的压缩块位置。具有相对频繁的行索引,使得在stripe中快速读取的过程中可以跳过很多行,尽管这个stripe的大小很大。在默认情况下,最大可以跳过10000行。拥有通过过滤谓词而跳过大量的行的能力,你可以在表的 secondary keys 进行排序,从而可以大幅减少执行时间。比如你的表的主分区是交易日期,那么你可以对次分区(state、zip code以及last name)进行排序。
六、hive中如何使用ORCFile
CREATE TABLE ... STORED AS ORC
ALTER TABLE ... [PARTITION partition_spec] SET FILEFORMAT ORC
SET hive.default.fileformat=Orc
所有关于ORCFile的参数都是在Hive QL语句的TBLPROPERTIES字段里面出现,他们是:
orc.compress ZLIB high level compression (one of NONE, ZLIB, SNAPPY)
orc.compress.size 262,144 number of bytes in each compression chunk
orc.stripe.size 268435456 number of bytes in each stripe
orc.row.index.stride 10,000 number of rows between index entries (must be >= 1000)
orc.create.index true whether to create row indexes
下面的例子是建立一个没有启用压缩的ORCFile的表
create table Addresses (
name string,
street string,
city string,
state string,
zip int
)
stored as orc
tblproperties ("orc.compress"="NONE");
七、序列号和压缩
对ORCFile文件中的列进行压缩是基于这列的数据类型是integer或者string。
Integer Column Serialization
Integer columns are serialized in two streams.
1、present bit stream: is the value non-null?
2、data stream: a stream of integers
Integer data is serialized in a way that takes advantage of the common distribution of numbers:
1、Integers are encoded using a variable-width encoding that has fewer bytes for small integers.
2、Repeated values are run-length encoded.
3、Values that differ by a constant in the range (-128 to 127) are run-length encoded.
The variable-width encoding is based on Google’s protocol buffers and uses the high bit to represent whether this byte is not the last and the lower 7 bits to encode data. To encode negative numbers, a zigzag encoding is used where 0, -1, 1, -2, and 2 map into 0, 1, 2, 3, 4, and 5 respectively.
Each set of numbers is encoded this way:
1、If the first byte (b0) is negative:
-b0 variable-length integers follow.
2、If the first byte (b0) is positive:
it represents b0 + 3 repeated integers
the second byte (-128 to +127) is added between each repetition
1 variable-length integer.
In run-length encoding , the first byte specifies run length and whether the values are literals or duplicates. Duplicates can step by -128 to +128. Run-length encoding uses protobuf style variable-length integers.
String Column Serialization
Serialization of string columns uses a dictionary to form unique column values The dictionary is sorted to speed up predicate filtering and improve compression ratios.
String columns are serialized in four streams.
1、present bit stream: is the value non-null?
2、dictionary data: the bytes for the strings
3、dictionary length: the length of each entry
4、row data: the row values
Both the dictionary length and the row values are run length encoded streams of integers.
八、ORC数据存储方法
在ORC格式的hive表中,记录首先会被横向的切分为多个stripes,然后在每一个stripe内数据以列为单位进行存储,所有列的内容都保存在同一个文件中。每个stripe的默认大小为256MB,相对于RCFile每个4MB的stripe而言,更大的stripe使ORC的数据读取更加高效。
对于复杂数据类型,比如Map,ORC文件会将一个复杂数据类型字段解析成多个子字段。下表中列举了ORC文件中对于复杂数据类型的解析
Data type | Chile columns |
---|---|
Array | 一个包含所有数组元素的子字段 |
Map | 两个子字段,一个key字段,一个value字段 |
Struct | 每一个属性对应一个子字段 |
Union | 每一个属性对应一个子字段 |
当字段类型都被解析后,会由这些字段类型组成一个字段树,只有树的叶子节点才会保存表数据,这些叶子节点中的数据形成一个数据流,如上图中的Data Stream。
为了使ORC文件的reader更加高效的读取数据,字段的metadata会保存在Meta Stream中。在字段树中,每一个非叶子节点记录的就是字段的metadata,比如对一个array来说,会记录它的长度。下图根据表的字段类型生成了一个对应的字段树。
在Hive-0.13中,ORC文件格式只支持读取指定字段,还不支持只读取特殊字段类型中的指定部分。
使用ORC文件格式时,用户可以使用HDFS的每一个block存储ORC文件的一个stripe。对于一个ORC文件来说,stripe的大小一般需要设置得比HDFS的block小,如果不这样的话,一个stripe就会分别在HDFS的多个block上,当读取这种数据时就会发生远程读数据的行为。如果设置stripe的只保存在一个block上的话,如果当前block上的剩余空间不足以存储下一个strpie,ORC的writer接下来会将数据打散保存在block剩余的空间上,直到这个block存满为止。这样,下一个stripe又会从下一个block开始存储。
三、索引
在ORC文件中添加索引是为了更加高效的从HDFS读取数据。在ORC文件中使用的是稀疏索引(sparse indexes)。在ORC文件中主要有两种用途的索引,一个是数据统计(Data Statistics)索引,一个是位置指针(Position Pointers)索引。
1. Data Statistics数据统计索引
ORC reader用这个索引来跳过读取不必要的数据,在ORC writer生成ORC文件时会创建这个索引文件。这个索引中统计的信息主要有记录的条数,记录的max, min, sum值,以及对text类型和binary类型字段还会记录其长度。对于复杂数据类型,比如Array, Map, Struct, Union,它们的子字段中也会记录这些统计信息。
在ORC文件中,Data Statistics有三个level。
(1)file level statistics
在ORC文件的末尾会记录文件级别的统计信息,会记录整个文件中columns的统计信息。这些信息主要用于查询的优化,也可以为一些简单的聚合查询比如max, min, sum输出结果。
(2)stripe level statistics
ORC文件会保存每个字段stripe级别的统计信息,ORC reader使用这些统计信息来确定对于一个查询语句来说,需要读入哪些stripe中的记录。比如说某个stripe的字段max(a)=10,min(a)=3,那么当where条件为a >10或者a <3时,那么这个stripe中的所有记录在查询语句执行时不会被读入。
(3)index group level statistics
为了进一步的避免读入不必要的数据,在逻辑上将一个column的index以一个给定的值(默认为10000,可由参数配置)分割为多个index组。以10000条记录为一个组,对数据进行统计。Hive查询引擎会将where条件中的约束传递给ORC reader,这些reader根据组级别的统计信息,过滤掉不必要的数据。如果该值设置的太小,就会保存更多的统计信息,用户需要根据自己数据的特点权衡一个合理的值。
2. Position Pointers位置指针索引
当读取一个ORC文件时,ORC reader需要有两个位置信息才能准确的进行数据读取操作。
(1)metadata streams和data streams中每个group的开始位置
由于每个stripe中有多个group,ORC reader需要知道每个group的metadata streams和data streams的开始位置。图1中右边的虚线代表的就是这种pointer。
(2)stripes的开始位置
由于一个ORC文件可以包含多个stripes,并且一个HDFS block也能包含多个stripes。为了快速定位指定stripe的位置,需要知道每个stripe的开始位置。这些信息会保存在ORC file的File Footer中。如图1中间位置的虚线所示。
四、文件压缩
ORC文件使用两级压缩机制,首先将一个数据流使用流式编码器进行编码,然后使用一个可选的压缩器对数据流进行进一步压缩。
一个column可能保存在一个或多个数据流中,可以将数据流划分为以下四种类型:
• Byte Stream
字节流保存一系列的字节数据,不对数据进行编码。
• Run Length Byte Stream
字节长度字节流保存一系列的字节数据,对于相同的字节,保存这个重复值以及该值在字节流中出现的位置。
• Integer Stream
整形数据流保存一系列整形数据。可以对数据量进行字节长度编码以及delta编码。具体使用哪种编码方式需要根据整形流中的子序列模式来确定。
• Bit Field Stream
比特流主要用来保存boolean值组成的序列,一个字节代表一个boolean值,在比特流的底层是用Run Length Byte Stream来实现的。
接下来会以Integer和String类型的字段举例来说明。
(1)Integer
对于一个整形字段,会同时使用一个比特流和整形流。比特流用于标识某个值是否为null,整形流用于保存该整形字段非空记录的整数值。
(2)String
对于一个String类型字段,ORC writer在开始时会检查该字段值中不同的内容数占非空记录总数的百分比不超过0.8的话,就使用字典编码,字段值会保存在一个比特流,一个字节流及两个整形流中。比特流也是用于标识null值的,字节流用于存储字典值,一个整形流用于存储字典中每个词条的长度,另一个整形流用于记录字段值。
如果不能用字典编码,ORC writer会知道这个字段的重复值太少,用字典编码效率不高,ORC writer会使用一个字节流保存String字段的值,然后用一个整形流来保存每个字段的字节长度。
在ORC文件中,在各种数据流的底层,用户可以自选ZLIB, Snappy和LZO压缩方式对数据流进行压缩。编码器一般会将一个数据流压缩成一个个小的压缩单元,在目前的实现中,压缩单元的默认大小是256KB。
五、内存管理
当ORC writer写数据时,会将整个stripe保存在内存中。由于stripe的默认值一般比较大,当有多个ORC writer同时写数据时,可能会导致内存不足。为了现在这种并发写时的内存消耗,ORC文件中引入了一个内存管理器。在一个Map或者Reduce任务中内存管理器会设置一个阈值,这个阈值会限制writer使用的总内存大小。当有新的writer需要写出数据时,会向内存管理器注册其大小(一般也就是stripe的大小),当内存管理器接收到的总注册大小超过阈值时,内存管理器会将stripe的实际大小按该writer注册的内存大小与总注册内存大小的比例进行缩小。当有writer关闭时,内存管理器会将其注册的内存从总注册内存中注销。
六、参数
参数名 | 默认值 | 说明 |
---|---|---|
hive.exec.orc.default.stripe.size | 256*1024*1024 | stripe的默认大小 |
hive.exec.orc.default.block.size | 256*1024*1024 | orc文件在文件系统中的默认block大小,从hive-0.14开始 |
hive.exec.orc.dictionary.key.size.threshold | 0.8 | String类型字段使用字典编码的阈值 |
hive.exec.orc.default.row.index.stride | 10000 | stripe中的分组大小 |
hive.exec.orc.default.compress | ZLIB | ORC文件的默认压缩方式 |
hive.exec.orc.skip.corrupt.data | false | 遇到错误数据的处理方式,false直接抛出异常,true则跳过该记录 |
本文在Hive-ORC文件存储格式的理论基础上,进一步分析一个实际的Hive ORC表中的数据存储形式。
一、表结构
库名+表名:fileformat.test_orc字段 | 类型 |
---|---|
category_id | string |
product_id | int |
brand_id | int |
price | double |
category_id_2 | string |
在hive中命令
desc formatted fileformat.test_orc;的结果如下图:
根据上图中的location信息,查看在HDFS上的文件:
二、查看dump文件
hive提供了一个--orcfiledump参数用于查看HDFS上ORC表格的文件信息,在hive-0.13版本中的使用方法为:
hive --orcfiledump <location-of-orc-file>,其他版本的使用方法可以去官方文档中查找。
下面是命令
hive --orcfiledump /user/hive/warehouse/fileformat.db/test_orc/000000_0的查询结果
三、dump文件分析
接下来的分析,请对照着文章 Hive-ORC文件存储格式 中的图1-ORC文件结构图进行。使用hql语句,统计出各字段的count, min, max, sum信息如下:
字段 | COUNT | MIN | MAX | SUM |
---|---|---|---|---|
category_id | 1000000 | 5011 | 975673 | 4.0222868968E11 |
product_id | 1000000 | 968 | 50997770 | 27158964508399 |
brand_id | 999130 | 0 | 1026427 | 774991825568 |
price | 1000000 | -0.0092 | 358000.0 | 1.8953626711045265E8 |
category_id_2 | 1000000 | 5010 | 5996 | 5.183530839E9 |
从dump文件的图片中可以看出,大致分成四个部分:
1、表结构信息
记录整张表的记录数,压缩方式,压缩大小,以及表结构。在表结构部分,ORC将整张表的所有字段构造成一个大的struct结构。对应图1-ORC文件结构图中的Postscript部分。2、Stripe统计信息
统计当前HDFS文件对应Stripe的信息,包括各个字段的count,min, max, sum信息。对于最外层的Struct,只统计其count值。由于这张表数据量不大,当前HDFS文件中只有一个Stripe。对应图1-ORC文件结构图中的Stripe Footer部分。3、File统计信息
统计内容和第二部分一致,不过这里统计的整张表的每个字段count, min, max, sum信息。对应图1-ORC文件结构图中的FileFooter部分。这里我们将dump文件中的统计信息,与各字段实际统计信息作对比。通过与上面表格中各字段统计信息对比,发现对于int类型和double类型的字段,min, max, sum的结果都是匹配的。但是对于string类型的字段,仅仅只有min, max统计结果一致,sum的结果不相同。
4、Stripe详细信息
统计各Stripe的offset,总记录行数等Stripe层次的信息。该Stripe中各字段的Index Data和Row Data,以及每个字段的编码方式。前面一行Stripe: offset: 3 data: 7847351 rows: 1000000 tail: 132 index: 7936应该也是保存在FileFooter中,后面各个字段统计信息对应图1-ORC文件结构图中的Index Data和Row Data部分。
从dump文件中的数据可以看出,每个字段的ROW_INDEX以及DATA信息是保存在一块连续空间中的,这块文件从offset=3开始。这也说明图1-ORC文件结构图中Row Data区的数据紧随Index Data区数据之后。
Index Data数据统计:
起始位置 | 字段 |
---|---|
3……21 | STRUCT |
22……1141 | category_id |
1142……3056 | product_id |
3057……5135 | brand_id |
5136……7201 | price |
7202……7938 | category_id_2 |
起始位置 | 字段 | 描述 |
---|---|---|
7939……59887 | category_id | 字段对应词条int流 |
59888……59898 | category_id | 词条长度int流 |
59899……60989 | category_id | 字典词条数据 |
60990……3525432 | product_id | 实际数据int流 |
3525433……3527085 | brand_id | 标识IF NULL的byte流 |
3527086……5708142 | brand_id | 实际数据int流 |
5708143……7855016 | price | double类型 |
7855017……7855212 | category_id_2 | 字段对应词条int流 |
7855213……7855219 | category_id_2 | 词条长度int流 |
7855220……7855289 | category_id_2 | 字典词条数据 |
这部分最后记录了每一个字段的存储方式,统计如下
字段 | 类型 | 存储方式 |
---|---|---|
STRUCT | DIRECT | |
category_id | String | DICTIONARY_V2 |
product_id | Int | DIRECT_V2 |
brand_id | Int | DIRECT_V2 |
price | Double | DIRECT |
category_id_2 | String | DICTIONARY_V2 |