3. 血缘关系的核心要素:节点、边与属性

聊血缘关系,咱们得先搞清楚它到底长什么样。说白了,它就是一张有向图。图里有什么?节点、边,还有附着在上面的属性。这三个东西,缺一个都不完整。

我在项目里见过不少团队,一上来就急着搭血缘平台,结果连「节点」和「边」都没定义清楚。最后查故障时,图是画出来了,但根本没法用。嗯,咱们先把基础打牢。

3.1 节点:血缘中的实体

节点就是图中的「点」。在数据世界里,最常见的节点有三类:表、字段和任务。

3.1.1 表节点

表是血缘关系中最粗粒度的单元。一张表就是一个节点。比如 ods_order、dwd_order_detail、ads_sales_summary,它们各自独立,又通过数据流动连在一起。

我个人习惯,在表节点上至少记录两个属性:存储引擎和数据量级。为什么?有一次排查一个报表延迟问题,我盯着血缘图看了半天,发现 ads_sales_summary 的上游表 dwd_order_detail 是 Hive 表,数据量 10 亿行,而下游任务却只给了 2 个 Map 的资源。这就是典型的「表节点属性缺失」导致的盲区。

表节点核心属性清单

  • 表名(全限定名,如 project.dataset.table)
  • 存储引擎(Hive / HBase / ClickHouse / MySQL)
  • 数据量级(行数、存储大小)
  • 分区信息(分区字段、分区数)
  • 生命周期(TTL、保留天数)

3.1.2 字段节点

字段节点是更细粒度的实体。它描述的是「列」级别的血缘。比如 order_id 从 ods_order 流到 dwd_order_detail,再到 ads_sales_summary 的 order_cnt。

字段级别的血缘,在故障排查中特别有用。我记得有一次,业务方反馈某个报表的「订单金额」字段突然变成 0。我顺着字段血缘一路回溯,发现 ads_sales_summary.amount 的上游字段 dwd_order_detail.pay_amount 在三天前被一个 ETL 任务改成了 COALESCE(pay_amount, 0)。而那个字段原本是 pay_amount,不允许为 NULL。改完之后,所有 NULL 都被转成了 0,但上游的过滤条件没同步更新,导致一批有效数据被误判为 0。

你看,如果没有字段级血缘,这种问题你根本不知道从哪查起。

我的建议:字段节点一定要记录「数据类型」和「空值约束」。这两个属性在字段变更时,能帮你快速判断影响范围。

3.1.3 任务节点

任务节点是「动作」的载体。它描述的是数据是怎么从上游流到下游的。常见的任务节点包括:

  • ETL 任务:如 Spark 作业、Hive SQL、DataFlow 管道
  • 数据同步任务:如 Canal、DataX、Flume
  • 数据服务任务:如 API 接口、报表查询

任务节点上,我建议至少记录:调度时间、执行状态、运行时长。为什么?因为故障往往不是「数据错了」,而是「数据没按时到」。你想想看,一个任务延迟了 10 分钟,下游 20 个任务全部等待,最终导致报表延迟 2 小时。这时候,任务节点的「运行时长」属性就是排查的关键线索。

3.2 边:依赖关系的载体

边就是节点之间的「线」。它描述的是数据流动的方向和依赖关系。边分为两种:数据流边和调度依赖边。

3.2.1 数据流边

数据流边表示「数据从 A 流到了 B」。比如 ods_order → dwd_order_detail,表示 dwd_order_detail 的数据来源于 ods_order。

数据流边通常带有「转换逻辑」属性。比如:

-- 数据流边:ods_order → dwd_order_detail
-- 转换逻辑:
INSERT OVERWRITE TABLE dwd_order_detail
SELECT order_id, user_id, pay_amount, create_time
FROM ods_order
WHERE dt = '${yesterday}'
  AND status = 'paid';

这个转换逻辑,在故障排查时就是「铁证」。我曾经遇到一个场景:业务方说某个字段值不对,我打开数据流边上的 SQL,发现 WHERE 条件里多了一个 AND channel = 'app'。这个条件把 PC 端的数据全过滤掉了。如果没有这个转换逻辑,你根本不知道数据是在哪一步被「篡改」的。

3.2.2 调度依赖边

调度依赖边表示「任务 B 必须在任务 A 完成后才能启动」。它不描述数据流向,只描述执行顺序。

举个例子:

  • Task_A (ods_order_etl) → Task_B (dwd_order_etl)
  • Task_B (dwd_order_etl) → Task_C (ads_sales_etl)

调度依赖边在故障排查中,主要用来分析「连锁延迟」。如果 Task_A 失败了,Task_B 和 Task_C 都会被阻塞。这时候,你只需要看调度依赖边的「状态」属性,就能快速定位阻塞点。

注意:数据流边和调度依赖边不要混为一谈。有些工具把两者合并了,但我在实践中发现,分开记录更有利于排查。数据流边告诉你「数据怎么变的」,调度依赖边告诉你「任务为什么没跑」。

3.3 属性:让血缘活起来

节点和边是骨架,属性才是血肉。没有属性的血缘图,就是一张死图。我个人认为,以下三个属性是必须的:时间戳、版本、状态。

3.3.1 时间戳

时间戳记录的是「什么时候发生的」。节点上的时间戳包括:

  • 创建时间:表或字段是什么时候创建的
  • 最后修改时间:表结构或字段定义是什么时候变更的
  • 数据更新时间:表里的数据是什么时候刷新的

边上的时间戳包括:

  • 调度时间:任务计划在什么时间执行
  • 实际执行时间:任务实际开始和结束的时间

时间戳在故障排查中的作用,我举个例子你就明白了。有一次,一个报表在早上 8 点准时刷新,但数据却是前天的。我查了血缘图,发现 ads_sales_summary 的上游表 dwd_order_detail 的数据更新时间是「昨天 23:00」,而 dwd_order_detail 的上游 ods_order 的数据更新时间是「前天 22:00」。问题出在哪?ods_order 的数据同步任务在昨天挂了,导致 dwd_order_detail 刷新的数据还是前天的。如果没有时间戳,你根本不知道数据「有多旧」。

3.3.2 版本

版本属性用来追踪「变更历史」。表结构会变,字段定义会变,任务逻辑也会变。每一次变更,都应该生成一个新版本。

我建议用 递增整数 或 时间戳 作为版本号。比如:

-- 表 dwd_order_detail 的版本历史
-- v1 (2024-01-01): 初始版本,包含 order_id, user_id, pay_amount
-- v2 (2024-03-15): 新增字段 channel, 修改 pay_amount 类型为 DECIMAL(18,2)
-- v3 (2024-06-01): 删除字段 old_status, 新增字段 status

版本属性在故障排查中,主要用于「回滚分析」。我记得有一次,一个字段突然报错,说类型不匹配。我查了字段的版本历史,发现三天前有人把 pay_amount 从 INT 改成了 STRING,但下游的报表任务没更新。回滚到 v1 版本后,问题立刻消失。

3.3.3 状态

状态属性描述的是「当前是什么情况」。节点和边都应该有状态。

实体 状态值 含义
表节点 ACTIVE / DEPRECATED / DELETED 表是否在用、已废弃、已删除
字段节点 ACTIVE / DEPRECATED / RENAMED 字段是否在用、已废弃、已重命名
任务节点 RUNNING / SUCCESS / FAILED / SKIPPED 任务当前执行状态
数据流边 ENABLED / DISABLED / BROKEN 数据流是否正常、被禁用、断裂
调度依赖边 WAITING / SATISFIED / TIMEOUT 依赖条件是否满足

状态属性在故障排查中,是「第一眼信息」。你打开血缘图,看到某个任务节点的状态是 FAILED,或者某条数据流边的状态是 BROKEN,问题范围立刻就缩小了。

3.4 核心要素关系图

下面这张图,把节点、边、属性的关系串起来了。你可以把它当作血缘关系的「最小知识框架」。

血缘关系核心要素框架图 血缘关系核心要素框架 表节点 ods_order / dwd_order 字段节点 order_id / pay_amount 任务节点 ETL / 同步 / 服务 数据流边 数据从A流到B 调度依赖边 任务B依赖任务A 时间戳 创建/修改/更新 版本 v1 / v2 / v3 状态 ACTIVE / FAILED 核心逻辑 节点(表/字段/任务)通过边(数据流/调度依赖)连接, 每个节点和边都附带属性(时间戳/版本/状态),构成完整的血缘关系图。

3.5 实战:用核心要素定位故障

理论说完了,咱们看一个真实案例。假设你收到报警:「ads_sales_summary 报表数据为空」。你怎么用血缘关系排查?

  1. 查状态:打开血缘图,看 ads_sales_summary 的上游任务节点状态。发现 dwd_order_etl 的状态是 FAILED。
  2. 查时间戳:看 dwd_order_etl 的实际执行时间,发现它从昨天 23:00 开始跑,跑了 2 小时还没结束,最终超时失败。
  3. 查版本:看 dwd_order_etl 的版本历史,发现三天前有人更新了任务逻辑,把 JOIN 条件改成了 LEFT JOIN,导致数据量暴增。
  4. 查数据流边:看 ods_order → dwd_order_detail 的数据流边,发现转换逻辑里多了一个 GROUP BY 操作,导致数据被聚合,下游拿不到明细。

你看,从状态到时间戳,再到版本和数据流边,每一步都在缩小问题范围。这就是血缘关系核心要素的价值。

我的经验:在搭建血缘系统时,先把节点、边、属性的模型定义清楚。不要贪多,先把「表、字段、任务」三个节点,「数据流、调度依赖」两条边,「时间戳、版本、状态」三个属性做扎实。后面再扩展,就水到渠成了。