Commit c0b24bef6981b8e851b1391c8970d104355b1e79
1 parent
6773cdbe
fix redis data
Showing
5 changed files
with
419 additions
and
4 deletions
Show diff stats
| ... | ... | @@ -0,0 +1,286 @@ |
| 1 | +# Redis Key格式问题修复方案 | |
| 2 | + | |
| 3 | +## 🔍 问题诊断 | |
| 4 | + | |
| 5 | +### 发现的问题 | |
| 6 | + | |
| 7 | +经过排查,发现了以下三个关键问题: | |
| 8 | + | |
| 9 | +1. **Redis Key 格式错误** ❌ | |
| 10 | + - **实际存储**: `i2i:content_name:*`, `i2i:deepwalk:*`, `i2i:session_w2v:*` | |
| 11 | + - **文档定义**: `item:similar:content_name:*`, `item:similar:deepwalk:*`, `item:similar:session_w2v:*` | |
| 12 | + - **影响**: 查询 `KEYS item:similar:*` 返回空,因为key前缀不匹配 | |
| 13 | + | |
| 14 | +2. **C++ Swing 未被加载** ❌ | |
| 15 | + - `collaboration/output/swing_similar.txt` 存在(15,177条记录,6.9MB) | |
| 16 | + - 但加载脚本没有处理这个文件 | |
| 17 | + - 应该存储为: `item:similar:swing_cpp:*` | |
| 18 | + | |
| 19 | +3. **Python Swing 文件缺失** ⚠️ | |
| 20 | + - `i2i_swing_20251022.txt` 不存在 | |
| 21 | + - Python Swing算法可能执行失败 | |
| 22 | + | |
| 23 | +### 数据统计 | |
| 24 | + | |
| 25 | +``` | |
| 26 | +Redis总Key数: 229,710 | |
| 27 | + ├─ i2i:* (错误格式): 176,150 | |
| 28 | + ├─ item:similar:* (正确格式): 0 | |
| 29 | + └─ interest:* (正确格式): 53,560 ✓ | |
| 30 | + | |
| 31 | +C++ Swing输出: 15,177条记录 (未加载) | |
| 32 | +Interest数据: 53,560条 (已正确加载) ✓ | |
| 33 | +``` | |
| 34 | + | |
| 35 | +**结论**: `interest:*` 有数据是因为它使用了正确的key格式,但 `item:similar:*` 为空是因为数据被错误地存储到了 `i2i:*` 格式。 | |
| 36 | + | |
| 37 | +--- | |
| 38 | + | |
| 39 | +## 🔧 根本原因 | |
| 40 | + | |
| 41 | +### 代码问题定位 | |
| 42 | + | |
| 43 | +**文件**: `scripts/load_index_to_redis.py` | |
| 44 | + | |
| 45 | +**错误代码** (第100行): | |
| 46 | +```python | |
| 47 | +count = load_index_file( | |
| 48 | + file_path, | |
| 49 | + redis_client, | |
| 50 | + f"i2i:{i2i_type}", # ❌ 错误! | |
| 51 | + expire_seconds | |
| 52 | +) | |
| 53 | +``` | |
| 54 | + | |
| 55 | +**应该是**: | |
| 56 | +```python | |
| 57 | +count = load_index_file( | |
| 58 | + file_path, | |
| 59 | + redis_client, | |
| 60 | + f"item:similar:{i2i_type}", # ✓ 正确! | |
| 61 | + expire_seconds | |
| 62 | +) | |
| 63 | +``` | |
| 64 | + | |
| 65 | +**缺失功能**: 没有加载 C++ Swing 的函数 | |
| 66 | + | |
| 67 | +--- | |
| 68 | + | |
| 69 | +## ✅ 修复方案 | |
| 70 | + | |
| 71 | +### 已完成的修复 | |
| 72 | + | |
| 73 | +✅ **1. 修复了 `load_index_to_redis.py`** | |
| 74 | + - 修正了key前缀: `i2i:` → `item:similar:` | |
| 75 | + - 添加了 `load_cpp_swing_index()` 函数 | |
| 76 | + - 添加了 `i2i_item_behavior` 到加载列表 | |
| 77 | + | |
| 78 | +✅ **2. 创建了清理脚本 `fix_redis_keys.py`** | |
| 79 | + - 清理错误格式的 `i2i:*` key | |
| 80 | + - 提供统计信息和确认提示 | |
| 81 | + | |
| 82 | +### 执行步骤 | |
| 83 | + | |
| 84 | +#### 步骤1: 清理错误的Redis Key | |
| 85 | + | |
| 86 | +```bash | |
| 87 | +cd /home/tw/recommendation/offline_tasks | |
| 88 | + | |
| 89 | +# 运行清理脚本 | |
| 90 | +python3 scripts/fix_redis_keys.py | |
| 91 | +# 输入 'yes' 确认删除错误格式的key | |
| 92 | +``` | |
| 93 | + | |
| 94 | +#### 步骤2: 重新加载数据(使用output_1022的数据) | |
| 95 | + | |
| 96 | +```bash | |
| 97 | +# 方式1: 从output_1022目录加载指定日期的数据 | |
| 98 | +python3 scripts/load_index_to_redis.py \ | |
| 99 | + --date 20251022 \ | |
| 100 | + --redis-host localhost \ | |
| 101 | + --redis-port 6379 \ | |
| 102 | + --expire-days 7 | |
| 103 | + | |
| 104 | +# 注意:确保output目录指向output_1022,或者临时修改OUTPUT_DIR | |
| 105 | +``` | |
| 106 | + | |
| 107 | +**或者,更简单的方式**: | |
| 108 | + | |
| 109 | +```bash | |
| 110 | +# 创建软链接指向output_1022 | |
| 111 | +cd /home/tw/recommendation/offline_tasks | |
| 112 | +rm -rf output # 如果当前output是目录 | |
| 113 | +ln -s output_1022/output output | |
| 114 | + | |
| 115 | +# 然后正常加载 | |
| 116 | +python3 scripts/load_index_to_redis.py \ | |
| 117 | + --redis-host localhost \ | |
| 118 | + --redis-port 6379 \ | |
| 119 | + --date 20251022 | |
| 120 | +``` | |
| 121 | + | |
| 122 | +#### 步骤3: 验证修复结果 | |
| 123 | + | |
| 124 | +```bash | |
| 125 | +redis-cli | |
| 126 | + | |
| 127 | +# 检查key数量 | |
| 128 | +DBSIZE | |
| 129 | + | |
| 130 | +# 查看正确格式的key | |
| 131 | +KEYS item:similar:* | |
| 132 | + | |
| 133 | +# 查看C++ Swing的key(应该有15177个) | |
| 134 | +KEYS item:similar:swing_cpp:* | |
| 135 | + | |
| 136 | +# 查看其他i2i算法的key | |
| 137 | +KEYS item:similar:deepwalk:* | |
| 138 | +KEYS item:similar:content_name:* | |
| 139 | +KEYS item:similar:session_w2v:* | |
| 140 | +KEYS item:similar:item_behavior:* | |
| 141 | + | |
| 142 | +# 验证interest数据仍然存在 | |
| 143 | +KEYS interest:* | |
| 144 | + | |
| 145 | +# 测试查询具体的key | |
| 146 | +GET item:similar:swing_cpp:3600052 | |
| 147 | +GET interest:hot:platform:essabuy | |
| 148 | +``` | |
| 149 | + | |
| 150 | +**预期结果**: | |
| 151 | +``` | |
| 152 | +KEYS item:similar:* 返回数量: ~176,000+ (包括C++ Swing的15,177条) | |
| 153 | +KEYS item:similar:swing_cpp:* 返回数量: 15,177 | |
| 154 | +KEYS item:similar:deepwalk:* 返回数量: ~48,376 | |
| 155 | +KEYS item:similar:content_name:* 返回数量: ~127,720 | |
| 156 | +KEYS interest:* 返回数量: 53,560 (保持不变) | |
| 157 | +``` | |
| 158 | + | |
| 159 | +--- | |
| 160 | + | |
| 161 | +## 📊 修复后的数据结构 | |
| 162 | + | |
| 163 | +### 正确的Redis Key格式 | |
| 164 | + | |
| 165 | +``` | |
| 166 | +# i2i相似度索引 | |
| 167 | +item:similar:swing_cpp:{item_id} → C++ Swing结果 | |
| 168 | +item:similar:swing:{item_id} → Python Swing结果 | |
| 169 | +item:similar:session_w2v:{item_id} → Session W2V结果 | |
| 170 | +item:similar:deepwalk:{item_id} → DeepWalk结果 | |
| 171 | +item:similar:content_name:{item_id} → 内容相似度(名称) | |
| 172 | +item:similar:content_pic:{item_id} → 内容相似度(图片) | |
| 173 | +item:similar:item_behavior:{item_id} → Item行为相似度 | |
| 174 | + | |
| 175 | +# 兴趣点聚合索引 | |
| 176 | +interest:hot:{dimension_key} → 热门商品 | |
| 177 | +interest:cart:{dimension_key} → 加购商品 | |
| 178 | +interest:new:{dimension_key} → 新品 | |
| 179 | +interest:global:{dimension_key} → 全局热门 | |
| 180 | +``` | |
| 181 | + | |
| 182 | +### 查询示例 | |
| 183 | + | |
| 184 | +```python | |
| 185 | +import redis | |
| 186 | +import json | |
| 187 | + | |
| 188 | +r = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True) | |
| 189 | + | |
| 190 | +# 查询C++ Swing相似商品 | |
| 191 | +similar_items = json.loads(r.get('item:similar:swing_cpp:3600052')) | |
| 192 | +# 返回: [[2704531, 0.00431593], [2503886, 0.00431593], ...] | |
| 193 | + | |
| 194 | +# 查询平台热门商品 | |
| 195 | +hot_items = r.get('interest:hot:platform:essabuy') | |
| 196 | +# 返回: "3090653:70.0000,3509349:70.0000,..." | |
| 197 | +``` | |
| 198 | + | |
| 199 | +--- | |
| 200 | + | |
| 201 | +## 🚨 Python Swing 问题 | |
| 202 | + | |
| 203 | +### 问题描述 | |
| 204 | +`i2i_swing_20251022.txt` 文件不存在,说明Python Swing算法没有成功执行。 | |
| 205 | + | |
| 206 | +### 可能原因 | |
| 207 | +1. 算法执行失败(内存不足、超时等) | |
| 208 | +2. Session数据问题 | |
| 209 | +3. 参数配置问题 | |
| 210 | + | |
| 211 | +### 排查步骤 | |
| 212 | + | |
| 213 | +```bash | |
| 214 | +# 查看Python Swing的日志 | |
| 215 | +cat /home/tw/recommendation/offline_tasks/output_1022/logs/debug/i2i_swing*.log | |
| 216 | + | |
| 217 | +# 检查session文件 | |
| 218 | +ls -lh /home/tw/recommendation/offline_tasks/output_1022/output/session.txt.* | |
| 219 | + | |
| 220 | +# 手动运行Python Swing测试 | |
| 221 | +cd /home/tw/recommendation/offline_tasks | |
| 222 | +python3 scripts/i2i_swing.py --lookback_days 30 --top_n 10 --debug | |
| 223 | +``` | |
| 224 | + | |
| 225 | +### 临时解决方案 | |
| 226 | +由于C++ Swing已经生成且性能更好,可以暂时只使用C++ Swing的结果: | |
| 227 | +- C++ Swing: `item:similar:swing_cpp:*` (15,177条记录) | |
| 228 | +- 文档推荐在生产环境使用C++ Swing | |
| 229 | + | |
| 230 | +--- | |
| 231 | + | |
| 232 | +## 📝 后续改进建议 | |
| 233 | + | |
| 234 | +### 1. 代码改进 | |
| 235 | +- ✅ 已修复key格式问题 | |
| 236 | +- ✅ 已添加C++ Swing加载功能 | |
| 237 | +- 建议: 添加单元测试验证key格式 | |
| 238 | + | |
| 239 | +### 2. 文档更新 | |
| 240 | +- 更新 `Redis数据规范.md` 中的加载示例代码 | |
| 241 | +- 添加故障排查指南 | |
| 242 | +- 标注C++ Swing为生产推荐 | |
| 243 | + | |
| 244 | +### 3. 监控告警 | |
| 245 | +- 添加Redis key格式验证 | |
| 246 | +- 监控各类索引的数量 | |
| 247 | +- Python Swing执行失败时告警 | |
| 248 | + | |
| 249 | +### 4. 自动化测试 | |
| 250 | +```python | |
| 251 | +# 建议添加测试用例 | |
| 252 | +def test_redis_key_format(): | |
| 253 | + """测试Redis key格式是否正确""" | |
| 254 | + keys = redis_client.keys('item:similar:*') | |
| 255 | + assert len(keys) > 0, "item:similar:* should not be empty" | |
| 256 | + | |
| 257 | + # 确保没有错误格式的key | |
| 258 | + wrong_keys = redis_client.keys('i2i:*') | |
| 259 | + assert len(wrong_keys) == 0, f"Found wrong format keys: {wrong_keys[:5]}" | |
| 260 | +``` | |
| 261 | + | |
| 262 | +--- | |
| 263 | + | |
| 264 | +## 🎯 总结 | |
| 265 | + | |
| 266 | +### 问题根源 | |
| 267 | +加载脚本使用了错误的Redis key前缀 `i2i:*` 而不是文档定义的 `item:similar:*` | |
| 268 | + | |
| 269 | +### 解决方案 | |
| 270 | +1. ✅ 修复了 `load_index_to_redis.py` 的key前缀 | |
| 271 | +2. ✅ 添加了C++ Swing加载功能 | |
| 272 | +3. ✅ 创建了清理脚本清理错误的key | |
| 273 | +4. ⏳ 需要重新加载数据 | |
| 274 | + | |
| 275 | +### 后续行动 | |
| 276 | +1. 执行 `fix_redis_keys.py` 清理错误的key | |
| 277 | +2. 重新运行 `load_index_to_redis.py` 加载数据 | |
| 278 | +3. 验证所有 `item:similar:*` key都能正常查询 | |
| 279 | +4. 排查Python Swing执行失败的原因 | |
| 280 | + | |
| 281 | +--- | |
| 282 | + | |
| 283 | +**创建时间**: 2025-10-22 | |
| 284 | +**版本**: v1.0 | |
| 285 | +**状态**: ✅ 修复方案已就绪,等待执行 | |
| 286 | + | ... | ... |
offline_tasks/doc/详细设计文档.md
offline_tasks/run.sh
| ... | ... | @@ -10,6 +10,15 @@ cd /home/tw/recommendation/offline_tasks |
| 10 | 10 | # mkdir output |
| 11 | 11 | # mkdir logs |
| 12 | 12 | |
| 13 | +# 检测并激活conda环境 | |
| 14 | +if [ "$CONDA_DEFAULT_ENV" != "tw" ]; then | |
| 15 | + echo "当前环境不是 tw,正在激活 tw 环境..." | |
| 16 | + source /home/tw/miniconda3/etc/profile.d/conda.sh | |
| 17 | + conda activate tw | |
| 18 | + echo "已激活 tw 环境" | |
| 19 | +else | |
| 20 | + echo "当前已经在 tw 环境中,无需重复激活" | |
| 21 | +fi | |
| 13 | 22 | |
| 14 | 23 | # ============================================================================ |
| 15 | 24 | # 配置区域 | ... | ... |
| ... | ... | @@ -0,0 +1,84 @@ |
| 1 | +#!/usr/bin/env python3 | |
| 2 | +""" | |
| 3 | +修复Redis中的key格式问题 | |
| 4 | +将错误的 i2i:* 格式迁移到正确的 item:similar:* 格式 | |
| 5 | +""" | |
| 6 | +import redis | |
| 7 | +import sys | |
| 8 | + | |
| 9 | +def fix_redis_keys(): | |
| 10 | + """修复Redis key格式""" | |
| 11 | + | |
| 12 | + # 连接Redis | |
| 13 | + redis_client = redis.Redis( | |
| 14 | + host='localhost', | |
| 15 | + port=6379, | |
| 16 | + db=0, | |
| 17 | + decode_responses=True | |
| 18 | + ) | |
| 19 | + | |
| 20 | + try: | |
| 21 | + redis_client.ping() | |
| 22 | + print("✓ Redis连接成功") | |
| 23 | + except Exception as e: | |
| 24 | + print(f"✗ Redis连接失败: {e}") | |
| 25 | + return 1 | |
| 26 | + | |
| 27 | + # 统计信息 | |
| 28 | + print("\n" + "="*80) | |
| 29 | + print("当前Redis数据统计:") | |
| 30 | + print("="*80) | |
| 31 | + | |
| 32 | + total_keys = redis_client.dbsize() | |
| 33 | + print(f"总Key数量: {total_keys}") | |
| 34 | + | |
| 35 | + # 统计各类型key | |
| 36 | + i2i_keys = redis_client.keys('i2i:*') | |
| 37 | + item_similar_keys = redis_client.keys('item:similar:*') | |
| 38 | + interest_keys = redis_client.keys('interest:*') | |
| 39 | + | |
| 40 | + print(f"错误格式 i2i:* 数量: {len(i2i_keys)}") | |
| 41 | + print(f"正确格式 item:similar:* 数量: {len(item_similar_keys)}") | |
| 42 | + print(f"interest:* 数量: {len(interest_keys)}") | |
| 43 | + | |
| 44 | + # 询问是否删除错误的key | |
| 45 | + print("\n" + "="*80) | |
| 46 | + print("⚠️ 警告:即将删除所有 i2i:* 格式的key(错误格式)") | |
| 47 | + print("="*80) | |
| 48 | + | |
| 49 | + if i2i_keys: | |
| 50 | + print(f"\n将删除 {len(i2i_keys)} 个错误格式的key") | |
| 51 | + print("示例:") | |
| 52 | + for key in i2i_keys[:5]: | |
| 53 | + print(f" - {key}") | |
| 54 | + | |
| 55 | + response = input("\n确认删除?(yes/no): ") | |
| 56 | + if response.lower() == 'yes': | |
| 57 | + # 使用pipeline批量删除 | |
| 58 | + pipe = redis_client.pipeline() | |
| 59 | + for key in i2i_keys: | |
| 60 | + pipe.delete(key) | |
| 61 | + pipe.execute() | |
| 62 | + print(f"✓ 已删除 {len(i2i_keys)} 个错误格式的key") | |
| 63 | + else: | |
| 64 | + print("取消删除操作") | |
| 65 | + else: | |
| 66 | + print("✓ 没有需要删除的错误格式key") | |
| 67 | + | |
| 68 | + # 最终统计 | |
| 69 | + print("\n" + "="*80) | |
| 70 | + print("清理后统计:") | |
| 71 | + print("="*80) | |
| 72 | + total_keys_after = redis_client.dbsize() | |
| 73 | + print(f"总Key数量: {total_keys_after}") | |
| 74 | + print(f"item:similar:* 数量: {len(redis_client.keys('item:similar:*'))}") | |
| 75 | + print(f"interest:* 数量: {len(redis_client.keys('interest:*'))}") | |
| 76 | + | |
| 77 | + print("\n✓ 修复完成!现在可以使用修复后的加载脚本重新加载数据。") | |
| 78 | + | |
| 79 | + return 0 | |
| 80 | + | |
| 81 | + | |
| 82 | +if __name__ == '__main__': | |
| 83 | + sys.exit(fix_redis_keys()) | |
| 84 | + | ... | ... |
offline_tasks/scripts/load_index_to_redis.py
| ... | ... | @@ -69,6 +69,37 @@ def load_index_file(file_path, redis_client, key_prefix, expire_seconds=None): |
| 69 | 69 | return count |
| 70 | 70 | |
| 71 | 71 | |
| 72 | +def load_cpp_swing_index(redis_client, expire_days=7): | |
| 73 | + """ | |
| 74 | + 加载C++ Swing相似度索引 | |
| 75 | + | |
| 76 | + Args: | |
| 77 | + redis_client: Redis客户端 | |
| 78 | + expire_days: 过期天数 | |
| 79 | + | |
| 80 | + Returns: | |
| 81 | + 加载的记录数 | |
| 82 | + """ | |
| 83 | + # C++ Swing输出文件 | |
| 84 | + file_path = os.path.join(os.path.dirname(OUTPUT_DIR), 'collaboration', 'output', 'swing_similar.txt') | |
| 85 | + | |
| 86 | + if not os.path.exists(file_path): | |
| 87 | + logger.warning(f"C++ Swing file not found: {file_path}, skipping...") | |
| 88 | + return 0 | |
| 89 | + | |
| 90 | + expire_seconds = expire_days * 24 * 3600 if expire_days else None | |
| 91 | + | |
| 92 | + logger.info(f"Loading C++ Swing indices from {file_path}...") | |
| 93 | + count = load_index_file( | |
| 94 | + file_path, | |
| 95 | + redis_client, | |
| 96 | + "item:similar:swing_cpp", | |
| 97 | + expire_seconds | |
| 98 | + ) | |
| 99 | + logger.info(f"Loaded {count} C++ Swing indices") | |
| 100 | + return count | |
| 101 | + | |
| 102 | + | |
| 72 | 103 | def load_i2i_indices(redis_client, date_str=None, expire_days=7): |
| 73 | 104 | """ |
| 74 | 105 | 加载i2i相似度索引 |
| ... | ... | @@ -84,7 +115,7 @@ def load_i2i_indices(redis_client, date_str=None, expire_days=7): |
| 84 | 115 | expire_seconds = expire_days * 24 * 3600 if expire_days else None |
| 85 | 116 | |
| 86 | 117 | # i2i索引类型 |
| 87 | - i2i_types = ['swing', 'session_w2v', 'deepwalk', 'content_name', 'content_pic'] | |
| 118 | + i2i_types = ['swing', 'session_w2v', 'deepwalk', 'content_name', 'content_pic', 'item_behavior'] | |
| 88 | 119 | |
| 89 | 120 | for i2i_type in i2i_types: |
| 90 | 121 | file_path = os.path.join(OUTPUT_DIR, f'i2i_{i2i_type}_{date_str}.txt') |
| ... | ... | @@ -97,7 +128,7 @@ def load_i2i_indices(redis_client, date_str=None, expire_days=7): |
| 97 | 128 | count = load_index_file( |
| 98 | 129 | file_path, |
| 99 | 130 | redis_client, |
| 100 | - f"i2i:{i2i_type}", | |
| 131 | + f"item:similar:{i2i_type}", # 修复: 使用正确的key前缀 | |
| 101 | 132 | expire_seconds |
| 102 | 133 | ) |
| 103 | 134 | logger.info(f"Loaded {count} {i2i_type} indices") |
| ... | ... | @@ -184,6 +215,13 @@ def main(): |
| 184 | 215 | redis_client.flushdb() |
| 185 | 216 | logger.info("Database flushed") |
| 186 | 217 | |
| 218 | + # 加载C++ Swing索引 | |
| 219 | + if args.load_i2i: | |
| 220 | + logger.info("\n" + "="*80) | |
| 221 | + logger.info("Loading C++ Swing indices") | |
| 222 | + logger.info("="*80) | |
| 223 | + load_cpp_swing_index(redis_client, args.expire_days) | |
| 224 | + | |
| 187 | 225 | # 加载i2i索引 |
| 188 | 226 | if args.load_i2i: |
| 189 | 227 | logger.info("\n" + "="*80) | ... | ... |