3. 特征存储与元数据管理
聊到特征复用,有个问题绕不开——特征到底存哪儿?怎么管?
我见过不少团队,特征写了一大堆,但问起来都说不清楚哪个特征在哪个表里、是谁生成的、版本对不对。说白了,这就是缺了特征存储和元数据管理。
这一章,我们就来拆解特征存储的架构设计、元数据注册中心怎么搭,以及特征 Schema 怎么管。
3.1 特征存储架构
特征存储不是简单的数据库。它要同时满足在线推理的低延迟和离线训练的高吞吐。我习惯把它分成三层来看。
| 层级 | 存储介质 | 典型场景 | 延迟要求 |
|---|---|---|---|
| 在线存储 | Redis / HBase / Cassandra | 实时特征查询 | < 10ms |
| 近线存储 | Kafka + Flink 状态后端 | 窗口聚合特征 | < 1s |
| 离线存储 | HDFS / S3 + Parquet | 批量特征计算 | 分钟级 |
这三层不是孤立的。离线存储会定期同步到在线存储,近线存储则作为两者之间的缓冲。我在项目中遇到过一个问题:在线特征更新太频繁,导致 Redis 内存暴涨。后来加了 TTL 和冷热分离,才算稳住。
核心原则:在线存储只放高频访问的特征,低频特征走离线查询。别把所有鸡蛋放一个篮子里。
3.2 元数据注册中心
元数据注册中心,说白了就是特征的「户口本」。每个特征从出生到废弃,所有信息都得记下来。
我建议至少包含以下字段:
- 特征名称:全局唯一,比如
user_purchase_30d_sum - 特征来源:哪个表、哪个字段、哪个 pipeline 生成的
- 特征类型:连续型、离散型、文本型、向量型
- 特征版本:语义版本号,比如
1.2.0 - 创建时间:首次注册的时间戳
- 责任人:谁负责维护这个特征
- 标签:用于搜索和分类,比如
用户画像、风控
嗯,这里要注意。元数据注册中心不能只存静态信息。它还得记录特征的依赖关系。比如特征 A 依赖特征 B 和特征 C,那 B 或 C 变更时,A 就得重新计算。
我的经验:用图数据库(比如 Neo4j)来存依赖关系,查询起来比关系型数据库快一个数量级。我曾经用 MySQL 存依赖,结果一个特征变更,要跑五分钟才知道影响范围。换成图数据库后,秒级返回。
3.3 特征 Schema 管理
特征 Schema 管理,是元数据管理中最容易出问题的环节。为什么?因为特征 Schema 变了,下游模型可能直接崩掉。
我总结了一套 Schema 管理的「三不原则」:
- 不删字段:老模型可能还在用,删了直接报错
- 不改类型:int 变 float,模型加载时类型不匹配,训练直接挂
- 不改语义:比如
user_age从「注册年龄」改成「当前年龄」,模型理解就错了
那怎么管理变更?我建议用 Protobuf 或 Avro 来定义特征 Schema。举个例子:
syntax = "proto3";
message UserFeature {
string user_id = 1;
int32 age = 2;
float purchase_30d_sum = 3;
repeated string tags = 4;
// 新增字段必须加 optional
optional float purchase_7d_sum = 5;
}
你看,新增字段用 optional,老模型不感知,新模型直接用。这就是向后兼容。
避坑指南:我曾经在项目里直接改了字段类型,从 int32 改成 int64,结果线上模型加载时抛异常,回滚花了两个小时。从那以后,我强制所有 Schema 变更必须走评审流程,并且要有兼容性检查。
3.4 架构总览
说了这么多,我们来看一张整体架构图。它把特征存储、元数据注册中心和 Schema 管理串在了一起。
这张图把整个流程串起来了。数据源经过特征计算,写入特征存储。元数据注册中心记录整个过程,Schema 管理保证变更安全。模型训练和在线推理从特征存储里取数据。
你想想看,如果没有元数据注册中心,一个特征改了,你都不知道哪些模型受影响。如果没有 Schema 管理,线上模型随时可能崩。这两块,是特征复用的基石。
总结一下:特征存储解决「存哪儿」的问题,元数据注册中心解决「是什么」的问题,Schema 管理解决「怎么变」的问题。三者缺一不可。