3. 架构设计原则:在线与离线存储分离、批流一体、特征共享与复用、元数据管理
Feature Store 的架构设计,说白了就是回答四个问题:特征数据怎么存、怎么算、怎么用、怎么管。我见过不少团队一上来就撸代码,结果半年后跑不动、改不了、查不清。嗯,这一章咱们就把这四个原则掰开揉碎讲清楚。
核心观点:Feature Store 不是“一个数据库”,而是一套面向特征生命周期的架构规范。在线与离线分离解决性能与容量矛盾;批流一体解决时效性与一致性矛盾;共享复用解决重复造轮子问题;元数据管理解决可观测性与治理问题。
3.1 在线与离线存储分离
为什么一定要分开?我刚开始做特征平台时,图省事把线上 Serving 和离线训练都放在同一个 MySQL 集群里。结果呢?离线 ETL 一跑大查询,线上推荐接口延迟直接飙到 5 秒。用户反馈“App 卡死了”,老板差点让我去修手机。
在线存储追求的是低延迟、高并发。通常用 Redis、Aerospike 或者 Cassandra。特征值按 Key-Value 存,毫秒级返回。离线存储追求的是高吞吐、大容量、支持复杂分析。Hive、HBase、或者对象存储上的 Parquet 文件是常见选择。
| 维度 | 在线存储(Online Store) | 离线存储(Offline Store) |
|---|---|---|
| 典型场景 | 实时推理、API 服务 | 批量训练、历史回溯 |
| 延迟要求 | 毫秒级(< 10ms) | 分钟级甚至小时级 |
| 数据格式 | Key-Value、Hash | 宽表、列存(Parquet/ORC) |
| 一致性模型 | 最终一致性为主 | 强一致性或快照隔离 |
| 存储引擎举例 | Redis、Aerospike、DynamoDB | Hive、Iceberg、Delta Lake |
我的经验:在线存储不要存原始事件,只存“计算好的特征值”。比如用户过去 7 天点击次数,而不是存 7 天的点击日志。否则 Redis 内存会爆炸,而且每次推理都要在服务端做聚合,延迟根本扛不住。
3.2 批流一体
批流一体这个词被炒得很热,但落地时坑不少。说白了,就是同一套特征逻辑,既能跑在批处理(比如每天凌晨算一次),也能跑在流处理(比如事件到达后实时更新)。
为什么要做?你想想看,模型训练时用的是离线算好的特征,上线后推理时用的却是实时算的特征。如果两套代码逻辑不一致,训练和推理之间就有“特征偏差”。我见过一个 CTR 模型,离线 AUC 0.82,上线后掉到 0.73,查了两周发现是特征归一化逻辑在批处理和流处理里写反了。
批流一体的核心是“同一份代码,两种执行模式”。Flink 是当前最成熟的方案。离线模式用 Batch 执行,流模式用 Streaming 执行。特征计算逻辑封装成 UDF 或 SQL,两边共用。
// 批流一体特征计算示例(Flink SQL)
-- 这段 SQL 在离线模式跑历史全量,在流模式跑实时增量
CREATE VIEW user_click_features AS
SELECT
user_id,
COUNT(*) AS click_count_7d,
AVG(dwell_time) AS avg_dwell_time_7d
FROM click_events
WHERE event_time >= NOW() - INTERVAL '7' DAY
GROUP BY user_id;
避坑指南:我曾经在批流一体项目里踩过一个坑——水位线(Watermark)设置不当。离线模式没有水位线问题,但流模式里水位线设得太短,导致晚到的数据被丢弃,特征值偏小。后来我统一把水位线延迟设为 5 分钟,并在离线回溯时也模拟这个延迟窗口,才对齐了两边的结果。
3.3 特征共享与复用
Feature Store 存在的最大价值之一,就是让团队不再重复造轮子。我见过一个公司,三个算法团队各自维护了一套“用户活跃度”特征,计算逻辑大同小异,但特征名不同、存储位置不同、更新频率不同。结果模型上线时互相冲突,线上推理都不知道该读哪个。
特征共享的核心是“注册-发现-复用”机制。每个特征在 Feature Store 里注册时,必须包含:特征名、所属实体(如 user_id)、计算逻辑(SQL 或代码)、更新频率、数据源。其他团队通过特征目录(Feature Catalog)搜索,找到后直接引用。
- 特征注册:定义特征元数据,包括名称、类型、描述、Owner。
- 特征发现:提供搜索和标签系统,支持按实体、业务域、更新频率筛选。
- 特征复用:通过 SDK 或 API 直接拉取,无需重新开发 ETL。
- 版本管理:特征逻辑变更时,旧版本仍可被历史训练回溯使用。
我的建议:特征共享不要搞成“大锅饭”。每个特征必须有明确的 Owner 和变更通知机制。否则 A 团队改了特征逻辑,B 团队不知情,模型效果突然下降,排查起来非常痛苦。我们当时用了一个简单的 Webhook,特征变更时自动通知所有引用方。
3.4 元数据管理
元数据是 Feature Store 的“大脑”。没有元数据管理,Feature Store 就是一个黑箱——你不知道特征从哪来、怎么算的、谁在用、数据质量怎么样。
元数据管理至少需要覆盖以下内容:
- 数据血缘:特征从原始数据源到最终值的完整链路。比如“user_click_count_7d”是从 click_log 表经过窗口聚合得到的。
- 特征统计:均值、方差、空值率、分布直方图。用于监控特征漂移。
- 使用追踪:哪些模型、哪些服务、哪些训练任务引用了这个特征。
- 变更历史:特征逻辑的每一次修改记录,包括修改人、时间、变更原因。
我个人习惯把元数据存在一个独立的 PostgreSQL 或 MySQL 实例里,不跟在线/离线存储混在一起。元数据查询频率不高,但要求强一致性和事务支持。
-- 特征元数据表结构示例
CREATE TABLE feature_metadata (
feature_id VARCHAR(64) PRIMARY KEY,
feature_name VARCHAR(128) NOT NULL,
entity_type VARCHAR(64) NOT NULL, -- user_id, item_id, etc.
feature_type VARCHAR(32), -- int, float, string, vector
description TEXT,
owner VARCHAR(64),
source_table VARCHAR(256),
compute_logic TEXT, -- SQL or code snippet
update_freq VARCHAR(16), -- realtime, hourly, daily
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
注意:元数据管理最容易被忽视的是“特征统计信息”的自动采集。我见过一个案例,特征“user_income_level”连续三个月全是 NULL,但没人发现,因为模型训练时自动丢弃了空值。直到业务方发现推荐结果异常,才追查到是上游数据源断了。从那以后,我要求每个特征注册时必须配置数据质量监控规则,空值率超过阈值自动告警。
3.5 架构总览:四个原则如何协同
这四个原则不是孤立的。在线与离线分离决定了存储层;批流一体决定了计算层;共享复用决定了使用层;元数据管理决定了治理层。把它们拼在一起,就是 Feature Store 的整体架构。
下面这张图展示了它们之间的关系:
从图上可以看出,存储和计算是“硬”基础设施,决定了 Feature Store 能不能跑得快、撑得住。共享和元数据是“软”基础设施,决定了团队能不能用得起来、出了问题能不能查得清。四个原则缺一不可。
总结:架构设计没有银弹。在线与离线分离意味着你要维护两套存储,运维成本翻倍;批流一体意味着你要引入 Flink 或类似框架,技术门槛提高;共享复用意味着你要推动组织协作,改变团队习惯;元数据管理意味着你要额外开发一套治理系统。但所有这些投入,换来的是特征的可信、可复用、可追溯。值不值?我做了三个 Feature Store 项目,答案是:值。