2. 数据血缘与元数据

聊数据版本控制,绕不开两个基础概念:数据血缘和元数据。

说白了,数据血缘就是数据的「族谱」。它记录数据从哪来、经过哪些加工、最终去了哪。而元数据,就是数据的「身份证信息」——描述数据的数据。

我刚开始做数据平台时,觉得这两个东西可有可无。直到有一次线上事故,某个报表字段突然变成空值,我花了整整两天才找到根因——一个上游ETL脚本改了字段名,下游没人知道。嗯,从那以后,我再也不敢忽视血缘和元数据了。

什么是数据血缘

数据血缘(Data Lineage),描述的是数据从产生到消亡的完整生命周期。它像一张有向图,每个节点代表一个数据实体(表、字段、文件),每条边代表一次数据流转或变换。

核心价值:出了问题,你能顺着血缘图快速定位「罪魁祸首」。而不是像无头苍蝇一样,到处翻代码、问同事。

举个例子。你看到一张报表上的「当日销售额」是 100 万。这个数字怎么来的?

  • 原始数据来自订单系统的 order 表
  • 经过 Flink 实时清洗,写入 dwd_order 明细表
  • 再聚合到 dws_sales_daily 日汇总表
  • 最后被 BI 工具读取展示

这就是一条简单的血缘链路。每个环节都记录了「谁、什么时间、用什么逻辑、处理了什么数据」。

血缘的粒度

我个人习惯把血缘分成三个粒度:

粒度 描述 典型场景
表级血缘 表与表之间的依赖关系 A表 → B表 → C表
字段级血缘 字段级别的映射与变换 order.amount → dws.total_amount
脚本级血缘 SQL/代码级别的数据流 INSERT INTO ... SELECT ... FROM ...

你想想看,如果只有表级血缘,你只知道「A表影响了B表」。但字段级血缘能告诉你「A表的amount字段,经过SUM()聚合后,变成了B表的total_amount」。排查问题时,后者显然更有用。

元数据管理的基础

元数据,就是「关于数据的数据」。它回答三个问题:

  • 有什么数据?——数据目录、表名、字段名
  • 数据长什么样?——数据类型、长度、约束、样本值
  • 数据怎么来的?——血缘关系、加工逻辑、更新时间

我在项目中遇到过一种典型情况:业务方问「用户画像表里有个user_level字段,这个值是怎么算的?」。如果没有元数据管理,你得去翻代码、问开发、查文档。有了元数据,直接查字段描述和血缘就能回答。

元数据的分类

业内一般把元数据分成三类:

类型 内容 举例
技术元数据 数据库、表结构、字段类型、分区信息、ETL脚本 CREATE TABLE user (id INT, name STRING)
业务元数据 业务定义、指标口径、数据质量规则 「活跃用户」定义为近30天有登录行为的用户
管理元数据 数据负责人、创建时间、访问权限、版本号 表 owner、数据更新频率、SLA

三者缺一不可。技术元数据告诉你「怎么用」,业务元数据告诉你「什么意思」,管理元数据告诉你「谁负责」。

一个小技巧:我习惯在元数据系统里给每个字段加一个「口径说明」字段。哪怕只有一句话,也能省去大量沟通成本。比如「gmv = 订单金额 - 退款金额,不含运费」。

血缘与元数据的关系

血缘是元数据的一种特殊形式。它描述的是数据之间的动态关系,而不是静态属性。

你可以这样理解:

  • 元数据是「名词」——描述数据是什么
  • 血缘是「动词」——描述数据怎么流动、怎么变换

没有元数据,血缘图上的节点就是一堆「无名氏」。没有血缘,元数据只能告诉你「有什么」,不能告诉你「从哪来、到哪去」。

数据版本控制中的血缘应用

在数据版本控制场景里,血缘的作用尤其明显。我举个例子:

  1. 你发布了一个新版本的数据处理脚本
  2. 新版本改了某个字段的计算逻辑
  3. 下游有 20 张报表依赖这个字段

如果没有血缘,你根本不知道哪些报表会受影响。有了血缘,系统自动列出所有下游依赖,你就能提前通知业务方,或者做兼容处理。

我曾经踩过的坑:有一次我改了上游一个维表的字段类型,从 STRING 改成 INT。我以为只有两张表引用它。结果上线后,下游三个实时任务直接报错。后来一查血缘图,发现还有一条隐藏的依赖链路——通过一个中间表间接引用了。从那以后,我每次改表结构,必先查血缘图。

数据血缘的核心结构

下面这张图展示了典型的数据血缘关系:

数据血缘关系图 订单系统 (源) 用户系统 (源) ETL 清洗任务 DWD 明细层 DWS 汇总层 BI 报表系统 数据API服务 说明: 每个节点代表一个数据实体,箭头代表数据流向。蓝色=源系统,紫色=处理过程,绿色=数仓,红色/橙色=应用。

元数据管理的关键能力

一个成熟的元数据管理系统,至少需要具备以下能力:

  • 自动采集:从数据源(Hive、MySQL、Kafka等)自动抓取元数据,不需要人工录入
  • 血缘解析:解析SQL脚本,自动构建字段级血缘关系
  • 版本管理:记录元数据的变更历史,支持回溯
  • 搜索与发现:支持按表名、字段名、业务关键词搜索
  • 影响分析:输入一个表或字段,列出所有下游依赖

我建议:刚开始做元数据管理时,别追求大而全。先搞定技术元数据自动采集和表级血缘。跑通之后,再逐步加字段级血缘和业务元数据。一步到位往往意味着一步都到不了。

血缘解析的常见方式

解析血缘关系,主要有三种方式:

方式 原理 优点 缺点
静态解析 解析SQL AST(抽象语法树) 无需运行,速度快 无法处理动态SQL、UDF
运行时捕获 拦截执行计划,记录输入输出 准确度高,覆盖所有场景 有性能开销,需要侵入系统
日志解析 分析数据库审计日志 无侵入,适合已有系统 解析复杂,实时性差

我个人习惯用「静态解析 + 运行时捕获」的组合。静态解析覆盖 80% 的常规场景,运行时捕获兜底剩下的 20%。

一个简单的血缘解析示例

假设你有这样一条 SQL:

INSERT INTO dws.daily_sales
SELECT 
  order_date,
  SUM(amount) AS total_amount
FROM dwd.orders
WHERE status = 'paid'
GROUP BY order_date;

解析器会提取出:

  • 目标表:dws.daily_sales
  • 源表:dwd.orders
  • 字段映射:dwd.orders.amount → dws.daily_sales.total_amount
  • 过滤条件:status = 'paid'
  • 聚合操作:SUM()

这些信息会被存入元数据库,供后续查询。

注意:字段级血缘解析对复杂 SQL(子查询、CTE、窗口函数)的准确率不是 100%。我建议在解析结果上增加人工校验入口,允许数据工程师修正血缘关系。别问我怎么知道的——我被不准的血缘图坑过不止一次。

元数据与版本控制的结合

数据版本控制离不开元数据。每次数据变更,元数据系统都应该记录:

  • 变更了什么(表结构、字段逻辑、分区策略)
  • 谁变更的
  • 什么时间变更的
  • 变更前后的版本号
  • 受影响的上下游

有了这些信息,你才能做到「一键回滚」和「影响分析」。否则,版本控制就是空谈。

好了,这一节的内容就这些。记住两句话:血缘告诉你数据怎么来的,元数据告诉你数据是什么。两者结合,才是数据版本控制的基石。