3、特征存储与元数据管理:特征存储选型、元数据定义与采集、特征血缘追踪

聊到特征复用,有个问题绕不开:特征到底该存哪儿?怎么管?

我见过不少团队,特征散落在各种地方——Hive 表里有一份,训练脚本里硬编码一份,线上服务 Redis 里又有一份。结果呢?特征对不上,模型推演效果崩了,查了半天才发现是训练和上线用的不是同一份特征逻辑。说白了,这就是特征存储和元数据没管好。

这一章,咱们就聊聊特征存储的选型思路、元数据怎么定义和采集,以及特征血缘怎么追踪。这些都是特征复用的地基,地基不稳,上层建筑迟早要塌。

3.1 特征存储选型:没有银弹,只有权衡

特征存储选型,我个人的习惯是先问三个问题:

  1. 特征怎么用? 是给离线训练用,还是给线上实时推理用?
  2. 特征怎么更新? 是批处理 T+1 更新,还是流式实时更新?
  3. 特征怎么查? 是点查(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 血缘怎么采集?

采集血缘主要有三种方式:

  1. SQL 解析: 解析特征计算 SQL 的 AST(抽象语法树),提取输入表和输出字段。这是最常用的方式。
  2. 代码插桩: 在特征计算代码中埋点,运行时记录输入输出。适合 Python/Java 写的特征逻辑。
  3. 调度系统日志: 从 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 知识体系总览

为了让你更直观地理解这三块内容的关系,我画了一张图:

特征存储与元数据管理知识体系 特征复用与治理 特征存储选型 元数据定义与采集 特征血缘追踪 离线数仓 KV 存储 特征平台 OLAP 引擎 基础信息 生产信息 消费信息 自动采集 表级血缘 字段级血缘 SQL 解析 代码插桩 核心原则 存储选型看场景,元数据管理要自动化,血缘追踪重实用

这张图把本章的三个核心模块串起来了。你可以看到,特征存储选型是基础,元数据管理是纽带,特征血缘追踪是保障。三者缺一不可。

3.5 落地建议

最后,给你几条落地建议:

  • 从小做起: 别想着一步到位。先管好 10 个核心特征的元数据和血缘,再逐步扩展。
  • 工具选型要匹配团队规模: 小团队用开源工具(Feast + DataHub)就够了,大团队才考虑商业产品。
  • 流程比工具重要: 再好的工具,没人用也是白搭。我建议把元数据录入和血缘检查加入 CI/CD 流程,强制执行。

嗯,特征存储和元数据管理这块,说到底就是一句话:让每个特征都有据可查、有源可溯。做到这一点,特征复用就不再是空话了。