血缘元数据模型:从采集到存储的实战选择
这一章,我们来聊聊血缘分析最核心的底盘——元数据模型。说白了,就是你要采集什么、怎么存、用什么图数据库来扛。我在好几个数据平台项目里都踩过这些坑,今天一次性给你讲透。
元数据采集范围:别贪多,也别漏掉关键
很多人一开始做血缘,恨不得把所有元数据都抓进来。结果呢?存储爆炸、查询慢、维护成本高。我个人的经验是:先圈定核心范围,再逐步扩展。
到底要采集哪些?我列了个清单,你对照着看:
| 元数据类别 | 具体内容 | 采集优先级 |
|---|---|---|
| 表级元数据 | 库名、表名、表类型(视图/物理表)、创建时间、最后修改时间 | P0(必须) |
| 字段级元数据 | 字段名、数据类型、注释、是否主键、是否可为空 | P0(必须) |
| ETL 作业元数据 | 作业名称、调度时间、输入表/字段、输出表/字段、转换逻辑摘要 | P1(强烈建议) |
| SQL 解析结果 | SQL 文本、解析后的 AST、SELECT 来源、WHERE 条件涉及的字段 | P1(强烈建议) |
| 数据质量规则 | 规则名称、关联表/字段、校验类型、最近执行结果 | P2(按需) |
| 数据权限信息 | 表/字段的访问权限、敏感等级、脱敏规则 | P2(按需) |
血缘关系模型设计:节点、边、属性
图数据库里,血缘关系就是一张大网。节点是实体,边是关系。设计得好,查询一条 SQL 就能把整条链路拉出来;设计得不好,写五层 JOIN 都查不明白。
节点设计
我习惯把节点分成三类:
- 数据节点:表、视图、文件、Kafka Topic
- 计算节点:ETL 作业、SQL 查询、存储过程、数据管道
- 业务节点:数据域、应用系统、数据产品
每个节点都有公共属性,比如 ID、名称、类型、描述、创建时间。不同类型的节点再挂各自特有的属性。举个例子:
// 表节点的属性示例
{
"id": "table_12345",
"name": "ods_order_detail",
"type": "HIVE_TABLE",
"database": "ods",
"owner": "data_team",
"row_count": 15000000,
"last_modified": "2024-03-15 10:30:00",
"description": "订单明细原始数据层"
}
// ETL 作业节点的属性示例
{
"id": "job_67890",
"name": "dwd_order_detail_etl",
"type": "SPARK_JOB",
"schedule": "daily 02:00",
"owner": "etl_team",
"last_run_time": "2024-03-16 02:15:30",
"status": "SUCCESS"
}
边设计
边是血缘的灵魂。我常用的边类型有这些:
- PRODUCES:计算节点 → 数据节点(作业产出了某张表)
- CONSUMES:计算节点 → 数据节点(作业读取了某张表)
- DERIVED_FROM:字段 → 字段(目标字段来源于源字段)
- DEPENDS_ON:作业 → 作业(作业 B 依赖作业 A 的输出)
边也可以带属性,比如转换逻辑、时间戳、数据量。我遇到过最头疼的情况是:一条边没有时间属性,结果回溯历史血缘时根本分不清哪个版本是对的。所以,每条边至少带上生效时间和版本号。
valid_from 和 valid_to。
元数据存储选型:Neo4j vs JanusGraph
选图数据库,说白了就是选「原生图」还是「可扩展图」。我两个都用过,给你掰扯清楚。
| 对比维度 | Neo4j | JanusGraph |
|---|---|---|
| 存储引擎 | 自研原生图存储 | 基于 HBase/Cassandra/BerkeleyDB |
| 查询语言 | Cypher(声明式,易上手) | Gremlin(函数式,学习曲线陡) |
| 事务支持 | ACID,强一致 | 最终一致性(取决于后端) |
| 水平扩展 | 企业版支持集群,社区版单机 | 天然支持分布式 |
| 查询性能 | 深度遍历快(毫秒级 5 层) | 浅层查询快,深度遍历需优化 |
| 部署复杂度 | 低,单机可跑 | 高,依赖外部存储和索引 |
| 社区生态 | 成熟,文档丰富 | 活跃但文档分散 |
我什么时候选 Neo4j?
如果你的血缘数据量在 千万节点以内,团队里 SQL 背景的人多,想快速出成果——选 Neo4j。我上一个项目就是用它,从设计到上线只用了两周。Cypher 写起来真的很爽,比如查某张表的上游链路:
MATCH (t:Table {name: 'dwd_order_detail'})
<-[:PRODUCES]-(j:Job)
<-[:DEPENDS_ON]-(upstream_j:Job)
-[:PRODUCES]->(upstream_t:Table)
RETURN upstream_t.name, upstream_j.name
一行 Cypher,五层关系就出来了。你想想看,用关系型数据库得写多少个 JOIN?
我什么时候选 JanusGraph?
如果你的数据量在 亿级以上,需要跨机房部署,或者你们公司已经有 HBase/Cassandra 集群——选 JanusGraph。它最大的优势就是「存得下、扩得开」。但代价是:Gremlin 的写法比较绕,团队需要学习成本。
举个例子,同样的查询用 Gremlin 写:
g.V().has('Table', 'name', 'dwd_order_detail')
.in('PRODUCES').hasLabel('Job')
.in('DEPENDS_ON').hasLabel('Job')
.out('PRODUCES').hasLabel('Table')
.values('name')
嗯,看着还行,但一旦涉及条件过滤、聚合、分页,Gremlin 的复杂度就上来了。
- 团队小、节奏快、数据量可控 → Neo4j
- 数据量大、已有大数据基础设施、需要弹性扩展 → JanusGraph
- 如果预算充足,也可以考虑 Neo4j 企业版,它支持集群和热备
一张图看懂血缘元数据模型
下面这张 SVG 图,把节点、边、存储选型的关系串起来了。你看一眼就能明白整体架构。
小结一下
这一章我们聊了三件事:
- 采集范围:先圈 P0 和 P1,别贪多。字段级元数据是血缘的命根子,一定要采。
- 模型设计:节点分三类,边带版本和时间戳。字段级血缘用 DERIVED_FROM 边来承载。
- 存储选型:Neo4j 快而简单,JanusGraph 大而灵活。没有银弹,看场景选。
嗯,模型设计这块,我建议你动手画一画。拿自己公司的数据流,试着标出节点和边,跑一遍 Cypher 或 Gremlin 查询。纸上得来终觉浅,绝知此事要躬行。