这是我参与「第四届青训营 」笔记创作活动的第10天
Lecture11. 数据湖三剑客:Delta Lake、Hudi 与 Iceberg 详解
01. 发展历史
1.1 数据湖发展阶段1 - Hadoop
数据湖最开始的概念——分布式存储HDFS
使用目录来区分不同的数据集
- /douyin
- /20220623/
- 20220624
- /toutiao
好处:
-
同一公司 / 组织可以使用共享存储
- 非常方便进行横向扩展
-
数据访问方便,灵活性高
坏处:
-
没有记录文件的schema(包括列名、列类型),经常使用Schema on Query的方式
- 用户自己得知道
-
难以得知数据集包含了那些文件,是通过什么样的分区组织的
-
如果多个程序都在修改这个数据集(修改数据、修改表结构),其他程序难以配合做修改
数据沼泽! :管理难度大
1.2 数据湖发展阶段2 - Hive
数据湖的演进——Hive Metastore:把元数据存储到mysql...中
对数据湖中的数据集进行集中“定义”
- 数据湖中存在了哪些数据集
- 它们都存储在什么目录
- 数据集的schema是什么样子的
- 数据集有哪些分区,每个分区的目录是什么
如果这张hive表是静态的,没有新增写入,则所有读取方都能很便捷的使用
问题来了:
- ①假设Reader A和B都正在读取分区/20220623下的文件
- 此时Writer B开始重写/20220623分区,一些文件被删了,一些文件增加了,一些文件还在修改中
- Reader A和B读到的文件可能是不同的!
我们需要Transaction ACID!
问题又来了:
- 分区/20220623下存储了schema为date | userld | phoneNumber的数据
- 需要注意:Hive allows us to add column after last column only
- 根据合规,我需要删掉phoneNumber,但是在Hive表上做不到。只好重写一张表(耗费资源)
- 我们需要支持更多样的schema变更!
1.3数据湖发展阶段3 - 湖仓一体
题外话——数据仓库
什么是数据仓库?
- 数据仓库将数据从数据源提取和转换,加载到目的地的,做数据汇报
- 数据仓库存储+计算不分离
- 数据仓库严格控制写入数据的schema
湖仓一体(数据湖的现状)︰
数据湖:成本低、数据仓库:存储结构化数据
- 结合了数据湖和数据仓库的优势
- 将数据仓库中对于数据的严格管理直接实现到了低成本的分布式存储之上
Key Features:
- Transaction ACID
- Schema管理
- 存储计算分离
- 支持多种计算引擎和文件格式
1.4 业界三大数据湖
Uber简介:
- 国外版滴滴,打车软件
- 在行程规划、拼车、顺风车等业务上数据量都很大心数据写入面临挑战
- 数据湖名称:Hudi
- 最初希望解决的问题:使数据写入能够更低延迟心开源时间:2016年
Netflix简介:
- 流媒体
- 开发工作:存储用户观看数据,推荐剧集
- 数据湖名称:lceberg
- 开源时间:2018
- 最初希望解决的问题:摆脱Hive架构带来的问题
databricks简介:
- 起源于UCB,参与Spark的制作
- 超级独角兽
- 主打湖仓一体
- 数据湖名称:Delta Lake
- 开源时间:2019(开源了,但没完全开源)
1.5 关于数据湖
- 数据相关概念比较新,一直处在演进当中
- 一开始是 HDFS,裸pb、txt日志等等,叫数据湖(管不动了就叫数据沼泽)
- 后来出现了了Iceberg、Hudi、Delta Lake了,数据湖概念就基本等于这些产品了
- 也更贴近于Lakehouse的概念
- 当前有什么问题,下一个阶段解决了什么问题
02. 核心技术
设计一个简单的数据湖
需求:
- 我想存一些数据,按照date分区schema是
- 每天都会写入新数据
- 需要使用列存格式(压缩性能好、能够选列)
2.1 文件结构
根目录mytable——分区目录2020-01-01
写入数据湖时
- 按照每条数据的date进行分区
- 额外使用metadata元数据文件记录表信息
需求:
- 我把2020-01-01的数据更新了一下,我想用新数据和老数据同时跑一下训练
2.2 Time travel
要点:
-
每次写入都生成一个新的元数据文件,记录变更
-
分区数据在Update时,不要删除旧数据,保证新旧共存
- 读旧版本的文件,可以读以前的内容
-
元数据中存储具体的文件路径,而不仅仅是分区文件夹
-
每一次写入操作,创建一个新的json文件,以递增版本号命名,记录本次新增/删除的文件。
-
每当产生N个json,做一次聚合,记录完整的分区文件信息
-
用checkpoint记录上次做聚合的版本号
需求:
小B同学也要用这个数据湖
- 我俩要是一起写,不会有写写冲突吧?
- 我俩要是同时一个人读一个人写,这个过程中不会有读写冲突吧?
2.3 Transaction
Transaction事务!
ACID,是指数据库在写入或更新资料的过程中,为保证事务是正确可靠的,所必须具备的四个特性。
以A给B转账10元为例:
- Atomicity:原子性——要么A-10 B+10,要么都不变
- Consistency:一致性——不可以A-10 B+5
- Isolation:事务隔离——A和C同时给B转10,B最终结果应是+20
- Durability:持久性——转账服务器重启,结果不变
数据湖中的ACID :
- Atomicity:原子性–本次写入要么对用户可见,要么不可见(需要设计)
- Consistency:一致性–输入是什么,落盘的就是什么(由计算引擎保证)
- Isolation:事务隔离–正确解决读写冲突和写写冲突(需要设计)
- Durability:持久性–落完数据后,即便服务器重启结果不变(由存储引擎保证)
2.3.1 原子性
写入流程:
- 写入parquet 数据文件
- 写入json元数据文件
如何确保原子性?(从用户可见性入手!)
-
用户只会读取以版本号数字命名的json文件,每次都读取到最大的版本号作为数据集的现状
-
新的写入写完parquet后开始写json文件,使用hash值对json文件命名,如a2fs4hfg8ee.json
- 先不写6.json,保证原子性,看6.json能不能被正确写入
-
直到json文件内容写入完毕,利用hdfs的renameIfAbsent能力将a2fs4hfg8ee.json替换为000006.json,到此为止commit完成,新的读取将会以000006.json作为最新版本
读写冲突已解决
- 新的写入除非已经commit,否则用户读不到
- 用户正在读的分区,被另一个写入进行了更新,数据不会进行替换,而是共存
2.3.2 事务隔离 写写冲突
| 写写冲突 | insert | update / delete |
|---|---|---|
| insert | 不会冲突 | |
| update / delete | 会冲突 | 会冲突 |
Update 写入流程:
-
从最新的版本中,获取需要update的分区
-
乐观锁先把该写入的文件全落盘,然后进入写json阶段
-
分几种情况:
-
发现版本号和一开始没区别,直接写新的版本。
-
发现版本号增加了,看看新增的这些版本有没有更新我要更新的分区(更新的同一个分区)?
- 没有,直接写新的版本。
- 有,两者都更新了同一分区,得重新update 了。
-
需求:
上级通知, phone列得删了
2.4 Schema Evolution 进化
Add/Drop/Rename
重要:
- 用户并不直接读取parquet文件本身,而是通过数据湖接口读取,如Dataset ds = simpleDataLake.read(mytable).option(date=2020-01-01)
- 数据湖内部会读取应该读的parquet,并在schema上做进一步处理
ID将data和metadata的列名做一—对应!
-
唯──确定的ID。新增列赋予新ID。删列ID不复用。
-
写入数据时,ID也写入数据文件
-
读取数据时,用ID做映射,如果
- Data中没有,metadata中有:ADD
- Data中有,metadata中没有:DROP
- Data和metadata中都有同一ID,但是name不同:RENAME
- 如果都有同一列名,而ID不同?先删再加(删的ID不复用)
03. 各有所长
3.1 Iceberg工作重点
用户体验
- Schema evolution
- Partition evolution
- Hidden partition
- Time Travel
- Version Rollback
性能
- 快速file plan
- 更多的filter方式
可靠性
- ACID Transaction
完全开源,由Apache孵化开发
3.1.1 Well-designed Metadata Layer
- Metadata files定义了表结构,存储了snapshot信息,分区列信息等
- Manifest lists存储了一个snapshot 中所有manifest的信息
- Manifests存储了一些data files的信息
- Data files就是具体的数据文件
3.1.2
一些有助于filter的数据被层层记录,比如:
-
Manifest file记录了每个data file的分区范围
-
Manifest list记录了每个manifest file的分区范围分区可以被快速定位!可以做manifest list级别裁剪。
-
Manifest file(列存)记录了每个data file每一列的最大值,最小值可以通过其他的列(Userld)做data file 级别裁剪。
3.1.3 Hidden Partition
传统的分区方式:
- 数据中包含了date列,则按照date分区;如果希望按照hour分区,则需要新增hour列
lceberg的分区方式:
-
数据中包含timestamp列,设置好partition transform方式
- 设置为date时,iceberg帮你转化为date分区
- 设置为hour时,iceberg帮你转化为hour分区
- lceberg记录了这层转化关系,并且按你的需要进行partition evolution
3.2 Hudi
Hadoop Upsert Delete and lncremental
Hudi工作重点:
- Timeline service: Hudi管理transaction的方式
- Hudi Table Type(独创): Copy on Write / Merge on Read
- 高效的Upserts: update or insert
- 索引表:快速定位一条数据的位置
- Streaming Ingestion Service
- 完全开源,由Apache孵化
3.2.1 Timeline Serivce & Upsert & lncremental
- Timeline Service:记录的信息类似于metadata
- Upsert:每一条样本都有一个主键PK,当Upsert一条数据时,如果现有的数据中有这个PK,则update这条数据。否则直接insert这条数据
- Incremental增量读取:某个时间点后新增的数据
3.2.2 Copy on Write
更新一个分区时,要把原来的数据全部读出来,做加工,再写进去
更新——产生上层方框
资源浪费。只需要改几列,但需要全读
3.2.3 Merge On Read 读时合并
绿框:base file
3.3 Delta Lake工作重点
- ACID Transaction
- Schema校验(不是evolution)
- 流批一体
- Time Travel
- Upsert/Delete
- Z-Order优化
- 只开源了一部分,由Databricks自己主导开发,Z-order等优化的实现未开源
04. 总结场景
4.1 三个数据湖的异同
4.2 三个数据湖的热度
4.3 技术选型
短期来看:每个项目都有一些属于自己的功能︰
-
如果强需求Upserts,也许Hudi是最好的选择
-
如果希望可扩展性强,那么设计优良的Iceberg是最好的选择
-
如果希望用上Z-Order等优化,那么掏钱买Databricks的企业版 Delta是不二之选
长期来看:数据湖取代 Hive,成为HDFS 上的表格式标准是必然的。在选择之前问自己四个问题:
- 我需要的feature在哪个数据湖上是最稳定的
- 哪一个数据湖能够用最简单的接入方式(SQL)用上最完善的功能
- 哪一个数据湖有计算引擎侧的支持和社区的支持
- 哪一个数据湖的版本管理做的最好,最鲁棒
字节跳动数据湖场景举例-Feature Store
Feature Store在字节的诞生
初心
- instance pb样本读放大、不能列裁剪=>很难落许多特征进样本
- instance pb样本写放大、copy-on-write =>很难做特征回溯调研
收益
- 支持落原始特征做特征调研
- 解决读放大、支持列裁剪
- 解决写放大、支持 merge-on-read
- 支持特征schema校验
- 列式存储优化存储空间
- Arrow格式优化序列化开销
Feature Store及其平台
-
基于Apache Iceberg 做了大量定制与优化
-
站内存储约220 PB训练样本
- 最大单表30PB,15K个特征
- 相比instance pb样本能节省约30%~50%空间
-
平台端到端体验完整、用户使用成本低
- 控制面板、特征监控、数据维护(样本回溯、TTL、脏数据处理等)
-
对特征回溯及特征调研场景做了专门强化
- 支持Update语义操作和数据分支
批流一体:近实时的数据写入与数据就绪(Readiness)
- 例子:消息队列直接入湖、流式 Upsert
- 应用:能同时训最新(流式)和历史(批式)
更强的算力:要求更快的读数据
- 例子:分布式任务扫描、向量化读与读时合并、统一内存格式(Apache Arrow)
- 应用:数据读取不应成为训练瓶颈