Blame view

docs/缓存与Redis使用说明.md 14.7 KB
8b74784e   tangwang   cache manage
1
2
3
4
5
6
  ## 缓存与 Redis 使用说明
  
  本仓库中 Redis 主要用于**性能型缓存**,当前落地的业务缓存包括:
  
  - **文本向量缓存**(embedding 缓存)
  - **翻译结果缓存**(Qwen-MT 等机器翻译)
8b74784e   tangwang   cache manage
7
8
9
10
11
12
13
  
  底层连接配置统一来自 `config/env_config.py` 的 `REDIS_CONFIG`
  
  - **Host/Port**`REDIS_HOST` / `REDIS_PORT`(默认 `localhost:6479`
  - **Password**`REDIS_PASSWORD`
  - **Socket & 超时**`REDIS_SOCKET_TIMEOUT` / `REDIS_SOCKET_CONNECT_TIMEOUT` / `REDIS_RETRY_ON_TIMEOUT`
  - **通用缓存 TTL**`REDIS_CACHE_EXPIRE_DAYS`(默认 `360*2` 天,代码注释为 “6 months”)
8b74784e   tangwang   cache manage
14
15
16
17
18
19
20
  
  ---
  
  ## 1. 缓存总览表
  
  | 模块 / 场景 | Key 模板 | Value 内容示例 | 过期策略 | 备注 |
  |------------|----------|----------------|----------|------|
5bac9649   tangwang   文本 embedding 与图片 ...
21
  | 向量缓存(text/image embedding) | 文本:`{EMBEDDING_CACHE_PREFIX}:embed:norm{0|1}:{text}`;图片:`{EMBEDDING_CACHE_PREFIX}:image:embed:norm{0|1}:{url_or_path}` | **BF16 bytes**(每维 2 字节大端存储),读取后恢复为 `np.float32` | TTL=`REDIS_CONFIG["cache_expire_days"]` 天;访问时滑动过期 | 见 `embeddings/text_encoder.py`、`embeddings/image_encoder.py`、`embeddings/server.py`;前缀由 `REDIS_CONFIG["embedding_cache_prefix"]` 控制 |
cd4ce66d   tangwang   trans logs
22
  | 翻译结果缓存(translator service) | `trans:{model}:{target_lang}:{source_text[:4]}{sha256(source_text)}` | 机翻后的单条字符串 | TTL=`services.translation.cache.ttl_seconds` 秒;可配置滑动过期 | 见 `translation/service.py` + `config/config.yaml` |
8b74784e   tangwang   cache manage
23
24
25
26
  下面按模块详细说明。
  
  ---
  
5bac9649   tangwang   文本 embedding 与图片 ...
27
  ## 2. 向量缓存(embeddings/text_encoder.py / embeddings/image_encoder.py / embeddings/server.py)
8b74784e   tangwang   cache manage
28
  
5bac9649   tangwang   文本 embedding 与图片 ...
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
  - **代码位置**
    - 调用侧:`embeddings/text_encoder.py`、`embeddings/image_encoder.py`
    - 服务侧:`embeddings/server.py`
    - 公共 Redis/BF16 编解码:`embeddings/redis_embedding_cache.py`、`embeddings/bf16.py`
  - **用途**:缓存文本/图片向量结果,避免重复推理、重复下载图片、重复占用模型并发槽位。
  
  ### 2.0 当前缓存链路总览
  
  - 现在是**双层缓存**
    - **调用侧缓存**`TextEmbeddingEncoder` / `CLIPImageEncoder` 在发 HTTP 请求前先查 Redis;
    - **服务侧缓存**`/embed/text` / `/embed/image` 在进入后端推理前再查 Redis。
  - 这次改动里,**BF16 存储格式本身没有变化**
    - 写入:`float32 -> BF16 -> bytes -> Redis`
    - 读取:`Redis bytes -> BF16 -> float32(np.float32)`
  - 这次新增的核心变化是:
    - **缓存 key 加入 normalize 维度**,避免 `normalize=true` 与 `normalize=false` 命中同一条缓存;
    - **服务端也启用同一套 Redis 向量缓存**,而不是只有 client 侧缓存;
    - **拆分 text / image 服务** 后,图片压力不会再拖慢文本服务;
    - **服务侧 full-cache-hit 可直接返回**,不会再进入模型限流槽位。
8b74784e   tangwang   cache manage
48
49
50
  
  ### 2.1 Key 设计
  
5bac9649   tangwang   文本 embedding 与图片 ...
51
  - 统一 helper:`embeddings/cache_keys.py`
5a01af3c   tangwang   多模态hashkey调整:1. 加...
52
53
54
  - 文本主 key(TEI/BGE):`build_text_cache_key(text, normalize=...)`
  - 多模态图片(CN-CLIP `/embed/image`):`build_image_cache_key(url, normalize=..., model_name=...)`,其中 `model_name` 来自 `services.embedding.image_backends.*.model_name`(与 `embeddings.config.CONFIG.MULTIMODAL_MODEL_NAME` 一致)
  - 多模态文本(CN-CLIP `/embed/clip_text`):`build_clip_text_cache_key(text, normalize=..., model_name=...)`
8b74784e   tangwang   cache manage
55
56
57
  - 模板:
  
  ```text
5a01af3c   tangwang   多模态hashkey调整:1. 加...
58
59
60
  TEI 文本: {EMBEDDING_CACHE_PREFIX}:embed:norm{0|1}:{text}
  CN-CLIP 图片: {EMBEDDING_CACHE_PREFIX}:image:embed:{model_name}:txt:norm{0|1}:{url_or_path}
  CN-CLIP 文本塔: {EMBEDDING_CACHE_PREFIX}:clip_text:embed:{model_name}:img:norm{0|1}:{text}
8b74784e   tangwang   cache manage
61
62
63
  ```
  
  - 字段说明:
3d588bef   tangwang   embeddings
64
    - `EMBEDDING_CACHE_PREFIX`:来自 `REDIS_CONFIG["embedding_cache_prefix"]`,默认值为 `"embedding"`,可通过环境变量 `REDIS_EMBEDDING_CACHE_PREFIX` 覆盖;
5a01af3c   tangwang   多模态hashkey调整:1. 加...
65
    - `model_name`:如 `CN-CLIP/ViT-H-14`;切换模型时自动使用新 key 空间,避免混用旧维度向量;
5bac9649   tangwang   文本 embedding 与图片 ...
66
    - `norm1` / `norm0`:分别表示 `normalize=true` / `normalize=false`
5a01af3c   tangwang   多模态hashkey调整:1. 加...
67
    - `text` / `url_or_path`:经 `strip` 后,若 Unicode 长度 **≤** ``CACHE_KEY_RAW_BODY_MAX_CHARS``(默认 256,见 ``embeddings/cache_keys.py``)则原样写入键尾;**更长**则改为 ``h:sha256:<64 hex>``(对 UTF-8 字节做 SHA-256)。TEI 文本与多模态共用 ``_stable_body_for_cache_key``
5bac9649   tangwang   文本 embedding 与图片 ...
68
69
70
  
  补充说明:
  
5a01af3c   tangwang   多模态hashkey调整:1. 加...
71
72
  - TEI 文本 raw key 仍为 `embed:norm{0|1}:...`(尾部负载规则同上)。
  - 多模态键名中 `txt` / `img` 段为项目内约定(与 `embeddings/cache_keys.py` 一致),用于区分图片 lane 与 clip 文本 lane。
8b74784e   tangwang   cache manage
73
74
75
  
  ### 2.2 Value 与类型
  
4a37d233   tangwang   1. embedding cach...
76
77
78
79
  - 类型:**BF16 bytes**(bfloat16),每一维用 2 字节无符号整数表示,按**大端**序列化。
  - 写入流程:FP32 向量 → BF16 → bytes → Redis
  - 读取流程:Redis bytes → BF16 → FP32(`np.float32`)向量
  - 典型示例:BGE-M3 1024 维向量在 Redis value 大小约为 \(1024*2=2048\) bytes(不含 Redis 元数据开销)。
8b74784e   tangwang   cache manage
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
  
  ### 2.3 过期策略
  
  - 初始化:
    - `self.expire_time = timedelta(days=REDIS_CONFIG.get("cache_expire_days", 180))`
    - `.env` 中可通过 `REDIS_CACHE_EXPIRE_DAYS` 配置,默认 `360*2`(代码注释标注为 6 个月)。
  - 写入:
    - `redis.setex(cache_key, self.expire_time, serialized_data)`
  - 访问(滑动过期):
    - 命中缓存后,会调用 `redis.expire(cache_key, self.expire_time)` 延长 TTL。
  
  ### 2.4 特殊处理
  
  - 若缓存中的向量 **为空 / shape 异常 / 含 NaN/Inf**,会:
    - 直接丢弃该缓存(并尝试 `delete` key);
    - 回退为重新调用向量服务。
  
5bac9649   tangwang   文本 embedding 与图片 ...
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
  ### 2.5 服务拆分与缓存/限流关系
  
  - 当前默认部署形态:
    - 文本 embedding 服务:`6005`
    - 图片 embedding 服务:`6008`
  - 调用方配置:
    - `EMBEDDING_TEXT_SERVICE_URL`
    - `EMBEDDING_IMAGE_SERVICE_URL`
  - 服务端配置:
    - `TEXT_MAX_INFLIGHT`
    - `IMAGE_MAX_INFLIGHT`
    - `EMBEDDING_SERVICE_KIND=all|text|image`
  - 关键行为:
    - **full-cache-hit** 请求在服务端会直接返回,不占用 `TEXT_MAX_INFLIGHT` / `IMAGE_MAX_INFLIGHT`
    - **cache miss** 才会进入对应模型 lane;
    - 因此高频重复图片请求不会再因为严格的图片限流而大量阻塞。
  
8b74784e   tangwang   cache manage
114
115
  ---
  
cd4ce66d   tangwang   trans logs
116
  ## 3. 翻译结果缓存(translation/service.py)
8b74784e   tangwang   cache manage
117
  
cd4ce66d   tangwang   trans logs
118
119
120
121
122
  - **代码位置**`translation/service.py`
  - **用途**:统一缓存所有 translation capability 的翻译结果。
  - **配置入口**
    - `config/config.yaml -> services.translation.cache`
    - `config/config.yaml -> services.translation.capabilities.*.use_cache`
8b74784e   tangwang   cache manage
123
124
125
  
  ### 3.1 Key 设计
  
cd4ce66d   tangwang   trans logs
126
  - 内部构造函数:`TranslationCache.build_key(...)`
8b74784e   tangwang   cache manage
127
128
129
  - 模板:
  
  ```text
cd4ce66d   tangwang   trans logs
130
  trans:{model}:{target_lang}:{source_text[:4]}{sha256(source_text)}
8b74784e   tangwang   cache manage
131
132
133
134
  ```
  
  其中:
  
cd4ce66d   tangwang   trans logs
135
136
137
138
  - `model`:capability 名称,如 `qwen-mt`、`llm`、`opus-mt-zh-en`
  - `target_lang`:目标语言,如 `en` / `zh`
  - `source_text[:4]`:原文前 4 个字符
  - `sha256(source_text)`:对完整原文做 SHA-256
8b74784e   tangwang   cache manage
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
  
  ### 3.2 Value 与类型
  
  - 类型:**UTF-8 字符串**,即翻译后的文本结果。
  - 存取逻辑:
    - 读取:`redis.get(key)` 返回 `str` 或 `None`
    - 写入:`redis.setex(key, expire_seconds, translation)`
  
  ### 3.3 过期策略
  
  - 配置来源:`config/config.yaml -> services.translation.cache`,经 `get_translation_cache_config()` 解析:
  
  ```yaml
  services:
    translation:
      cache:
8b74784e   tangwang   cache manage
155
156
        ttl_seconds: 62208000        # 默认约 720 天
        sliding_expiration: true
cd4ce66d   tangwang   trans logs
157
158
159
160
161
162
163
164
165
166
167
168
169
      capabilities:
        qwen-mt:
          use_cache: true
        llm:
          use_cache: true
        deepl:
          use_cache: true
        nllb-200-distilled-600m:
          use_cache: true
        opus-mt-zh-en:
          use_cache: true
        opus-mt-en-zh:
          use_cache: true
8b74784e   tangwang   cache manage
170
171
172
  ```
  
  - 运行时行为:
cd4ce66d   tangwang   trans logs
173
    - translator service 启动时初始化共享 Redis cache;
8b74784e   tangwang   cache manage
174
175
176
177
178
179
180
    - **读缓存**
      - 命中后,若 `sliding_expiration=True`,会调用 `redis.expire(key, expire_seconds)`
    - **写缓存**
      - 使用 `redis.setex(key, expire_seconds, translation)`
  
  ### 3.4 关联模块
  
cd4ce66d   tangwang   trans logs
181
182
  - `api/translator_app.py` 通过 `TranslationService` 统一复用同一套缓存逻辑;
  - 所有翻译后端都通过 `TranslationService` 接入缓存。
8b74784e   tangwang   cache manage
183
184
185
  
  ---
  
a3275468   tangwang   已把本仓库里的 `/indexer...
186
  ## 4. 商品内容理解缓存(已迁出)
8b74784e   tangwang   cache manage
187
  
a3275468   tangwang   已把本仓库里的 `/indexer...
188
  本仓库原先存在一套用于 `qanchors` / `enriched_*` 生成的 Redis 缓存实现,但对应内容理解服务已经迁移到独立项目,当前仓库代码中不再读写这类缓存,也不再把它作为运行时能力的一部分维护。
8b74784e   tangwang   cache manage
189
190
191
192
193
194
195
196
197
  
  ---
  
  ## 5. Redis 运维脚本工具
  
  `scripts/redis/` 下提供三个脚本,用于查看缓存数量、内存占用与健康状态。连接配置均来自 `config/env_config.py` 的 `REDIS_CONFIG`,运行前需在项目根目录执行(或保证 `PYTHONPATH` 含项目根),以便加载配置。
  
  ### 5.1 redis_cache_health_check.py(缓存健康巡检)
  
a3275468   tangwang   已把本仓库里的 `/indexer...
198
  **功能**:按**业务缓存类型**(embedding / translation)做健康巡检,不扫全库。
8b74784e   tangwang   cache manage
199
200
201
202
  
  - 对每类缓存:SCAN 匹配对应 key 前缀,统计**匹配 key 数量**(受 `--max-scan` 上限约束);
  - **TTL 分布**:对采样 key 统计 `no-expire-or-expired` / `0-1h` / `1h-1d` / `1d-30d` / `>30d`
  - **近期活跃 key**:从采样中选出 `OBJECT IDLETIME <= 600s` 的 key,用于判断是否有新写入;
a3275468   tangwang   已把本仓库里的 `/indexer...
203
  - **样本 key 与 value 预览**:对 embedding 显示 ndarray 信息,对 translation 显示译文片段。
8b74784e   tangwang   cache manage
204
  
a3275468   tangwang   已把本仓库里的 `/indexer...
205
  **适用场景**:日常查看两类缓存是否在增长、TTL 是否合理、是否有近期写入;与「缓存总览表」中的 key 设计一一对应。
8b74784e   tangwang   cache manage
206
207
208
209
  
  **用法示例**
  
  ```bash
a3275468   tangwang   已把本仓库里的 `/indexer...
210
  # 默认:检查 embedding / translation 两类
8b74784e   tangwang   cache manage
211
212
  python scripts/redis/redis_cache_health_check.py
  
a3275468   tangwang   已把本仓库里的 `/indexer...
213
  # 只检查某一类
8b74784e   tangwang   cache manage
214
  python scripts/redis/redis_cache_health_check.py --type embedding
a3275468   tangwang   已把本仓库里的 `/indexer...
215
  python scripts/redis/redis_cache_health_check.py --type translation
8b74784e   tangwang   cache manage
216
217
218
219
220
221
222
223
224
225
226
227
  
  # 按自定义 pattern 检查(不按业务类型)
  python scripts/redis/redis_cache_health_check.py --pattern "mycache:*"
  
  # 调整采样与扫描规模
  python scripts/redis/redis_cache_health_check.py --sample-size 100 --max-scan 50000 --db 0
  ```
  
  **常用参数**
  
  | 参数 | 说明 | 默认 |
  |------|------|------|
a3275468   tangwang   已把本仓库里的 `/indexer...
228
  | `--type` | 缓存类型:`embedding` / `translation`,可多选 | 两类都检查 |
8b74784e   tangwang   cache manage
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
  | `--pattern` | 自定义 key pattern(如 `mycache:*`),指定后忽略 `--type` | - |
  | `--db` | Redis 数据库编号 | 0 |
  | `--sample-size` | 每类采样的 key 数量 | 50 |
  | `--max-scan` | 每类最多 SCAN 的 key 数量上限 | 20000 |
  
  ---
  
  ### 5.2 redis_cache_prefix_stats.py(按前缀统计条数与内存)
  
  **功能****全局视角**,扫描当前 DB 下所有 key,按 key 的**前缀**(第一个冒号前)分类,统计每类 key 的**条数****内存占用量**(含占比),并输出每类示例 key 与 Redis 总内存信息。
  
  - 使用 `SCAN` 扫全库,按前缀聚合;
  - 内存优先用 Redis `MEMORY USAGE`,不可用时用 key+value 长度估算;key 过多时按 `--sample-size` 采样后按均值推算总内存;
  - 输出:前缀、条数、内存及计算方式、占比、简要说明(如「翻译缓存」「向量化缓存」);末尾附 Redis 总内存与 maxmemory(若配置)。
  
  **适用场景**:了解「哪一类前缀占了多少条、多少内存」,做容量规划或清理决策;支持多 DB(`--all-db`)或指定前缀(`--prefix`)缩小范围。
  
  **用法示例**
  
  ```bash
  # 默认 DB 0,全库按前缀统计
  python scripts/redis/redis_cache_prefix_stats.py
  
  # 统计所有有数据的 DB
  python scripts/redis/redis_cache_prefix_stats.py --all-db
  
  # 指定 DB
  python scripts/redis/redis_cache_prefix_stats.py --db 1
  
  # 只统计指定前缀(可多个)
a3275468   tangwang   已把本仓库里的 `/indexer...
259
  python scripts/redis/redis_cache_prefix_stats.py --prefix trans embedding
8b74784e   tangwang   cache manage
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
  
  # 全 DB + 指定前缀
  python scripts/redis/redis_cache_prefix_stats.py --all-db --prefix trans embedding
  ```
  
  **常用参数**
  
  | 参数 | 说明 | 默认 |
  |------|------|------|
  | `--prefix` | 只统计这些前缀下的 key(如 `trans` `embedding`) | 全库按前缀统计 |
  | `--db` | 数据库编号(0–15) | 0 |
  | `--all-db` | 对所有有数据的 DB 分别执行 | 否 |
  | `--sample-size` | 单前缀 key 过多时,用于内存采样的数量 | 100 |
  | `--real` | 对单前缀 key 数 ≤10000 时计算全部 key 真实内存(较慢) | 否 |
  
  ---
  
  ### 5.3 redis_memory_heavy_keys.py(大 key / 内存占用排查)
  
  **功能**:找出当前 DB 中**占用内存最多的 key**,并分析「按 key 估算的总内存」与「Redis 实际使用内存」的差异原因。
  
  - 全库 `SCAN` 获取 key 列表,对 key 采样(默认最多 1000)调用 `MEMORY USAGE`(或估算)得到单 key 内存;
  - 按内存排序,输出**占用最高的 N 个 key**`--top`,默认 50);
  - 输出:前缀分布、采样统计、估算总内存 vs 实际内存、差异说明(碎片、内部结构等);并检测超大 value(>1MB)、key 类型分布。
  
  **适用场景**:内存异常升高时定位大 key;理解为何「按 key 加总」与 `used_memory` 不一致。
  
  **用法示例**
  
  ```bash
  # 显示占用内存最多的 50 个 key(默认)
  python scripts/redis/redis_memory_heavy_keys.py
  
  # 显示前 100 个
  python scripts/redis/redis_memory_heavy_keys.py --top 100
  ```
  
  **常用参数**
  
  | 参数 | 说明 | 默认 |
  |------|------|------|
  | `--top` | 显示内存占用最高的 N 个 key | 50 |
  
  ---
  
  ### 5.4 三个脚本的选用建议
  
  | 需求 | 推荐脚本 |
  |------|----------|
a3275468   tangwang   已把本仓库里的 `/indexer...
309
  | 看两类业务缓存(embedding/translation)的数量、TTL、近期写入、样本 value | `redis_cache_health_check.py` |
8b74784e   tangwang   cache manage
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
  | 看全库或某前缀的 key 条数与内存占比 | `redis_cache_prefix_stats.py` |
  | 找占用内存最多的大 key、分析内存差异 | `redis_memory_heavy_keys.py` |
  
  ---
  
  ## 6. 其他 Redis 相关代码(测试与替身)
  
  除上述运维脚本外,以下代码使用 Redis 或其替身,但不定义新的业务缓存 key 协议:
  
  - **单元测试**`tests/test_embedding_pipeline.py` 中的 `_FakeRedis` 用于 mock embedding 缓存;`tests/test_translator_failure_semantics.py` 中的 `_RecordingRedis` 用于验证翻译失败时不写缓存。
  
  ---
  
  ## 7. 添加新缓存时的建议
  
  新增 Redis 缓存时,建议遵循以下约定:
  
  - **配置集中**
    - key 前缀、TTL 优先放在 `config/env_config.py`(通用)或 `config/config.yaml -> services.<capability>.cache`
    - 避免在业务代码中硬编码 TTL。
  - **命名规范**
    - 统一使用 `prefix:维度1:维度2:...` 的扁平 key 结构;
    - 对长文本/value 使用 `md5`/`sha256` 做哈希,避免过长 key。
  - **文档同步**
    - 新增缓存后,应在本文件中补充一行总览表 + 详细小节;
    - 若缓存与外部系统/历史实现兼容(如 Java 侧翻译缓存),需在说明中显式标注。