CMU15-721 Note2 | Data Formats & Encoding I
文章发布时间:
最后更新时间:
文章总字数:
预计阅读时间:
页面浏览: 加载中...
最后更新时间:
文章总字数:
1.7k
预计阅读时间:
5 分钟
页面浏览: 加载中...
1. AP和TP工作负载
- OLAP:大段的只读的数据上的顺序扫描
- 只要通过顺序扫描查找到目标元组
- OLTP:使用索引查找元组而不是顺序扫描
- 针对low selectivity predicates (where, HAVING, ...)创建索引
- 同样需要容纳新增的更新
2. 顺序扫描的优化方案
- 数据编码/压缩
- 预取
- 并行化
- 聚集/排序
- 推迟元组拼接(Late Materialization)
- 物化视图/结果缓存
- 数据跳过(Data Skipping)
- 数据向量化(比如SIMD)
- 代码专业化/编译(比如JIT)
3. 存储模型
存储模型指数据库在物理上如何在磁盘和内存中组织元组。
3.1. N-ary Storage Model (NSM)
即行存:
- 单个元组的几乎所有属性连续存放在单个文件页中(除非有超过文件页大小的属性)
- 对以下OLTP负载较为理想:
- 访问个别实体的事务
- 插入查询较多的事务
- 倾向使用迭代器模型(火山模型)
- 一个文件页的大小通常是4KB的倍数(Oracle 4 KB, Postgres 8 KB, MySQL 16 KB)
Decomposition Storage Model (DSM)
即列存:
- 在一个文件中连续存储所有元组的某一个属性
- 对以下OLAP负载较为理想:
- 很大的只读查询,但只使用了表的某几列
- 倾向于使用向量化模型(批处理模型)
- 文件块的大小更大(几百MB),但也可能仍然使用更小的文件块来组织元组
- 可能需要将变长数据转换成定长数据来储存(因此可以使用偏移量来访问一个属性)
- 页头会储存元信息(比如null-bitmap)
- 元组ID两种方案
- 定长偏移
- 所有值都是定长,根据ID查询元组方便
- 变长数据要转换成定长数据(Dictionary Compression可以一定程度上压缩定长数据)
- 嵌入元组ID(Bad Idea)
- 某列的值会和ID存在一起
- 根据ID查询元组需要额外的数据结构辅助
- 定长偏移
3.2. Hybrid Storage Model (PAX)

- 优势:
- 列存结构:快速处理
- 元组各属性距离近
3.3. 开源持久数据格式
- 大多数数据库使用专有二进制格式来存储持久数据,在各系统间传输数据需要将数据转换成基于文本的格式(CSV, JSON, XML)
- 开源二进制文件格式使得跨系统读取数据文件更加容易和高效
4. 数据文件格式设计要点
4.1. 文件元数据
- 文件需要是自包含的。这可以增加其可移植性。应当包含所有必要的用来解释其内容的信息,而不需要额外数据依赖。
- 每个文件维护全局元数据(通常在尾部),比如:表架构、行组偏移量、元组总数等
4.2. 数据格式布局
- 大部分格式使用PAX存储模型
- 不同的实现,行组的大小不一定相同,会权衡计算和内存
- Parquet: 元组数量 (e.g. 1 million)
- ORC: 物理存储大小 (e.g. 250 MB)
- Arrow: 元组数量 (e.g., 1024 *1024)
4.3. 类型系统
- 类型系统决定了格式支持哪些类型:
- 物理类型:低级字节表示(e.g. IEEE-754, 浮点数的表示)
- 逻辑类型:映射到物理类型的辅助类型
- 类型系统的复杂性决定了上游生产者/消费者需要实现多少
- Parquet:实现了很少物理类型,逻辑类型提供了描述原始数据的注解
- ORC:提供了更完整的物理类型支持
4.4. 编码方案
编码方案指定了格式如何存储相邻或相关的数据。可以在某种编码方案上再进行某种编码方案来提升压缩度。常用的有字典编码、RLE、Bitpacking等
4.4.1. 字典编码
- 使用很小的定长整数代码来代替经常出现的值,同时维护从代码向原始值的映射
- 代码可以是原始值在字典中的序号(使用哈希表)或者在字典中的字节偏移量
- 可以选择将字典中的值排序
- 可以进一步压缩字典和被编码的列
- 当字典中不同值的数量过大时,必须有对应的处理方案
- 设计决策点:
- 适合使用字典编码的数据类型:
- Parquet: 任何类型
- ORC: 仅字符串
- 对已编码数据压缩方案:
- Parquet: RLE + Bitpacking
- ORC: RLE, Delta Encoding, Bitpacking, FOR
- 是否支持导出字典:
- Parquet: 不支持
- ORC: 不支持
- 适合使用字典编码的数据类型:
4.5. 文件块压缩
- 使用通用方式压缩数据
- 需要考虑:
- 计算开销
- 压缩与解压缩速度
- 数据不透明性
- 因为对象存储便宜且空间巨大,该项优化越来越无效果
4.6. 过滤器
- 区域映射(Zone Maps)
- 在文件级别和行组级别维护列最大最小值
- Parquet和ORC在行组头储存区域映射
- 布隆过滤器(Bloom Filters)
- 记录行组中每列值是否存在,如果数值是聚集的会更有效
- 布隆过滤器可以判断值是否一定不存在这块,但不能判断是否一定存在
4.7. 嵌套数据
- 真实世界的数据经常包含半结构化数据(JSON, Protobufs等)
- 文件格式希望能像一般的列一样编码这些数据
4.7.1. Record Shredding
额外记录以下内容:
- Repetition Level:该值路径上,该值和上一个值的最近公共祖先节点的孩子节点的层数
- Definition Level:该值所在路径上,非空节点所在的最深层的层数
4.7.2. Length+Presence Encoding
额外记录以下内容:
- Length:子节点个数
- Presence:记录是否存在
5. 实验的分析结论与总结
- 字典编码是所有数据类型的可行选择,而不仅仅是字符串:现实世界中数据的重复性和相关性更高
- 简单的编码方案利于现代系统:运行时判断每块使用哪种编码方案增加分支预测错误
- 块压缩变得不必要:网络和磁盘的速度越来越快,而CPU正在成为瓶颈
6. 现存格式的一些缺陷
- 变长的数据块:对SIMD向量化不友好
- 块压缩:无法随机读
- 部分编码方案过于依赖相邻值:RLE等
- 向量化兼容性:不同版本和厂商ISA的SIMD能力不同