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 个指标字段的血缘补全——稳扎稳打比一步到位靠谱。
下面这张图是我常用的血缘元数据核心模型,你可以直观感受一下实体之间的关系:
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 '血缘元数据-数据集表';