数据存储格式

news2025/1/20 11:54:27

文章目录

  • 数据存储格式
    • 1 行列存储比较
    • 2 ORC文件格式
      • 2.1 文件级
        • 2.1.1 Post scripts
        • 2.1.2 File Footer
        • 2.1.3 File MetaData
      • 2.2 Stripe级
        • 2.2.1 Stripe Footer
        • 2.2.2 Row Data
        • 2.2.3 Index Data
    • 3 Parquet文件格式
      • 3.1 Header
      • 3.2 Data
        • 3.2.1 Row Group
        • 3.2.2 Column Chunk
        • 3.2.3 Page
      • 3.3 Footer
        • 3.3.1 Index
        • 3.3.2 MetaData
    • 4 ORC和Parquet对比

数据存储格式

1 行列存储比较

数仓实际开发中常用的数据存储格式主要分为3类,TextFile、ParquetFile、ORCFile。其中TextFIle为行式存储,Parquet和ORC都是列式存储,这里先说一下行式存储和列式存储区别。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OAMpqcKq-1671615081745)(/Users/jinlong/data/Typora_WorkSpase/存储格式01.png)]

如上图所示,行式存储在查询的数据的时候是整行的扫描,不管查询的数据是否只是其中的几列都会把整行的数据进行扫描。而列式存储在查询数据的时候只会扫描所查询的列。这样比较列式存储的查询效率相对于行式存储的查询效率是要高出很多的。

行存储列存储
查询查询时整行扫描,效率较低查询时只扫描需要查询的列,效率较高
存储不利于压缩,压缩比较差,占用空间多列式存储的时候可以为每一列创建一个字典,存储的时候就仅存储数字编码即可,降低了存储空间需求
写入每行包含所有字段,插入和修改效率较高每列数据单独存储,插入和修改效率较低

2 ORC文件格式

ORC文件的总体结构如下图

结构说明

主体说明
Stripe是ORC文件存储数据的地方,默认250M。
Index Data轻量级的index,默认是每10000行数据做一个索引。索引记录的是字段在’Row Data’中的offset(偏移量),并且还记录了每个字段的最大值和最小值,如感兴趣可查看源码。
Row Data就是存储具体数据的地方,存储方式是先取部分行,再将部分行中的数据按列存储,每列还可能分为多个Stream。
Stripe Footer存储各个Stream的类型长度等信息。
File Footer存储每个Stripe的行数、每个Column的数据类型、每列的最值和聚合信息等内容。
Postscript记录了整个文件的压缩类型、文件版本、File Footer的长度相关信息。

ORC格式的文件对数据的查询和索引分为3个层次:文件级、Stripe级、Row Group级。也就是说在扫描数据的时候只会扫描到这个文件的某个Stripe下的部分Row Group,这样就大大减少了需要读取的数据量。读取步骤如下图:

2.1 文件级

ORC文件首先是通过file tail来记录文件级别的元数据信息,这样有利于在SQL查询时更快的跳过没必要查询的文件。file tail主要由postscripts、file footer、file metastore自下而上组成,这三部分都是使用Protocol Buffer存储(提供添加字段不需要改写reader的能力),如下图所示:

2.1.1 Post scripts

上面有提到Post scripts存储了File Footer、File MetaData长度信息、文件版本信息、文件压缩类型信息。这里再对Post scripts做一个详细说明,Post scripts部分永远不会被压缩并且在文件末尾前一个字节结束(也就是说整个File tail的长度=FooterSize + MetaDataSize + PostscriptsSize + 1byte)。ORC文件的读取过程是从底部向上读取,通常ORC Reader会直接读取文件的最后16KB,这部分同时包含了Footer和Postscripts。文件的最后一个字节包含了Post scripts的序列化长度,该长度必须小于256byte。当Post script被解析后,Footer的压缩序列化长度就可以获得,从而就可以被解压缩和解析。

代码信息如下:


message PostScript {
 // Footer长度
 optional uint64 footerLength = 1;
 // 压缩类型
 optional CompressionKind compression = 2;
 // 压缩块大小
 optional uint64 compressionBlockSize = 3;
 // writer的版本信息
 repeated uint32 version = 4 [packed = true];
 // 文件元数据长度
 optional uint64 metadataLength = 5;
 // 魔数,为了确定此结构式符合规范的ORC文件
 optional string magic = 8000;
}
 
enum CompressionKind {
 NONE = 0;
 ZLIB = 1;
 SNAPPY = 2;
 LZO = 3;
 LZ4 = 4;
 ZSTD = 5;
}

2.1.2 File Footer

File footer部分包含了文件主体的布局,类型schema信息、行数、每列的统计信息。从‘ORC File’的整体结构图中可以看出ORC文件整体主要分为三大部分Header、Body、Tail,Header部分由’ORC’组成、Body包含索引和行、Tail则是文件信息,File Footer就包含这三部分的元信息。

代码信息如下:


message Footer {
 // Header长度,始终为3
 optional uint64 headerLength = 3;
 // 文件头和正文长度
 optional uint64 contentLength = 2;
 // Stripes的数量
 repeated StripeInformation stripes = 3;
 // 结构类型信息
 repeated Type types = 4;
 // 用户元数据信息
 repeated UserMetadataItem metadata = 5;
 // 行数
 optional uint64 numberOfRows = 6;
 // 文件中每列的统计信息
 repeated ColumnStatistics statistics = 7;
 // 索引项中的最大行数
 optional uint32 rowIndexStride = 8;
 // Each implementation that writes ORC files should register for a code
 // 0 = ORC Java
 // 1 = ORC C++
 // 2 = Presto
 // 3 = Scritchley Go from https://github.com/scritchley/orc
 // 4 = Trino
 optional uint32 writer = 9;

2.1.3 File MetaData

File MetaData主要记录的Stripe级别的统计信息,上面File Footer中的ColumnStatistics是文件级别的信息。这些统计信息可以通过谓词下推跳过部分Stripe的读取。代码信息如下:

message StripeStatistics {
 repeated ColumnStatistics colStats = 1;
}
 
message Metadata {
 repeated StripeStatistics stripeStats = 1;
}

2.2 Stripe级

ORC文件的主体就是由一系列的Stripe组成的,通常每个Stripe的大小约为250M且彼此独立。ORC文件中每列都是由多个Steam组成的,这些Steam彼此相邻,通过文章最开始的图是可以看到的。

对于二进制数ORC使用三个Steam:Present、Data、Length,它们存储每个值的长度。每个Stripe都是由Stripe Footer、Row Data、Index Data组成,如下图;

2.2.1 Stripe Footer

Stripe Footer包含了每列的编码信息、Stream的元信息(如位置等),代码信息如下:

message StripeFooter {
 // Stream的位置信息
 repeated Stream streams = 1;
 // 字段编码信息
 repeated ColumnEncoding columns = 2;
 optional string writerTimezone = 3;
 // 字段加密信息
 repeated StripeEncryptionVariant encryption = 4;
}

2.2.2 Row Data

Row Data存储了真正的数据内容,这也是ORC文件核心所在。一个文件分为多个Stripe,每个Stripe负责一个或多个Row Group(每个Row Group默认为10000行),如下图:

Stream类型说明

类型位置说明
PRESENTRow Data基本上在各Stripe对应所有列的位置都会出现,按位标记是否非NULL。
DRow Data在各Stripe的基本类型列中出现(也就是不包含struct、map、list等复杂嵌套类型),记录数据内容本身。
LENGTHRow Data在各Stripe中string、varchar、char、list、map等需要记录每个值的长度的列出现,顾名思义。
DICTIONARY_DATARow Data在各Stripe中string、varchar、char等采用了字典编码(类似RLE也是一种减小文件占用存储的技术,重复值只记录一次,并记录各重复值在文中出现的位置)的字符类型列出现,用来记录该列所有的distinct值(即重复内容只记录一次)。
SECONDARYRow Data在各Stripe中decimal、timestamp等列中出现,用来和DATA Stream搭配(副手),例如timestamp类型的列中,DATA Stream中记录该列在当前Stripe范围中每一行的秒值,而SECONDARY Stream就记录该列在当前Stripe范围中每一行的纳秒值。
ROW_INDEXIndex Data存储当前列在该Stripe中某一个Stream的某个row group的起始位置和列偏移量,以及当前列在该Stripe的某个row group中的Statistics统计信息。
BLOOM_FILTERIndex Data用于记录当前列在该Stripe中每一个row group的布隆过滤器信息,用于谓词下推跳过不用读取的行组。

2.2.3 Index Data

关于Index Data上面介绍过,索引记录的是字段在’Row Data’中的offset(偏移量),并且还记录了每个字段的最大值和最小值。而实际上Index Data同样是以Stripe Stream的形式存在,Bloom Filter Stream与Row Group Index Stream是在每个Stripe头上交错存储的。 这种布局便于在单次读取操作中同时读取bloom stream和row index stream,如下图所示:

3 Parquet文件格式

Parquet文件同样是列式存储,下面介绍一下Parquet文件的内部存储结构。

官网文件结构:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xd745MLx-1671615081746)(/Users/jinlong/data/Typora_WorkSpase/存储格式09.gif)]

上图就是官网上文件结构图,个人感觉这个结构图看起来不是很直观,这里对文件结构做了一个简单的拆解,如下图:

结构说明

主体说明
Header只有4byte,用来体现文件类型,PAR1代表平普通Partquet文件,PARE代表加密Partquet文件
Data存放真实数据的地方,以Row Group为单位,Row Group又拆分成Column,Column继续拆分成Page,Page就是最小存储单元。
Index记录所有的索引包括Min-Max Index、Column Index 、Offset Index、Bloom Index。其实Index归属于Footer内的,这里单独列出来了是为了更直观体现Parquet文件结构。
FooterFooter内部包含了索引信息和元数据信息,索引上面介绍了,元数据包括File MetaData、Row Group MetaData、、Column MetaData。

3.1 Header

Header在上面的表格中已经进行阐述了,Header内部记录的信息很少,说直白就是起到了一个铭牌作用。Header内部会有一个魔数(Magic Number)同ORC文件类似,这个魔数会标记Parquet文件的类型,是属于普通文件还是加密文件。

3.2 Data

Data在这里是指一个逻辑概念,内部细分成了Row Group、Column Chunk、Page,这里先对Row Group进行一下说明。

3.2.1 Row Group

实际生产环境中,我们以一个数据集作为一个对象进行存储,数据集中包含的数据从几十条到几十亿条不等,所以在存储成Parquet文件时会先进行一次水平的切分,就会讲数据切成一个或多个切片,这些个切片就是我们说到的’Row Group’。进行水平切分的主要原因还是因为HDFS存储数据的单位是数据块(Block),一般默认是128M一个数据块,如果没有将数据集进行水平切分,只要数据集足够大(超过128M),一条record的数据就会跨越多个数据块,这会导致什么呢?显而易见会大大增加IO开销。在Parquet的官方文档中可以看到,是建议我们将HDFS中的数据块大小设置为1G的,并且把Parquet的parquet.block.size设置为1G,目的就是将一个Row Group存放在一个数据块中。

HDFS中数据块和实际Parquet文件的Row Group对照关系如下图:

这里的对照图只是理想状态下的对照关系,在实际生产中存在各种复杂的业务,并不容易做到这样理想的存储方式。

3.2.2 Column Chunk

上面提到了会将数据集进行水平切分,其中的切片就是Row Group,而Column Chunk就是再将Row Group进行垂直切分得到的,将Row Group进行垂直切分后会得到一个或多个列,每一列就是Column Chunk,切分完成后,再将这些Column Chunk顺序保存。

结构切分如下图所示:

上图只是对切分过程做一个更直观的展示,最终切分完成数据结构并不是如图所示。最终的数据还是以列进行顺序存储。

3.2.3 Page

Page就是Parquet文件存储数据的最小单元,每个Page的大小默认为1M,其实将数据切分到Column Chunk时结构已经很简单了,再将结构进行进一步切分主要是为了让数据读取的粒度足够小,便于单条或者小批次的数据查询,而且由于Page是最小的存储单位,所以同时Page也就作为压缩的最小单位,如果没有Page这一级别就会对整个Column Chunk进行压缩,将整个Column Chunk压缩后就不能读取中间的数据,必须将整个Column Chunk进行解压才能读取数据,这样就会降低查询效率。

Page结构如下图:

名称说明
Page Header这里面记录的就是一种数据交换格式同ORC文件相同使用的都是Protocol Buffer。
Repetition levels主要用来表达数组类型字段的长度,但它并不直接记录长度,而是通过记录嵌套层级的变化来间接地表达长度,即如果嵌套层级不变,那么说明数组还在延续,如果嵌套层级变了,说明前一个数组结束了
Definition levels与repetition level类似的,definition level主要用来表达null的位置。因为Parquet文件里不会显式地存储null,所以通过definition level来判断某个值是否是null。
values顾名思义,记录的就是实际数据值。

3.3 Footer

其实Footer中主要包含了两种内容,一是Index,二是MetaData。Footer实际就是Parquet元数据大本营,读取Parquet文件的时候第一步就是读取Footer中的内容,通过Footer中记录的元数据和索引数据进一步查找对应的实际数据。

3.3.1 Index

Parquet文件中的索引数据主要分为Min-Max Index、Column Index、Offset Index、Bloom Index。

名称说明
Min-Max Index记录Page内部Column的Min_Value和Max_Value的索引位置。
Column Index记录了每个ColumnChunk的全部Page的Min-Max Value索引位置。
Offset Index记录Page在文件中的offset和Row Range索引位置。
Bloom Index记录布隆过滤器的索引位置。对于基数较大,或者非排序的过滤场景,Min_Max很难发挥作用,BloomFilter可以加速过滤匹配。

3.3.2 MetaData

Footer中的元数据主要分类三类,文件元数据(File MetaData)、行组元数据(Row Group MetaData)、字段元数据(Column MetaData)

名称说明
File MetaData记录了文件的版本信息、结构信息(SchemaInfo)、键值对信息(Extra key/value pair)。
Row Group MetaDataRow Group即为Block,这部分记录的就是Block的offset和size。
Column MetaData记录了Column Chunk的offset和size。

如下图:

4 ORC和Parquet对比

比较项ParquetORC
现状Apache顶级项目、来源、列式存储Apache顶级项目、来源、列式存储
公司Twitter/ClouderaHortonworks
列编码支持多种编码、字典。REL、delta等支持主流编码,与Parquet类似
嵌套式结构支持比较完美多层级嵌套,表达起来复杂,底层未采用google dremel类似实现,性能和空间损失较大
ACID不支持支持
Update不支持支持
索引统计信息粗粒度索引,block/group/chunk级别统计信息粗粒度索引,file/Stripe/row级别统计信息
查询性能ORC较高ORC较高
数据压缩能力ORC较高ORC较高
支持查询引擎hive/impala/drillHive

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/105985.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

正则表达式判断数字

判断 正负整数,正负小数 表达式: ^[-]?([0]{1,1}|[1-9]{1,1}[0-9]*?)[.]?[\\d]{1,}$ import java.util.Scanner; import java.util.regex.Pattern; public static void main(String[] args) { Pattern pattern Pattern.compile("^[-]?([0]{1,1}|[1-9]{1,1}…

3dtiles数据解析

1.解析json文件 2.解析b3dm模型 (1)b3dm模型文件时二进制文件,其中包含glTF文件: 当使用tiny_gltf库解析glTF时,需要减去(28byte featuretable的byte batchTable的byte ): bool TinyGLTF::ExtractGltfFromMemory(Model *model,std::string…

JVM - 内存区域划分 类加载机制 垃圾回收机制

目录 1. 内存区域划分 2. 类加载 2.1 双亲委派模型 3. 垃圾回收机制 (GC) 3.1 如何判断一个对象是否为 "垃圾" 3.1 可达性分析 3.2 垃圾回收算法 1. 内存区域划分 JVM 作本质上是一个 Java 进程, 它启动的时候, 就会从操作系统申请一大块内存, 并且把这一大块…

CSS学习(七):盒子模型,圆角边框,盒子阴影和文字阴影

原文链接:CSS学习(七):盒子模型,圆角边框,盒子阴影和文字阴影 1. 盒子模型 页面布局要学习三大核心:盒子模型,浮动和定位。学习好盒子模型能非常好的帮助我们页面布局。 1.1 看透…

肽基脯氨酰异构酶底物:1926163-51-0,WFY-pSer-PR-AMC

WFYpSPR-AMC, Pin1底物类似显色底物H- trp - phi - tir - ser (PO₃H₂)-Pro-Arg-pNA。 磷酸肽在生命过程中发挥重要作用,磷酸化的位置在多肽上的Tyr、Ser,Thr,。目前磷酸肽合成一般都采用磷酸化氨基酸,目前使用的都是单苄基磷酸化…

Kafka Producer - 分区机制实战

Kafka Producer - 分区机制实战 上一篇介绍了kafka Producer 生产者发送数据的程序代码,以及对生产者分区机制的相关介绍,今天继续深入的了解下分区机制的原理、测试验证、自定义分区。 在学习之前先在本地机器搭建一个单机版的双节点集群环境&#xf…

80.【Spring5】

Spring《解耦》(一)、Spring 简介1.历史:2.Spring 目的3.Spring 引入4.优点5.Spring 七大模块组成:6.扩展(约定大于配置)(二)、IOC理论推导(Inversion of Contro)1.以前的三层分级2.现在对三层架构的更新3.什么是IOC(三)、HelloSpring1.怎么使用Spring?…

技术分享 | 缓存穿透 - Redis Module 之布隆过滤器

作者:贲绍华 爱可生研发中心工程师,负责项目的需求与维护工作。其他身份:柯基铲屎官。 本文来源:原创投稿 *爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。 一、场景案例 假…

设计模式-抽象工厂模式

1、什么是抽象工厂模式 抽象工厂(AbstractFactory)模式的定义:是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。抽象工厂模式是工厂方法模式的…

Tiny ImageNet 数据集分享

ImageNet官网上的数据集,动辄就100G,真的是太大了。 有需要Tiny Image Net 数据集的小伙伴可以点击这个下载链接: http://cs231n.stanford.edu/tiny-imagenet-200.zip数据集简介: Tiny ImageNet Challenge 来源于斯坦福 CS231N …

uwb无线定位系统的原理和介绍

uwb无线定位系统是在 uwb平台上部署的定位基站,通过发射无线信号,将 uwb定位系统部署在需要安装的位置,同时结合定位基站所支持工作环境条件(如:温度、湿度、光照等)和定位算法,实现在不同的地理…

使用elesticsearch-7.10.0版本连接elasticsearch-head

背景: 由于esasticsearch-5.5.1中没有登录,登出的安全校验,在安全测评时,经常被检查到高危漏洞,因此项目经常要升级到es7版本。 问题一:jdk版本不满足要求,提示如下 future versions of Elasti…

Js实现轮盘抽奖功能,一招帮你解决选择困难症

不知道今天自己该吃什么,一招帮你解决选择困难症。 通过htmlcssjs实现一个轮盘抽奖功能。我们可以将平时吃的饭菜输入到代码中,每到纠结的时候只需点开抽一次就可以了。 实现步骤 html代码: 整体实现的结构是一个大的圆形,分成…

热门项目披露:成都双流板桥轨道城市发展有限公司100%股权转让

热门项目披露:成都双流板桥轨道城市发展有限公司100%股权转让;该项目由 西南联合产权交易所 发布,于2022年12月9日被塔米狗平台收录。 项目方 成都双流板桥轨道城市发展有限公司, 成立于 2021年9月7日 , 注册资金 100…

域控制器交付量「翻番」,汽车中间件赛道竞争升级

作为软件定义汽车的关键环节,智能汽车中间件赛道,正在成为兵家必争之地。 从传统IT架构的角度看,中间件位于上层应用和底层操作系统之间;除了基础的通信交互外,中间件还承载着屏蔽底层复杂性的功能,向下适配…

005:UITableView

介绍: 提示:数据量大、样式较为统一、分组的需要以及滚动的需求。 图示: UITableViewDataSource: 提示UITableView作为视图,只负责展示,协助管理,不管数据需要开发者为UITableView提供展示需…

Framework底层原理——Binder调用流程分析

binder是一个非常好的跨进程通信工具,Android对其进行了各种封装,虽然我们用起来简单,但是理解起来却比较困难。 1.自己设计一个跨进程通信机制 在理解binder之前呢,首先我们想一下,如果我们自己设计一个跨进程通信的…

简单Thinkphp5.1如何使用Topsdk\Topapi

一淘模板(56admin.cn)给大家介绍tp5.1相关知识,其中主要记录tp5.1是怎么使用Topsdk\Topapi(对接淘宝客开放平台),希望对需要的朋友有所帮助! 1、公司有一项目需要对接淘宝开放平台 先去申请帐号…

tensorrt debug问题汇总

目录 1. Dynamic dimensions required for input: input, but no shapes were provided. Automatically overriding 2. sampleMNIST.obj : error LNK2019: 无法解析的外部符号 cudaStreamCreate 3. Assertion failed: (smVersion < SM_VERSION_A100) &&…

条码管理系统,助力企业打造轻量级数字化车间

在原辅材料供应、生产管理、仓储物流、市场营销等相关业务环节中,采取适当的软硬件技术手段,实时记录产品信息。通过查询可以随时跟踪产品的生产状态、仓储状态和流向,达到可追溯管理的目的。随着制造企业对精细化管理要求的提高,…