3. 血缘元数据模型:元数据定义、实体关系模型、属性与标签设计

聊血缘分析,绕不开一个核心问题——元数据怎么建模。

说白了,血缘追踪的本质就是记录数据从哪来、经过谁、变成啥、去了哪。如果元数据模型没搭好,后面所有的溯源、影响分析、质量监控都是空中楼阁。我早期参与过一个数据中台项目,团队一上来就急着做血缘采集,结果跑了三个月发现字段对不上、表关系乱成一锅粥……嗯,后来老老实实回来重新设计元数据模型。

这一章,我就把我在多个项目中沉淀下来的血缘元数据模型讲清楚。包括三个层面:元数据定义、实体关系模型、属性与标签设计。

3.1 元数据定义:先搞清楚我们在描述什么

元数据,就是「关于数据的数据」。但在血缘场景下,我们需要更精确的定义。

我个人习惯把血缘元数据分为三个层次:

  • 技术元数据:表结构、字段类型、分区信息、存储路径、ETL脚本等。这是血缘追踪的基础。
  • 业务元数据:字段的业务含义、指标定义、数据域归属、负责人等。没有业务元数据,血缘图就是一张死图。
  • 操作元数据:谁在什么时间执行了什么操作、输入输出是什么、运行状态如何。这是血缘关系的「动态证据」。

核心原则:血缘元数据必须同时覆盖「静态结构」和「动态行为」。只记录表结构不记录操作日志,血缘就断了;只记录操作日志不记录结构,血缘就乱了。

举个例子。一张订单明细表,技术元数据告诉你它有 order_id、user_id、amount 三个字段;业务元数据告诉你 amount 是「订单实付金额」,属于「交易域」;操作元数据告诉你每天凌晨 2 点由 Spark 任务从 ODS 层写入 DWD 层。三者合在一起,你才能完整理解这条血缘链路。

3.2 实体关系模型:血缘世界的「地图」

有了元数据定义,接下来要回答:这些元数据之间怎么关联?

我习惯用 ER 图来设计血缘元数据模型。核心实体就五个:

实体 含义 示例
Dataset 数据集,可以是表、文件、消息队列 dwd_order_detail、ods_user_log
Field 数据集中的字段/列 order_id、user_id、amount
Process 数据处理过程,ETL 任务、SQL 脚本、API 调用 spark_etl_job_001、datax_sync_task
Operation 一次具体的执行实例 2025-03-21 02:00:00 的 run_id=abc123
Lineage 血缘关系记录,描述上下游依赖 field_A → process_X → field_B

实体之间的关系,我总结为三种:

  • Dataset 包含 Field:一对多。一张表有 N 个字段。
  • Process 消费/产出 Dataset:多对多。一个任务可以读多张表、写多张表。
  • Field 通过 Process 产生 Lineage:核心关系。字段级别的血缘就是通过 Process 串联起来的。

我的经验:不要一开始就追求字段级血缘全覆盖。先做到表级血缘,再逐步下钻到字段级。我在某金融项目里就是先跑通表级血缘,三个月后才把核心 200 个指标字段的血缘补全——稳扎稳打比一步到位靠谱。

下面这张图是我常用的血缘元数据核心模型,你可以直观感受一下实体之间的关系:

血缘元数据核心实体关系模型 血缘元数据核心实体关系模型 Dataset 数据集(表/文件) Field 字段/列 Process 处理过程(ETL/SQL) Operation 执行实例 Lineage 血缘关系 包含 1:N 消费/产出 M:N 产生 1:N 记录 1:N 字段级血缘 模型要点说明: • Dataset 和 Field 是静态结构,Process 和 Operation 是动态行为 • Lineage 是核心关系实体,记录字段级别的上下游依赖 • 每个 Operation 对应一次具体的执行,是血缘可追溯性的保障

3.3 属性与标签设计:让元数据「活」起来

实体和关系搭好了框架,但真正让血缘系统好用的是属性和标签。

属性描述「是什么」,标签描述「怎么用」。我分开讲。

3.3.1 核心属性设计

每个实体都需要一组核心属性。我以 Dataset 和 Field 为例:

实体 属性 说明 示例
Dataset dataset_id 全局唯一标识 ds_dwd_order_detail_001
name 数据集名称 dwd_order_detail
layer 数据分层 dwd / dws / ads
storage_type 存储类型 hive / hbase / kafka
owner 负责人 zhangsan@company.com
Field field_id 字段唯一标识 fld_order_id_001
name 字段名 order_id
data_type 数据类型 string / bigint / decimal
business_meaning 业务含义 订单唯一编号

我曾经踩过的坑:早期设计时没有给 Field 加 business_meaning 属性,结果血缘图上全是 order_id、amt、cust_no 这种字段名,业务方根本看不懂。后来花了两个月补业务含义,还被业务老大吐槽「你们搞技术的连字段是啥意思都不写清楚」。从那以后,我要求所有核心字段必须带业务含义属性。

3.3.2 标签体系设计

标签是元数据的「调味料」。同样的 Dataset,打上不同标签,使用方式完全不同。

我常用的标签分类:

  • 质量标签:高/中/低、待验证、已废弃。用于标识数据可信度。
  • 敏感度标签:公开/内部/敏感/高度敏感。用于权限控制。
  • 生命周期标签:临时/长期/归档。用于存储策略。
  • 治理标签:待治理/已治理/无需治理。用于问题追踪。

标签设计有个关键原则:标签要可继承。什么意思?

如果 Dataset 被打上了「高度敏感」标签,那么它下面的所有 Field 应该自动继承这个标签。我在项目中用标签继承机制,避免了大量重复打标工作。你想想看,一张表有 200 个字段,如果每个字段都要手动打敏感标签,那得打到猴年马月去。

我的建议:标签继承要有「覆盖」机制。Dataset 级别是「高度敏感」,但某个字段可能是「公开」的(比如脱敏后的统计值)。允许子级覆盖父级标签,但要记录覆盖原因和审批人。

3.4 模型落地的几个关键点

理论讲完了,说点实战中容易出问题的地方。

第一,唯一标识要稳定。 dataset_id、field_id 一旦确定就不要轻易改。我见过一个项目,因为表名重构导致 dataset_id 全变了,历史血缘全部断裂。后来我们强制用「逻辑 ID + 版本号」的方式,物理路径变了但逻辑 ID 不变。

第二,关系实体要支持时间维度。 血缘不是静态的。同一个 Process 今天和昨天的输入输出可能不同。Lineage 实体必须带有效时间范围(valid_from / valid_to),这样才能做时间旅行式的血缘回溯。

第三,属性扩展要预留。 你永远不知道未来会新增什么属性。我习惯在每个实体上加一个 ext_properties 字段,用 JSON 格式存储扩展属性。这样新增属性时不需要改表结构。

-- 示例:Dataset 表的 DDL(简化版)
CREATE TABLE lineage_meta_dataset (
    dataset_id      STRING      COMMENT '全局唯一ID',
    name            STRING      COMMENT '数据集名称',
    layer           STRING      COMMENT '数据分层',
    storage_type    STRING      COMMENT '存储类型',
    owner           STRING      COMMENT '负责人',
    valid_from      TIMESTAMP   COMMENT '生效时间',
    valid_to        TIMESTAMP   COMMENT '失效时间',
    ext_properties  MAP<STRING,STRING> COMMENT '扩展属性',
    tags            ARRAY<STRING>      COMMENT '标签列表',
    PRIMARY KEY (dataset_id)
) COMMENT '血缘元数据-数据集表';