3、特征存储与元数据管理:特征存储选型、元数据定义与采集、特征血缘追踪
聊到特征复用,有个问题绕不开:特征到底该存哪儿?怎么管?
我见过不少团队,特征散落在各种地方——Hive 表里有一份,训练脚本里硬编码一份,线上服务 Redis 里又有一份。结果呢?特征对不上,模型推演效果崩了,查了半天才发现是训练和上线用的不是同一份特征逻辑。说白了,这就是特征存储和元数据没管好。
这一章,咱们就聊聊特征存储的选型思路、元数据怎么定义和采集,以及特征血缘怎么追踪。这些都是特征复用的地基,地基不稳,上层建筑迟早要塌。
3.1 特征存储选型:没有银弹,只有权衡
特征存储选型,我个人的习惯是先问三个问题:
- 特征怎么用? 是给离线训练用,还是给线上实时推理用?
- 特征怎么更新? 是批处理 T+1 更新,还是流式实时更新?
- 特征怎么查? 是点查(key-value),还是范围扫描?
这三个问题问完,选型方向基本就定了。我整理了一张对比表,你可以参考一下:
| 存储类型 | 典型产品 | 适用场景 | 延迟 | 一致性 | 成本 |
|---|---|---|---|---|---|
| 离线数仓 | Hive / Spark | 批量训练、历史回溯 | 分钟~小时级 | 强(快照) | 低 |
| KV 存储 | Redis / HBase | 线上实时特征查询 | 毫秒级 | 最终一致 | 中 |
| 特征存储平台 | Feast / Tecton | 离在线统一管理 | 毫秒~秒级 | 强(双写) | 高 |
| OLAP 引擎 | ClickHouse / Doris | Ad-hoc 分析、小批量特征 | 秒级 | 强 | 中 |
我的经验: 别一上来就上特征存储平台。如果你的特征数量少于 100 个,团队不到 10 人,用 Redis + Hive 组合拳完全够用。我见过一个团队,5 个人的时候就上了 Feast,结果运维成本比特征开发成本还高,得不偿失。
选型时还有一个容易忽略的点:特征时效性。举个例子,用户画像特征(性别、年龄)一天更新一次就够了,但实时点击率特征必须秒级更新。这两种特征如果混在一个存储里,要么浪费资源,要么满足不了延迟要求。我的建议是分层存储——冷热分离,时效性要求高的放 Redis,低的放 Hive。
3.2 元数据定义与采集:给特征上户口
特征多了以后,最头疼的问题是什么?是没人知道这个特征是什么意思、谁创建的、怎么算出来的。
我遇到过最离谱的一次:一个叫 user_score_v3 的特征,线上用了半年,结果发现它其实是 user_score_v2 的别名,逻辑完全一样。就因为命名不规范,浪费了半年的存储和计算资源。
所以,元数据管理必须做。说白了,就是给每个特征上一个「户口本」。
3.2.1 元数据该包含什么?
我个人习惯把元数据分成三类:
- 基础信息: 特征名称、描述、数据类型、创建人、创建时间。
- 生产信息: 数据源、计算逻辑(SQL 或代码)、更新频率、存储位置。
- 消费信息: 被哪些模型使用、被哪些特征依赖、上线时间。
举个例子,一个特征的元数据记录大概长这样:
{
"feature_name": "user_7d_avg_order_amount",
"description": "用户近7天平均订单金额",
"data_type": "float",
"owner": "zhangsan",
"created_at": "2024-01-15",
"source": "dwd.user_order_di",
"compute_logic": "SELECT user_id, AVG(amount) FROM dwd.user_order_di WHERE dt >= DATE_SUB('${dt}', 7) GROUP BY user_id",
"update_frequency": "daily",
"storage": "hive://default.user_features",
"used_by_models": ["ctr_model_v2", "ltr_model_v1"],
"depends_on": ["user_order_di"]
}
注意: 元数据不是一次定义就完事的。特征的生命周期会变化——可能被废弃、可能被新特征替代。我建议每季度做一次元数据审计,清理那些「僵尸特征」。我曾经清理过一次,发现团队里 30% 的特征已经三个月没人用了。
3.2.2 元数据怎么采集?
采集方式有两种:
- 手动录入: 适合初期,特征少的时候。但别依赖它,人一定会偷懒。
- 自动采集: 通过解析 SQL、代码注释、调度系统日志,自动提取元数据。这是正道。
我推荐的做法是「半自动」:自动采集 80% 的信息(数据源、计算逻辑、更新频率),剩下的 20%(描述、owner)靠人工补充。这样既保证了效率,又保证了质量。
3.3 特征血缘追踪:出了问题能溯源
特征血缘,说白了就是回答一个问题:这个特征是怎么来的?
你想想看,线上模型效果突然下跌,你第一个反应是什么?肯定是查特征。但如果不知道特征依赖了哪些上游表、经过了哪些计算步骤,你怎么查?
特征血缘追踪就是帮你解决这个问题的。它记录了特征从数据源到最终产出的完整链路。
3.3.1 血缘的粒度
血缘可以分两个粒度:
- 表级血缘: 特征依赖了哪些表。比如
user_7d_avg_order_amount依赖dwd.user_order_di。 - 字段级血缘: 特征依赖了哪些字段,经过了什么变换。比如
amount字段经过AVG聚合变成了特征值。
字段级血缘更精细,但采集成本也更高。我个人的建议是:核心特征做字段级血缘,普通特征做表级血缘就够了。
3.3.2 血缘怎么采集?
采集血缘主要有三种方式:
- SQL 解析: 解析特征计算 SQL 的 AST(抽象语法树),提取输入表和输出字段。这是最常用的方式。
- 代码插桩: 在特征计算代码中埋点,运行时记录输入输出。适合 Python/Java 写的特征逻辑。
- 调度系统日志: 从 Airflow、DolphinScheduler 等调度系统中提取任务依赖关系。
下面是一个简单的 SQL 解析血缘示例:
-- 原始 SQL
INSERT INTO feature.user_7d_avg_order_amount
SELECT user_id, AVG(amount) as avg_amount
FROM dwd.user_order_di
WHERE dt >= '2024-01-01'
GROUP BY user_id;
-- 解析后的血缘
{
"feature": "user_7d_avg_order_amount",
"input_tables": ["dwd.user_order_di"],
"input_fields": ["user_id", "amount"],
"transform": "AVG(amount)",
"output_fields": ["user_id", "avg_amount"]
}
核心观点: 血缘追踪的价值不在于采集那一刻,而在于排查问题时能快速定位。我经历过一次线上事故,模型效果暴跌,靠血缘追踪 10 分钟就定位到是上游一张 Hive 表的数据质量出了问题。如果没有血缘,光靠人工排查,至少得半天。
3.4 知识体系总览
为了让你更直观地理解这三块内容的关系,我画了一张图:
这张图把本章的三个核心模块串起来了。你可以看到,特征存储选型是基础,元数据管理是纽带,特征血缘追踪是保障。三者缺一不可。
3.5 落地建议
最后,给你几条落地建议:
- 从小做起: 别想着一步到位。先管好 10 个核心特征的元数据和血缘,再逐步扩展。
- 工具选型要匹配团队规模: 小团队用开源工具(Feast + DataHub)就够了,大团队才考虑商业产品。
- 流程比工具重要: 再好的工具,没人用也是白搭。我建议把元数据录入和血缘检查加入 CI/CD 流程,强制执行。
嗯,特征存储和元数据管理这块,说到底就是一句话:让每个特征都有据可查、有源可溯。做到这一点,特征复用就不再是空话了。