特征存储架构:在线存储与离线存储、特征计算与存储分离、特征血缘与元数据管理
聊特征存储,绕不开架构。说白了,特征存储不是扔一个 Redis 或者 MySQL 就完事了。我见过太多团队,一开始图省事,把特征全塞进一个库里,结果线上延迟爆炸,离线训练又跑不动。嗯,今天咱们就把这块掰开揉碎讲清楚。
在线存储 vs 离线存储:两个世界,一套特征
先问个问题:为什么不能只用一种存储?
在线推理要求毫秒级响应,QPS 动不动几千上万。离线训练呢?要扫全量数据,吞吐量是 MB/s 甚至 GB/s 级别。这两种负载对存储的要求完全相反——一个要低延迟、高并发;一个要高吞吐、大容量。
我个人习惯把在线存储和离线存储看作「同一套特征的两个副本」:
| 维度 | 在线存储 | 离线存储 |
|---|---|---|
| 典型引擎 | Redis、Aerospike、DynamoDB | HDFS、S3、Hive、Iceberg |
| 延迟要求 | < 10ms | 秒级到分钟级 |
| 数据格式 | Key-Value、Hash | Parquet、ORC、Avro |
| 更新频率 | 实时写入 / T+1 批量同步 | 小时级 / 天级批量 |
| 数据量级 | TB 级(热数据) | PB 级(全量历史) |
关键原则:在线存储只存「当前有效」的特征值,离线存储保留「所有历史快照」。两者通过统一的特征名称和版本号关联。
我在项目中遇到过一个问题:在线特征用 Redis 存,离线特征用 Hive 表存,两边特征名不一致,导致线上和离线 AUC 差 5 个点。排查了两天才发现是命名规范没对齐。所以后来我强制要求:特征名必须全局唯一,在线和离线用同一个 schema 定义。
特征计算与存储分离:解耦才是王道
早期特征工程怎么做?计算和存储绑在一起。比如在 Spark 作业里算完特征直接写 Hive,线上服务自己算自己存。这有什么问题?
- 计算逻辑重复:离线写一套 PySpark,在线写一套 Java,两边逻辑对不上
- 存储紧耦合:换存储引擎要改计算代码
- 扩缩容困难:计算和存储争资源
我建议的做法是:特征计算层和特征存储层通过「特征视图」解耦。计算层只负责产出特征值,不关心数据最终落到哪里。存储层只负责读写,不关心特征怎么算出来的。
举个例子,我们用 Flink 做实时特征计算,输出到 Kafka。然后一个统一的「特征同步服务」从 Kafka 消费,分别写入 Redis(在线)和 HDFS(离线)。这样如果某天要把 Redis 换成 Aerospike,只需要改同步服务的下游,计算逻辑完全不动。
我的经验:计算与存储分离后,最大的收益不是性能,而是「团队协作效率」。特征工程师专注写计算逻辑,平台工程师专注优化存储,各管各的,互不干扰。
这里有个坑——数据一致性。我曾经遇到过在线和离线特征差了 2 小时的情况,原因是同步服务挂了,Kafka 积压。后来加了监控和自动重试,才把延迟控制在分钟级。
特征血缘与元数据管理:没有血缘,特征就是黑盒
你想想看,一个模型上线后效果变差了,你怎么知道是哪个特征出了问题?如果没有血缘追踪,你只能靠猜。
特征血缘,说白了就是记录「特征从哪里来、经过什么变换、被哪些模型使用」。我把它分成三个层级:
- 数据源血缘:特征来自哪个原始表、哪个字段、哪个时间窗口
- 计算血缘:特征经过哪些算子(聚合、JOIN、UDF)
- 消费血缘:特征被哪些模型、哪些线上服务引用
元数据管理是血缘的基础。每个特征至少要有这些元数据:
| 元数据字段 | 说明 | 示例 |
|---|---|---|
| feature_name | 全局唯一特征名 | user_click_7d_cnt |
| feature_type | 数据类型 | int64 / float32 / string |
| owner | 负责人 | zhangsan@company.com |
| source_table | 原始数据表 | ods.user_behavior_log |
| compute_logic | 计算逻辑描述或代码路径 | flink_job/user_click_agg |
| online_store | 在线存储位置 | redis://cluster-01:6379/0 |
| offline_store | 离线存储位置 | hdfs://ns1/features/user_click/ |
| version | 特征版本 | v2.3.1 |
注意:元数据一定要自动化采集,不要靠人工维护。我见过一个团队用 Excel 管理特征元数据,结果三个月后 Excel 和实际特征对不上了,整个特征体系崩塌。
血缘追踪的实现方式,我推荐两种:
- 代码注入:在特征计算框架(如 Flink、Spark)中嵌入血缘采集逻辑,自动记录输入输出
- 日志解析:从计算任务的日志中解析血缘关系,适合已有系统改造
下面这张图展示了特征存储的整体架构,包括在线/离线存储、计算与存储分离、以及血缘元数据流转:
最后说一句,元数据管理不是一次性工程。我建议每周做一次血缘完整性校验,确保每个特征都能追溯到原始数据源。否则时间一长,特征库就会变成「垃圾场」——没人知道哪些特征还能用,哪些已经废弃了。