深入浅出 HBase 实战 | 青训营笔记

106 阅读5分钟

这是我参与「第四届青训营 」笔记创作活动的第4天 juejin.cn/post/712681…

Parquet 和 ORC:高性能列式存储

一个大数据查询作业,可以简单的概括为以下几个步骤:

  • 从存储层读取文件
  • 计算层解析文件内容,运行各种计算算子
  • 计算层输出结果,或者把结果写入存储层

行存 vs 列存

数据格式层

  • 数据格式层:定义了存储层文件内部的组织格式,计算引擎通过格式层的支持来读写文件
  • 严格意义上,并不是一个独立的层级,而是运行在计算层的一个Library image

OLTP vs OLAP

OLTP 和 OLAP 作为数据查询和分析领域两个典型的系统类型,具有不同的业务特征,适配不同的业务场景 image

行式存储格式 (行存) 与 OLTP

  • 每一行 (Row) 的数据在文件的数据空间里连续存放的
  • 读取整行的效率比较高,一次顺序 IO 即可
  • 在典型的 OLTP 型的分析和存储系统中应用广泛,例如:MySQL、Oracle、RocksDB 等 image

列式存储格式 (列存) 与 OLAP

  • 每一列 (Column) 的数据在文件的数据空间里连续存放的
  • 同列的数据类型一致,压缩编码的效率更好
  • 在典型的 OLAP 型分析和存储系统中广泛应用,例如:
    • 大数据分析系统:Hive、Spark,数据湖分析
    • 数据仓库:ClickHouse,Greenplum,阿里云 MaxCompute image

Parquet (列存)

Parquet 中的数据编码

  • 在 Parquet 的 ColumnChunk 里,同一个 ColumnChunk 内部的数据都是同一个类型的,可以通过编码的方式更高效的存储 下面举例介绍常见的 Encoding:
  • Run Length Encoding (RLE):适用于列基数不大,重复值较多的场景,例如:Boolean、枚举、固定的选项等
  • Bit-Pack Encoding: 对于 32位或者64位的整型数而言,并不需要完整的 4B 或者 8B 去存储,高位的零在存储时可以省略掉。适用于最大值非常明确的情况下。 -一般配合 RLE 一起使用
  • Dictionary Encoding:适用于列基数 (Column Cardinality) 不大的字符串类型数据存储; -构造字典表,用字典中的 Index 替换真实数据 -替换后的数据可以使用 RLE + Bit-Pack 编码存储

Parquet 中的压缩方式

  • Page 完成 Encoding 以后,进行压缩
  • 支持多种压缩算法 -snappy: 压缩速度快,压缩比不高,适用于热数据 -gzip:压缩速度慢,压缩比高,适用于冷数据 -zstd:新引入的压缩算法,压缩比和 gzip 差不多,而且压缩速度略低于 Snappy

索引和排序 Index and Ordering

  • 和传统的数据库相比,索引支持非常简陋
  • 主要依赖 Min-Max Index 和 排序 来加速查找
  • Page:记录 Column 的 min_value 和 max_value
  • Footer 里的 Column Metadata 包含 ColumnChunk 的全部 Page 的 Min-Max Value
  • 一般建议和排序配合使用效果最佳
  • 一个 Parquet 文件只能定义一组 Sort Column,类似聚集索引概念 image 典型的查找过程:
  • 读取 Footer
  • 根据 Column 过滤条件,查找 Min-Max Index 定位到 Page
  • 根据 Page 的 Offset Index 定位具体的位置
  • 读取 Page,获取行号
  • 从其他 Column 读取剩下的数据

Bloom Filter 索引

  • 适用场景 -对于列基数比较大的场景,或者非排序列的过滤,Min-Max Index 很难发挥作用
  • 引入 Bloom Filter 加速过滤匹配判定
  • 每个 ColumnChunk 的头部保存 Bloom Filter 数据
  • Footer 记录 Bloom Filter 的 page offset image

过滤下推 Predicate PushDown

  • parquet-mr 库实现,实现高效的过滤机制
  • 引擎侧传入 Filter Expression
  • parquet-mr 转换成具体 Column 的条件匹配
  • 查询 Footer 里的 Column Index,定位到具体的行号
  • 返回有效的数据给引擎侧 优点:
  • 在格式层过滤掉大多数不相关的数据
  • 减少真实的读取数据量 image

Parquet & Spark

作为最通用的 Spark 数据格式 主要实现在:ParquetFileFormat

  • 支持向量化读:spark.sql.parquet.enableVectorizedReader
  • 向量化读是主流大数据分析引擎的标准实践,可以极大的提升查询性能
  • Spark 以 Batch 的方式从 Parquet 读取数据,下推的逻辑也会适配 Batch 的方式

ORC (大数据分析领域使用最广的列存格式之一)

ACID 特性

  • 支持 Hive Transactions 实现,目前只有 Hive 本身集成
  • 类似 Delta Lake / Hudi / Iceberg
  • 基于 Base + Delta + Compaction 的设计

索引增强

  • 支持 Clusterd Index,更快的主键查找
  • 支持 Bitmap Index,更快的过滤

其他优化

  • 小列聚合,减少小 IO
    • 重排 ColumnChunk image
  • 异步预取优化 -在计算引擎处理已经读到的数据的时候,异步去预取下一批次数据 image

Parquet vs ORC 对比

  • 从原理层面,最大的差别就是对于 NestedType 和复杂类型处理上
  • Parquet 的算法上要复杂很多,带来的 CPU 的开销比 ORC 要略大
  • ORC 的算法上相对加单,但是要读取更多的数据
  • 因此,这个差异的对业务效果的影响,很难做一个定性的判定,更多的时候还是要取决于实际的业务场景

列存演进

数仓中的列存

  • 典型的数仓,例如 ClickHouse 的 MergeTree 引擎也是基于列存构建的
    • 默认情况下列按照 Column 拆分成单独的文件,也支持单个文件形式 image
  • 支持更加丰富的索引,例如 Bitmap Index、Reverted Index、Data Skipping Index、Secondary Index 等
  • 湖仓一体的大趋势下,数仓和大数据数据湖技术和场景下趋于融合,大数据场景下的格式层会借鉴更多的数仓中的技术

存储侧下推

  • 更多的下推工作下沉到存储服务侧
  • 越接近数据,下推过滤的效率越高 -例如 AWS S3 Select 功能 image 挑战:
  • 存储侧感知 Schema
  • 计算生态的兼容和集成

Column Family 支持

  • 背景:Hudi 数据湖场景下,支持部分列的快速更新
  • 在 Parquet 格式里引入 Column Family 概念,把需要更新的列拆成独立的 Column Family
  • 深度改造 Hudi 的 Update 和 Query 逻辑,根据 Column Family 选择覆盖对应的 Column Family
  • Update 操作实际效果有 10+ 倍的提升 image