应用三阶段
01.初步了解如何使用这种格式?
02.进一步如何优化这种格式
03.再然后是在面对新的问题时候,如何解决问题
格式具体是如何设计的,以及为什么这样设计?
这个系统为了解决什么问题?为此提供了什么功能?
数据持久化 是将内存中的数据模型转换为存储模型,以及将存储模型转换为内存中的数据模型的统称.
据持久化对象的基本操作有:保存、更新、删除、加载、查询等
二进制持久化
--序列化和反序列化 Parquet文件格式选用 thrift完成文件元数据的序列化和反序列化
--索引
数据模型
Parquet 的 数据模型 也是 schema 表达方式, Parquet文件格式选用 thrift完成文件元数据的序列化和反序列化。
在parquet-format项目的thrift目录下,文件parquet.thrift详细定义了parquet文件的元数据类型 用关键字 message 表示
schema协议
采用类似于Protobuf 协议来描述,每个字段有三个属性:重复性(Repetition)、类型 (Type)和名称 (Name)
optional 可选,required 必需和 repeated 重复字段
Parquet的设计者引入了两个新的概念:
repetition level 和 definition level。这两个值会保存额外的信息,可以用来重构出数据原本的结构
repetition level主要用来表达数组类型字段的长度,但它并不直接记录长度,而是通过记录嵌套层级的变化来间接地表达长度
repetition level主要用来表达数组的长度
definition level主要用来表达null的位置
对非数组类型的值不保存repetition level”,“对必填字段不保存definition level”等,真正存储这两个level时,也使用的是bit-packing + RLE编码
工程实现
Parquet 的 存储模型 主要由行组(Row Group)、列块(Column Chuck)、页(Page)组成
这个系统为了解决什么问题?为此提供了什么功能?
实质:列式存储一个类型包含嵌套结构的数据集。
Header data index Footer
data: Row-group
ColumnChunk ColumnChunk ColumnChunk
page page page page page
01.存储的对象是一个数据集,而这个数据集往往包含上亿条record,所以我们会进行一次水平切分,把这些record切成多个“分片”,每个分片被称为Row Group
02.水平切分之后,就轮到列式存储标志性的垂直切分
repetition level 和 definition level。
把一个嵌套结构打平以后拆分成多列,其中每一列的数据所构成的分片就被称为Column Chunk
03.Page是Parquet文件最小的读取单位,同时也是压缩的单位
Column Chunk,Parquet会进行最后一次水平切分,分解成为一个个的Page。每个Page的默认大小为1m
Header的内容很少,只有4个字节,本质是一个magic number,用来指示文件类型
“PAR1”代表的是普通的Parquet文件,
“PARE”代表的是加密过的Parquet文件。
Index是Parquet文件的索引块,主要为了支持“谓词下推”(Predicate Pushdown)功能
Parquet的索引有两种 一种是Max-Min统计信息,一种是BloomFilter
Footer是Parquet元数据的大本营,包含了诸如schema,Block的offset和size,Column Chunk的offset和size等所有重要的元数据。
另外Footer还承担了整个文件入口的职责
元数据信息存储在数据之后,包含了所有列块元数据信息的起始位置。
读取的时候首先从文件末尾读取文件元数据信息,再在其中找到感兴趣的 Column Chunk 信息,并依次读取
Block-File Parquet 是以二进制存储数据
Parquet 不使用任何分隔符来分隔数据。相反,它依赖于编码和压缩方案来有效地存储和检索数据
Parquet 常用的编码技术包括:
字典编码(dictionary encoding)
Run-length encoding(RLE):用来优化具有重复值的列
位打包(bit packing):用来优化具有小整数值的列
Parquet 中常用的压缩算法有 Snappy、Gzip 和 LZO
序列化阶段
1.使用编程语言内置的序列化机制
2.使用一种广泛支持的、与语言无关的格式,比如 JSON (或者 XML
3.使用Thrift, Protocol Buffers 或 Avro 高效的跨语言数据序列化和代码生成
Thrift、ProtoBuf 和 Avro 都支持模式演进--用于接口和数据升级
Avro 和 ProtoBuf 标准化了单一的二进制编码,
而 Thrift 则 包含 了各种不同的序列化格式(它称之为“协议”)
Iceberg数据存储格式
Iceberg 是一个分布式列式存储库
开放式表格式则充当元数据抽象层
1.Iceberg表结构
Table Tablet Snapshot
Manifest Partition Block
表(Table):存储数据的逻辑单元 Tablet(分片 Snapshot(快照):代表一个Table在某个时间点的数据状态,由一份或多份Tablet组成
Manifest(清单):描述Table状态的元数据,包括Table的Schema(模式)、Partition Spec(分区规范)和Current Snapshot ID(当前快照ID)等信息。
Partition(分区):将数据按照指定规则分隔成的逻辑单元,
Block(块):其中存储的是Partition的数据,每个块都有一个唯一的ID,块的大小可以在表级别进行配置
每一个 snapshot 对应一组 manifest,每一个 manifest 再对应具体的数据文件
2. Iceberg数据文件 是指实际存储表格数据的文件,通常以Parquet格式存储
3. Snapshot 表快照 :记录了表格在某个特定时间点的状态和元数据信息,包括架构、分区规则、数据文件等等。
data files 数据文件 是实际存储表格数据的文件,通常以Parquet格式存储
Manifest 清单列表 :是Iceberg表格的所有数据文件的列表,包括数据文件的元数据信息,如大小、数据量、创建时间、分区信息等等。
CyberRT_Recorder和ROS_Bag
Bag 的设计思路
消息的保存和读取 就涉及到一个广义上的问题序列化和反序列化
序列化 :
反序列化; 通过消息类型名称(字符串)来生成对象,这在很多语言中叫做反射(reflection
何快速的查找-索引
把一个包拆分为几个块(Chunk),而chunk中有消息的时间段
index data则更进一步,直接索引了不同类型的消息在块中的时间戳和偏移
1.消息的持久化
01.序列化和反序列化
02.消息索引
2. 序列化和反序列化
Rosbag的序列化和反序列化是自己实现的,ros是单个消息序列化,apollo是整个chunk序列化
apollo record则采用了protobuf来进行序列化和反序列化
3.消息索引
4.功能
Bag header 头信息放在文件首部,
Chunk info 块的索引和消息元信息的索引都是放在文件的末尾-2个统计record都会保存在文件末尾。
Index data
具体示例
Rosbag storage format
概念:
Bag
header_len/header/data_len/data
header/Chunk/Connection/Message data/Index data/Chunk info
Rosbag 文件由许多的record组成,每个record由header和data组成, header和data,还需要保存header_len和data_len
六种 record
Bag header(op=0x03) Message data(op=0x02) Index data(op=0x04) Chunk(op=0x05) Chunk info(op=0x06) Connection(op=0x07)
Chunk 用来存储链接(connection)和消息数据(message data)
信息头(Headers)。每个记录头包含一系列 name=value 字段 Op 码所有的信息头必须包含Op码字段
record0 Bag header 主要存放bag包整体的信息,必须是第一个 record
record1:
Message data 块的结构之一,消息序列化之后以二进制存储,
通过Connection获取消息格式后进行反序列化
Connection 块的结构之一,存放信息的格式信息,有了消息的格式,才能解析消息
Chunk 主要的数据结构,可以被压缩,可以理解为把N个消息打包为一个块(Chunk) 一个块中可能有多种不同的消息
Chunk info 块的结构之一,主要描述块的信息,例如消息的起始和结束时间等
Index data 索引数据,因为一个块比较大,索引消息在块中的位置
数据结构的读取顺序为:
先解析 Bag header,获取到 index_pos
然后跳到 index_pos 读取 Connection
接着读取 Chunk info,上述2个步骤相当于建立起了整个Bag包,块的索引
接着根据 Chunk info 逐个读取 Chunk ,先解析Chunk,获取压缩类型和数据大小
接着跳到 Chunk 尾部解析 Index data ,这里是一个消息对应一个 index
最后根据 index data 实例化消息(通过接口 instantiateBuffer )
CyberRT Record文件由许多的 Section 组成
Header
Section Type: ChunkHeader ChunkBoady RecordInfo Index Channel
Section 0:第一个Section 0为HEADER类型。Header中指明了索引区Index的位置
Section 1:
Index Data 包含一个SingleIndex类型数组,作为Section的索引,保存了Channel、ChunkHeader、ChunkBody三种Section的位置和简要数据。
record.proto中并不存在Chunk这个结构,而是用ChunkHeader和ChunkBody两部分来表示。
现代数据湖仓架构
Apache Iceberg 其定义特性之一是元数据与数据的分离,允许高效的基于快照的隔离和规划。
三个关键组件之上:存储层、开放式表格式和计算引擎
引入了 Iceberg 来支持模式演进、特征回填和并发读写
最开始反序列化为 Arrow ,后续的操作就完全基于 Arrow 进行,从而降低了序列化和反序列化开销,进一步提升训练速度
Parquet 并不支持数据回填
开放式表格式和对象存储正在重新定义组织构建其数据系统
ADS:applicationData Service应用数据服务
DW DWD,DWB,DWS DWD:data warehouse details DWB:data warehouse base 数据基础层 DWS:data warehouse service 数据服务层
ODS:Operation Data Store 数据准备区
基于 Protobuf 定义的半结构化数据
比较典型的就是小文件问题和存储成本问题
小文件问题是怎么产生的
如何解决小文件问题
小文件合并的核心是
如何把一个分区下的多个 Parquet 小文件合并成一个,
由于 Parquet 格式具有特殊的编码规则,文件内部被划分为多个功能子模块,我们不能直接把 2 个 Parquet 文件首尾拼接进行合并
借鉴了 Parquet 社区所提供的 merge 工具
一类是被压缩和编码后的实际数据,而另一类则是记录了数据是如何被编码和排列的元数据。
快速合并 相比于 普通合并
数据存储方式
通过文件块的方式对数据进行整理
通过数据库对数据进行管理
存储格式
扁平行存储--嵌套行存储
json
常见的json结构就是典型的行式存储嵌套结构
每条record依次存储每个层级的数据,并记录嵌套层次。
rosbag
Bag包由一系列的记录序列组成,每个记录的格式。
序列化方式
持久化:消息的保存和读取就涉及到一个广义上的问题序列化和反序列化
消息保存和解析、快速查找和可视化
保存:
<header_len><header><data_len><data>
解析:bag包中的Connection就是用来解决了消息解析的问,它包含了数据类型和格式,
而每个Message data中可以找到Connection,从而找到消息类型,进行解析。
通过消息类型名称(字符串)来生成对象,这在很多语言中叫做反射(reflection)
查询性能
快速查找
一个包拆分为几个块(Chunk) Index data则更进一步,直接索引了不同类型的消息在块中的时间戳和偏移,从而方便快速查找。
索引的方式:Index data索引消息,主要是为了快速检索信息
parquet
高效存储和检索数据
存储 高效的数据压缩和编码方案
解析· 读取 Parquet 文件的第一步就是先读取里面的 Footer,拿到元数据之后,再根据元数据跳到指定的 Row Group 和 Column chunk 中,读取真正的数据
应用:支持多种编程语言
一个 Parquet 文件里面有 1 亿条记录,现在切成了 10 个 Row Group,那么每个 Row Group 里面就是 1000W 条记录
Row Group 里面对各自的 1000W 条记录会采用列式存储
Column chunk 每一列的数据就叫做 Column chunk --每个 Column chunk 会对应多个 Page,
Page: 每个 Page 的大小默认是 1M。而之所以要进一步切分成 Page,主要是为了让数据读取的粒度足够小,便于单条数据和小批量数据的查询。
page 是 Parquet 文件的最小读取单位,同时也是压缩单位
Header,Index 和 Footer 都属于元数据
Header 的内容很少,只有 4 个字节,本质是一个 magic number,用来指示文件类型。
这个 magic number 目前有两种变体,分别是 "PAR1" 和 "PARE"。
其中 "PAR1" 代表的是普通的 Parquet 文件,"PARE" 代表的是加密过的 Parquet 文件
Index 是 Parquet 文件的索引块,主要为了支持谓词下推功能
Parquet 的索引有两种,
一种是 Max-Min,一种是 BloomFilter。
Max-Min 索引是对每个 Page 都记录它所含数据的最大值和最小值
Footer 元数据都存在 Footer 里,比如 schema,Row Group 的 offset 和 size,Column Chunk 的 offset 和 size。
Parquet 却写在后面
1.序列化
Thrift 是 Apache Parquet 项目中用于定义数据 schema 的序列化协议。
Parquet 通过 Thrift 序列化文件元数据,实现高效的数据存储和读写
src/main/thrift/parquet.thrift
数据模型:Parquet 的数据模型也是 schema 表达方式,用关键字 message 表示。
每个字段包含三个属性,
repetition属性(required/repeated/optional)、
数据类型(primitive基本类型/group复杂类型)及
字段名
required 表示字段是必需的,optional 表示字段是可选的,repeated 表示字段可以接收多个值,group 用来表示数据的嵌套结构。
存储模型:存储格式或文件格式
Parquet 的存储模型主要由行组(Row Group)、列块(Column Chuck)、页(Page)组成
Parquet 文件还包含header与footer信息,分别存储文件的校验码与Schema等信息
数据: 定长和变长数据
怎样把一个嵌套结构进行列式存储,然后在使用的时候又可以在内存还原成嵌套结构-使反序列化没有歧义
repetition level 和 definition level
Repetition Level用于标记路径上的分叉点, 分叉点歧义-
Definition Level用于标记空槽位所在的节点,两者配合可以无歧义地还原嵌套结构
对非数组类型的字段不保存 repetition level,对 required 字段不保存 definition level
存储这两个字段时,还会通过 bit-packing + RLE 来进行压缩。
编码方法(如Plain、RLE、Bit-packing和DictionaryEncoding)
RLE ( Run-Length Encoding)游程编码 通过统计数据中连续重复的“run”(即连续相同值的序列),仅存储起始值和重复次数。例如,序列1,1,1,1,3,3被压缩为(4,1)和(2,3)
Bit-Packing 位压缩——通过将数据分为固定位宽的“word”,仅存储有效位,去除冗余的0
2. 通过存储页 page 的 min/max 索引实现快速定位
列式存储:仅加载查询涉及的列数据,减少磁盘I/O
倒排索引、bitmap索引、范围索引、预排序、各种的 Cache 机制、读写分离等
应用场景
OLAP 场景,按列存储和扫描 parquet在存储嵌套数据上更有优势
3.其他技术
矢量化的Parquet读取 Vectorization 机制 向量化读取
数据可视化
通过浏览器
通过Client
对象存储
对象存储的底层原理主要基于分布式存储技术和数据分块处理机制,通过全局唯一标识符(如UUID)管理数据对象
参考
《Dremel: Interactive Analysis of Web-Scale Datasets》 介绍怎样将嵌套结构进行列式存储
Parquet的Repetition and Definition Levels原理分析 https://zhuanlan.zhihu.com/p/1935064218765328825
详解Parquet文件格式原理 https://zhuanlan.zhihu.com/p/538163356
Cyber_数据解析—Apollo_Record&rosbag保存格式
Rosbag格式分析 https://zhuanlan.zhihu.com/p/494474804
字节跳动基于Iceberg的海量特征存储实践 https://www.163.com/dy/article/HNF0BBCR0511CUMI.html