ORC文件介绍
什么是ORC文件
ORC的全称是(Optimized Row Columnar),ORC文件格式是一种Hadoop生态圈中的列式存储格式,它的产生早在2013年初,最初产生自Apache Hive,用于降低Hadoop数据存储空间和加速Hive查询速度。ORC文件是以二进制的方式存储的,不可以直接读取,但由于ORC的自描述特性,其读写不依赖于 Hive Metastore 或任何其他外部元数据。本身存储了文件数据、数据类型及编码信息。因为文件是自包含的,所以读取ORC文件数据无需考虑用户使用环境。ORC具有以下一些优势:
- ORC是列式存储,有多种文件压缩方式,并且有着很高的压缩比。
- 文件是可切分(Split)的。因此,在Hive中使用ORC作为表的文件存储格式,不仅节省HDFS存储资源,查询任务的输入数据量减少,使用的MapTask也就减少了。
- 提供了多种索引,row group index、bloom filter index。
ORC文件结构

ORC文件由三大部分组成:Header, FileTail 和 Stripes
Header
在整个orc文件的最开始,会有orc三个字,用来标识文件类型,这个可以称为文件的Header
Stripes
一个ORC文件包含一个或多个stripe, 一组行形成一个stripe,每次读取文件是以行组为单位。每个stripe之间相互独立。细节架构如下图所示

每个Stripe包含以下三个部分:
- Index Data:各column的最大值,最小值,及每个数据段在stripe中的位置
- Row Data:实际存储数据的单元,利用列存储原理,对不同列可以实现不同的压缩方案,所有的列数据可以组成行数据。
- Stripe Footer:它包含每列的编码形式、Stream的元信息
Stream保存了用户真正关心的业务数据内容,这也是ORC列式存储的根本所在:正如上面的架构图一样,一个大文件由各Stripe分割,每个Stripe负责一个或多个行组(一个行组默认10000行),在一个Stripe范围内,各列的数据内容以Stream的形式按列存储。为了描述每个Stream,ORC以字节为单位存储Stream的类型、列ID和Stream的大小。每个Stream中存储内容的详细信息取决于列的类型和编码。也就是说,在一个Stripe中的每一列都可能有多个表示不同信息的Stream。
FileTail
FileTail包含以下三个部分:Postscript, File Footer, File Metadata
Postscript: 文件的最后一个字节保存着PostScript的长度,它的长度不会超过256字节,PostScript提供了解释文件其余部分的必要信息,包括文件的 Footer 和 Metadata 部分的长度、文件的版本以及使用的一般压缩类型(例如 none、zlib 或 snappy)、文件内部每个压缩块的最大长度(每次分配内存的大小)以及一些版本信息。
File Footer: 包含文件主体的布局,类型架构信息,行数和每个列的统计信息。
File Metadata: 该部分包含各Stripe级别粒度的列统计信息,File Footer中的列统计信息是文件级别的
官网链接:orc.apache.org/specificati...
Trino读取ORC文件
以TableScanOperator读取数据的流程为例:
整个读取的调用链路流程还是挺清晰明了的,但最重要的是StripeReader是如何去读取构造Stripe文件的,需要分析代码来研究
读取各个列的数据
首先通过StripeFooter获取需要读取的各个列的streams

接着将目标streams转换为一组<StreamId, DiskRange>的map, DiskRange代表这个stream的offset和length, 标明stream的位置。然后调用readDiskRanges方法去读取目标位置的数据
在readDiskRanges方法中,会把每个stream的offset转换为orc file中的绝对值offset,然后调用orcDataSource去构造每个stream的orcDataReader
在orcDataSource的readFully方法中,会根据每个diskRange的length, 区分为smallRagne和largeRange, 默认的区分大小为8M, 然后采用不同的方法进去读取

对于smallRanges, 会通过mergeAdjacentDiskRanges方法判断相邻的两个smallRange,如果merge之后的diskrange小于8M,且该两个smallRange的距离小于默认值1M,则合并成新的diskRange。
然后判断是否设置了lazy read(默认为True), 如果是,则给每个diskRange构造对应的MergedOrcDataReader, 在真正使用数据的时候才会去读取。否则通过直接从hdfs或者其他数据源接口读取数据放在内存中,给每个diskRange构造对应的MemoryOrcDataReader。
对于largeRanges, 会对每个largeRange构造对应的DiskOrcDataReader。在DiskOrcDataReader中,默认每次最大读取8M的数据。
利用索引来进行过滤
读取完数据后会从ORC文件中读取RowGroupIndex和BloomFilter
RowGroupIndex其实就是min max索引,就像之前提到的,index data中会存有每个列的最大值和最小值,就用该数据来构造RowGroupIndex。当判断条件中有大于小于时,则可用RowGroupIndex来进行过滤,而当查询条件中包含对某个列的等值判断时,就可以使用BloomFilter中判断是否包含该值,进行判断是否进行过滤。
在对stripe使用RowGroupIndex和BloomFilter过滤完成后,剩下的selectedRowGroups就是我们真正要读的数据


构造新的RowGroups和Stripe
根据上一步拿到的过滤后的数据来构造新的RowGroups和Stripe作为返回数据。

最后从每个列的columnReader中读取数据,每个列封装为一个block,多个block组合成page对象。Trino中对数据的操作就是基于这个page对象的。