数据胡三剑客:Delta Lake、Hudi 与 Iceberg 详解 | 青训营笔记

269 阅读7分钟

这是我参与「第四届青训营 」笔记创作活动的的第 5 天

1. 发展历史

数据湖发展阶段——Hadoop

  • 使用目录来区分不同的数据集

  • 好处

    • 统一公司/组织可以使用共享存储
    • 数据访问方便,灵活性高
  • 坏处

    1. 没有记录文件的 schema (包括列名、列类型),经常是用 Schema on Query 的方式
    2. 难以得知数据集包含了哪些文件,是通过怎样的分区组织的
    3. 如果多个程序都在修改这个数据集(修改数据、修改表结构),其他程序难以配合做修改

数据湖发展阶段——Hive

  • 对数据湖中的数据进行集中“定义”

    • 数据湖中存在了那些数据集
    • 他们都存储在什么目录
    • 数据集的 schema 是什么样子的
    • 数据集有哪些分区,每个分区的目录是什么
  • 如果这张 hive 表是静态的,没有新增写入,则所有读取方都能很便捷的使用

数据湖发展阶段——湖仓一体

  • 数据仓库

    • 数据仓库将数据从数据源提取和转换,加载到目的地
    • 数据仓库存储 + 计算不分离
    • 数据仓库严格控制写入数据的 schema
-数据仓库数据湖(阶段1)
成本
存储计算分离
ACID

湖仓一体(数据湖现状):

  • 结合了数据湖和数据仓库的优势

  • 将数据仓库中对于数据的严格管理直接实现到了低成本的分布式存储之上

  • key feature

    • Transaction ACID
    • Schema 管理
    • 存储计算分离
    • 支持多种计算引擎和文件格式

关于“数据湖”

  • 数据相关概念比较新,一直处于演进当中
  • 一开始是 HDFS,裸 pb、txt 日志等,叫数据湖(管不动了就叫数据沼泽)
  • 后来出现了 Iceberg、Hudi、Delta Lake 了,数据湖概念就基本等于这些产品了
  • 也更贴近于 Lakehouse 的概念

2. 核心技术

写入数据湖时:

  1. 按照每条数据的 date 进行分区
  2. 额外使用 metadata 文件记录表信息

Time travel

要点:

  1. 每次写入都生成一个新的元数据文件,记录变更
  2. 分区数据在 Update 时,不要删除旧数据,保证新旧共存
  3. 元数据中存储具体的文件路径,而不仅仅是分区文件夹

操作:

  1. 每一次写入操作,创建一个新的 json 文件,以递增版本号命名,记录本次新增/删除文件
  2. 每当产生 N 个 json,做一次聚合,记录完整的分区文件信息
  3. 用 checkpoint 记录上次做聚合的版本号

Transaction

ACID,是指数据库再写入或更新资料的过程中,为保证事务是正确可靠的,所必须具备的四个特性:

  • Atomicity 原子性
  • Consistency 一致性
  • Isolation 事务隔离
  • Durability 持久性

数据湖中的 ACID

  • Atomicity 原子性 - 本次写入要么对用户可见,要么不可见(需要设计)
  • Consistency 一致性 - 输入是什么,落盘的就是什么(由计算引擎保证)
  • Isolation 事务隔离 - 正确解决读写冲突和写写冲突(需要设计)
  • Durability 持久性 - 落完数据后,即便服务器重启结果不变(由存储引擎保证)

原子性

  • 写入流程

    1. 写入 parquet 数据文件
    2. 写入 json 元数据文件
  • 如何确保原子性(从用户可见性入手)

    1. 用户只会读义版本号数字命名的 json 文件,每次都读取到最大的版本号作为数据集的现状
    2. 新的写入写完 parquet 后开始写 json 文件,使用 hash 值对 json 文件命名,如 a2fs4hfg8ee.json
    3. 直到 json 文件内容写入完毕,利用 hdfs 的 renameIfAbsert 能力将 a2fs4hfg8ee.json 替换为 0000006.json,到此位置 commit 完成,新的读取将会以 0000006.json 作为最新版本
  • 读写冲突已解决

    1. 新的写入除非已经 commit,否则用户读不到
    2. 用户正在读的分区,被另一个写入进行更新,数据不会进行替换,而是共存

事务隔离

写写冲突InsertUpdate/Delete
Insertcan not conflict
Update/Deletecan conflictcan conflict

Update 写入流程:

  1. 从最新的版本中,获取需要 update 的分区

  2. 乐观锁先把该写入的文件全落盘,然后进入写 json 阶段

  3. 分几种情况

    1. 发现版本号和一开始没区别,直接写新版本

    2. 发现版本号增加了,看看新增的这些版本有没有更新我要更新的分区?

      1. 没有,直接写新的版本
      2. 有,两者都更新了同一分区,得重新 update 了

Schema Evolution

Add/drop/Rename

  • 用户并不直接读取 parquet 文件本身,而是通过数据湖接口读取,如 Dataset<Row> ds = simpleDataLake.read(mytable).option(date=2020-01-01)
  • 数据湖内部读取应该读的是 parquet,并在 schema 上做进一步处理

ID 将 data 和 metadata 的列名做一一对应

  1. 唯一确定的 ID。新增列赋予新的 ID。删列 ID 不复用

  2. 写入数据时,ID 也写入数据文件

  3. 读取数据时,用 ID 做映射,如果

    1. Data 中没有,metadata 中有:ADD
    2. Data 中有,metadata 中没有:DROP
    3. Data 和 metadata 中都有同一 ID,但是 name 不同:RENAME
    4. 都有同一列名,而 ID 不同:先 DROP 后 ADD

3. 各有所长

Iceberg 工作重点

  • 用户体验

    1. Schema evolution
    2. Partition evolution
    3. Hidden partition
    4. Time Travel
    5. Version Rollback
  • 性能

    1. 快速 file plan
    2. 更多的 filter 方式
  • 可靠性

    ACID Transaction

  • 完全开源,由 Apache 孵化开发

Well-desgned Metadata Layer

image-20220809222636388.png

  • Metadata files 定义了表结构,存储了 snapshot 信息,分区列信息等
  • Manifest lists 存储了一个 snapshot 中所有 manifest 的信息
  • Manifest 存储了一些 data files 的信息
  • Data files 就是具体数据文件

Data file Filter

一些有助于 filter 的数据被层层记录,比如:

  1. Manifest file 记录了每个 data file 的分区范围
  2. Manifest list 记录了每个 manifest file 的分区范围,分区可以被快速定位!可以做 manifest file 级别裁剪
  3. Manifest file 记录了每个 data file 每一列的最大值,最小值可以通过其他的列(UserId)做 data file 级别裁剪

Hidden Partition

  • 传统的分区方式

    • 数据中包含了 date 列,则按照 date 分区;如果希望按照 hour 分区,则需要新增 hour 列
  • Iceberg 的分区方式

    • 数据中包含 timestamp 列,设置好 partition transform 方式

      • 设置为 date 时,iceberg 帮你转化为 date 分区
      • 设置为 hour 时,iceberg 帮你转化为 hour 分区
      • Iceberg 记录了这层转化关系,并且按你的需要进行 partition evolution

Hudi

Hadoop Upsert Delete and Incremental

Hudi 工作重点:

  1. Timeline service:Hudi 管理 transaction 的方式
  2. Hudi Table Type:Copy on Write / Merge on Read
  3. 高效的 Upserts:update or insert
  4. 索引表:快速定位一条数据的位置
  5. Streaming Ingestion Service
  6. 完全开源,由 Apache 孵化

Timeline Service & Upsert & Incremental

  • Timeline Service:记录的信息类似于 metadata
  • Upsert :每一条样本都有一个主题 PK,当 Upsert 一条数据时,如果现有数据中有这个 PK,则 uopdate 这条数据,否则直接 insert 这条数据
  • Incremental:某个时间点后新增的数据

Copy on Write

image-20220809224133399.png

Merge on Read

image-20220809224500118.png

Delta Lake 工作重点

  1. ACID Transaction
  2. Schema 校验(不是 evolution)
  3. 流批一体
  4. Time Travel
  5. Upsert/Delete
  6. Z-Order 优化
  7. 只开源一部分,由 Databricks 自己主导开源,Z-order 等优化的实现为开源

4. 总结场景

三个数据湖的异同

image-20220809224829654.png

技术选型

短期来看:每个项目都有一些属于自己的功能

  • 如果强需求 Upsert,也许 Hudi 是最好的选择
  • 如果希望可扩展性强,那么设计优良的 Iceberg 是最好的选择
  • 如果希望用上 Z-Order 等优化,那么掏钱买 Databricks 的企业版 Delta 是不二之选

长期来看:数据湖取代 Hive,称为 HDFS 上的表格是标准是必然的,在选择之前问自己四个问题:

  1. 我需要的 feature 在哪个数据湖上是最稳定的
  2. 哪一个数据湖能够用简单的接入方式(SQL)用上完善的功能
  3. 哪一个数据湖有机酸引擎侧的支持和社区的支持
  4. 哪一个数据湖的版本管理做的好,最鲁棒