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 就是一个黑箱——你不知道特征从哪来、怎么算的、谁在用、数据质量怎么样。

元数据管理至少需要覆盖以下内容:

  1. 数据血缘:特征从原始数据源到最终值的完整链路。比如“user_click_count_7d”是从 click_log 表经过窗口聚合得到的。
  2. 特征统计:均值、方差、空值率、分布直方图。用于监控特征漂移。
  3. 使用追踪:哪些模型、哪些服务、哪些训练任务引用了这个特征。
  4. 变更历史:特征逻辑的每一次修改记录,包括修改人、时间、变更原因。

我个人习惯把元数据存在一个独立的 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 架构设计原则协同图 在线与离线存储分离 存储层 批流一体 计算层 特征共享与复用 使用层 元数据管理 治理层 说明: 存储层和计算层是基础设施,决定了 Feature Store 的性能天花板。 使用层和治理层是用户体验,决定了团队是否愿意用、用得顺不顺。 低延迟 + 高吞吐 存储与计算协同 训练/推理一致性 批流与元数据协同 可发现 + 可治理 共享与元数据协同

从图上可以看出,存储和计算是“硬”基础设施,决定了 Feature Store 能不能跑得快、撑得住。共享和元数据是“软”基础设施,决定了团队能不能用得起来、出了问题能不能查得清。四个原则缺一不可。

总结:架构设计没有银弹。在线与离线分离意味着你要维护两套存储,运维成本翻倍;批流一体意味着你要引入 Flink 或类似框架,技术门槛提高;共享复用意味着你要推动组织协作,改变团队习惯;元数据管理意味着你要额外开发一套治理系统。但所有这些投入,换来的是特征的可信、可复用、可追溯。值不值?我做了三个 Feature Store 项目,答案是:值。