概述
RealTimeU2IRecall 内置了 I2I(物品到物品)缓存功能,可以对 trigger item 的相似 item 列表进行本地内存缓存,在保留用户行为实时性的同时,减少 FeatureStore 的查询压力。
配置方式
在 RecallConfs 的 RealTimeUser2ItemDaoConf 中添加 I2ICacheSize 和 I2ICacheTime:
{
"RecallConfs": [
{
"Name": "realtime_u2i",
"RecallType": "RealTimeU2IRecall",
"RecallCount": 200,
"RealTimeUser2ItemDaoConf": {
"UserTriggerDaoConf": {
"FeatureStoreName": "fs_pairec",
"FeatureStoreViewName": "user_behavior_seq",
"ItemCount": 20,
"TriggerCount": 10,
"EventWeight": "click:1.0;buy:3.0",
"WeightExpression": "1.0 / (1.0 + (currentTime - eventTime) / 3600.0)"
},
"Item2ItemFeatureViewName": "item_i2i_table",
"I2ICacheSize": 100000,
"I2ICacheTime": 3600
}
}
]
}
配置字段说明
| 字段 | 类型 | 说明 |
|---|---|---|
I2ICacheSize |
int | I2I 缓存的最大条目数(LRU 本地缓存),设为 0 或不设则不启用 |
I2ICacheTime |
int | 缓存过期时间(秒),默认 3600 。保证i2i数据凌晨更新,则影响不大。 |
缓存工作原理
执行流程
用户请求
↓
1. 从 FeatureStore 读取用户行为序列
→ 得到 trigger item 列表(如 [itemA, itemB, itemC])
↓
2. 对每个 trigger item,查 I2I 缓存:
- cache.GetIfPresent("itemA") → 命中:直接用缓存的相似 item 列表
- cache.GetIfPresent("itemB") → 未命中:加入待查询列表
↓
3. 对未命中缓存的 trigger item,批量查 FeatureStore 的 I2I 表
↓
4. 查到的结果写入缓存:
cache.Put("itemB", "sim_item1:0.9,sim_item2:0.8,...")
↓
5. 如果某个 trigger item 在 I2I 表中无数据,写入空值(负缓存):
cache.Put("itemX", "")
核心逻辑说明
- Cache Key:trigger item 的 item_id
- Cache Value:该 item 的相似 item 列表,格式为
item_id1:score1,item_id2:score2,... - 负缓存:如果某个 trigger item 在 I2I 表中查不到数据,会写入空字符串,避免每次都去查 FeatureStore
- 缓存类型:本地内存 LRU 缓存(基于
cache.New),不依赖外部 Redis
与 BaseRecall 用户级缓存的对比
RealTimeU2IRecall 支持两层缓存,它们作用在不同层级:
| 维度 | BaseRecall Cache(用户级) | I2I Cache(物品级) |
|---|---|---|
| Cache Key | CachePrefix + user_id |
trigger_item_id |
| 缓存内容 | 该用户的最终召回结果 | 某个 item 的相似 item 列表 |
| 缓存位置 | 外部 Redis 或本地缓存 | 本地内存 LRU 缓存 |
| 适用层级 | 跳过整个召回流程 | 只跳过 I2I 表查询,trigger 仍实时获取 |
| 实时性影响 | 牺牲实时性(整体缓存) | 保留实时性(trigger 实时,只有 I2I 关系缓存) |
| 配置字段 | CacheAdapter / CacheConfig / CachePrefix / CacheTime |
I2ICacheSize / I2ICacheTime |
两层缓存配置示例
如果同时启用两层缓存:
{
"RecallConfs": [
{
"Name": "realtime_u2i",
"RecallType": "RealTimeU2IRecall",
"RecallCount": 200,
"CacheAdapter": "redis",
"CacheConfig": "{\"redisName\": \"your_redis_name\"}",
"CachePrefix": "rt_u2i_",
"CacheTime": 60,
"RealTimeUser2ItemDaoConf": {
"UserTriggerDaoConf": {
... },
"Item2ItemFeatureViewName": "item_i2i_table",
"I2ICacheSize": 100000,
"I2ICacheTime": 3600
}
}
]
}
执行顺序:
- 先查 用户级缓存(Redis),命中则直接返回
- 未命中 → 实时获取用户 trigger → 对每个 trigger 查 I2I 缓存(本地内存)
- I2I 缓存未命中的 → 查 FeatureStore → 写回 I2I 缓存
- 最终结果写回用户级缓存(Redis)
推荐配置
| 场景 | I2ICacheSize | I2ICacheTime | 说明 |
|---|---|---|---|
| I2I 数据每天更新 | 10000 ~ 50000 | 3600 ~ 7200 | 覆盖热门 trigger item |
| I2I 数据小时级更新 | 5000 ~ 20000 | 600 ~ 1800 | 平衡实时性和缓存效果 |
| item 池较小 | 覆盖全量 item | 1800 ~ 3600 | 几乎消除 I2I 查询 |
| 高 QPS + FeatureStore 压力大 | 尽量大 | 1800 | 有效保护下游 |
注意事项
- 内存占用:I2I 缓存是本地内存缓存,
I2ICacheSize设置过大会增加内存占用。每个缓存条目大约占用几百字节到几 KB(取决于相似 item 数量) - 多实例不共享:本地缓存在每个引擎实例独立维护,不同实例的缓存不共享,冷启动时各实例需要独立预热
- 负缓存:I2I 表中查不到数据的 item 也会被缓存(空字符串),避免重复查询。如果 I2I 数据后续补充了,需等缓存过期后才能生效
- 与用户级缓存的关系:建议只启用 I2I 缓存,不启用用户级缓存,这样可以保留用户行为的实时性