一.场景问题总结
1.CDC的乱序问题
如下图中左侧所示,数据分片更新时产生先+D 后+I消息,由于数据分库分表导致先输出+I 后输出+D,数据产生乱序。
解决方案:基于Watermark + State + TimerService 多流保序合并
- Watermark判断延迟的数据
- State对乱序的数据进行修正位置
- TimeService基于Watermark的时间范围,对到达时间超过此范围的数据,判定为过期,不会往下游传递。
2.多个不同的源表同步到一张湖表,产生schema冲突
在整库全量同步实践中(例如从多个省份的MySQL向StarRocks迁移),不同省份的源端数据库可能存在Schema差异化(例如字段名不同、数据类型不一致或新增/删除列)。而当前社区工具(如Flink CDC Connector或SMT)的0.9版本仅支持CDC场景下的同类型Schema变更(如MySQL ALTER TABLE),无法自动处理跨省分异构Schema的全量迁移冲突
解决方案:联通自己开发了一个CdcSchemaCommonUtils工具,实现全增量一体化的Schema
假设各省分MySQL表的订单表结构不同:
- 省A有字段
order_id,price,create_time - 省B有字段
order_id,amount,update_time CdcSchemaCommonUtils会自动合并为Starrocks目标表的统一Schema(包含所有字段),并在后续增量阶段持续适配变更。
3.多个数据源表的schema变更时间不一致,插入同一张湖表,产生数据类型不一致问题
如下图原本定义为int字段,随着业务需求的变化,需要改成string。但在实际情况中,各个省份的变更进度不一致,有的省份已经完成了类型变更,有的省份还保持着原来的类型。然而增量消息的同步并不会因为这种变更而停止,它会持续不断地进行,导致后续未变更省分增量数据无法写入,产生异常冲突。
解决方案:多场景写入Schema Compatibility实现
通过实现Schema Compatibility 指定可兼容的场景,比如:对于Int -> String 是可兼容的场景,而Sting -> Int是非法的,保障在多写入场景下,源端字段类型冲突的问题,导致数据无法更新。
4.Flink多流JOIN存在大状态问题,用湖怎么解决?优势是什么?
(1) partial update的方案
Paimon支持partial update,也就是部分更新,比如 a表有三个字段分别是id、name、age;b表有两个字段id、score,那么我们可以在paimon中建表四个字段:id、name、age、score;然后在数据装载的时候,案例如下:
-- Stream1写入基础信息 @T1时刻
INSERT INTO paimon_user_profile VALUES (1001, 'Alice', NULL, NULL);
-- Stream2写入行为数据 @T2时刻(T2>T1)
INSERT INTO paimon_user_profile VALUES (1001, NULL, '2025-05-08', 5);
-- Stream1更新姓名 @T3时刻(T3>T2)
INSERT INTO paimon_user_profile VALUES (1001, 'Alice Smith', NULL, NULL);
-- 最终结果如下:
| user_id | name | last_login | page_views |
|---------|-------------|-------------------|------------|
| 1001 | Alice Smith | 2025-05-08 | 5 |
优势: 这个功能能够对相同主键的多条记录进行合并,取每个列的最后一个非空值。然而,这种 Merge 操作并不是在 Flink 的计算任务中完成的,而是在 Paimon 表的 Compaction 任务中进行的。由于 Paimon 的存储采用 LSM(Log-Structured Merge-Tree)分层有序的数据结构,在进行 Compaction 时,能够轻松地将不同层的相同记录合并。
与以前实时数仓的各自方案对比
- 首先,它完全消除了 Streaming Join 的磁盘随机读问题。
- 其次,数据存储可以统一收敛到数据湖仓,这意味着不再需要外部的 HBase 或其他 KV 系统,从而节省了相关成本。在实际案例中,仅省去 HBase 的一部分存储,每月就能节省约五万元。
- 此外,系统的稳定性得到了提升,因为 Flink 的状态不再需要保存 TB 级别的数据。最后,作业逻辑得以简化。通过使用 Paimon,只需使用 SQL 就能完成任务,而不再需要像以前那样编写 Flink 的定制 Timer 逻辑进行优化。
(2) partial update的compaction冲突,导致ck失败
<1> 方案一: 专用异步compaction任务
首先,建表的时候要配置一个参数
"write-only": "true"
然后,单独启动一个FlinkJob去异步compaction
SET 'execution.checkpointing.interval'='200s';
SET 'execution.checkpointing.tolerable-failed-checkpoints'='60';
SET 'taskmanager.memory.managed.fraction'='0.05';
SET 'taskmanager.memory.task.off-heap.size'='1G';
SET 'heartbeat.timeout'='6000s';
SET 'execution.checkpointing.timeout'='720m';
SET 'execution.runtime-mode' = 'streaming';
CALL sys.compact(
`table` => 'test_db.test_pu1',
options => '
snapshot.expire.limit=5000,
snapshot.num-retained.min=300,
snapshot.num-retained.max=700,
snapshot.time-retained=20h,
sink.parallelism=32,
full-compaction.delta-commits=5000,
target-file-size=512mb,
write-buffer-spillable=true,
write-buffer-size=128 mb,
num-sorted-run.stop-trigger=2147483647,
sort-spill-threshold=10,
lookup.wait=false
'
);
<2> 方案二: Single Planner
解决方案:将多条流的写入任务,合成一个FlinkJob,并且sql写全部的column,没有的先写null,然后底层在最后的writer阶段会自动识别是否插入同一张paimon表且sql的列是否一致,然后进行Single Planner合并优化
在实际使用过程中经常会遇到一个问题:当一个作业包含多个Source ,并将数据写入同一个 Paimon 表时,如果多个 Flink 尝试同时对该表进行 Compaction 操作,Paimon 通常不支持这种行为。这会导致作业在执行 Compaction 时失败,进而引发作业持续 Failover ,最终导致作业不可用。
解决方案: 1.19之后的Flink有Single Planner 将能够自动识别是否多个 Flink 组件正在向同一张表写入数据。在满足特定条件时将多个 Flink 的上游结果进行 Union 操作,并仅使用一个 Flink 组件来统一写入所有数据。这样在进行 Compaction 操作时,就只有一个判断逻辑,从而避免了之前提到的冲突问题。
5.传统保留changelog多且离线数据同步只能全量,效率很低,湖仓怎么解决?
在引入Paimon 之前,传统的解决方案主要有两种:
1.保存原始 Changelog 数据:这种方法直接保存原始的 Changelog 数据,在查询时通过 View 按照主键和时间进行聚合,让用户查询到聚合后的表。优点是维护简便,数据直接以追加形式写入,维护时只需清理较旧的 Changelog 数据。然而,这种方法的可扩展性不足,对于更新频繁的场景,清理旧 Log 数据的频率较高。此外,每次查询时的聚合操作会引入较大的延迟。 2.离线批量数据导入:这是离线链路的方式,其缺点是数据新鲜度低,因为每次导入都需要全量数据。这样导致数据更新不及时,无法满足时效性要求。
解决方案: Paimon 支持多种 Changelog Producer,适应不同的业务场景和需求。例如,在 Streaming Upsert 中,虽然很多计算场景下用户不需要消费精准的 Changelog,但如果有需求,Paimon 也能提供多种 Changelog 模式:
- INPUT 模式:适合写入 CDC 数据。
- LOOKUP 模式:适合数据量较小但对 Changelog 延迟要求较高的场景。
- FULL-COMPACTION 模式:适合数据量较大但延迟要求较高的场景,通常延迟在 10 到 20 分钟之间。
- 使用 Paimon 替换 Iceberg 的收益主要有以下几点:
- 降低 Compaction 的资源消耗:Paimon 的 LSM 结构在合并数据时更高效,减少了资源消耗。
- 减少写入的小文件:Paimon 不需要写两个额外的 Delete 文件,从而适当减少了小文件问题。
- 提升 Streaming read 体验:Paimon 的 LSM 结构非常适合增量读取的批处理场景,能够更高效地处理增量数据。
6.针对无主键表性能差的解决方案
解决方案:Range Partition and Sort
1.Range Partition 的目标是将用户指定列中值相近的数据放在一起。为了实现这一点会引入两个算子: Local Sample 和 Global Sample 。这两个算子的本质是对批处理作业的输入数据进行采样。 2.接下来分析列值的取值范围。一旦确定了这个范围,下游会有一个名为“Assign Range Index”的算子。该算子会根据取值范围分配一系列 range ,并计算每个数据具体属于哪个 range ,然后将这个 index 添加到数据中。当数据继续向下游流动并进行 shuffle 时,会根据这个 index 来确定数据的分发。这样下游的算子在处理数据时,相同 value 的数据会被发送到同一个下游算子进行处理。在数据写出之前会将这个 index 删除。这样就相当于为无模式表的数据划分了一系列 range ,每个range 内的数据值都比较接近。 3.接下来会进入 Sort 阶段,每个算子会对自己负责的 range 内的数据进行排序,并最终将数据写入到各自的数据文件中。这样就对用户指定的列完成了一次全局排序。经过这样的排序后可以加速下游的查询性能,同时提高列式存储文件的压缩率,从而节省存储空间的使用效率。
7.Flink-kakfa出问题可以重置offset,那么flink-paimon怎么办呢?
问题 : 当我们以流任务去消费 Paimon 表时,遇到的问题是在以往消费消息队列时,任务出现问题可以回拉重算,但是在 Paimon 表默认只保留1个小时的数据,如果要回答更久的数据,它就需要去调长 Snapshot 保存时长。虽然这可以临时满足业务需求,但在使用过程中发现如果保留太多版本的 Snapshot,会带来存储放大的问题。这中间会有很多自动 Compaction 的过程,并不是表面意义上的保留,它放大的数据量取决于 LSM Tree 数据结构本身的放大效应。
解决 : 通过将changelog producer配置改为lookup或full-compaction,实现Changelog 和 Snapshot 的生命周期解耦(默认是none,这种情况下,snapshot和changelog是绑定的) ,Snapshot 类似于数据库里的当前快照,Changelog 类似于数据库里的Binlog,在数据库里也可单独配置 Binlog 生命周期。当 Snapshot 过期时,其中的 Data files 就会被清理。
当 Snapshot4 过期时,对应 Delta Manifest 里所指向 Delete 文件就会被自动清理。而其中 Changelog 部分就会被保留,Changelog 的原数据就会被额外的更新在新的 Changelog 元数据中,这样, 可达到 Changelog 解耦的目的。
对于没有 Changelog,如增量读取的 Delta 文件类型,通过标记文件的来源是 Compaction 还是 Append来判断在是否在 Snapshot 过期的时候要清理这些文件。
1.Compaction 生成的文件是可被安全清理的 2.Append 新增的文件只有当对应的 Changelog 元数据过期时候才会被安全清理。
8.Paimon存储的文件碎片化及合并对用户不可见
目前数据入湖比较大的痛点——文件碎片化严重。下图在 Paimon 网站上摘录。CDC数据在进行一些入湖时可以看到,数据有原数据、真实数据包括 Manifest、datafile,这样就会导致原数据以及 datafile 有表多的问题。特别是在一些场景中例如数据的上报延迟,就会导致很多小文件写入到不同的分区上,给 Hdfs 的压力增大。同时在对象存储上访问频率增加,会导致一些作业存在性能瓶颈。Paimon 会通过自己的自动优化机制定期进行一些小文件合并,但是也存在一些问题,例如在进行小文件合并时对用户而言是黑盒的操作,没有办法控制合并频率、生命周期或者合并的时间。
解决方案:采用Apache Amoro+CDC;通过 Amoro 平台对这些湖仓作业进行管控优化,包括对这些表的小文件进行自动合并,合并过程比较智能,包括对湖仓的一些原数据进行统一管理和后续对索引进行优化的工作。
9.Paimon原生聚合性能不足
问题:Paimon 的 Merge-on-Read 表时,受限于 Paimon SDK(Java)单线程处理多文件的排序与合并,在高并发场景下完全无法满足业务对 “秒级响应” 的需求。
解决方案:使用Starrocks或Doris的Parquet Reader去读取Paimon的数据,然后借助他俩自身引擎的能力去聚合,不让Paimon去聚合了
10.对于非分区或单分区过大的物化视图更新的代价太高问题
问题:对于非分区表,或者单分区数据量较大的表,依然有较高的更新成本。
解决方案:目前Doris有一个新功能,小米开发了 Paimon 表的快照级别的增量读取能力,如:
SELECT * FROM paimon_table@incr('startSnapshotId'='0', 'endSnapshotId'='5')
该功能可以仅读取指定 snapshot 区间的增量数据,这样更新的成本就没那么大了
11.HDFS不稳定问题
问题:使用Flink的检查点或者Paimon的人,都清楚HDFS多副本读取时,默认 60 秒的超时阈值和网络抖动,导致查询延迟波动极大,各种服务不稳定问题。
解决方案:
- HDFS 快速超时与重试:HDFS 在读取数据时,会利用多副本机制,当一个副本的读取时间超过阈值后,会切换到另一个副本尝试读取。超时阈值由参数
dfs.client.socket-timeout控制,默认是 60 秒。这导致首次读取的超时时间过长,在 HDFS 抖动或负载较高的情况下,会导致查询延迟显著增加。我们通过将该阈值降低到 100 毫秒,让读取情况进行快速的超时重试,显著降低了查询长尾,P99 性能提升 1 倍,总体性能提升 10%。 - Doris 数据缓存:针对高并发查询场景,单纯的降低 HDFS 的重试超时时间,无法彻底的解决 HDFS 查询延迟高的问题。因此,我们利用 Doris 的数据缓存能力,将热点数据缓存在本地高速磁盘上,完美解决了高并发场景的查询延迟问题。在开启缓存的情况下,从 5 并发到 80 并发,查询延迟可以降低 25% 到 300%。
12.Paimon小文件问题
问题:由于Paimon的高频写入、Checkpoint机制和分区更新特性,可能会在HDFS中生成大量小文件,导致NN的元数据请求压力增加。
解决方案:
(1) Append表对NameNode请求的影响因素以下因素会影响Append表在HDFS NameNode(NN)上的create、delete和rename请求:
- 目标文件大小(target-file-size)
- 影响:增大
target-file-size可使每个文件承载更多数据,降低文件滚动和关闭的频率,从而减少小文件生成,减轻NN的元数据管理负担,同时提升存储资源利用率。 - 优化策略:将小文件判断阈值调整为小于32MB,以减少实时文件合并的频率,后期可通过离线合并进一步优化。
- 权衡:较大的
target-file-size可能增加写入延迟,需根据业务需求权衡。
- 影响:增大
- Sink算子并行度
- 影响:Flink Sink算子的并行度较低时,生成的文件数量减少,单个文件大小可能增大(尤其在数据流量较小时,需依赖数据快速达到target-file-size)。
- 优化策略:根据输入流量和
target-file-size合理估算并设置Sink算子并行度,以平衡文件数量和写入性能。 - 权衡:降低并行度会减少文件生成,但可能降低业务写入吞吐量和增加写入延迟。
- Checkpoint频率
- 影响:每次Checkpoint操作会在HDFS中生成新的文件和目录。延长Checkpoint间隔可减少生成的文件数量,降低小文件对NN的压力。
- 权衡:较长的Checkpoint间隔可能导致状态恢复延迟增加,并影响下游任务的数据消费实时性。
(2) 主键表对NameNode请求的影响因素主键表对NN的create、delete和rename请求的影响因素包括以下方面:
- 写缓冲区大小(write-buffer-size)
- 影响:
write-buffer-size决定每次写入的缓冲区大小。较小的缓冲区会导致频繁的数据刷新到HDFS,生成较多小文件。增大write-buffer-size可降低写入频率,减少小文件数量,通常与target-file-size配合使用以提升写入效率。 - 权衡:较大的
write-buffer-size会增加内存占用,并可能引入更高的批量写入延迟。
- 影响:
- Bucket数量
- 影响:减少Bucket数量可降低文件生成数量,从而减少NN的元数据开销。
- 权衡:减少Bucket可能导致数据在单个Bucket中过于集中,文件体积增大,进而降低并发写入能力。
- Checkpoint频率
- 影响:与Append表类似,每次Checkpoint会在HDFS中生成新的文件和目录。在中小流量场景下,延长Checkpoint间隔可减少文件数量,降低NN负担。
- 权衡:较长的Checkpoint间隔可能增加状态恢复和下游任务消费的延迟。
- Flink Sink并行度
- 影响:降低Sink并行度可减少生成的文件数量,从而降低小文件对NN的压力。
- 权衡:降低并行度可能导致写入速率下降,影响整体吞吐量,尤其在高并发写入场景下。
- 数据文件Compact和Level层级
- 影响:Compact操作会将多个小文件合并为较大文件,逐步提升文件层级,从而减少小文件数量。较少的层级数可降低Compact操作频率,Paimon在合并过程中会删除已被合并的小文件,仅保留合并后的大文件,显著减轻NN负担。
- 优化策略:合理配置Compact策略和层级数,以平衡合并开销和文件数量。
- 分区更新频率
- 影响:主键频繁更新会导致不同分区的数据文件反复生成和更新,尤其在分区粒度较小或分区数量较多时,会显著增加文件数量,加重Compact操作开销和NN的元数据管理负担。
- 优化策略:优化分区设计,减少不必要的高频更新,合理控制分区粒度和数量。