血缘元数据模型:从采集到存储的实战选择

这一章,我们来聊聊血缘分析最核心的底盘——元数据模型。说白了,就是你要采集什么、怎么存、用什么图数据库来扛。我在好几个数据平台项目里都踩过这些坑,今天一次性给你讲透。

元数据采集范围:别贪多,也别漏掉关键

很多人一开始做血缘,恨不得把所有元数据都抓进来。结果呢?存储爆炸、查询慢、维护成本高。我个人的经验是:先圈定核心范围,再逐步扩展。

到底要采集哪些?我列了个清单,你对照着看:

元数据类别 具体内容 采集优先级
表级元数据 库名、表名、表类型(视图/物理表)、创建时间、最后修改时间 P0(必须)
字段级元数据 字段名、数据类型、注释、是否主键、是否可为空 P0(必须)
ETL 作业元数据 作业名称、调度时间、输入表/字段、输出表/字段、转换逻辑摘要 P1(强烈建议)
SQL 解析结果 SQL 文本、解析后的 AST、SELECT 来源、WHERE 条件涉及的字段 P1(强烈建议)
数据质量规则 规则名称、关联表/字段、校验类型、最近执行结果 P2(按需)
数据权限信息 表/字段的访问权限、敏感等级、脱敏规则 P2(按需)
我的建议:先搞定 P0 和 P1,跑通一条完整的血缘链路。等业务方尝到甜头了,再慢慢加 P2。我曾经在一个项目里一上来就全量采集,结果光解析 Hive 的 DDL 就跑了三天三夜……后来学乖了,分阶段来。

血缘关系模型设计:节点、边、属性

图数据库里,血缘关系就是一张大网。节点是实体,边是关系。设计得好,查询一条 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 图,把节点、边、存储选型的关系串起来了。你看一眼就能明白整体架构。

血缘元数据模型核心架构 数据节点 表 / 视图 / 文件 / Topic 计算节点 ETL / SQL / 存储过程 业务节点 数据域 / 应用 / 产品 PRODUCES CONSUMES DEPENDS_ON 字段级血缘:DERIVED_FROM 源字段 → 转换逻辑 → 目标字段(带版本和时间戳) Neo4j 原生图存储 · Cypher · ACID 适合千万级 · 快速交付 社区版单机 · 企业版集群 JanusGraph HBase/Cassandra · Gremlin 适合亿级 · 天然分布式 部署复杂 · 学习成本高 选型决策要点 数据量 < 1000万 → Neo4j 社区版 | 数据量 1000万~1亿 → Neo4j 企业版 | 数据量 > 1亿 → JanusGraph

小结一下

这一章我们聊了三件事:

  • 采集范围:先圈 P0 和 P1,别贪多。字段级元数据是血缘的命根子,一定要采。
  • 模型设计:节点分三类,边带版本和时间戳。字段级血缘用 DERIVED_FROM 边来承载。
  • 存储选型:Neo4j 快而简单,JanusGraph 大而灵活。没有银弹,看场景选。

嗯,模型设计这块,我建议你动手画一画。拿自己公司的数据流,试着标出节点和边,跑一遍 Cypher 或 Gremlin 查询。纸上得来终觉浅,绝知此事要躬行。