4、特征血缘的元数据模型:特征表、特征视图、特征服务、特征源表之间的关联关系建模
好,咱们进入正题。上一章聊了血缘分析的价值,这一章咱们得落地了——把特征世界里那几个核心实体之间的关系,用元数据模型给它固定下来。
说白了,就是回答一个问题:一个特征到底是怎么来的?
我个人习惯把特征平台的血缘模型拆成四个核心实体:特征源表、特征表、特征视图、特征服务。它们之间的连线,就是血缘的骨架。
核心观点:血缘元数据模型不是ER图,它是数据流动的拓扑结构。你建模的不是表,是“数据怎么变”的过程。
4.1 四个核心实体,先认个脸
咱们一个一个来过。我在项目中见过不少团队把这四个东西混为一谈,结果血缘图画出来跟蜘蛛网似的,谁也看不懂。
| 实体 | 英文名 | 一句话定义 | 我见过的坑 |
|---|---|---|---|
| 特征源表 | Feature Source Table | 原始数据层,比如ODS/DWD层的业务表 | 有人把中间ETL结果也当源表,血缘链断了 |
| 特征表 | Feature Table | 经过加工后,存储特征值的宽表或KV表 | 特征表里混入非特征字段,血缘对不上 |
| 特征视图 | Feature View | 逻辑层,把多个特征表按业务场景组装 | 视图直接引用源表,跳过了特征表 |
| 特征服务 | Feature Service | 对外暴露的API,返回特征值给模型 | 服务里硬编码特征逻辑,血缘无法追踪 |
嗯,这里要注意:特征视图和特征表的区别,很多人搞混。我简单解释一下——特征表是物理存储,特征视图是逻辑组装。你可以把特征视图理解成一个“菜单”,特征表是“食材”。
4.2 关联关系:五条核心血缘链路
四个实体之间,一共有五条必须建模的关联关系。我画了一张图,你先看个全貌。
图里五条线,我分别说一下。
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 小结:建模的底线
嗯,这一章内容不少。我最后总结三条底线,你建模时对照一下:
- 四个实体必须完整:源表、特征表、视图、服务,一个都不能少。
- 五条链路必须清晰:标准链路是①→②→③,④和⑤是反模式,必须治理。
- 字段级血缘必须记录:表级血缘只能应付汇报,字段级血缘才能干活。