分区标记完成,流批一体?
先说结论
所有的前提都必须是开启checkpoint写入
- 对于实时采集,若想实现一条采集分别给离线和实时用,那么需要如下配置
(1) 配置partition.timestamp-formatter:指定将分区的formatter转为timestamp,便于计算done时间;单dt为yyyyMMdd,dt和hour的多分区为yyyyMMdd HH:mm:ss
(2) 配置partition.timestamp-pattern:指定分区字段的模式;单dt为$dt,dt和hour的多分区为$dt $hour:00:00
(3) 配置partition.time-interval:指定分区的时间跨度;如'1d','1h'
(4) 配置partition.idle-time-to-done:指定分区空闲时间;流式写入判定分区多长时间无新数据写入,则执行mark-done-action操作
(5) 配置partition.mark-done-action:指定done操作,具体操作如下,可指定一个和多个。\
| 配置值 | 核心行为 | 底层实现逻辑 |
|---|---|---|
success-file(默认) | 在分区目录下生成 _SUCCESS 文件,文件内包含 creationTime/modificationTime(记录创建/修改时间,用于排查延迟数据) | 由 SuccessFileMarkDoneAction 实现:1. 拼接分区路径 + _SUCCESS 文件名;2. 写入/更新包含时间戳的 JSON 内容; 3. 若文件已存在,仅更新 modificationTime。 |
done-partition | 向元数据存储(Metastore)中添加后缀为 .done 的分区(如原分区 dt=20240501 → dt=20240501.done) | 由 AddDonePartitionAction 实现:1. 解析分区键值对(如 dt=20240501);2. 对最后一个分区键的值追加 .done 后缀;3. 调用 PartitionHandler 在元数据中创建该新分区。 |
mark-event | 向元数据存储中标记该分区的「LOAD_DONE」事件(标记分区加载完成) | 由 MarkPartitionDoneEventAction 实现:1. 解析分区路径为键值对格式; 2. 调用 PartitionHandler.markDonePartitions() 向元数据写入「完成事件」。 |
http-report | 向远程 HTTP 服务器上报分区完成的信息(含表名、分区路径、自定义参数等) | 由 HttpReportMarkDoneAction 实现:1. 构造 JSON 请求体(包含 table/path/partition/params);2. 向配置的 partition.mark-done-action.http.url 发送 POST 请求;3. 校验响应中 result 字段为 success(否则抛异常)。 |
custom | 自定义分区完成逻辑(需实现 PartitionMarkDoneAction 接口) | 由用户自定义类实现: 1. 配置 partition.mark-done-action.custom.class 指定实现类;2. 框架通过反射实例化该类并调用 markDone(String partition) 方法;3. 需保证自定义类实现 close() 方法释放资源。 |
- 对于离线依赖的情况,如离线表2依赖离线表1的done标记,那么只需要配置Hint
/*+ OPTIONS('partition.end-input-to-done'= 'true') */,离线批处理insert完成后,会自动执行mark-done-action操作,这样离线表1在insert完成后就自动done了,离线表2就可以开始执行了(是根据你的调度系统操作的)。
(1) DDL建表指定
CREATE TABLE my_partitioned_table (
f0 INT,
f1 INT,
f2 INT,
...
dt STRING
) PARTITIONED BY (dt) WITH (
'partition.timestamp-formatter'='yyyyMMdd', -- 将分区字段dt转为时间戳
'partition.timestamp-pattern'='$dt',
'partition.time-interval'='1 d', -- 确定分区的时间跨度
'partition.idle-time-to-done'='15 m', -- 流式写入判定分区多长时间无新数据写入标记为done,给离线那边做批任务启动的标记
-- 技术原理:通过监控文件系统的 modificationTime 判断活跃状态,结合水印机制处理乱序数据。
'partition.mark-done-action'='success-file'
);
<1> partition.mark-done-action多种模式
首先,partition.mark-done-action有多种模式,让我们依次分析
-
'success-file': 在目录中添加 '_success' 文件。 _SUCCESS文件内容如下
{ "creationTime" : 1762506303378, -- 2025-11-07 17:05:03 "modificationTime" : 1762506303378 -- 2025-11-07 17:05:03 -- modificationTime的值不是表中的dt,而是系统通过文件系统的最后写入时间来的,默认没有延迟数据到来的话,modificationTime=creationTime,若分区已经done标记后,还是来了延迟数据,那么modificationTime会修改 } -
'done-partition': 在Hive的HMS元数据存储中添加 'xxx.done' 分区,从而对hive可见该分区数据
必须配置'metastore.partitioned-table'='true',这样才能把paimon的done标记后的分区同步到Hive中,实现Hive中也能展示该done分区,从而进行离线操作
-
'mark-event': 将分区事件标记到元数据存储。
-
'http-report': 向远程HTTP服务器报告分区标记完成,如azkaban去写脚本检查http的done标记。
必须配置'partition.mark-done-action.http.url'='xxx:xxx'且还要配置'partition.mark-done-action.http.params'='{json串}',如下,这样访问该url的http接口就可以得到相应的json文件了
{ "table": "table fullName", "path": "table location path", "partition": "mark done partition", "params" : "custom params" } -
'custom': 使用策略类创建标记分区策略。
必须配置'partition.mark-done-action.custom.class' = 'xxx',指定自定义类,且自定义类必须实现PartitionMarkDoneAction接口
<2> 流程分析
| 阶段 | 触发条件 | 具体动作 |
|---|---|---|
| 时间窗口关闭 | creationTime + time-interval = 2025-04-21T23:59:59Z | 系统认为该分区的 理论时间窗口已结束时间窗口仅用于计算分区的理论生命周期,不会自动校验数据的事件时间是否在窗口内。 |
| 延迟写入检测 | modificationTime > creationTime + time-interval(超2分钟) | 在元数据中标记该分区存在 延迟数据(但不会自动扩展时间窗口) |
| 标记完成触发 | 最后一次写入时间 20250422 00:02:00 ≥ 20250421 23:59:59 后,等待 idle-time-to-done=15m | 实际标记完成时间为 2025-04-22T00:17:00Z(需满足属于20250422分区的,在15分钟内都无新数据) |
| 下游任务调度 | 检测到 _SUCCESS 文件生成(包含最终 modificationTime) | 在 00:17:00 后启动离线任务(而非 00:02:00) |
- 离线任务启动时间:由
idle-time-to-done决定(示例中为00:17:00),与modificationTime超界时间无直接关系,然后离线任务会去拿取分区为21号的所有数据(包括那条22号来的的延迟数据) - 延迟数据影响:
modificationTime超界会记录在_SUCCESS文件中,但 不会阻止标记完成 - 数据完整性风险:超界写入的数据本应该属于下一个分区(如
2025-04-22),但被错误写入历史分区(如2025-04-21),从而影响21号的数据结果
默认情况下,partition mark done 会创建一个 _SUCCESS 文件,_SUCCESS文件的内容是 json,包含modificationTime和creationTime,它们可以帮助你了解是否有延迟数据。下游系统通过比较这两个时间戳可判断是否存在延迟数据(如 modificationTime > creationTime + time-interval则判定为延迟数据,可用insert overwrite去解决延迟数据的影响)
技术解决方案
(1) 写入端约束(根源治理)
-- 启用写入时间校验(需Paimon 2.3+)
ALTER TABLE my_table SET (
'partition.time-validator' = 'strict' -- 可以在建表的时候指定,默认是none
);
- 严格模式:拒绝写入
modificationTime > creationTime + time-interval的数据 - 警告模式:允许写入但记录审计日志(
'partition.time-validator' = 'warn')
(2) 处理端补偿(事后修复)
# 方1:下游任务增加时间窗口过滤
spark.read.format("paimon")
.load("my_table")
.filter("dt='20250421' AND event_time BETWEEN '2025-04-21 00:00:00' AND '2025-04-21 23:59:59'")
# 方2:使用Paimon CLI修复分区归属
paimon-cli migrate-partition \
--source dt=20250421 \
--target dt=20250422 \
--filter "event_time >= '2025-04-22 00:00:00'" # 假设存在隐藏的事件时间字段
(2) insert完毕后自动打done标记 -- 针对批写入
执行如下sql,Hint配置partition.end-input-to-done'= 'true'则会在这次insert完毕后,自动打done标记,执行ddl中的相应partition.mark-done-action操作
INSERT INTO T /*+ OPTIONS('partition.end-input-to-done'= 'true') */ VALUES (0, 'p1', 1), (0, 'p2', 2)
这样,当这条insert语句执行完毕,那么就会自动打上done标记