4、特征血缘的元数据模型:特征表、特征视图、特征服务、特征源表之间的关联关系建模

好,咱们进入正题。上一章聊了血缘分析的价值,这一章咱们得落地了——把特征世界里那几个核心实体之间的关系,用元数据模型给它固定下来。

说白了,就是回答一个问题:一个特征到底是怎么来的?

我个人习惯把特征平台的血缘模型拆成四个核心实体:特征源表、特征表、特征视图、特征服务。它们之间的连线,就是血缘的骨架。

核心观点:血缘元数据模型不是ER图,它是数据流动的拓扑结构。你建模的不是表,是“数据怎么变”的过程。

4.1 四个核心实体,先认个脸

咱们一个一个来过。我在项目中见过不少团队把这四个东西混为一谈,结果血缘图画出来跟蜘蛛网似的,谁也看不懂。

实体 英文名 一句话定义 我见过的坑
特征源表 Feature Source Table 原始数据层,比如ODS/DWD层的业务表 有人把中间ETL结果也当源表,血缘链断了
特征表 Feature Table 经过加工后,存储特征值的宽表或KV表 特征表里混入非特征字段,血缘对不上
特征视图 Feature View 逻辑层,把多个特征表按业务场景组装 视图直接引用源表,跳过了特征表
特征服务 Feature Service 对外暴露的API,返回特征值给模型 服务里硬编码特征逻辑,血缘无法追踪

嗯,这里要注意:特征视图和特征表的区别,很多人搞混。我简单解释一下——特征表是物理存储,特征视图是逻辑组装。你可以把特征视图理解成一个“菜单”,特征表是“食材”。

4.2 关联关系:五条核心血缘链路

四个实体之间,一共有五条必须建模的关联关系。我画了一张图,你先看个全貌。

特征血缘元数据模型——核心关联关系 特征源表 Source Table 特征表 Feature Table 特征视图 Feature View 特征服务 Feature Service ① 加工 ② 组装 ③ 暴露 ④ 跨层引用(⚠️ 需治理) ⑤ 跨层引用(⚠️ 需治理) 链路说明 ①→②→③:标准血缘链路 ④、⑤:跨层引用(反模式) 治理目标:消除④和⑤

图里五条线,我分别说一下。

4.2.1 链路①:特征源表 → 特征表(加工关系)

这是最基础的血缘。特征表里的每一列,都来自某个源表的某个字段经过ETL加工。我在建模时,会在字段级别记录映射关系。比如:

-- 特征表 user_30d_purchase_amt 的字段映射
feature_column: total_amt
source: dwd_order_di.pay_amount
transform: SUM(pay_amount) WHERE dt >= date_sub(current_date(), 30)

我的习惯:字段级血缘一定要记录transform表达式,不能只记“来自哪个表”。否则回溯时你只知道“从哪来”,不知道“怎么变的”。

4.2.2 链路②:特征表 → 特征视图(组装关系)

特征视图是逻辑层,它把多个特征表按实体ID和时间戳组装在一起。比如一个“用户实时推荐特征视图”,可能引用了3张特征表:用户画像表、实时行为表、物品属性表。

这里要注意:特征视图不存储数据,它只是一个元数据定义。血缘模型里,视图到表的关联是多对多的。

4.2.3 链路③:特征视图 → 特征服务(暴露关系)

特征服务是最终对外提供特征的API。一个服务可以暴露一个或多个特征视图。血缘模型里,服务到视图的关联是一对多的。

我曾经遇到一个坑:某个服务上线后,特征值突然不准了。查了半天,发现服务直接引用了特征表,跳过了视图层。这就是图里的链路⑤——跨层引用。

4.2.4 链路④和⑤:跨层引用(反模式)

这两个是需要治理的。为什么?

  • 链路④:特征视图直接引用源表。这意味着视图层绕过了特征表的加工逻辑,血缘链断了。
  • 链路⑤:特征服务直接引用特征表。这意味着服务绕过了视图层的组装逻辑,你没法知道这个服务到底用了哪些特征。

避坑指南:我曾经在一个项目里发现,线上80%的特征服务都存在链路⑤的问题。原因很简单——开发图省事,直接调特征表的API。结果血缘图里,服务到源表的路径全断了,根本做不了影响分析。后来我们强制要求:所有特征必须通过视图暴露,服务只能引用视图。

4.3 元数据模型的核心属性

光有实体和关系还不够,每个实体还得有足够的属性来支撑血缘分析。我列一下每个实体必须记录的字段。

实体 必填属性 说明
特征源表 表名、库名、字段列表、数据更新频率、数据新鲜度SLA 源表变更时,需要通知下游所有特征表
特征表 表名、主键、特征字段列表、每个字段的加工SQL、产出时间 加工SQL必须可解析,用于字段级血缘
特征视图 视图名、引用的特征表列表、关联键、时间窗口 关联键决定了视图的粒度
特征服务 服务名、暴露的视图列表、API路径、请求参数、响应格式 服务下线时,需要通知所有调用方

4.4 建模时的三个关键决策

说完了模型本身,我再分享三个我在实际建模中踩过的坑,以及最终的决策。

决策一:字段级 vs 表级血缘?

我的建议是:必须做字段级。表级血缘只能告诉你“这个特征表来自那个源表”,但字段级才能告诉你“这个特征列来自源表的哪个字段、经过了什么计算”。

举个例子:特征表里有10个特征列,其中9个来自A表,1个来自B表。表级血缘会记录“来自A表和B表”,但字段级血缘能精确到每个列的来源。做影响分析时,字段级能帮你定位到具体哪个特征会受影响。

决策二:血缘存储用图数据库还是关系型数据库?

我个人习惯用图数据库(比如Neo4j或JanusGraph)。原因很简单:血缘分析的本质是图遍历。你想想看,一个特征服务挂了,你要回溯所有上游——源表、特征表、视图。用关系型数据库做多表JOIN,性能扛不住。

我的经验:如果团队没有图数据库基础设施,也可以用关系型数据库+血缘关系表来模拟。核心是建一张edge表,记录from_entity、to_entity、relation_type。查询时用递归CTE,也能实现基本的血缘回溯。

决策三:血缘的时效性怎么处理?

特征血缘不是静态的。特征表可能每天更新,源表可能每周变更schema。我的做法是:给血缘加时间戳。每条血缘关系都记录生效时间和失效时间。这样你可以回溯“某个时间点的特征是从哪来的”。

-- 血缘关系表示例
CREATE TABLE feature_lineage_edges (
    id BIGINT PRIMARY KEY,
    from_entity_type VARCHAR(32),   -- SOURCE_TABLE / FEATURE_TABLE / FEATURE_VIEW / FEATURE_SERVICE
    from_entity_id VARCHAR(128),
    to_entity_type VARCHAR(32),
    to_entity_id VARCHAR(128),
    relation_type VARCHAR(32),      -- PROCESS / ASSEMBLE / EXPOSE
    effective_start TIMESTAMP,
    effective_end TIMESTAMP,
    created_by VARCHAR(64),
    created_at TIMESTAMP
);

4.5 小结:建模的底线

嗯,这一章内容不少。我最后总结三条底线,你建模时对照一下:

  1. 四个实体必须完整:源表、特征表、视图、服务,一个都不能少。
  2. 五条链路必须清晰:标准链路是①→②→③,④和⑤是反模式,必须治理。
  3. 字段级血缘必须记录:表级血缘只能应付汇报,字段级血缘才能干活。