介绍 PAI-Rec 两种本地特征缓存的选型与配置:FeatureViewCache 把视图全量缓存供状态过滤直接读取,FeatureCache 按需共享缓存供场景特征配置复用。
PAI-Rec 特征缓存
PAI-Rec 引擎提供两种本地特征缓存,用于降低特征读取的在线请求量与过滤/取特征的耗时,二者机制不同、适用场景不同,可以同时使用。
FeatureViewCache(全量视图缓存):把 FeatureStore 中的一个特征视图全量加载到引擎本地内存并持续同步,当前由状态过滤(ItemStateFilter)消费。
FeatureCache(按需共享缓存):按请求的 key 按需从 FeatureStore 取数并缓存,按名字全局定义、可被多个场景的特征配置复用。
如何选择
维度 | FeatureViewCache | FeatureCache |
缓存内容 | 指定视图的全部数据行 | 请求实际用到的那批 key 对应的数据行 |
加载方式 | 引擎启动/热加载时全量扫描,之后持续同步 | 首次访问时按 key 回源,命中后不再回源 |
内存占用 | 与视图总行数成正比,常驻不淘汰 | 受 |
数据时效 | Stream 视图秒级增量同步;Batch 视图按 | 写入后 |
数据视角 | 仅按视图的 join id 读取,实际消费方是 item 维度 | user 或 item;数据源可以是视图,也可以是"模型 + 实体" |
消费方 | 目前为状态过滤(ItemStateFilter) | 特征配置 |
典型适用 | 候选集大、状态需秒级生效、视图行数可控 | 特征读取量大但 key 分散,可容忍 TTL 窗口内的陈旧 |
不适用 | 视图行数超出单机内存可承载规模 | 需要整表本地化,或需要严格读取最新值 |
选型建议:
目标是"不再逐 item 在线点查",且需要状态变更秒级生效,视图行数能被单机内存容纳 → 用 FeatureViewCache。
目标只是"减少重复点查",key 分散、每次请求命中的 item 占比低 → 用 FeatureCache,用
CacheSize+CacheTime控制内存与陈旧窗口。两类需求同时存在时,可以并存:状态过滤走 FeatureViewCache,
FeatureConfs/UserFeatureConfs的特征加载走 FeatureCache。
FeatureViewCache:特征视图全量缓存
功能说明
FeatureViewCache 把 FeatureStore 中的某个特征视图全量加载到推荐引擎本地内存,并持续与源视图保持同步,供状态过滤(ItemStateFilter)等模块直接读取。启用后,状态过滤不再对每个 item 发起在线点查,而是直接命中本地缓存。
适用场景
推荐在以下场景启用:
状态过滤依赖实时表(Stream 特征视图),如下线、失效、库存、审核状态等需要秒级生效的过滤场景。
过滤候选集大、状态字段相对稳定,全量缓存后本地判定,避免大规模在线点查。
希望降低 FeatureStore/FeatureDB 的在线查询压力,把高频状态查询收敛为本地读取加后台同步。
以下场景需谨慎或改用其他方式:
视图数据量极大,远超单机内存可承载的规模。
状态只需离线级时效,可直接使用定时全量刷新的离线视图。
核心能力
能力 | 说明 |
全量本地缓存 | 启动时将特征视图全量加载到本地内存 |
实时增量同步 | Stream 视图的数据变更秒级同步到本地缓存 |
定时全量校准 | 周期性全量刷新,兜底纠正删除与数据漂移 |
按名复用 | 全局定义一份缓存,可被多处按名引用,避免重复加载 |
配置热加载 | 修改配置后自动生效,更新过程不阻塞在线请求 |
高可用降级 | 缓存未就绪时状态过滤自动放行,避免推荐结果被清空 |
数据时效性
Stream(实时)视图:源数据变更后,增量更新在秒级同步到本地缓存,状态过滤随即生效,是高时效状态过滤的推荐选择。
Batch(离线)视图:按
RefreshIntervalMinutes定时全量刷新。全量刷新间隔下限为 60 分钟:全量扫描有一定成本,为避免对 FeatureDB 造成频繁全量扫描压力,配置值小于 60(含未配置)时按 60 处理。该下限只约束定时全量校准,Stream 视图的秒级增量同步不受影响。
配置缓存定义
在 FeatureViewCacheConfs 中按名字定义缓存,一份定义可被多处引用:
{
"FeatureViewCacheConfs": {
"item_status_cache": {
"FeatureStoreName": "pairec_fs",
"FeatureStoreViewName": "item_status_view",
"SelectFields": "status,is_valid,category",
"RefreshIntervalMinutes": 60
}
}
}字段 | 类型 | 是否必填 | 说明 |
FeatureStoreName | string | 是 | 引用数据源配置( |
FeatureStoreViewName | string | 是 | FeatureStore 项目中的特征视图名,实时状态过滤建议使用 Stream 视图 |
SelectFields | string | 否 | 需要缓存的字段,逗号分隔,默认为 |
RefreshIntervalMinutes | int | 否 | 定时全量刷新间隔,单位分钟,默认值与下限均为 60 |
配置校验说明:
FeatureStoreName在FeatureStoreConfs中不存在、FeatureStoreViewName在项目中不存在,或SelectFields包含视图未定义的字段时,该缓存不会被创建,并输出module=FeatureViewCache ... error=init failed日志;引用它的状态过滤在构建阶段报错。请先按该日志定位并修正配置。SelectFields必须覆盖过滤条件(FilterParams)用到的全部字段,否则过滤判定会读到空值。
状态过滤引用缓存
在 ItemStateFilter 的 ItemStateDaoConf 中通过 FeatureViewCacheName 引用缓存:
{
"FilterConfs": [
{
"Name": "item_status_filter",
"FilterType": "ItemStateFilter",
"ItemStateDaoConf": {
"FeatureViewCacheName": "item_status_cache",
"DefaultFieldValues": { "status": 1 }
},
"FilterParams": [
{ "Name": "status", "Domain": "item", "Operator": "equal", "Type": "int", "Value": 1 }
]
}
]
}字段 | 类型 | 是否必填 | 说明 |
FeatureViewCacheName | string | 使用缓存时必填 | 引用 |
DefaultFieldValues | map | 否 | 视图中不存在该 item 时用于判定的兜底属性值 |
FilterParams | [ ]object | 否 | 状态过滤条件,语义与直接读取 FeatureStore 的状态过滤一致 |
说明:
配置了
FeatureViewCacheName后优先走本地缓存,无需再配置AdapterType、ItemFieldName、WhereClause、SelectFields。缓存按 item 维度读取(依据视图对应的特征实体 join id),因此配置的视图应使用 item 实体。其他模块如需读取同一视图,需要单独接入。
可用性与降级
首次加载未完成或 FeatureDB 故障导致缓存未就绪:状态过滤自动放行(不拦截 item),避免缓存不可用期间把推荐结果整体清空;后台以 30 秒间隔自动重试,直到首次全量加载成功。
缓存配置被移除导致引用失效:状态过滤同样放行,并在日志中提示缓存不可用。
缓存就绪后:视图中确实不存在的 item,按
DefaultFieldValues或空值判定,与直接读取 FeatureStore 的状态过滤行为一致。此时如果SelectFields未覆盖FilterParams用到的字段,可能出现全部 item 被过滤的情况。配置热加载期间:在线请求读取不阻塞;新配置会先构建并完成预热,成功后才原子切换到新实例,构建失败则继续使用旧实例。
最佳实践
SelectFields只取必要字段,减少内存占用与加载网络开销。评估单机内存:占用约为「条目数 × 每条字段规模」,10 万级条目、少量字段通常为几十 MB 量级。
缓存名保持语义化(如
item_status_cache),同一视图只定义一份缓存。高时效场景优先使用 Stream 视图,实时性由增量同步保证,定时全量刷新只作兜底校准,60 分钟通常足够。
常见问题
Q1:日志报 feature view cache not found, name:xxx?
说明引用方找不到该缓存,依次检查:
FeatureViewCacheConfs中是否存在该名字(大小写需一致)。该缓存的
FeatureStoreName是否能在FeatureStoreConfs中找到。FeatureStoreViewName是否为项目中真实存在的视图名。向前查找
module=FeatureViewCache ... initial load failed的具体原因,该条日志才是根因。
Q2:为什么刷新间隔最小只能 60 分钟,会影响实时性吗?
不会。全量扫描有成本,限制下限是为避免频繁全量扫描 FeatureDB。Stream 视图的秒级增量同步与定时全量刷新是两条独立机制。
Q3:缓存还没加载完或 FeatureDB 临时故障时,会不会把 item 全部过滤掉?
不会。缓存未就绪时状态过滤自动放行,后台自动重试直到恢复。
Q4:一次全量扫描返回空结果,会不会被当作"视图为空"而清空缓存?
会。成功返回的空扫描被视为视图真实为空,按有效结果处理:缓存会据此清理过期条目并标记为就绪。若上线时上游数据尚未产出,需确认视图已有数据,或为过滤配置 DefaultFieldValues。
FeatureCache:按需共享特征缓存
功能说明
FeatureCache 是由 FeatureCacheConfs 统一定义的按需缓存:缓存定义同时持有容量/TTL 与取数来源(数据源、视图或模型、读取字段、SDK 参数)。场景侧的特征配置只需要引用缓存名,并在请求时提供查找 key,不再各自重复配置数据源与缓存参数。
该能力主要解决两个问题:
同一份特征被多个场景反复加载,每个场景各自维护一份私有缓存,数据源与字段定义分散、容易不一致。
希望在一个地方统一控制某类特征的缓存容量与过期时间。
适用场景
多个场景(或多个
FeatureLoadConfs)读取同一份 user 或 item 特征。特征读取量大但 key 分散,逐次点查成本高,且能接受
CacheTime窗口内的陈旧。希望统一收敛某类特征的缓存策略(容量、TTL、读取字段)。
不适用于:需要整表本地化、需要严格读取最新值(应使用 FeatureViewCache 或直接读取 FeatureStore)。
配置缓存定义
{
"FeatureCacheConfs": {
"user_profile_cache": {
"FeatureStoreName": "pairec_fs",
"FeatureStore": "user",
"FeatureStoreViewName": "user_profile_view",
"SelectFields": "age,city,level",
"CacheSize": 100000,
"CacheTime": 600
},
"item_profile_cache": {
"FeatureStoreName": "pairec_fs",
"FeatureStore": "item",
"FeatureStoreModelName": "rank_v1",
"FeatureStoreEntityName": "item",
"CacheSize": 50000,
"CacheTime": 3600
}
}
}字段 | 类型 | 是否必填 | 说明 |
FeatureStoreName | string | 是 | 引用 |
FeatureStore | string | 是 | 枚举值 |
FeatureStoreViewName | string | 二选一 | 特征视图名,配置后按视图取数 |
FeatureStoreModelName | string | 二选一 | 模型名,与 |
FeatureStoreEntityName | string | 二选一 | 特征实体名,仅在配置模型时使用 |
SelectFields | string | 否 | 视图投影字段,逗号分隔,默认 |
FeatureStoreOptions.DlrmHSTU | bool | 否 | 是否启用 DlrmHSTU 取数。仅 user 视角支持 |
CacheSize | int | 是 | 缓存条目上限,必须大于 0;写满后按 LRU 淘汰 |
CacheTime | int | 否 | 写入后的过期时间,单位秒,默认 3600 |
配置校验规则(不满足时引擎启动/配置加载直接报错):
FeatureStoreViewName与FeatureStoreModelName/FeatureStoreEntityName互斥:视图模式不能同时配模型与实体;模型模式必须同时配模型和实体,且不能配SelectFields。模型模式下,
FeatureStoreEntityName必须是该模型实际使用的实体;user 视角的模型需要存在 label 表。视图/模型/实体在 FeatureStore 中不存在时,加载即报错。
场景引用缓存
场景侧在 FeatureDaoConf 中通过 FeatureCacheName 引用,并保留查找键与落点配置:
{
"FeatureConfs": {
"scene_name": {
"FeatureLoadConfs": [
{
"FeatureDaoConf": {
"FeatureCacheName": "user_profile_cache",
"FeatureKey": "user:uid",
"FeatureStore": "user"
}
}
]
}
}
}字段 | 类型 | 是否必填 | 说明 |
FeatureCacheName | string | 是 | 引用 |
FeatureKey | string | 是 | 查找键来源,如 |
FeatureStore | string | 是 | 特征写入的落点, |
引用时的约束(不满足时构造特征 DAO 阶段直接报错):
FeatureCacheName目前只支持featurestore适配器:场景侧必须保留"AdapterType": "featurestore"。写hologres、tablestore、redis等其他值,或省略该字段,都会在构建特征 DAO 阶段报错FeatureCacheName is only supported by the featurestore adapter。这个字段在该路径下不参与选路(选路由FeatureCacheName决定),仅作校验,但仍然必填。
如果特征存放在 Hologres 等其他数据源,不能使用共享缓存;需要缓存时只能用旧路径的 DAO 私有缓存(在FeatureDaoConf上配CacheSize/CacheTime)。不允许与
FeatureAppendKey同时使用。不允许在场景侧重复配置数据源、读取字段与 SDK 参数(
FeatureStoreName、FeatureStoreViewName、FeatureStoreModelName、FeatureStoreEntityName、UserSelectFields、ItemSelectFields、FeatureStoreOptions),这些统一由FeatureCacheConfs提供。其中FeatureStoreName会被忽略并打印告警。FeatureStore必须与缓存定义的视角一致,否则报错。LoadFromCacheFeaturesName在本路径下不生效(只有未识别适配器的空实现才会从 cacheFeatures 回读):两条特征配置要复用同一份数据,请让它们引用同一个FeatureCacheName,不要用 cacheFeatures 中转。user 视角下的 sequence feature 加载暂不支持共享缓存,请使用视图/模型路径。
在 UserFeatureConfs 中使用
UserFeatureConfs(User 特征预取)同样支持 FeatureCacheName,写法与 FeatureConfs 一致:
{
"UserFeatureConfs": {
"scene_name": {
"AsynLoadFeature": true,
"FeatureLoadConfs": [
{
"FeatureDaoConf": {
"FeatureCacheName": "user_profile_cache",
"FeatureKey": "user:uid",
"FeatureStore": "user"
}
}
]
}
}
}在该配置块中使用时额外注意:
UserFeatureConfs要求FeatureStore必须为user(否则配置加载报错),因此只能引用FeatureStore为user的缓存定义,item 视角的缓存无法在此复用。场景侧不要再保留
FeatureStoreName,共享路径不读取它并会打印ignoredConfig告警;保留FeatureStoreViewName、UserSelectFields等字段会直接报错。user 视角不回填空值:回源取不到数据时本次加载失败,不会把空结果写入缓存,与 item 视角不同。
热加载按场景整体签名比对(
FeatureLoadConfs全部字段参与),修改FeatureCacheName会触发该场景重建。
读取与缓存行为
命中:直接使用缓存值,不再访问 FeatureStore。item 视角下可在 debug 输出中看到命中标记。
未命中:按
FeatureKey回源取数,取到后写入缓存;同一请求内多个 item 会合并为一次批量查询。item 视角的空结果:批量查询未返回的 item,会以空结果写入缓存,并在
CacheTime内不再回源。视图数据补齐后需要等待 TTL 过期才能读到新值,CacheTime不宜设置过大。user 视角的空结果:回源未取到数据时本次加载失败,不会写入空值。
过期与淘汰:条目在
CacheTime后过期,命中不续期;条目数达到CacheSize后淘汰最久未使用的条目。数据副本:从缓存取出的属性只在顶层做隔离副本,嵌套的 map / slice 为只读共享,消费方不得原地修改嵌套值。
配置热加载
定义完全一致时沿用原实例,已有缓存数据保留。
定义中任意字段变化(含
CacheSize/CacheTime等缓存策略,以及数据源、视图/模型、读取字段、SDK 参数)都会重建缓存实例,已有缓存数据清空后重新预热。调大容量或调整 TTL 同样会带来一次冷启动,建议在低峰期操作。缓存定义非法时本次热加载整体失败并保留原配置,可按日志
reload config error定位。同名缓存不允许在 user / item 之间切换视角,需要新的缓存名。
某个缓存名从配置中删除后,已构建的特征 DAO 会按构造时的定义直接查询 FeatureStore(不再走缓存);新建的特征 DAO 引用已删除的缓存名时会报错。
热加载期间在线请求不阻塞;新配置构建失败时保留原有缓存实例。
最佳实践
缓存名按数据的业务含义命名(如
user_profile_cache),一个视图/模型对应一份缓存。SelectFields只取实际用到的字段;模型模式下字段由模型定义决定,无需配置。CacheSize按常驻内存评估:条目数 × 每条字段规模;CacheTime按业务对陈旧度的容忍度设置。场景侧只写
FeatureCacheName、FeatureKey、FeatureStore与落点相关配置,不要在场景里重复数据源与字段。需要秒级新鲜度的特征不要用本条路径,改用 FeatureViewCache 或直查 FeatureStore。
常见问题
Q1:启动报 feature cache not found, name:xxx?
引用方找不到该缓存。检查 FeatureCacheConfs 中是否存在该名字(大小写需一致),以及该缓存定义是否校验通过。
Q2:为什么场景里配了 FeatureStoreName 却不生效?
共享缓存的取数来源由缓存定义持有,场景侧的 FeatureStoreName 会被忽略并打印告警。请把数据源配到 FeatureCacheConfs。
Q3:item 特征查不到时,后续请求会一直查不到吗?
item 视角下未返回的 key 会以空结果缓存,并在 CacheTime 内不再回源。如果视图数据会延迟补齐,请把 CacheTime 调小,或确认上游写入是否正常。
Q4:多个场景能共用一份缓存吗?
可以。缓存按名全局定义,多个场景用相同的 FeatureCacheName 引用即可共用同一份数据;FeatureStore 视角必须与缓存定义一致。