摘要:本文整理自同程旅行大数据计算组负责人吴祥平,在 Apache Paimon Meetup 的分享。本篇内容主要分为四个部分:
1. Apache Paimon 引入
2. Apache Paimon 应用建设
3. Apache Paimon 优化实践
4. 未来规划和期待
Tips:点击「阅读原文」免费领取 5000CU*小时 Flink 云资源
3 月底,在 Paimon 的公众号上发表了一篇文章,展示了 Paimon 在内部的调研和实践效果。三个月过去了,今天将通过更深入的实践和应用,与大家分享一下 Paimon 在内部的一些实践经验和大数据量场景下的优化细节。希望能够对正在调研和即将接入的一些伙伴有所帮助。
在开始之前,先简单介绍一下同程旅行大数据平台的建设历程。
2013 年我们开始建设了大数据平台,经历了多个阶段的引进。2014-2018 年主要基于 Hadoop 的大数据平台建设,然后基于 Hive 和 Spark 的数据仓库以及数据湖。2020 年开始引入 Hudi 构建了基于 Hudi 的数据湖,将大部分 ODS 层的表都迁移到了 Hudi 上。2022 年开始关注 Paimon,当时的名字是 Flink TableStore。
上图是目前的整体架构图。湖仓部分是 Hudi 和 Paimon 混用的阶段,加速层的 OLAP 主要使用 Starrocks。
01
Apache Paimon 引入
Apache Paimon 是一款支持高吞吐数据摄入、变更追踪、高效分析的数据库平台。底层存储利用了 LSM 的数据结构,支持高效的写入和查询的同时,能够支持多种分布式文件系统和计算引擎。
如果大家想了解跟多 Apache Paimon 的原理和细节,可以去看 Paimon 的官网,那里有非常详尽的文件操作流程和原理,在这里就不再赘述了。接下来主要和大家分享一下,我们在引入 Paimon 的一些考虑和背景。
首先是数据一致性。排查过数据湖,数据丢失的同学都知道这个过程非常痛苦。原因有很多,有可能是因为现场安全,或者是状态管理,又或者是数据写入的顺序问题。这些问题都有可能会导致数据丢失,而且非常难排查,这也是我们引入 Paimon 的一个重要原因。
在 Paimon 的实践过程中,我们几乎没有碰到任何一起数据丢失或者一致性的问题,我想这应该是得意于 Paimon 简单的湖仓抽象的设计,以及它良好的状态管理机制和严谨的测试用例,这些都保证了 Paimon 的数据一致性。
其次是生态。Paimon 的生态非常丰富,支持多种分布式文件系统和计算引擎。而且如果你去看 Paimon,它在各个引擎上集成的代码,你会发现大道至简,各个引擎的集成都非常自然,而且很容易让人理解,这对组建的维护者来说是非常重要的。而且 Paimon 的生态也在不断的扩展,包括 Doris、Starrocks 的集成。
最后是性能。Paimon 的性能非常优秀,这里我想和大家分享一下我们在 Paimon 性能测试中的一些结果。我们在全量+增量写入的场景中,相对 Hudi,Paimon 在相同计算资源的情况下,摄入的速度要优于 Hudi MOR 的摄入,大概有 3 倍左右的差距。这个差距主要是因为 Paimon 的写入是基于 LSM 的,而 Hudi MOR 是基于类 LSM,底层文件的无序会导致整个合并过程更加重量一点。
查询场景下会更明显,我们在同样数据量的情况下,Paimon 的查询速度要优于 Hudi,大概有 7 倍左右的差距,特别是点查。这个差距主要也是因为 Paimon 底层有序的数据组织,能够在查询的时候裁剪掉大部分的数据。而 Hudi 的查询是基于文件的,需要遍历整个文件,这个差距在数据量越大的情况下也会越明显。
下面介绍一下内部 Paimon 的一些实践规模,我们主要实践的应用场景有以下三个:
第一个,实时写入 ODS 层场景。目前有 114 家实时 Paimon 表,在运行中摄入 ODS 层的任务。最大单表日增量是 2000 万+,最大单表数据量是 90 亿+。所有的任务已经稳定运行了两个月以上,目前还在不断的迁移中。
第二个,局部更新场景。众所周知,宽表在湖仓的构建中是非常常见的,Paimon 提供的局部更新能力能够很好的支持宽表的构建,目前我们有十多张宽表在 Paimon 上运行。随着 Paimon 做到真正局部更新之后,我们也会把更多的宽表场景迁移到 Paimon 中来,这个后面会进行详细的介绍。
第三个,流读\增量读场景。这个场景主要是为了支持实时数仓的建设,目前我们主要有 20+的流毒场景在 Paimon 上运行。通过流读的方式极大地缩减了数据可见时间,提升了数据的实时性,同时也降低了底层数仓的压力。这个场景也是我们后续会大量迁移的场景之一。
场景的应用是需要结合收益的,实践下来我们发现,Paimon 在摄入计算资源层面相对 Hudi 减少了 30%左右,实时 ODS 层的存储减少了 40%。其次在写入和查询性能上,全量和增量在相同内存配置的情况下,摄入的性能提升了 3 倍左右,查询的性能提升了 7 倍。在各大企业都在降本增效的背景下,这些收益对我们来说是非常可观的。
02
Apache Paimon 应用建设
区别于社区的 CDC 模式,我们对 CDC 的集成做了一层抽象。库和 Binlogo 之间有一层抽象层,Binlogo 解析中间件主要负责各个数据源的 Binlogo,包括 MySQL、PG、Mango,以及一些内部资源的 DB。这样做是为了将 Binlogo 进行服务化,解耦上层集成平台和 Binlogo 的抽象,更容易进行扩展,而且 API 保存不变。
那么 Binlogo 解析中间键之后是消息队列。我们采用基于增量 MQ 的一个 Binlogo 的消息队列。这样做是为了减少 Binlogo 在应用过程中的重复生产,让同一个队列可以让多个消费者去使用。但这套框架的缺点也比较明显,就是无法像社区 CDC 那样进行完备的结构引进或者整库同步,未来我们也会去强化这一点。
有了基础抽象能力之后,我们希望终端用户不用太关心中间 Binlogo 采集的中间件以及队列的信息,只需要关注数据源表和它的目标表就可以了。让终端用户更加专注于业务,而不是关心技术细节。
我们将集成进行了抽象,将集成分为两个层面。第一个是用户层,用户可以选择他们熟悉的业务数据。第二个是对接层,可以自动对接我们的数据库团队、中间件团队、消息队列团队。通过这样的抽象,在底层组件替换的时候就会非常方便。当时 Paimon 上线的时候大概只花了 1-2 天的时间进行对接。
下面介绍一下湖仓表的任务以及指标的管理。我们沿用了 Hudi 的一些指标,比如消费延迟、合并延迟、写入 writer 的数量,同时我们也制定了一些我们非常关心的指标,通过图表的方式全局的呈现这个指标进行全局的监控。
此外,社区也在不断的完善中,PIP-3 已经正式引入了 Paimon 的一些指标体系,相信未来能够在更多维度上,对 Paimon 表进行更加全面的监控,第一时间发现写入的问题。
03
Apache Paimon 优化实践
接下来分享一下我们在实践过程中,碰到的一些问题,主要是分为三个方面。首先是写入性能和稳定性,包括一些初始化和内存控制上的问题;其次是查询性能和稳定性上的问题;最后是局部更新以及字段注释的问题。
下面我将会结合我们的实践,分享一些和社区的解决方案给大家,希望能够帮助大家更好的使用 Paimon。
第一个问题,Paimon 在写入任务的二次初始化慢。Paimon 在写入任务不依赖于 checkpoint 重启的时候,它需要从 Manifest 里面加载已有的分区、bucket 和 LSM 树 Level 信息,用于后面的续写。在实践过程中,随着 commit 数量的增加,Manifest 数量的上涨也非常明显。我们实践下来单分区加载最大要花费 18 秒左右。并发写分区越多的情况下,初始化加载比较慢的情况就会比较严重。
下面看下 Paimon 的元数据文件的组织结构。每个分区的元数据信息都存储在元数据目录下的 Manifest 文件里,每个 Manifest 里又存储了分区 bucket 信息、LSM 树信息以及 Level 信息。默认每一个 Manifest 的大小大约为 8M 左右,每个分区 Manifest 的数量会随着 commit 数量的增加而增加,同时元数据文件的数量也会增加,然后初始化的时间就会被拉长。
解决方案也很简单,主要有两个方向,第一个是分缓存,第二个是增加一些筛选。
分缓存是通过缓存减少与底层文件交互的次数,提升加载性能。同时在加载过程中,根据当前写入的分区和 Bucket 的信息裁剪掉不必要的 Manifest 文件,减少加载的实体数量,加快加载速度。
有兴趣的同学也可以参考一下上图中的两个链接,如果在实践过程中碰到类似的问题,可以尝试去配置'write-manifest-cache'这个参数,增加它的缓存,然后根据实际应用大小去调整它的大小。
我们实践下来,上千个分区并发写 200 多个分区情况下,配置 2 个 G 左右的 Manifest 缓存就够了。
另外,社区在 1199 这个 PR 里也对 Manifest 的合并进行了优化,理论上 Manifest 的文件会小很多。
最终我们应用了这两个优化项后,实际测试下来,单个分区的初始化时间由原来的 18 秒降低到了 50 毫秒左右,整个任务二次初始化的时间大大减少了。
第二个问题,commit 阶段的内存控制。这个和 Manifest 的加载也有一定的关系,因为单次 commit 的变更分区和桶数是不固定的,commit 阶段可能会加载非常多的 Manifest,就会导致内存的占用过高,甚至出现 OOM 的情况。
最终社区通过更改整个加载 Manifest 里的一个并发框架,将原来的 stream 模式改为了消费队列的模式,更加细粒度的控制 commit 阶段的内存使用。
如果大家在实践过程中,commit 阶段出现类似的问题,可以尝试去配置'scan.manifest.parallelism'这个参数,目前我们配置的是 15,相对来说比较保守,任务基本能稳定运行。大家在实践过程中也可以不断尝试,去找到一个比较合适的配置。
第三个问题,流批 Split 大小控制。随着 Paimon 表数据量不断的增长,流批读 Paimon 的时候,他的 Split 下发阶段可能会触发 akka.framesize 的限制,导致任务报错。从上图可以看到,这个报错不是很明显,所以不能很快的去定位到它的根因。
大家如果在实践过程中碰到了这样的错误,可以尝试去调整'scan.split-enumerator.batch-size'这个参数,控制流读的时候下发 Split 的大小。
第四个问题,加速时间戳旅行读。在时间旅行过程中,比如我们读取某一个时间戳的数据量的时候会发现,初始化 Jobmanager 耗时非常久。经过分析主要是在根据时间戳定位 Snapshot 这个阶段,原来用的是遍历的模式,耗时基本上会在 10 分钟左右。后来我们改成了二分的模式,耗时从原来 10 分钟缩短到了 1 分钟不到,就能根据具体的时间戳定位到某一个快照信息,快速的将整个数据读取出来。
第五个问题,多表流读数据均衡问题。在多流读的时候,我们发现有一些 task 和 slot 没有被分配任务,也就是说它处于一个空闲的状态。内部我们增加了表名和分区的 Hash 条件,如上图。
然后让流读的数据更加均衡的流入到各个算子中,提升了整个容器的利用率。近期我们也会把 pr 提交到社区里,大家可以关注一下。
第六个问题,局部更新。在实践过程中我们发现,局部更新在延时场景中可能会出现数据更新异常的情况。如上图所示,两条数据在经过 Merge 之后,比较字段变成了 4637 的值,但 4635 的延时更新就无法应用了。因为 4635 比 4637 要小,最终导致数据不一致。
这个问题的主要原因是数据来源有很多不同的字段,来源于不同的流,但是只有一个比较近的,是无法满足我们数据延时到达的场景的。
最后社区也引入了 sequence group 的概念,即将不同的字段对应到不同的比较字段里,实现独立比较,最终实现真正的更新能力。大家在实践局部更新的时候也可以采用这样的声明方式,去解决局部更新延时到达的问题。
第七个问题,中文 Comment 乱码的问题。目前在 Flink 1.17.2 上已经有 Flink 的 pr 进行修复了,大家可以升级到 1.17.2 上去解决。
或者参考这个 pr,改到自己 Flink 源码里。我们内部因为是 1.17.0 的版本,所以主要是通过在 Comment 前增加一个 utf-8 这样的形式解决了这个乱码的问题。
最后总结一下整个实践过程,首先,Paimon 的读写性能是令人印象非常深刻的;其次,其非常简单的抽象设计,带来非常强的扩展性;最后也是最重要的一点,有一帮非常活跃的人,基本上有什么问题都能非常迅速的得到解决。
04
未来规划和期待
关于未来规划主要分为了以下三个方面。
统一的管理。未来我们将会借鉴或者引入网易的 Arctic 能力,更好管理数据湖中的表,包括整个表的合并、监控等等。
强化集成能力。因为我们的架构不能很好的应对结构演进或者总库同步,未来我们将会参考 CDC 的设计,通过 stream 的方式完成整个同步或者结构演进能力的补齐。
扩大 Paimon 的应用范围。比如 Paimon 里类似于 Kafka Consumer group 的能力,取代有部分需要保留时间更长的 kafka 的场景。同时也会扩大局部更新的应用范围,以及正在设计中的 No bucket table 的能力,能够很好的去替代 Hive 场景。此外,还会跟进 AIGC 的能力,加上 Paimon 构建 Fabric 的能力,未来会有更多的应用场景。
Q&A
问:ODS 表接入数量达到 100+,200+以后,入湖任务的稳定性这块如何治理?
答:我们目前通过采集 Flink,消息队列以及自定义部分 Paimon 表指标,结合指标制定稳定性阈值,第一时间发现同步延迟,合并延迟或者资源问题,再根据实际情况进行调优。
问:如果某个超大表入湖效率比较慢,除了拆分超大表单独入湖的方式之外,请问还有方式加快 Paimon 入湖效率吗?
答:超大表的话可以考虑加大分桶数,或者使用最新的动态分桶,同时全量入湖阶段可以适当增加任务资源(内存和并行度),另外 Paimon 入湖时,也可以增大 checkpoint 的间隔,调大:write-buffer-size 在内存中缓存更多数据,同时开启 write-buffer-spillable;最后也可以考虑将 Compaction 独立,让入湖任务所有资源都用于数据摄入。
活动视频回顾 & PPT 获取
建议前往 Apache Flink 学习网:
https://flink-learning.org.cn/activity/detail/f7aeb46d723678642e1a96d780d78baa
视频回顾/PPT 下载:关注 Apache Flink 公众号/ Apache Paimon 公众号,回复 0615
往期精选
▼ 「Apache Paimon Meetup 活动回顾」扫下方图片观看全场直播回放▼
▼ 关注「Apache Flink」,获取更多技术干货 ▼