一文带你了解Lakehouse的并发控制:我们是否过于乐观?

2022-01-21 13:59:13 浏览数 (1)

1. 概述

如今数据湖上的事务被认为是 Lakehouse 的一个关键特征。但到目前为止,实际完成了什么?目前有哪些方法?它们在现实世界中的表现如何?这些问题是本博客的重点。

有幸从事过各种数据库项目——RDBMS (Oracle[1])、NoSQL 键值存储 (Voldemort[2])、流数据库 (ksqlDB[3])、闭源实时数据存储,当然还有 Apache Hudi, 我可以肯定地说,工作负载的不同深刻地影响了不同数据库中采用的并发控制机制。本博客还将介绍我们如何重新思考 Apache Hudi 数据湖的并发控制机制。

首先,我们直截了当点,RDBMS 数据库提供了最丰富的事务功能集和最广泛的并发控制机制[4],不同的隔离级别、细粒度锁、死锁检测/避免等其他更多机制,因为它们必须支持行级变更和跨多个表的读取,同时强制执行键约束[5]并维护索引[6]。而NoSQL 存储提供了非常弱的保证,例如仅仅提供最终一致性和简单的行级原子性,以换取更简单的工作负载的更好的扩展性。传统数据仓库基于列存或多或少提供了您在 RDBMS 中可以找到的全套功能,强制[7]执行锁定和键约束,而云数据仓库似乎更多地关注存算分离架构,同时提供更少的隔离级别。作为一个令人惊讶的例子,没有强制执行[8]键约束。

2. 数据湖并发控制中的陷阱

从历史看来,数据湖一直被视为在云存储上读取/写入文件的批处理作业,有趣的是看到大多数新工作如何扩展此视图并使用某种形式的“乐观并发控制[9]”(OCC)来实现文件版本控制。OCC 作业采用表级锁来检查它们是否影响了重叠文件,如果存在冲突则中止操作,锁有时甚至只是在单个 Apache Spark Driver节点上持有的 JVM 级锁,这对于主要将文件附加到表的旧式批处理作业的轻量级协调来说可能没问题,但不能广泛应用于现代数据湖工作负载。此类方法是在考虑不可变/仅附加数据模型的情况下构建的,这些模型不适用于增量数据处理或键控更新/删除。OCC 非常乐观地认为真正的冲突永远不会发生。将 OCC 与 RDBMS 或传统数据仓库的完全成熟的事务功能进行比较的开发人员布道是完全错误的,直接引用维基百科——“如果频繁地争用数据资源,重复重启事务的成本会显着损害性能,在这种情况下,其他并发控制方法[10]可能更适合。” 当冲突确实发生时,它们会导致大量资源浪费,因为你有每次尝试运行几个小时后都失败的批处理作业!

想象一下两个写入进程的真实场景:一个每 30 分钟生成一次新数据的摄取写入作业和一个执行 GDPR 的删除作业,需要 2 小时才能完成删除。这些很可能与随机删除重叠文件,并且删除作业几乎可以保证每次都饿死并且无法提交。在数据库方面,将长期运行的事务与乐观混合会导致失望,因为事务越长,它们重叠的可能性就越高。

那么有什么替代方案呢?锁?维基百科还说 – “但是,基于锁(“悲观”)的方法也可能提供较差的性能,因为即使避免了死锁,锁也会极大地限制有效的并发性。”。这就是 Hudi 采用不同方法的地方,我们认为这种方法更适合现代数据湖事务,这些事务通常是长期运行的,甚至是连续的。与数据库的标准读/写相比,数据湖工作负载与高吞吐量流处理作业共享更多特征,这就是我们借鉴的地方。在流处理中,事件被序列化为单个有序日志,避免任何锁/并发瓶颈,用户可以每秒连续处理数百万个事件。Hudi 在 Hudi 时间线[11]上实现了一个文件级、基于日志的并发控制协议,而该协议又依赖于对云存储的最低限度的原子写入。通过将事件日志构建为进程间协调的核心部分,Hudi 能够提供一些灵活的部署模型,与仅跟踪表快照的纯 OCC 方法相比,这些模型提供更高的并发性。

3. 模型 1:单写入,内联表服务

并发控制的最简单形式就是完全没有并发。数据湖表通常在其上运行公共服务以确保效率,从旧版本和日志中回收存储空间、合并文件(Hudi 中的Clustering)、合并增量(Hudi 中的Compaction)等等。Hudi 可以简单地消除对并发控制的需求,并通过支持这些开箱即用的表服务并在每次写入表后内联运行来最大化吞吐量。

执行计划是幂等的,持久化至时间线并从故障中自动恢复。对于大多数简单的用例,这意味着只需写入就足以获得一个不需要并发控制的管理良好的表。

4. 模型2:单写入,异步表服务

我们上面的删除/摄取示例并不是那么简单。虽然摄取/写入可能只是更新表上的最后 N 个分区,但删除甚至可能跨越整个表,将它们混合在同一个工作负载中可能会大大影响摄取延迟,因此Hudi 提供了以异步方式运行表服务的选项,其中大部分繁重的工作(例如通过压缩服务实际重写列数据)是异步完成的,消除了任何重复的浪费重试,同时还使用Clustering技术。因此单个写入可以同时使用常规更新和 GDPR 删除并将它们序列化到日志中。鉴于 Hudi 具有记录级索引并且 avro 日志写入要便宜得多(与写入 parquet 相比,后者可能要贵 10 倍或更高),摄取延迟可以持续,同时享受出色的可回溯性。事实上我们能够在 Uber[12] 将这个模型扩展到 100 PB数据规模,通过将所有删除和更新排序到同一个源 Apache Kafka 主题中,并发控制不仅仅是锁,Hudi 无需任何外部锁即可完成所有这一切。

5. 模型3:多写入

但是并不总是可以将删除序列化到相同的写入流中,或者需要基于 sql 的删除。对于多个分布式进程,某种形式的锁是不可避免的,但就像真正的数据库一样,Hudi 的并发模型足够智能,可以将实际写入表的内容与管理或优化表的表服务区分开来。Hudi 提供了类似的跨多个写入器的乐观并发控制,但表服务仍然可以完全无锁和异步地执行。这意味着删除作业只能对删除进行编码,摄取作业可以记录更新,而压缩服务再次将更新/删除应用于基本文件。尽管删除作业和摄取作业可以像我们上面提到的那样相互竞争和饿死,但它们的运行时间要低得多,浪费也大大降低,因为压缩完成了parquet/列数据写入的繁重工作。

综上所述,在这个基础上我们还有很多方法可以改进。

•首先,Hudi 已经实现了一种标记机制[1],可以跟踪作为活动写入事务一部分的所有文件,以及一种可以跟踪表的活动写入者的心跳机制。这可以由其他活动事务/写入器直接使用来检测其他写入器正在做什么,如果检测到冲突,则尽早中止[2],从而更快地将集群资源返回给其他作业。•虽然在需要可序列化快照隔离时乐观并发控制很有吸引力,但它既不是最佳方法,也不是处理写入者之间并发性的唯一方法。我们计划使用 CRDT 和广泛采用的流处理概念,通过我们的日志合并 API[3] 实现完全无锁的并发控制,这已经被证明[4]可以为数据湖维持巨大的连续写入量。•谈到键约束,Hudi 是当今唯一确保唯一键约束[5]的湖事务层,但仅限于表的记录键。我们将寻求以更通用的形式将此功能扩展到非主键字段,并使用上述较新的并发模型。

最后,要使数据湖成功转型为Lakehouse,我们必须从“Hadoop 仓库”愿景的失败中吸取教训,它与新的“Lakehouse”愿景有着相似的目标。设计人员没有密切关注与数据仓库相关的缺失技术差距,并且对实际软件产生了不切实际的期望。随着事务和数据库功能最终成为数据湖的主流,我们必须应用这些经验教训并对当前的缺点保持坦率。如果您正在构建一个 Lakehouse,我希望这篇文章能鼓励您仔细考虑围绕并发控制的各种操作和效率方面。通过试用 Apache Hudi[6] 加入我们快速发展的社区,或加入 Slack[7] 进行进一步交流。

引用链接

[1] Oracle: https://www.oracle.com/database/ [2] Voldemort: https://www.slideshare.net/vinothchandar/voldemort-prototype-to-production-nectar-edits [3] ksqlDB: https://www.confluent.io/blog/ksqldb-pull-queries-high-availability/ [4] 机制: https://dev.mysql.com/doc/refman/5.7/en/innodb-locking-transaction-model.html [5] 键约束: https://dev.mysql.com/doc/refman/8.0/en/create-table-foreign-keys.html [6] 索引: https://dev.mysql.com/doc/refman/8.0/en/create-table-secondary-indexes.html [7] 强制: https://docs.teradata.com/r/a8IdS6iVHR77Z9RrIkmMGg/wFPZS4jwZgSG21GnOIpEsw [8] 强制执行: https://docs.snowflake.com/en/sql-reference/constraints-overview.html#supported-constraint-types [9] 乐观并发控制: https://en.wikipedia.org/wiki/Optimistic_concurrency_control [10] 并发控制方法: https://en.wikipedia.org/wiki/Concurrency_control [11] 时间线: https://hudi.apache.org/docs/timeline [12] Uber: https://eng.uber.com/uber-big-data-platform/ [13] 标记机制: https://hudi.apache.org/blog/2021/08/18/improving-marker-mechanism/ [14] 尽早中止: https://issues.apache.org/jira/browse/HUDI-1575 [15] 合并 API: https://github.com/apache/hudi/blob/bc8bf043d5512f7afbb9d94882c4e43ee61d6f06/hudi-common/src/main/java/org/apache/hudi/common/model/HoodieRecordPayload.java#L38 [16] 被证明: https://hudi.apache.org/blog/2021/09/01/building-eb-level-data-lake-using-hudi-at-bytedance/#functionality-support [17] 唯一键约束: https://hudi.apache.org/docs/key_generation [18] Apache Hudi: https://hudi.apache.org/docs/overview [19] Slack: https://join.slack.com/t/apache-hudi/shared_invite/enQtODYyNDAxNzc5MTg2LTE5OTBlYmVhYjM0N2ZhOTJjOWM4YzBmMWU2MjZjMGE4NDc5ZDFiOGQ2N2VkYTVkNzU3ZDQ4OTI1NmFmYWQ0NzE

0 人点赞