目前市面上流行的三大开源数据湖方案分别为:Delta、Apache Iceberg 和 Apache Hudi。其中,由于 Apache Spark 在商业化上取得巨大成功,所以由其背后商业公司 Databricks 推出的 Delta 也显得格外亮眼。Apache Hudi 是由 Uber 的工程师为满足其内部数据分析的需求而设计的数据湖项目,它提供的 fast upsert/delete 以及 compaction 等功能可以说是精准命中广大人民群众的痛点,加上项目各成员积极地社区建设,包括技术细节分享、国内社区推广等等,也在逐步地吸引潜在用户的目光。Apache Iceberg 目前看则会显得相对平庸一些,简单说社区关注度暂时比不上 Delta,功能也不如 Hudi 丰富,但却是一个野心勃勃的项目,因为它具有高度抽象和非常优雅的设计,为成为一个通用的数据湖方案奠定了良好基础。
很多用户会想,看着三大项目异彩纷呈,到底应该在什么样的场景下,选择合适的数据湖方案呢?今天我们就来解构数据湖的核心需求,深度对比三大产品,帮助用户更好地针对自身场景来做数据湖方案选型。
首先,我们来逐一分析为何各技术公司要推出他们的开源数据湖解决方案,他们碰到的问题是什么,提出的方案又是如何解决问题的。我们希望客观地分析业务场景,来理性判断到底哪些功能才是客户的痛点和刚需。
以 Databricks 推出的 delta 为例,它要解决的核心问题基本上集中在下图:
在没有 Delta 数据湖之前,Databricks 的客户一般会采用经典的 Lambda 架构来构建他们的流批处理场景。以用户点击行为分析为例,点击事件经 Kafka 被下游的 Spark Streaming 作业消费,分析处理(业务层面聚合等)后得到一个实时的分析结果,这个实时结果只是当前时间所看到的一个状态,无法反应时间轴上的所有点击事件。所以为了保存全量点击行为,Kafka 还会被另外一个 Spark Batch 作业分析处理,导入到文件系统上(一般就是 parquet 格式写 HDFS 或者 S3,可以认为这个文件系统是一个简配版的数据湖),供下游的 Batch 作业做全量的数据分析以及 AI 处理等。
这套方案其实存在很多问题 :
第一、批量导入到文件系统的数据一般都缺乏全局的严格 schema 规范,下游的 Spark 作业做分析时碰到格式混乱的数据会很麻烦,每一个分析作业都要过滤处理错乱缺失的数据,成本较大;
第二、数据写入文件系统这个过程没有 ACID 保证,用户可能读到导入中间状态的数据。所以上层的批处理作业为了躲开这个坑,只能调度避开数据导入时间段,可以想象这对业务方是多么不友好;同时也无法保证多次导入的快照版本,例如业务方想读最近 5 次导入的数据版本,其实是做不到的。
第三、用户无法高效 upsert/delete 历史数据,parquet 文件一旦写入 HDFS 文件,要想改数据,就只能全量重新写一份的数据,成本很高。事实上,这种需求是广泛存在的,例如由于程序问题,导致错误地写入一些数据到文件系统,现在业务方想要把这些数据纠正过来;线上的 MySQL binlog 不断地导入 update/delete 增量更新到下游数据湖中;某些数据审查规范要求做强制数据删除,例如欧洲出台的 GDPR 隐私保护等等。
第四、频繁地数据导入会在文件系统上产生大量的小文件,导致文件系统不堪重负,尤其是 HDFS 这种对文件数有限制的文件系统。
所以,在 Databricks 看来,以下四个点是数据湖必备的。
事实上, Databricks 在设计 Delta 时,希望做到流批作业在数据层面做到进一步的统一(如下图)。业务数据经过 Kafka 导入到统一的数据湖中(无论批处理,还是流处理),上层业务可以借助各种分析引擎做进一步的商业报表分析、流式计算以及 AI 分析等等。
所以,总结起来,我认为 Databricks 设计 Delta 时主要考虑实现以下核心功能特性:
Uber 的业务场景主要为:将线上产生的行程订单数据,同步到一个统一的数据中心,然后供上层各个城市运营同事用来做分析和处理。在 2014 年的时候,Uber 的数据湖架构相对比较简单,业务日志经由 Kafka 同步到 S3 上,上层用 EMR 做数据分析;线上的关系型数据库以及 NoSQL 则会通过 ETL(ETL 任务也会拉去一些 Kakfa 同步到 S3 的数据)任务同步到闭源的 Vertica 分析型数据库,城市运营同学主要通过 Vertica SQL 实现数据聚合。当时也碰到数据格式混乱、系统扩展成本高(依赖收 Vertica 商业收费软件)、数据回填麻烦等问题。后续迁移到开源的 Hadoop 生态,解决了扩展性问题等问题,但依然碰到 Databricks 上述的一些问题,其中最核心的问题是无法快速 upsert 存量数据。
如上图所示,ETL 任务每隔 30 分钟定期地把增量更新数据同步到分析表中,全部改写已存在的全量旧数据文件,导致数据延迟和资源消耗都很高。此外,在数据湖的下游,还存在流式作业会增量地消费新写入的数据,数据湖的流式消费对他们来说也是必备的功能。所以,他们就希望设计一种合适的数据湖方案,在解决通用数据湖需求的前提下,还能实现快速的 upsert 以及流式增量消费。
Uber 团队在 Hudi 上同时实现了 Copy On Write 和 Merge On Read 的两种数据格式,其中 Merge On Read 就是为了解决他们的 fast upsert 而设计的。简单来说,就是每次把增量更新的数据都写入到一批独立的 delta 文件集,定期地通过 compaction 合并 delta 文件和存量的 data 文件。同时给上层分析引擎提供三种不同的读取视角:仅读取 delta 增量文件、仅读取 data 文件、合并读取 delta 和 data 文件。满足各种业务方对数据湖的流批数据分析需求。
最终,我们可以提炼出 Uber 的数据湖需求为如下图,这也正好是 Hudi 所侧重的核心特性。
Netflix 的数据湖原先是借助 Hive 来构建,但发现 Hive 在设计上的诸多缺陷之后,开始转为自研 Iceberg,并最终演化成 Apache 下一个高度抽象通用的开源数据湖方案。Netflix 用内部的一个时序数据业务的案例来说明 Hive 的这些问题,采用 Hive 时按照时间字段做 partition,他们发现仅一个月会产生 2688 个 partition 和 270 万个数据文件。他们执行一个简单的 select 查询,发现仅在分区裁剪阶段就耗费数十分钟。
他们发现 Hive 的元数据依赖一个外部的 MySQL 和 HDFS 文件系统,通过 MySQL 找到相关的 parition 之后,需要为每个 partition 去 HDFS 文件系统上按照分区做目录的 list 操作。在文件量大的情况下,这是一个非常耗时的操作。同时,由于元数据分属 MySQL 和 HDFS 管理,写入操作本身的原子性难以保证。即使在开启 Hive ACID 情况下,仍有很多细小场景无法保证原子性。另外,Hive Metastore 没有文件级别的统计信息,这使得 filter 只能下推到 partition 级别,而无法下推到文件级别,对上层分析性能损耗无可避免。最后,Hive 对底层文件系统的复杂语义依赖,使得数据湖难以构建在成本更低的 S3 上。
于是,Netflix 为了解决这些痛点,设计了自己的轻量级数据湖 Iceberg。在设计之初,作者们将其定位为一个通用的数据湖项目,所以在实现上做了高度的抽象。虽然目前从功能上看不如前面两者丰富,但由于它牢固坚实的底层设计,一旦功能补齐,将成为一个非常有潜力的开源数据湖方案。
总体来说,Netflix 设计 Iceberg 的核心诉求可以归纳为如下:
我们可以把上述三个项目针对的痛点,放到一张图上来看。可以发现标红的功能点,基本上是一个好的数据湖方案应该去做到的功能点。
在理解了上述三大方案各自设计的初衷和面向的痛点之后,接下来我们从 7 个维度来对比评估三大项目的差异。通常人们在考虑数据湖方案选型时,Hive ACID 也是一个强有力的候选人,因为它提供了人们需要的较为完善功能集合,所以这里我们把 Hive ACID 纳入到对比行列中。
综合起来看,Snapshot Isolation 隔离级别的并发性是相对比较好的。
这里有两个对比项,一个是 schema 变更的支持情况,我的理解是 Hudi 仅支持添加可选列和删除列这种向后兼容的 DDL 操作,而其他方案则没有这个限制。另外一个是数据湖是否自定义 schema 接口,以期跟计算引擎的 schema 解耦。这里 Iceberg 是做的比较好的,抽象了自己的 schema,不绑定任何计算引擎层面的 schema。
目前 Iceberg 和 Hive 暂时不支持流式消费,不过 Iceberg 社区正在 issue 179 上开发支持。
这里主要从计算引擎的写入和读取路径、底层存储可插拔、文件格式四个方面来做对比。
Iceberg 是抽象程度做得最好的数据湖方案,四个方面都做了非常干净的解耦。Delta 是 databricks 背后主推的,必须天然绑定 Spark;Hudi 的代码跟 Delta 类似,也是强绑定 Spark。存储可插拔的意思是说,是否方便迁移到其他分布式文件系统上(例如 S3),这需要数据湖对文件系统 API 接口有最少的语义依赖,例如若数据湖的 ACID 强依赖文件系统 rename 接口原子性的话,就难以迁移到 S3 这样廉价存储上,目前来看只有 Hive 没有太考虑这方面的设计;文件格式指的是在不依赖数据湖工具的情况下,是否能读取和分析文件数据,这就要求数据湖不额外设计自己的文件格式,统一用开源的 parquet 和 avro 等格式。这里,有一个好处就是,迁移的成本很低,不会被某一个数据湖方案给绑死。
这里 One line demo 指的是,示例 demo 是否足够简单,体现了方案的易用性,Iceberg 稍微复杂一点(我认为主要是 Iceberg 自己抽象出了 schema,所以操作前需要定义好表的 schema)。做得最好的其实是 Delta,因为它深度跟随 Spark 易用性的脚步。
Python 支持其实是很多基于数据湖之上做机器学习的开发者会考虑的问题,可以看到 Iceberg 和 Delta 是做的很好的两个方案。
出于数据安全的考虑,Iceberg 还提供了文件级别的加密解密功能,这是其他方案未曾考虑到的一个比较重要的点。
这里需要说明的是,Delta 和 Hudi 两个项目在开源社区的建设和推动方面,做的比较好。Delta 的开源版和商业版本,提供了详细的内部设计文档,用户非常容易理解这个方案的内部设计和核心功能,同时 Databricks 还提供了大量对外分享的技术视频和演讲,甚至邀请了他们的企业用户来分享 Delta 的线上经验。Uber 的工程师也分享了大量 Hudi 的技术细节和内部方案落地,研究官网的近 10 个 PPT 已经能较为轻松理解内部细节,此外国内的小伙伴们也在积极地推动社区建设,提供了官方的技术公众号和邮件列表周报。Iceberg 相对会平静一些,社区的大部分讨论都在 Github 的 issues 和 pull request 上,邮件列表的讨论会少一点,很多有价值的技术文档要仔细跟踪 issues 和 PR 才能看到,这也许跟社区核心开发者的风格有关。
我们把三个产品(其中 Delta 分为 databricks 的开源版和商业版)总结成如下图:
如果用一个比喻来说明 Delta、Iceberg、Hudi、Hive-ACID 四者差异的话,可以把四个项目比做建房子。由于开源的 Delta 是 Databricks 闭源 Delta 的一个简化版本,它主要为用户提供一个 table format 的技术标准,闭源版本的 Delta 基于这个标准实现了诸多优化,这里我们主要用闭源的 Delta 来做对比。
Delta 的房子底座相对结实,功能楼层也建得相对比较高,但这个房子其实可以说是 Databricks 的,本质上是为了更好的壮大 Spark 生态,在 Delta 上其他的计算引擎难以替换 Spark 的位置,尤其是写入路径层面;Iceberg 的建筑基础非常扎实,扩展到新的计算引擎或者文件系统都非常的方便,但是现在功能楼层相对低一点,目前最缺的功能就是 upsert 和 compaction 两个,Iceberg 社区正在以最高优先级推动这两个功能的实现;Hudi 的情况要相对不一样,它的建筑基础设计不如 iceberg 结实,举个例子,如果要接入 Flink 作为 Sink 的话,需要把整个房子从底向上翻一遍,把接口抽象出来,同时还要考虑不影响其他功能,当然 Hudi 的功能楼层还是比较完善的,提供的 upsert 和 compaction 功能直接命中广大群众的痛点。Hive 的房子,看起来是一栋豪宅,绝大部分功能都有,把它做为数据湖有点像靠着豪宅的一堵墙建房子,显得相对重量级一点,另外正如 Netflix 上述的分析,细看这个豪宅的墙面是其实是有一些问题的。