首页
/ 字节跳动推荐系统特征存储设计:从万亿级数据到毫秒级响应的架构实践

字节跳动推荐系统特征存储设计:从万亿级数据到毫秒级响应的架构实践

2026-02-04 05:03:16作者:邓越浪Henry

引言:特征存储的技术挑战与设计目标

在推荐系统领域,特征存储(Feature Store)作为连接数据工程与机器学习的核心枢纽,面临着双重挑战:既要处理日均万亿级的特征更新,又要满足在线服务毫秒级的查询延迟。字节跳动的Monolith平台通过创新的分布式架构和高效的存储引擎,构建了一套支持高吞吐写入与低延迟读取的特征存储系统。本文将深入剖析其设计原理,包括数据模型、存储架构、分布式策略与性能优化,为构建大规模推荐系统特征存储提供实践指南。

核心设计指标

指标 目标值 技术挑战
特征规模 日均新增10亿+特征 如何高效存储稀疏特征
查询延迟 P99 < 5ms 数据本地化与缓存策略
更新频率 秒级实时更新 读写分离与一致性保障
容错能力 99.99%可用性 分布式部署与故障转移
存储成本 压缩率>10:1 高效编码与存储优化

特征数据模型设计:灵活应对多样化特征需求

Monolith平台采用Protocol Buffers定义统一的特征数据模型,支持离散特征、连续特征和序列特征等多种类型,同时通过可扩展的结构设计满足不同业务场景需求。

核心数据结构定义

// idl/matrix/proto/feature.proto
syntax = "proto2";
package idl.matrix.proto;

// 离散序列特征
message Fixed64List {
  repeated fixed64 value = 1 [packed = true];
}

// 浮点型连续值序列特征
message FloatList {
  repeated float value = 1 [packed = true];
}

message Feature {
  optional string name = 1;  // 特征名称,以fc_开头
  
  // 基础特征类型(一次只能使用一种)
  repeated fixed64 fid = 2 [packed = true];          // 离散id特征
  repeated float float_value = 3 [packed = true];     // 连续浮点特征
  repeated int64 int64_value = 4 [packed = true];     // 连续整数特征
  repeated bytes bytes_value = 5;                     // 原始字节特征
  
  // 序列特征类型
  repeated Fixed64List fid_list = 6;                  // 离散id序列
  repeated FloatList float_list = 7;                  // 浮点序列
  repeated Int64List int64_list = 8;                  // 整数序列
  repeated BytesList bytes_list = 9;                  // 字节序列
}

特征类型适用场景

特征类型 存储格式 典型应用 空间效率
fid packed fixed64 用户ID、物品ID 高(每个ID占8字节)
float_value packed float 点击率、时长 中(每个值占4字节)
fid_list 嵌套Fixed64List 用户行为序列 极高(共享存储结构)
bytes_value 原始字节 文本、图像特征 低(无压缩)

核心架构设计:分层存储与计算分离

Monolith特征存储采用分层架构,将计算与存储分离,通过多级缓存和分片策略实现高可用与高性能。

整体架构流程图

flowchart TD
    subgraph 数据写入层
        A[离线特征ETL] -->|批量导入| B[分布式文件系统]
        C[实时特征流] -->|Kafka| D[流处理服务]
    end
    
    subgraph 存储层
        B --> E[特征存储主集群]
        D --> E
        E --> F{哈希分片}
        F --> G[PS节点1]
        F --> H[PS节点2]
        F --> I[PS节点N]
    end
    
    subgraph 服务层
        J[特征查询API] --> K[本地缓存]
        K --> L[一致性哈希路由]
        L --> M[远程PS查询]
        M --> G
        M --> H
        M --> I
    end
    
    subgraph 监控与运维
        N[指标收集] --> O[Prometheus]
        P[日志分析] --> Q[ELK]
    end

核心组件解析

  1. 特征存储主集群:基于分布式哈希表实现,支持PB级数据存储
  2. PS节点:负责分片数据的存储与计算,每个节点管理部分哈希空间
  3. 一致性哈希路由:确保特征查询能够高效定位到目标节点
  4. 多级缓存:包括本地内存缓存、Redis集群缓存和PS节点缓存

分布式特征存储实现:从单节点到大规模集群

Monolith通过创新的分布式哈希表设计和分片策略,解决了大规模特征存储的扩展性问题。

分布式哈希表设计

# monolith/native_training/distributed_ps.py
class DistributedHashTable(hash_table_ops.BaseHashTable):
  def __init__(self, ps_num, config, hash_table_factory):
    self._ps_num = ps_num
    self._hash_tables = []
    learning_rate_tensor = config.call_learning_rate_fns()
    
    for i in range(self._ps_num):
      with ps_device(i):  # 绑定到特定PS节点
        config.set_learning_rate_tensor(learning_rate_tensor)
        self._hash_tables.append(hash_table_factory(i, config))
  
  def lookup(self, ids: tf.Tensor) -> tf.Tensor:
    unique_ids, idx = tf.unique(ids)
    indices = tf.math.floormod(unique_ids, self._ps_num)  # 哈希分片
    split_ids = distribution_ops.split_by_indices(indices, unique_ids, self._ps_num)
    split_embeddings = []
    
    for i in range(self._ps_num):
      with ps_device(i):
        embeddings_part = self._hash_tables[i].lookup(split_ids[i])
        split_embeddings.append(embeddings_part)
    
    # 合并查询结果
    return distribution_ops.map_id_to_embedding(split_ids, split_embeddings, ids)

分片策略对比

分片方式 实现原理 优点 缺点
哈希取模 id % 节点数 实现简单,负载均匀 扩容时需数据迁移
一致性哈希 哈希环映射 支持平滑扩容 实现复杂,小规模集群负载不均
动态分片 基于数据量自动调整 资源利用率高 需中央协调服务

Monolith采用哈希取模+预分片策略,通过固定分片数量(通常256或512)实现逻辑分片与物理节点的解耦,兼顾扩展性与简单性。

特征生命周期管理:从创建到淘汰的全流程

特征存储不仅需要高效存储特征,还需要管理特征的全生命周期,包括创建、更新、老化和淘汰。

特征配置示例

# monolith/native_training/feature.py
@dataclass
class FeatureSlotConfig:
  name: str = None
  has_bias: bool = False
  bias_initializer: entry.Initializer = entry.ZerosInitializer()
  default_vec_initializer: entry.Initializer = entry.RandomUniformInitializer()
  default_vec_optimizer: entry.Optimizer = entry.AdagradOptimizer(initial_accumulator_value=1.0)
  default_vec_compressor: entry.Compressor = entry.Fp16Compressor()
  hashtable_config: entry.HashTableConfig = entry.CuckooHashTableConfig()
  occurrence_threshold: int = 0  # 出现次数阈值
  expire_time: int = 36500  # 特征过期时间(天)

特征淘汰机制

Monolith实现了两种特征淘汰策略:

  1. 基于时间的淘汰:通过expire_time配置特征的存活周期,定期清理过期特征
  2. 基于频率的淘汰:通过occurrence_threshold设置最低出现次数,过滤低频特征
# monolith/native_training/hash_table_ops.py
def save(self, basename: tf.Tensor) -> "HashTable":
  return self._copy_with_new_table(
    hash_table_ops.monolith_hash_table_save(
      self._table,
      basename,
      slot_expire_time_config=self._slot_expire_time_config,
      nshards=self._saver_parallel
    )
  )

高性能查询优化:从毫秒到微秒的响应提速

为满足推荐系统低延迟需求,Monolith从多个层面进行查询优化:

多级缓存架构

stateDiagram-v2
    [*] --> 请求
    请求 --> 本地缓存: 检查LRU缓存
    本地缓存 --> 命中: 返回结果
    本地缓存 --> 远程查询: 未命中
    远程查询 --> Redis集群: 检查分布式缓存
    Redis集群 --> 命中2: 返回结果并更新本地缓存
    Redis集群 --> PS节点查询: 未命中
    PS节点查询 --> 返回结果: 更新Redis和本地缓存
    返回结果 --> [*]

特征压缩与编码优化

Monolith支持多种特征压缩策略,显著降低网络传输和存储开销:

压缩算法 压缩率 性能开销 适用场景
FP16 2:1 连续特征向量
变长编码 3-5:1 整数ID序列
LZ4 2-4:1 中高 字符串特征
稀疏表示 10-100:1 高维稀疏特征

批处理与预取优化

通过请求批处理和预取技术,Monolith将随机查询转为批量操作,大幅提升吞吐量:

# monolith/native_training/distribution_ops.py
def fused_reduce_sum_and_split(value_rowids, embeddings, batch_size_tensor, slice_dims):
  # 融合reduce和split操作,减少内存访问
  return hash_table_ops.monolith_fused_reduce_sum_and_split(
    value_rowids, embeddings, batch_size_tensor, slice_dims
  )

持久化与故障恢复:数据可靠性保障

特征数据的可靠性至关重要,Monolith通过多重机制确保数据不丢失:

检查点机制

# monolith/native_training/serving.md
# 模型检查点结构示例
hdfs:///user/model_checkpoint/exported_models/
├── entry/               # 入口服务模型
├── ps_0/                # PS节点0的检查点
├── ps_1/                # PS节点1的检查点
└── ps_2/                # PS节点2的检查点

Monolith采用异步增量检查点机制,平衡性能与数据一致性:

  • 全量检查点:每日一次,保存完整数据
  • 增量检查点:每小时一次,仅保存变更数据
  • 实时WAL:记录写操作日志,确保崩溃恢复

主从复制与故障转移

每个PS节点维护多个副本,通过半同步复制确保数据可靠性:

  • 写操作需至少同步到一个从副本才返回成功
  • 主节点故障时,自动提升从节点为新主
  • 采用Raft协议实现副本间的一致性

实践指南:特征存储的最佳使用方式

特征定义最佳实践

  1. 特征命名规范

    • 使用fc_前缀标识特征列,如fc_user_id
    • 包含特征类型和业务含义,如fc_item_category_seq
  2. 特征选择建议

    • 离散特征优先使用fid类型,节省存储空间
    • 高基数特征(>1000万)建议使用哈希映射
    • 序列特征长度控制在200以内,避免过度计算

性能调优参数

参数 推荐值 说明
哈希表类型 CuckooHash 平衡查找速度与内存效率
压缩算法 FP16 对嵌入向量压缩率高,精度损失小
本地缓存大小 2GB 根据内存情况调整,建议不超过总内存的20%
批处理大小 1024 权衡延迟与吞吐量

常见问题排查

  1. 查询延迟高

    • 检查是否命中缓存
    • 分析热点特征分布
    • 调整分片策略,避免热点集中
  2. 内存占用过大

    • 启用更激进的压缩算法
    • 降低本地缓存大小
    • 调整特征过期策略
  3. 数据不一致

    • 检查检查点恢复流程
    • 验证主从复制状态
    • 确认网络稳定性

总结与展望

Monolith特征存储通过创新的分布式架构、高效的数据结构和精细化的优化策略,成功支撑了字节跳动大规模推荐系统的特征管理需求。未来,特征存储将向以下方向发展:

  1. 智能特征工程:结合AI自动生成和选择特征
  2. 实时特征计算:降低特征从产生到可用的延迟
  3. 多模态特征支持:统一管理文本、图像等多模态特征
  4. 自适应存储策略:根据特征特性自动选择最优存储方式

通过不断演进,特征存储将不仅是数据的容器,更成为机器学习系统的"特征大脑",为推荐系统提供更强大的动力。

附录:核心API参考

特征定义API

# 创建特征槽
config = FeatureSlotConfig(
  name="user_behavior",
  has_bias=True,
  default_vec_initializer=RandomUniformInitializer(minval=-0.01, maxval=0.01),
  default_vec_optimizer=AdagradOptimizer(initial_accumulator_value=0.1),
  expire_time=30  # 30天过期
)
slot = feature_factory.create_feature_slot(config)

# 创建特征列
fc = FeatureColumn(
  feature_slot=slot,
  feature_name="user_click_seq",
  combiner=FeatureColumn.reduce_sum()
)

特征查询API

# 批量查询特征
def lookup_features(feature_names, ids):
  embeddings = {}
  for name in feature_names:
    embeddings[name] = feature_store.lookup(name, ids[name])
  return embeddings

特征更新API

# 实时更新特征
def update_features(feature_name, ids, values):
  return feature_store.update(
    feature_name,
    ids=tf.convert_to_tensor(ids, dtype=tf.int64),
    values=tf.convert_to_tensor(values, dtype=tf.float32)
  )

文档版本:v1.0
最后更新:2025-09-07
适用场景:推荐系统、机器学习平台、大规模特征管理

收藏与分享:如果本文对您有帮助,请点赞收藏,关注获取更多推荐系统技术实践分享!
下期预告:《万亿参数模型的分布式训练实践》即将发布,敬请期待!

登录后查看全文
热门项目推荐
相关项目推荐