1.为什么会冲突?
Paimon可能有两种类型的提交失败:
- 快照冲突:快照 ID 已被抢占,表已从另一个作业生成新快照。好,让我们再次提交。
- 文件冲突:此作业要删除的文件已被其他作业删除。此时,作业只能失败。(对于流式处理作业,它将失败并重启,有意故障转移一次)
(1) 快照冲突
Paimon 的快照 ID 是唯一的,所以只要 Job 将自己的快照文件写入文件系统,就认为是成功的。
Paimon利用文件系统的重命名机制提交快照,该机制对HDFS而言是安全的,因为它确保了事务性和原子性的重命名操作。
但对于OSS和S3等对象存储,其'RENAME'操作不具备原子语义。我们需要配置Hive或jdbc元数据存储库,并为目录启用'lock.enabled'选项。否则可能存在快照丢失的风险。
(2) 文件冲突
Paimon 采用乐观并发控制(Optimistic Concurrency Control, OCC)机制来处理多个写入者同时修改同一张表的情况。
- 读取当前状态:每个写入任务开始时,都会基于当前最新的快照进行操作。
- 执行写入:任务在本地执行写入逻辑,生成新的数据文件。
- 提交与冲突检测:在提交阶段,提交器会尝试创建一个新的快照。在创建之前,它会检查自任务开始以来,是否有其他写入者已经提交了新的快照。冲突检测主要关注是否有对相同分区或桶的并发修改。
- 重试:如果检测到冲突,提交会失败,然后任务会根据配置的重试策略(如指数回退)进行重试。重试时,任务会重新读取最新的快照,并再次应用其变更。
这种无锁化的设计避免了传统数据库中锁带来的性能瓶颈和死锁问题,使得 Paimon 能够支持高并发的写入场景。
当 Paimon 提交文件删除(只是逻辑删除)时,它会检查与最新快照的冲突。 如果存在冲突(这意味着文件已被逻辑删除),则它不能再在此提交节点上继续。 因此,它只能有意触发故障转移后重新启动,并且 Job 将从文件系统中检索最新状态 希望解决这个冲突。
一句话:如果当前版本的某个文件已经被压缩了,然后我现在想把操作这个文件,系统会发现这个文件在压缩的时候就被执行了删除(逻辑上),因此他会发生冲突,他认为已经删除的文件是不可以被操作的。
Paimon 会确保这里没有数据丢失或重复,但如果两个 streaming job 同时写入并且产生冲突,你会看到它们不断重启,这不是一件好事。
冲突的本质在于删除文件(逻辑上) ,而删除文件源于压缩,所以只要我们关闭写入作业的压缩(也就是将 'write-only' 设置为 true)并额外启动一个单独的作业来执行压缩工作。 一切都很好。
- 配置
write-only = true - 开启专用压缩任务
Dedicated Compaction Job
2.Dedicated Compaction Job -- 专用压缩任务 解决冲突
默认情况下,Paimon writer 在写入记录时会根据需要执行Compaction。这对于大多数用例来说已经足够了,但有两个缺点:
- 这可能会导致写入吞吐量不稳定,因为执行压缩时吞吐量可能会暂时下降。
- Compaction会将某些数据文件标记为“已删除”(并未真正删除)。如果多个writer标记同一个文件,则在提交更改时会发生冲突。 Paimon 会自动解决冲突,但这可能会导致作业重新启动。
为了避免这些缺点,用户还可以选择在writer中不做compaction,而是运行专门的作业来进行Compaction。由于Compaction仅由专用作业执行,因此writer可以连续写入记录而无需暂停,并且不会发生冲突。
| 选项 | 必需的 | 默认 | 类型 | 描述 |
|---|---|---|---|---|
| write-only | No | false | Boolean | 如果设置为 true,将跳过Compaction和快照过期。此选项与独立Compaction一起使用。 |
Flink SQL目前1.2版本已经支持compaction相关的语句,这个作业即使没数据进来,也不会退出,会一直执行 具体配置看官网:Dedicated Compaction | Apache Paimon
首先,建表的时候要配置一个参数 "write-only": "true"
然后,单独启动一个FlinkJob去异步compaction
SET 'execution.checkpointing.interval'='240s';
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
'
);
如果提交一个批处理作业(execution.runtime-mode:batch),当前所有的表文件都会被Compaction。如果您提交一个流作业(execution.runtime-mode: Streaming),该作业将持续监视表的新更改并根据需要执行Compaction。
3.Single Planner --Sink Union 解决冲突
在很多情况下,用户可能只希望通过一个作业的多个 Flink 来完成必要的 Passbook 操作,而不希望额外启动一个专门的作业来进行 Compaction 。但遗憾的是当前可能无法直接让该作业自行处理 Compaction 。
为了实现这一目标需要在 Flink 的 Single Planner 中进行一些改造。改造完成后, Flink Single Planner 将能够自动识别是否多个 Flink 组件正在向同一张表写入数据。在满足特定条件时将多个 Flink 的上游结果进行 Union 操作,并仅使用一个 Flink 组件来统一写入所有数据。这样在进行 Compaction 操作时,就只有一个判断逻辑,从而避免了之前提到的冲突问题。
4.补充
默认情况下,Paimon支持对不同分区的并发写入,也就是说往不同分区同时写入数据,是不会冲突的,而往相同分区的同一个桶写数据是可能冲突的
实时+离线方式是streaming job将记录写入Paimon的最新分区;同时批处理作业(覆盖)将记录写入历史分区。