这是我参与「第五届青训营 」伴学笔记创作活动的第 29 天 本节课程主要介绍了数据湖经历的三个阶段,从一开始的 Hadoop 到 Hive,再到后来的湖仓一体,逐步剖析这个过程如何演进;此外,在课程中讲师还对目前业界内主流的三大数据湖进行了简单的分析。
发展历史
Hadoop

- 数据湖最开始的概念—分布式存储HDFS
- 使用目录来区分不同的数据集
- /douyin
- /20220623
- /20220624
- /toutiao
- /douyin
- 好处:
- 同一公司/组织可以使用共享存储
- 数据访问方便,灵活性高
单机存储 -> 分布式存储(方便扩容,相当于无限大)
- 坏处:
- 没有记录文件的schema (包括列名、列类型), 经常使用Schema on Query的方式
- 难以得知数据集包含了那些文件,是通过什么样的分区组织的
- 如果多个程序都在修改这个数据集(修改数据、修改表结构), 其他程序难以配合做修改
数据沼泽,太杂太乱,不好协作,不好管理
Hive

- 数据湖的演进—Hive Metastore
- 对数据湖中的数据集进行集中"定义”
- 数据湖中存在了哪些数据集
- 它们都存储在什么目录
- 数据集的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变更!
- And more !
湖仓一体

-
题外话—数据仓库:
- 什么是数据仓库?
- 数据仓库将数据从数据源提取和转换,加载到目的地
- 数据仓库存储+计算不分离
- 数据仓库严格控制写入数据的schema
- 什么是数据仓库?
-
数据仓库 VS 数据湖:


数据湖主要就是存,数据仓库是存算不分离的。
数据仓库不太适合Machine Learning,内部是结构化的数据。但是对于数据湖来说,里面都是原始没有加工过的数据,对于机器学习和人工智能会更加友好昂!!!
- 湖仓一体:

- 湖仓一体(数据湖的现状):
- 结合了数据湖和数据仓库的优势
- 将数据仓库中对于数据的严格管理直接实现到了低成本的分布式存储之上
- Key Features :
- Transaction ACID
- Schema管理
- 存储计算分离
- 支持多种计算引擎和文件格式
业界三大数据库
- Uber -> Hudi

- Netflix -> Iceberg

- Data bricks -> delta lake

关于数据湖
- 数据相关概念比较新, -直处在演进当中
- 一开始是HDFS,裸pb、txt日志等等,叫数据湖(管不动了就叫数据沼泽)
- 后来出现了了Iceberg、 Hudi、 Delta Lake了,数据湖概念就基本等于这些产品了
- 也更贴近于Lakehouse的概念
核心技术
设计一个简单的数据湖
First Demand
- 最开始的设计:

- 写入数据湖时
- 按照每条数据的date进行分区
- 额外使用metadata文件记录表信息,如下图所示


Second Demand(Time Travel)

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

Third Demand(Transaction)

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

- 写入流程:
- 写入parquet数据文件
- 写入json元数据文件
- 如何确保原子性? (从用户可见性入手! )
- 用户只会读取以版本号数字命名的json文件,每次都读取到最大的版本号作为数据集的现状
- 新的写入写完parquet后开始写json文件,使用hash值对json文件命名,如a2fs4hfg8ee.json
- 直到json文件内容写入完毕,利用hdfs的renameIfAbsent能力将a2fs4hfg8ee.json替换为000006.json,到此为止commit完成,新的读取将会以000006.json作为最新版本
- 读写冲突已经被解决,how?
- 新的写入除非已经commit,否则用户读不到
- 用户正在读的分区,被另一个写入进行更新,数据不会进行替换,而是共存
事务隔离

➢ Update写入流程:
- 从最新的版本中,获取需要update的分区
- 乐观锁先把该写入的文件全落盘,然后进入写json阶段
- 分几种情况:
- 发现版本号和一开始没区别,直接写新的版本。
- 发现版本号增加了,看看新增的这些版本有没有更新我要更新的分区?
- 没有,直接写新的版本。
- 有, 两者都更新了同一分区,得重新update了。
Fourth Demand(Schema Evolution)
上级通知:Phone列要删掉了
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是不一样的昂!因此会出现这种情况捏!
各有所长
Iceberg
- 用户体验
- Schema evolution
- Partition evolution
- Hidden partition
- Time Travel
- Version Rollback
- 性能
- 快速file plan
- 更多的filter方式
- 可靠性
- ACID Transaction
- 完全开源,由Apache孵化开发
Well-designed Metadata Layer

- Metadata files 定义了表结构,存储了snapshot信息,
- Manifest lists 存储了一个snapshot中所有manifest的信息
- Manifests 存储了一些data files的信息
- Data files 就是具体的数据文件
Data File Filter
些有助于 filter的数据被层层记录 ,比如 :
- Manifest file记录了每个data file的分区范围
- Manifest list 记录了每个manifest file的分区范围分区可以被快速定位!可以做manifest list级别裁剪。
- 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工作重点 :
- Timeline service: Hudi 管理 transaction 的方式
- Hudi Table Type: Copy on Write / Merge on Read
- 高效的 Upserts: update or insert
- 索引表: 快速定位一条数据的位置
- Streaming Ingestion Service
- 完全开源,由Apache孵化
Timeline Service & Upsert & Incremental

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

Merge On Read

Write的时候不去实际写入文件中(开销太大),如果改动小的话,存在一个单独的Update文件中。直接在读文件的时候,读出原始文件,和Update文件,然后做一个Merge,得到最终的结果就ok了昂!
Delta Lake工作重点
- ACID Transaction
- Schema校验(不是evolution )
- 流批一体
- Time Travel
- Upsert/Delete
- Z-Order 优化
- 只开源了一部分,由Databricks自己主导开发,Z-order等优化的实现末开源
流批一体

总结场景
三个数据湖的异同

三个数据湖的热度

技术选型
- 短期来看:每个项目都有一些属于自己的功能:
- 如果强需求Upserts,也许Hudi是最好的选择
- 如果希望可扩展性强,那么设计优良的Iceberg是最好的选择
- 如果希望用上Z- Order等优化,那么掏钱买Databricks的企业版Delta是不=之选
- 长期来看:数据湖取代Hive,成为HDFS上的表格式标准是必然的,在选择之前问自己四个问题:
- 我需要的feature在哪个数据湖上是最稳定的
- 哪一个数据湖能够用最简单的接入方式( SQL)用上最完善的功能
- 哪一个数据湖有计算引擎侧的支持和社区的支持
- 哪一个数据湖的版本管理做的最好,最鲁棒
字节跳动数据湖场景举例-Feature Store

Iceberg上进行了很多优化昂!!!

场景举例
➢ 批流一体:近实时的数据写入与数据就绪(Readiness)
- 例子:消息队列直接入湖、流式Upsert
- 应用:能同时训最新(流式)和历史(批式)
➢ 更强的算力:要求更快的读数据
- 例子:分布式任务扫描、向量化读与读时合并、统一内存格式( Apache Arrow )
- 应用:数据读取不应成为训练瓶颈
总结
- 数据湖的最新发展状态是湖仓-体
- 数据湖以低存储成本提供了'ACID Transaction、Schema evolution、Time travel等高级功能
References
- 《Delta Lake: High-Performance ACID Table Storage over Cloud Object Stores》
- iceberg.apache.org
- hudi.apache.org/
- www.dremio.com/subsurface/…