1. 引言
从确保准确预计到达时间到预测最佳交通路线,在Uber平台上提供安全、无缝的运输和交付体验需要可靠、高性能的大规模数据存储和分析。2016年,Uber开发了增量处理框架Apache Hudi,以低延迟和高效率为关键业务数据管道赋能。一年后,我们开源了该解决方案,以使得其他有需要的组织也可以利用Hudi的优势。接着在2019年,我们履行承诺,进一步将其捐赠给了Apache Software Foundation,差不多一年半之后,Apache Hudi毕业成为Apache Software Foundation顶级项目。为纪念这一里程碑,我们想分享Apache Hudi的构建、发布、优化和毕业之旅,以使更大的大数据社区受益。
2. 什么是Apache Hudi
Apache Hudi是一个存储抽象框架,可帮助组织构建和管理PB级数据湖,通过使用upsert和增量拉取等原语,Hudi将流式处理带到了类似批处理的大数据中。这些功能通过统一的服务层(几分钟左右即可实现数据延迟),帮助我们更快,更新鲜地获取服务数据,从而避免了维护多个系统的额外开销。更灵活地,Apache Hudi还可以在Hadoop分布式文件系统(HDFS)或云存储上运行。
Hudi在数据湖上启用原子性、一致性、隔离性和持久性(ACID)语义。Hudi的两个最广泛使用的功能是upserts和增量拉取,它使用户能够捕获变更数据并将其应用于数据湖,为了实现这一点,Hudi提供了可插拔索引机制,以及自定义索引实现。Hudi具有控制和管理数据湖中文件布局的能力,这不仅能克服HDFS NameNode节点和其他云存储限制,而且对于通过提高可靠性和查询性能来维护健康的数据生态系统也非常重要。另外Hudi支持多种查询引擎,例如Presto,Apache Hive,Apache Spark和Apache Impala。
图1. Apache Hudi通过在表上提供不同的视图来摄取变更日志、事件和增量流,以服务于不同的应用场景
从总体上讲,Hudi在概念上分为3个主要组成部分:需要存储的原始数据;用于提供upsert功能的索引数据以及用于管理数据集的元数据。内核方面,Hudi维护在不同时间点在表上执行的所有动作的时间轴,在Hudi中称为即时,这提供了表格的即时视图,同时还有效地支持了按序到达的数据检索,Hudi保证时间轴上的操作是原子性的,并且基于即时时间,与数据库中进行更改的时间是一致的。利用这些信息,Hudi提供了同一Hudi表的不同视图,包括用于快速列式文件性能的读优化视图,用于快速数据摄取的实时视图以及用于将Hudi表作为变更日志流读取的增量视图,如上图1所示。
Hudi将数据表组织到分布式文件系统上基本路径(basepath)下的目录结构中。表分为多个分区,在每个分区内,文件被组织成文件组,由文件ID唯一标识。每个文件组包含几个文件切片,其中每个切片包含在某个特定提交/压缩(commit/compaction)瞬间生成的基本数据文件(*.parquet),以及包含对基本数据文件进行插入/更新的一组日志文件(*.log)。Hudi采用了Multiversion Concurrency Control(MVCC),其中压缩操作将日志和基本文件合并以生成新的文件片,而清理操作则将未使用的/较旧的文件片去除,以回收文件系统上的空间。
Hudi支持两种表类型:写时复制和读时合并。写时复制表类型仅使用列文件格式(例如,Apache Parquet)存储数据。通过写时复制,可以通过在写过程中执行同步合并来简单地更新版本并重写文件。
读时合并表类型使用列式(例如Apache Parquet)和基于行(例如Apache Avro)文件格式的组合来存储数据。更新记录到增量文件中,然后以同步或异步压缩方式生成列文件的新版本。
Hudi还支持两种查询类型:快照查询和增量查询。快照查询是从给定的提交或压缩操作开始对表进行"快照"的请求。利用快照查询时,写时复制表类型仅暴露最新文件片中的基本/列文件,并且与非Hudi表相比,可保证相同的列查询性能。写入时复制提供了现有Parquet表的替代品,同时提供了upsert/delete和其他功能。对于读时合并表,快照查询通过动态合并最新文件切片的基本文件和增量文件来提供近乎实时的数据(分钟级)。对于写时复制表,自给定提交或压缩以来,增量查询将提供写入表的新数据,并提供更改流以启用增量数据管道。
3. Apache Hudi在Uber的使用
在Uber,我们在各种场景中都使用到了Hudi,从在Uber平台上提供有关行程的快速、准确的数据,从检测欺诈到在我们的UberEats平台上提供餐厅和美食推荐。为了演示Hudi的工作原理,让我们逐步了解如何确保Uber Marketplace中的行程数据在数据湖上是最新的,从而改善Uber平台上的骑手和驾驶员的用户体验。行程的典型生命周期始于骑手提出的行程,然后随着行程的进行而继续,直到行程结束且骑手到达最终目的地时才结束。Uber的核心行程数据以表格形式存储在Uber的可扩展数据存储Schemaless中。行程表中的单个行程条目在行程的生命周期中可能会经历许多更新。在Uber使用Hudi之前,大型Apache Spark作业会定期将整个数据集重新写入HDFS,以获取上游在线表的插入、更新和删除,从而反映出行程状态的变化。就背景而言,在2016年初(在构建Hudi之前),一些最大的任务是使用1000个executors并处理超过20TB的数据,此过程不仅效率低下,而且难以扩展。公司的各个团队都依靠快速、准确的数据分析来提供高质量的用户体验,为满足这些要求,我们当前的解决方案无法扩展进行数据湖上的增量处理。使用快照和重新加载解决方案将数据移至HDFS时,这些低效率的处理正在写到到所有数据管道,包括使用此原始数据的下游ETL,我们可以看到这些问题只会随着规模的扩大而加剧。
在没有其他可行的开源解决方案可供使用的情况下,我们于2016年末为Uber构建并启动了Hudi,以构建可促进大规模快速,可靠数据更新的事务性数据湖。Uber的第一代Hudi利用了写时复制表类型,该表类型每30分钟将作业处理速度提高到20GB,I/O和写入放大减少了100倍。到2017年底,Uber的所有原始数据表都采用了Hudi格式,运行着地球上最大的事务数据湖之一。
图2. Hudi的写时复制功能使我们能够执行文件级更新,从而大大提高数据的新鲜度
4. 改进Apache Hudi
随着Uber数据处理和存储需求的增长,我们开始遇到Hudi的写时复制功能的局限性,主要是需要继续提高数据的处理速度和新鲜度,即使使用Hudi"写时复制"功能,我们的某些表收到的更新也分散在90%的文件中,从而导致需要重写数据湖中任何给定的大型表的数据,重写数据量大约为100TB。由于写时复制甚至为单个修改的记录重写整个文件,因此写复制功能导致较高的写放大和损害的新鲜度,从而导致HDFS群集上不必要的I/O以及更快地消耗磁盘空间,此外,更多的数据表更新意味着更多的文件版本,以及HDFS文件数量激增,反过来,这些需求导致HDFS Namenode节点不稳定和较高的计算成本。
为了解决这些日益增长的担忧,我们实现了第二种表类型,即"读时合并"。由于读时合并通过动态合并数据来使用近实时的数据,为避免查询端的计算成本,我们需要合理使用此模式。"读时合并"部署模型包括三个独立的作业,其中包括一个摄取作业,包括由插入、更新和删除组成的新数据,一个次要的压缩作业,以异步方式主动地压缩少量最新分区的更新/删除内容,以及一个主要的压缩作业,该作业会缓慢稳定地压缩大量旧分区中的更新/删除。这些作业中的每一个作业都以不同的频率运行,次要作业和提取作业的运行频率比主要作业要高,以确保其最新分区中的数据以列格式快速可用。通过这样的部署模型,我们能够以列式为数千个查询提供新鲜数据,并将我们的查询侧合并成本限制在最近的分区上。使用读时合并,我们能够解决上面提到的所有三个问题,并且Hudi表几乎不受任何对数据湖的更新或删除的影响。现在,在Uber,我们会根据不同场景同时使用Apache Hudi的写时复制和读时合并功能。
图3. Uber的Apache Hudi团队开发了一种数据压缩策略,用于读时合并表,以便频繁将最近的分区转化为列式存储,从而减少了查询端的计算成本
有了Hudi,Uber每天向超过150PB数据湖中插入超过5,000亿条记录,每天使用30,000多个core,超过10,000多个表和数千个数据管道,Hudi每周在我们的各种服务中提供超过100万个查询。
5. Apache Hudi经验总结
Uber在2017年开源了Hudi,为其他人带来了该解决方案的好处,该解决方案可大规模提取和管理数据存储,从而将流处理引入大数据。当Hudi毕业于Apache软件基金会下的顶级项目时,Uber的大数据团队总结了促使我们构建Hudi的各种考虑因素,包括:
- 如何提高数据存储和处理效率?
- 如何确保数据湖包含高质量的表?
- 随着业务的增长,如何继续大规模有效地提供低延迟的数据?
- 在分钟级别的场景中,我们如何统一服务层?
如果没有良好的标准化和原语,数据湖将很快成为无法使用的"数据沼泽"。这样的沼泽不仅需要花费大量时间和资源来协调、清理和修复表,而且还迫使各个服务所有者构建复杂的算法来进行调整、改组和交易,从而给技术栈带来不必要的复杂性。
如上所述,Hudi通过无缝地摄取和管理分布式文件系统上的大型分析数据集来帮助用户控制其数据湖,从而弥补了这些差距。建立数据湖是一个多方面的问题,需要在数据标准化、存储技术、文件管理实践,数据摄取与数据查询之间折衷性能等方面进行取舍。在我们建立Hudi时与大数据社区的其他成员交谈时,我们了解到这些问题在许多工程组织中普遍存在。我们希望在过去的几年中,开源和与Apache社区的合作,在Hudi基础上发展可以使其他人在不同行业对大数据运营有更深入的了解。在Uber之外,Apache Hudi已在多家公司用于生产,其中包括阿里云,腾讯云,AWS、Udemy等。
6. 未来计划
图4. Apache Hudi场景包括数据分析和基础架构运行状况监视
Hudi通过对数据集强制schema,帮助用户构建更强大、更新鲜的数据湖,从而提供高质量的见解。
在Uber,拥有全球最大的事务数据湖之一为我们提供了各种Apache Hudi用例场景的机会,由于以这种规模解决问题并提高效率可能会产生重大影响,因此有直接的动机促使我们更加深入。在Uber,我们已经使用了先进的Hudi原语,如增量拉取来帮助建立链式增量流水线,从而减少了作业的计算空间,而这些作业本来会执行大型扫描和写入。我们根据特定的用例场景和要求调整读时合并表的压缩策略。自从我们将Hudi捐赠给Apache基金会以来,最近几个月,Uber贡献了一些功能,例如嵌入式时间轴服务以实现高效的文件系统访问,删除重命名以支持云友好的部署并提高增量拉取性能。
在接下来的几个月中,Uber计划为Apache Hudi社区贡献更多新功能。其中一些功能可通过优化计算使用量以及改善数据应用程序的性能来帮助降低成本,我们还将更深入地研究如何根据访问模式和数据应用程序需求来改善存储管理和查询性能。
有关我们如何计划实现这些目标的更多信息,您可以阅读一些RFC,包括支持列索引和O(1)查询计划的智能元数据,将Parquet表高效引导到Hudi,记录级别索引支持更快速插入,这些RFC由Uber的Hudi团队向Apache社区提出。
随着Apache Hudi毕业成为Apache顶级项目,我们很高兴为该项目雄心勃勃的路线图做出贡献。Hudi使Uber和其他公司可以使用开放源文件格式,在未来证明其数据湖的速度,可靠性和交易能力,从而消除了许多大数据挑战,并构建了丰富而可移植的数据应用程序。
Apache Hudi是一个成长中的社区,具有令人兴奋且不断发展的发展路线图。如果您有兴趣为这个项目做贡献,可点击这里。