Paimon Partition Mark Done --- 流批一体化之一

172 阅读5分钟

分区标记完成,流批一体?

先说结论

所有的前提都必须是开启checkpoint写入

  1. 对于实时采集,若想实现一条采集分别给离线和实时用,那么需要如下配置
    (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=20240501dt=20240501.doneAddDonePartitionAction 实现:
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() 方法释放资源。
  1. 对于离线依赖的情况,如离线表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有多种模式,让我们依次分析

  1. '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会修改
    }
    
  2. 'done-partition': 在Hive的HMS元数据存储中添加 'xxx.done' 分区,从而对hive可见该分区数据

    必须配置'metastore.partitioned-table'='true',这样才能把paimon的done标记后的分区同步到Hive中,实现Hive中也能展示该done分区,从而进行离线操作

  3. 'mark-event': 将分区事件标记到元数据存储。

  4. '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"
    }
    
  5. '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,包含modificationTimecreationTime,它们可以帮助你了解是否有延迟数据。下游系统通过比较这两个时间戳可判断是否存在延迟数据(如 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标记