OpenLineage 事件设计:Job、Run 与 Dataset

从 Job、Run、Dataset 三类核心实体出发,设计可互操作、可扩展、可回放的 OpenLineage 事件。

袋鼠云
袋鼠云
数据治理

设计 OpenLineage 事件时,先把三个概念分开:Job 是作业定义,Run 是该作业的一次执行,Dataset 是被读取或写出的数据对象。Job 解释“做什么”,Run 解释“这一次怎样做、结果如何”,Dataset 解释“对哪些数据发生了关系”。事件再用生命周期状态和 Facet 补充上下文。只要这三个实体的标识稳定,血缘消费者才可以把不同引擎产生的事件拼成一张可追溯的图。

先确定核心标识

Job 由 namespace 和 name 共同识别。name 应该表达逻辑作业,而不是某次调度实例或临时容器名称;同一个逻辑作业在重试时仍应保持一致。namespace 用来隔离不同调度系统、租户或环境,避免两个系统恰好使用相同名称时发生碰撞。

Run 是 Job 的一次运行实例,核心标识是 run.runId。每次真正的执行,包括失败后重试,都应生成新的 runId。不能把“2026-07-29”直接当作 runId,因为同一天可能有补数、重跑和并发执行。运行事件还应带 eventTime 和 eventType,这样消费者能区分开始、完成、失败、中止和可选的进行中更新。

Dataset 由 namespace 和 name 识别。namespace 应指向数据源边界,例如某个数据库、对象存储或消息系统;name 则表达表、文件、主题或其他数据对象。不要把运行时间、随机 UUID 或临时 Pod 名称塞进 Dataset name,否则每次运行都会产生一个看似全新的数据集,血缘无法聚合。

一个最小事件可以有这样的结构:

{
  "eventType": "COMPLETE",
  "eventTime": "2026-07-29T10:30:00Z",
  "run": {"runId": "一次执行的唯一标识"},
  "job": {"namespace": "scheduler", "name": "daily_orders"},
  "inputs": [{"namespace": "warehouse", "name": "orders_raw"}],
  "outputs": [{"namespace": "warehouse", "name": "orders_clean"}],
  "producer": "https://example.invalid/producer"
}

示例中的 producer 只是说明事件由谁产生,实际应使用可验证且稳定的生产者标识。生产事件还应补齐版本化 schema URL 和必要的 Facet,而不是把所有信息都放进自定义字符串。

用事件表达作业生命周期

对有明确开始和结束的批处理作业,通常需要先发 START,再发 COMPLETE、FAIL 或 ABORT 中的结束事件。START 说明这次 Run 已开始,并可以携带输入、代码版本或父运行信息;结束事件说明结果状态。长时间运行或流式作业不能简单套用“每天一次”的批处理假设,应明确事件触发方式和周期性进度更新的含义。

事件接收方可能先收到结束事件,或者因为网络重试收到重复事件。因此生产者要保证 runId、eventType 和 eventTime 具有可解释性,消费者也要设计幂等处理。不能用“最后收到的消息”盲目覆盖所有字段:一次运行的 START 可能包含输入,COMPLETE 可能包含输出和统计,二者应按实体和 Facet 合并,而不是互相抹掉。

父子作业是另一个容易混淆的边界。一个调度器运行总任务,子任务在不同引擎中执行时,子 Run 可以通过 parent Facet 指向父 Job 和父 Run。这样既保留任务本身的执行关系,又能在跨系统查询时回答“哪次上层编排触发了这次计算”。父关系不是把多个 Run 合并成一个 Run,子任务仍应有自己的 runId。

元数据资产 MetaDataAssets

覆盖元数据资产盘点、血缘分析、数据质量与数据安全的元数据资产管理平台

了解产品

Facet 应该放什么信息

Facet 是挂在 Run、Job、输入 Dataset 或输出 Dataset 上的原子元数据。可以按问题选择它们,而不是创建一条无法解释的长 JSON。

Job Facet 适合记录作业定义和执行意图,例如 SQL、源代码位置、作业类型或处理类型。SQL 放在 Job Facet 中,表达“这个作业执行了什么逻辑”;不要把同一条 SQL 复制到每个 Dataset name 中。Run Facet 适合记录本次运行的参数、父关系、名义时间、查询画像或运行统计,强调“这一次执行的上下文”。

Dataset Facet 适合记录数据对象的 schema、数据集类型、版本或数据源属性。输入和输出都可以有 schema,但输入 schema 与输出 schema 不一定相同。对于表、视图、文件、主题等对象,应使用能表达类型的字段;临时中间产物也应明确标注其临时属性,避免被误认为长期业务资产。

列级血缘应放在输出 Dataset 的相应 Facet 中,描述输出字段由哪些输入字段派生。只有在确实掌握字段映射时才发送列级关系;“整表到整表”不能冒充列级血缘。聚合、过滤、重命名和多输入合并都需要保留足够的变换语义,否则下游只能知道字段相关,却无法解释原因。

自定义 Facet 必须使用项目独有前缀,避免与标准 Facet 重名;其 schema URL 应指向不可变的版本化地址,不能长期指向会变化的分支。已有同名 Facet 在同一实体上会替换此前实例,所以命名和更新策略必须在生产者之间约定。

把数据质量结果挂到 Run

如果一次运行执行了完整性或唯一性检查,结果可以通过 Test Run Facet 关联到该 Run。这里要分清两个字段:status 说明测试有没有发现问题,severity 说明发现问题后是否阻断。一个测试可以是 status: fail、severity: warn,表示确实发现违反规则,但流水线仍继续;如果把这两个概念合并成一个布尔值,血缘消费者就无法解释“失败为何没有阻断”。

建议每条测试至少有稳定名称和状态,并在需要排障时携带类型、期望值、实际值和规则内容。测试没有执行也应记录为 skip 或等价状态,不能把缺失结果当成通过。质量事件的价值不在于把所有明细塞进血缘,而在于让一次生产运行能被筛选、比较和回溯。

数据治理

统一标准、质量、安全与资产管理,沉淀高质量数据集,支撑经营决策、监管合规与 AI 应用。

了解解决方案

一条可落地的实施路径

第一步,给每类引擎定义 namespace 规则和 Job name 规则,先解决标识稳定性。第二步,发出只包含 Job、Run、输入和输出 Dataset 的最小生命周期事件,验证接收方能正确合并。第三步,再按查询需求逐个增加 SQL、schema、代码版本、父关系和质量 Facet。第四步,为重试、回填、部分输出和临时数据写测试,确认不会制造重复或错误边。

例如每日订单作业在 9 点启动,读取 orders_raw 的 7 月 28 日分区,写出 orders_clean 的同一分区;如果第一次运行失败、第二次运行成功,应保留两个不同 Run,并让第二次的 COMPLETE 只代表第二次执行。查询某个输出分区时,可以继续看到失败运行的证据,也可以沿着成功运行回到实际输入。

常见失败模式

最常见的是把 Dataset name 写成带时间戳的临时路径,结果每个批次都是孤岛。其次是所有重试复用 runId,导致成功状态覆盖失败状态。第三是 Facet 只发字段名、不发 schema URL 或 producer,消费者无法判断结构和来源。第四是把“任务成功”直接当成“数据质量通过”,没有传递测试状态与严重级别。第五是自定义 Facet 没有前缀,升级后与标准字段冲突。

上线前可以用三组问题验收:同一逻辑作业的不同重试能否聚合到同一个 Job?一次回填是否能和日常调度区分而不污染 Dataset 标识?看到一条输出边时,能否找到产生它的 Run、输入数据、代码或 SQL,以及该 Run 的质量结果?如果答案是否定的,应先修正事件模型,再继续增加可视化功能。

开启Data+AI 之旅

填写表单,获取专属解决方案

400-002-1024

工作日 9:00-18:00

关注公众号

获取最新产品动态与技术干货

关注公众号

企业微信

扫码添加企业微信