Delta Lake的调研报告
参考:DeltaLake官网,Delta 初探,DeltaLake,DeltaLake详解,阿里文档
架构图
Delta Lake简介
在说 Delta Lake 之前,要先提一下 Data Lake ,Data Lake 的主要思想是将企业中的所有数据进行统一管理。例如基于 Hadoop 的 Data Lake 方案可以非常低成本的存储所有类型的数据。
基于 hadoop 的方案只支持批量插入,且用户读取时可能无法获取最新数据,多用户同时进行写操作还会发生异常,带来脏数据的问题,并不可靠,并且更新及删除操作非常困难 (需要整块重写),无法保证数据一致性 (例如 spark 读取时缓存了 parquet 元数据,若元数据变化需要进行 refresh)。
Delta Lake 不仅能解决上述问题,还能对数据进行各种增强,例如 time travel 等。
– | – |
---|---|
运用领域 | 数据湖 |
诞生起因 | 数据从业者能够利用他们现有的数据湖,同时确保数据质量。 |
技术栈 | 基于Spark(2.4.2版以后)运用。底层使用Parquet文件格式。存储在HDFS、云存储上。 |
使用方法 | Spark-shell、Spark程序 |
版本对应:
Delta Lake version | Apache Spark version |
---|---|
0.7.0及以上 | 3.0.0 以上 |
0.7.0 以下 | 2.4.2-2.4.x |
解决4个数据湖问题:
1、数据湖的读写是不可靠的:因不安全写入导致脏数据。
2、数据湖中的数据质量很低:非结构化数据转储到数据湖中是非常容易的,没有任何验证模式和数据的机制导致数据质量差。没有Schema不能保证数据可用。
3、随着数据的增加,处理性能很差:数据大导致处理数据性能差。
4、数据湖中数据的更新非常困难:需要读写整个分区或表数据修改后写回。
特性:
1、支持ACID事务:
跨多集群的并发写入,也可以同时修改数据集并查看表的一致性快照,这些写入操作将按照串行执行(乐观锁)
在作业执行期间修改了数据,读取时也能看到一致性快照。
2、Schema管理:
自动验证写入数据是否和表结构相同,如果不同多写入列则报错,少写入列则null填充。
手动添加和自动更新Schema
3、可伸缩的元数据处理:元数据信息存储在事务日志中,不是存储在元存储(metastore)中,可固定的时间内列出大型目录中的文件,并且在读取数据时非常高效
4、数据版本:类似于Hbase的版本号。
5、统一的批处理和流 sink:可使用作为 Apache Spark structured streaming 高效的流 sink,结合 ACID 事务和可伸缩的元数据处理,高效的流 sink 现在支持许多接近实时的分析用例,而且无需维护复杂的流和批处理管道
6、数据存储格式采用开源 Apache Parquet
7、支持更新和删除: 支持 merge, update 和 delete 等 DML 命令
8、流批同表: Spark流批操作可共享同一张表。即是流的表,也是批的表。
文件目录结构:
_delta_log:事务日志
part-< taskId>-< jobid>-c< bucketId>.snappy.parquet:实际存储文件(parquet格式+snappy压缩)
相关机制
更新机制:
delta lake更新数据时会先定位待更新数据所在的文件,使用spark join获得结果数据集,将更新后的数据和文件中其他不需要更新的数据一起写入到新的文件里,同时在commit log中记录AddFile(新文件)和RemoveFile(旧文件)两种action。
merge的自动模式:
在merge的时候,有两种模式:默认模式和自动模式。(开启方法:spark.config(“spark.databricks.delta.schema.autoMerge.enabled”,true) )
默认模式下:不会更改Schema,当merge的两张表Schema不相同时,自动抛弃不同的Schema,丢失造成数据。
自动模式:自动更改Schema,当merge的两张表Schema不相同时,自动合并不同的Schema。
测试案例传送门:Dalta Lake测试案例
df df1
+---+---+----+ || +---+---+----+---+---+
| id| rn|flag| || | id| rn|flag| A| B|
+---+---+----+ || +---+---+----+---+---+
| 4| 3| 100| || | 4| 3| 200| a| B|
| 1| 3| 100| || | 1| 3| 200| a| B|
| 3| 3| 100| || | 3| 3| 200| a| B|
| 2| 3| 100| || | 2| 3| 200| a| B|
| 5| 3| 100| || +---+---+----+---+---+
+---+---+----+
df.merge(df1)的情况下(merge into df)
默认模式 自动模式
+---+---+----+ || +---+---+----+-----+-----+
| id| rn|flag| || | id| rn|flag| A | B |
+---+---+----+ || +---+---+----+-----+-----+
| 4| 3| 200| || | 4| 3| 200| a | B |
| 1| 3| 200| || | 1| 3| 200| a | B |
| 3| 3| 200| || | 3| 3| 200| a | B |
| 2| 3| 200| || | 2| 3| 200| a | B |
| 5| 3| 100| || | 5| 3| 100| null| null|
+---+---+----+ || +---+---+----+-----+-----+
事务日志(_delta_log):
作用:
是跟踪所有用户对表所做的所有更改的中央存储库;
根据事务日志,保证用户看到的表始终是最新版本的;
用户不能对表进行不同的、有冲突的更改;
记录每一次操作成功后表的状态,包括对应的元数据等信息。
位置:建表时自动创建子目录_delta_log下
命名:对表的其他更改将按升序数字顺序生成后续的json文件,从000000.json开始,下一次提交以000001.json的形式写出,依此类推
何时生成:每个操作生成一个事务日志文件,日志文件中记录了所有的有序原子操作
存储内容:每个JSON文件是一次更改,修改一行内容是一行数据。如下是增加2行数据。
检查点文件:
delta lake每提交10次就会自动生成检查点文件。这些检查点文件在一个时间点上保存了整个表的状态,采用Parque格式。
等于每10次操作一个表全量数据快照。
数据回溯:
每个在事件日志下,每个json文件是一个回溯版本。比如你文件下一共有10个json文件(000000.json~000010.json),那么你就有10个可回溯的版本。
默认情况下,没有在Delta表上运行VACUUM(清除历史),增量表将提交历史记录保留30天。如果运行VACUUM,您将无法恢复到默认的7天之前的数据版本。
查看历史操作方式:dff.history()
+-------+-------------------+------+--------+---------+------------------------------------------+----+--------+---------+-----------+--------------+-------------+----------------------------------------
|version|timestamp |userId|userName|operation|operationParameters |job |notebook|clusterId|readVersion|isolationLevel|isBlindAppend|operationMetrics |userMetadata|
+-------+-------------------+------+--------+---------+------------------------------------------+----+--------+---------+-----------+--------------+-------------+-----------------------------------------------------------------------------------+------------+
|7 |2021-01-28 18:06:04|null |null |DELETE |[predicate -> ["(`id` = 2)"]] |null|null |null |6 |null |false |[numRemovedFiles -> 1, numDeletedRows -> 1, numAddedFiles -> 1, numCopiedRows -> 3]|null |
|6 |2021-01-28 18:04:01|null |null |WRITE |[mode -> Overwrite, partitionBy -> []] |null|null |null |5 |null |false |[numFiles -> 1, numOutputBytes -> 886, numOutputRows -> 4] |null |
|5 |2021-01-28 18:03:59|null |null |UPDATE |[predicate -> (id#395 > 2)] |null|null |null |4 |null |false |[numRemovedFiles -> 1, numAddedFiles -> 1, numUpdatedRows -> 2, numCopiedRows -> 2]|null |
|4 |2021-01-28 18:03:24|null |null |WRITE |[mode -> Overwrite, partitionBy -> []] |null|null |null |3 |null |false |[numFiles -> 1, numOutputBytes -> 886, numOutputRows -> 4] |null |
|3 |2021-01-28 18:03:22|null |null |UPDATE |[predicate -> (id#395 > 2)] |null|null |null |2 |null |false |[numRemovedFiles -> 1, numAddedFiles -> 1, numUpdatedRows -> 2, numCopiedRows -> 2]|null |
|2 |2021-01-28 17:14:39|null |null |UPDATE |[predicate -> (id#395 > 2)] |null|null |null |1 |null |false |[numRemovedFiles -> 1, numAddedFiles -> 1, numUpdatedRows -> 2, numCopiedRows -> 2]|null |
|1 |2021-01-28 16:55:04|null |null |WRITE |[mode -> Overwrite, partitionBy -> []] |null|null |null |0 |null |false |[numFiles -> 1, numOutputBytes -> 886, numOutputRows -> 4] |null |
|0 |2021-01-28 16:47:43|null |null |WRITE |[mode -> ErrorIfExists, partitionBy -> []]|null|null |null |null |null |true |[numFiles -> 5, numOutputBytes -> 2007, numOutputRows -> 4] |null |
+-------+-------------------+------+--------+---------+------------------------------------------+----+--------+---------+-----------+--------------+-------------+----------------------------------------
项目 | Value | Description |
---|---|---|
version | long | 操作生成的表版本。 |
timestamp | timestamp | 提交此版本时间。 |
userId | string | 运行该操作的用户的ID。 |
userName | string | 运行该操作的用户名。 |
operation | string | 操作参数名称。 |
operationParameters | map | 操作的动作。 |
job | struct | 运行该操作的作业的详细信息。 |
notebook | struct | 运行该操作的详细信息。 |
clusterId | string | 运行操作的群集的ID。 |
readVersion | long | 读取以执行写操作的表的版本。 |
isolationLevel | string | 用于此操作的隔离级别。 |
isBlindAppend | boolean | 此操作是否附加数据。 |
operationMetrics | map | 操作的指标(例如,行数和已修改的文件) |
userMetadata | string | 用户定义的提交元数据(如果已指定) |
总结:
1、底层:使用 parquet 文件格式
2、Delta Lake = Parquet文件 + Meta 文件 + 一组操作的API
3、Delta Lake 其实只是一个Lib库 Delta Lake 是一个lib 而不是一个service,不同于HBase,他不需要单独部署,而是直接依附于Spark计算引擎的。目前只支持Spark引擎。这意味什么呢?Delta Lake 和普通的parquet文件使用方式没有任何差异,你只要在你的Spark代码项目里引入delta包,按标准的Spark datasource操作即可,可谓部署和使用成本极低。
优点 | 缺点 |
---|---|
支持事务,支持校验,支持DML | 过度依赖Spark |
数据版本,可追溯数据历史 | Delta Lake不支持多表事务和外键 |
小文件合并 | Delta不支持DStream API |
流批同表 | 没有权限和用户管理 |
个人理解:
Delta Lake 是一个开源的存储层,为数据湖带来了可靠性。
Delta Lake可以做到流式数据的DML操作,实时的展示操作后的数据信息。
也可以做到流批同表的操作,对于操作简易很多。
Delta Lake 太过依赖Spark,目前仅支持Spark。
还有个痛点,因为涉及到数据存储层,其中的血缘问题也无法解决。
没有兼容atlas,希望后面版本可以支持。
数据湖 Delta对比Iceberg(对比时间2021-04)
类型 | 对比点 | Delta Lake 0.7.0 | Iceberg 0.11.0 |
---|---|---|---|
存储 | 存储支持 | HDFS, S3 (Databricks), OSS | HDFS, S3 (Databricks) |
- | 压缩支持 | parquet | avro,parquet |
- | 历史回滚 | 支持 | 支持 |
- | 快照 | 支持 | 支持 |
- | 支持引擎 | Spark | Spark、Flink |
Hive | 建表 | Hive建表其他引擎无法操作。支持建立映射表 | Hive建表其他引擎无法操作。支持建立映射表 |
- | 查询 | 不能直接查询Spark表,需要映射 | 不能查询Spark\Flink的iceberg表,需要映射 |
- | insert/delete/update/merge | 不支持 | 不支持 |
- | alter table | 不支持 | 不支持 |
- | 主键 | 不支持 | 不支持 |
Spark3.0-SQL | 链接方式 | catalog | catalog(操作时需切换namespace) |
- | 建/删表 | 支持 | 支持 |
- | 查询 | 支持 | 支持 |
- | alter table | 支持 | 支持 注意:改数据类有限制(只允许加宽类型int -> bigint。类型不可以随意转换int 不可转string) |
- | 历史查看/回滚/删除 | 支持查看/删除 | 支持(需要调用自带的CALL,测试暂未通过) |
- | 建立快照 | 支持(操作10次自动快照),但不支持手动创建快照 | 支持(需要调用自带的CALL,测试暂未通过),并可手动设置快照,自定义快照名称 |
- | 与其他引擎兼容 | 只允许Hive外表查看,不允许更改 | 只允许Hive外表查看,不允许更改 允许Flink查看及操作 |
- | 其他 | 表运维操作少于iceberg。 Delta不支持:删除表未引用文件、使用快照建立临时表、其他表迁移至ice表等 | 不允许show create table show tables 只展示ice表 |
Spark3.0-Streaming | source | 支持 | 不支持 |
- | sink | 支持(无主键) | 支持(无主键) |
- | merge sink | 不支持 | 不支持 |
Flink1.11.0-SQL | 链接方式 | 不支持Flink | catalog |
- | 建/删表 | 不支持Flink | 支持 |
- | 查询 | 不支持Flink | 支持 |
- | insert/delete/update/merge | 不支持Flink | 仅支持insert into |
- | alter table | 不支持Flink | 仅支持改表名 如果需要更改表结构/类型等操作,需切换Spark引擎操作 |
- | 与其他引擎兼容 | 不支持Flink | 只允许Hive外表查看,不允许更改,允许Spark查看及操作 |
Flink1.11.0-Streaming | source | 不支持Flink | 不支持 |
- | sink | 不支持Flink | 支持(无主键) |
- | merge sink | 不支持Flink | 不支持 |