# 工作总结：微服务性能优化与架构

本文档汇总**三个微服务的性能优化**、**架构设计**及**性能测试基线**，便于汇报与后续复现。

---

## 一、完成三个微服务的性能优化

### 1. 文本向量（Embedding）

**调研与选型**：对 TEI（Text Embeddings Inference）、vLLM 与原有 SentenceTransformers 方案进行对比评估。

| 方案 | 特点 | 结论 |
|------|------|------|
| **SentenceTransformers** | 本地 Python 进程，单机推理，无独立服务化 | 此前用于 Qwen3-Embedding-0.6B，扩展性与运维性一般 |
| **vLLM** | 高吞吐推理框架，更适合生成式与重排混合场景 | 纯 embedding 场景通常不作为首选 |
| **TEI** | HuggingFace 官方 embedding 专用推理服务，Docker 部署 | **当前最优选型** |

**当前方案**：以 **TEI** 为文本向量后端，模型为 `Qwen/Qwen3-Embedding-0.6B`；文本 embedding 服务（端口 **6005**）将 `POST /embed/text` 请求转发至 TEI（默认端口 **8080**）。

**具体配置与脚本**：
- **配置**：`config/config.yaml` → `services.embedding.backend: "tei"`，`services.embedding.backends.tei.base_url: "http://127.0.0.1:8080"`、`model_id: "Qwen/Qwen3-Embedding-0.6B"`、`timeout_sec: 20`。
- **启动**：`scripts/start_tei_service.sh`（Docker 容器）；环境变量：`TEI_DEVICE=cuda`（默认）、`TEI_PORT=8080`、`TEI_MODEL_ID=Qwen/Qwen3-Embedding-0.6B`、`TEI_VERSION=1.9`、`TEI_MAX_BATCH_TOKENS=4096`、`TEI_MAX_CLIENT_BATCH_SIZE=24`、`TEI_DTYPE=float16`；T4 自动选镜像 `ghcr.io/huggingface/text-embeddings-inference:turing-1.9`。
- **编排**：`./scripts/service_ctl.sh start tei` 或 `start embedding`（text embedding 会校验 TEI `/health` 后再就绪）。

**工程化收益**：
- **独立服务**：TEI 以 Docker 容器运行，与主程序 `.venv` 解耦；embedding 使用 `.venv-embedding`，便于独立扩缩容与升级。
- **协议统一**：与 `local_st`（SentenceTransformers）可插拔，通过 `config.yaml` 的 `services.embedding.backend` 或环境变量 `EMBEDDING_BACKEND` 切换。
- **T4 适配**：按 GPU 架构自动选择镜像（T4 用 `turing-*`，Ampere+ 用 `cuda-*`）；`TEI_MAX_BATCH_TOKENS`、`TEI_MAX_CLIENT_BATCH_SIZE`、`TEI_DTYPE` 已按短文本场景调优。
- **健康检查与编排**：`service_ctl.sh` 对 tei 校验容器存在且 `GET http://127.0.0.1:8080/health` 成功；embedding 启动前会等待 TEI 健康。

详见：`docs/TEI_SERVICE说明文档.md`、`docs/QUICKSTART.md` §4。

---

### 2. 重排（Reranker）

**后端**：vLLM 部署 **Qwen3-Reranker-0.6B**，对外提供 `POST /rerank`（query + docs → scores），端口 **6007**。

**针对 T4 的优化（已落地配置）**：
- **精度**：`dtype: "float16"`，降低显存与计算量。
- **Prefix Caching**：`enable_prefix_caching: true`，对重复前缀（如相同 query）做缓存，减少重复计算。
- **CUDA 图**：`enforce_eager: false`（默认），利用 vLLM 的 CUDA graph 降低 kernel 启动开销。
- **按文档长度分批**：`sort_by_doc_length: true`，请求内先按文档长度排序再按 `infer_batch_size` 分批推理，减少 padding 浪费。
- **参数搜索结论**：在 T4、1000-doc 口径下对 `infer_batch_size` 做了 24/32/48/64 对比；**单请求延迟（c=1）** 上 `infer_batch_size=64` 最优，故当前默认 `infer_batch_size: 64`；`max_model_len: 256` 满足 query+doc 短文本场景；`gpu_memory_utilization: 0.36` 与 T4 16GB 匹配。

**具体配置**（`config/config.yaml` → `services.rerank.backends.qwen3_vllm`）：
```yaml
model_name: "Qwen/Qwen3-Reranker-0.6B"
engine: "vllm"
max_model_len: 256
tensor_parallel_size: 1
gpu_memory_utilization: 0.36
dtype: "float16"
enable_prefix_caching: true
enforce_eager: false
infer_batch_size: 64
sort_by_doc_length: true
instruction: "Given a shopping query, rank product titles by relevance"
```
环境变量覆盖：`RERANK_BACKEND`、`RERANKER_SERVICE_URL`、`RERANK_VLLM_INFER_BATCH_SIZE`、`RERANK_VLLM_SORT_BY_DOC_LENGTH` 等。启停：`./scripts/service_ctl.sh start reranker`，健康：`curl -sS http://127.0.0.1:6007/health`。

详见：`reranker/DEPLOYMENT_AND_TUNING.md`、`reranker/README.md`。

---

### 3. 图片向量（Image Embedding）

**方案**：**clip-as-service**（CN-CLIP，模型由配置控制），由独立服务提供图片向量化能力。

**具体内容**：
- **端口**：clip-as-service 默认 **51000**（`CNCLIP_PORT`）；文本走 TEI（8080），图片走 clip-as-service。
- **API**：文本 embedding 服务默认暴露 `POST /embed/text`（6005），图片 embedding 服务默认暴露 `POST /embed/image`（6008）；图片请求由 `embeddings/server.py` 按配置调用实现 `ImageEncoderProtocol` 的后端（clip-as-service 或本地 CN-CLIP）。
- **环境与启停**：CN-CLIP 使用独立虚拟环境 `.venv-cnclip`；启动 `scripts/start_cnclip_service.sh`，或 `./scripts/service_ctl.sh start cnclip`；设备可通过 `CNCLIP_DEVICE=cuda`（默认）或 `cpu` 指定。
- **配置**：图片后端在 `config/config.yaml` 的 `services.embedding` 下配置（若存在 image 相关 backend）；clip-as-service 默认模型由 `embeddings/config.py` 的 `CLIP_AS_SERVICE_MODEL_NAME` 控制，flow 配置在 `third-party/clip-as-service/server/torch-flow-temp.yml`。

详见：`docs/CNCLIP_SERVICE说明文档.md`、`embeddings/README.md`。

---

### 4. 翻译（Translation）

**背景**：原使用 DeepL，后迁移至 **qwen-mt**（如 `qwen-mt-flash`）。qwen-mt 云端限速约 **RPM=60（每分钟 60 请求）**，此前未做大商品量压测，未暴露问题；高并发索引或查询场景下易触限。

**当前方案**：
- **统一 translator service**：业务侧统一走 6006，按 `model + scene` 选择能力，不再存在翻译 provider 分支。
- **配置入口**：`config/config.yaml` → `services.translation`，显式声明 `service_url`、`default_model`、`default_scene`、各 capability 的 `backend`、`base_url/api_url`、timeout 与本地模型运行参数。
- **内部规则收口**：scene 集合、语言码映射、LLM prompt 模板、本地模型方向约束统一放在 `translation/` 内部，不再散落在 `config/`、`query/` 等位置。
- **调用位置**：QueryParser 与 Indexer 均通过 `translation.create_translation_client()` 获取客户端，不写死 URL 或模型名。
- **缓存**：translator service 对所有 translation capability 统一接入 Redis 缓存；每个 capability 通过 `use_cache` 控制开关，key 格式固定为 `trans:{model}:{target_lang}:{source_text[:4]}{sha256}`。
- **场景支撑**：在线索引（indexer）与 query 请求（QueryParser）共用同一套 provider 配置；可按环境或租户通过修改 `config.yaml` 或环境变量切换 provider/model。
- **待配合**：**金伟侧对索引侧翻译调用做流量控制**（限流/排队/批量聚合），避免索引高峰打满 qwen 限速，影响在线 query 翻译。

---

### 5. 内容理解字段（支撑 Suggest）

**能力**：支持根据商品标题批量生成 **qanchors**（锚文本）、**enriched_attributes**、**tags**，供索引与 suggest 使用。

**具体内容**：
- **接口**：`POST /indexer/enrich-content`（FacetAwareMatching 服务端口 **6001**）。请求体为 `items` 数组，每项含 `spu_id`、`title`（必填）及可选多语言标题等；单次请求最多 **50 条**，建议批量调用。响应 `results` 与 `items` 一一对应，每项含 `spu_id`、`qanchors`（按语言键，如 `qanchors.zh`、`qanchors.en`，逗号分隔短语）、`enriched_attributes`、`tags`。
- **索引侧**：微服务组合方式下，调用方先拿不含 qanchors/tags 的 doc，再调用本接口补齐后写入 ES 的 `qanchors.{lang}` 等字段；索引 transformer（`indexer/document_transformer.py`、`indexer/product_enrich.py`）内也可在构建 doc 时调用内容理解逻辑，写入 `qanchors.{lang}`。
- **Suggest 侧**：`suggestion/builder.py` 从 ES 商品索引读取 `_source: ["id", "spu_id", "title", "qanchors"]`，对 `qanchors.{lang}` 用 `_split_qanchors` 拆成词条，以 `source="qanchor"` 加入候选，排序时 `qanchor` 权重大于纯 title（`add_product("qanchor", ...)`）；suggest 配置中 `sources: ["query_log", "qanchor"]` 表示候选来源包含 qanchor。
- **实现与依赖**：内容理解内部使用大模型（需 `DASHSCOPE_API_KEY`），支持多语言与 Redis 缓存（如 `product_anchors`）；逻辑与 `indexer/product_enrich` 一致。

**状态**：内容理解字段已接入索引与 suggest 链路；依赖内容理解（qanchors/tags）的**全量数据尚未全部完成一轮**，后续需持续跑满并校验效果。

详见：`indexer/ANCHORS_AND_SEMANTIC_ATTRIBUTES.md`、`docs/搜索API对接指南-05-索引接口（Indexer）.md`（`enrich-content` 等）、`api/routes/indexer.py`（enrich-content 路由）。

---

## 二、架构

### 1. Translator Service 与动态选择翻译

- **设计**：翻译已从 provider 架构中独立出来，采用 **一个 translator service + 多个 capability backend**；配置单一来源为 `config/config.yaml` 的 `services.translation` 块，`service_url` / `default_model` / `default_scene` 不再接受环境变量静默覆盖。
- **翻译（具体实现）**：
  - **业务入口**：`translation.create_translation_client()`
  - **服务编排**：`translation/service.py`
  - **后端实现**：`translation/backends/qwen_mt.py`、`translation/backends/llm.py`、`translation/backends/deepl.py`、`translation/backends/local_seq2seq.py`
  - **调用方**：`query/query_parser.py`、`indexer/incremental_service.py`、`indexer/indexing_utils.py`
- **效果**：仅改 `services.translation.default_model` 或启用的 capability，即可切换云端/本地翻译能力；调用方始终只连 6006。

### 2. 服务的监控与拉起机制

- **脚本**：`scripts/service_ctl.sh` 统一负责各服务的生命周期与监控；依赖 `scripts/lib/load_env.sh` 与项目根目录 `.env`。
- **服务与端口**：
  - 核心：backend **6002**、indexer **6004**、frontend **6003**、eval-web **6010**（搜索评估 UI）。
  - 可选：embedding(text) **6005**、embedding-image **6008**、translator **6006**、reranker **6007**、tei **8080**、cnclip **51000**。
  - 端口可由环境变量覆盖：`API_PORT`、`INDEXER_PORT`、`FRONTEND_PORT`、`EVAL_WEB_PORT`、`EMBEDDING_TEXT_PORT`、`EMBEDDING_IMAGE_PORT`、`TRANSLATION_PORT`、`RERANKER_PORT`、`TEI_PORT`、`CNCLIP_PORT`。
- **命令**：
  - `./scripts/service_ctl.sh start [service...]` 或 `up all` / `start all`（all 含 tei、cnclip、embedding、embedding-image、translator、reranker、backend、indexer、frontend、eval-web，按依赖顺序）；`stop`、`restart`、`down` 同参数；`status` 默认列出所有服务。
  - 启动时：backend/indexer/frontend/embedding/translator/reranker 会写 pid 到 `logs/<service>.pid`，并执行 `wait_for_health`（GET `http://127.0.0.1:<port>/health`）；reranker 健康重试 90 次，其余 30 次；TEI 校验 Docker 容器存在且 `/health` 成功；cnclip 无 HTTP 健康则仅校验进程/端口。
- **监控常驻**：
  - `./scripts/service_ctl.sh monitor-start <targets>` 启动后台监控进程，将 targets 写入 `logs/service-monitor.targets`，pid 写入 `logs/service-monitor.pid`，日志追加到 `logs/service-monitor.log`。
  - 轮询间隔 `MONITOR_INTERVAL_SEC` 默认 **10** 秒；连续 **3** 次（`MONITOR_FAIL_THRESHOLD`）健康失败则触发重启；重启冷却 `MONITOR_RESTART_COOLDOWN_SEC` 默认 **30** 秒；每小时最多重启 `MONITOR_MAX_RESTARTS_PER_HOUR` 默认 **6** 次；超限时调用 `scripts/ops/wechat_alert.py` 告警（若存在）。
- **日志**：各服务按日滚动到 `logs/<service>-<date>.log`，通过 `scripts/ops/daily_log_router.sh` 与 `LOG_RETENTION_DAYS`（默认 30）控制保留。

详见：`scripts/service_ctl.sh` 内注释及 `docs/Usage-Guide.md`。

### 3. Suggest 索引与增量

- **当前**：suggest 索引由**全量脚本触发**，入口为 `main.py build-suggestions`，或封装脚本 `scripts/build_suggestions.sh <tenant_id> [args...]`。
  - **全量示例**：`./scripts/build_suggestions.sh 162 --mode full --days 30 --publish-alias`（重建建议索引，近 30 天数据，并发布别名）。
  - **增量示例**：`./scripts/build_suggestions.sh 162 --mode incremental --overlap-minutes 30`（按 watermark 增量更新）；脚本内部调用 `main.py build-suggestions --tenant-id <id> ...`。
  - 构建逻辑在 `suggestion/builder.py` 的 `SuggestionIndexBuilder`：从 ES 商品索引（含 `title`、`qanchors`）与查询日志等拉取数据，写入 versioned 建议索引并切换 alias。
- **尚未完成的“增量机制”**：指**自动/事件驱动的增量**（如商品变更或日志写入时自动刷新建议索引）；当前 incremental 模式为“按 watermark 再跑一次构建”，仍为脚本主动触发，非持续增量流水线。
- **依赖**：suggest 候选依赖商品侧 **内容理解字段**（qanchors/tags）；`sources: ["query_log", "qanchor"]` 表示候选来自查询日志与 qanchor；当前内容理解未全量跑完一轮，suggest 数据会随全量重建逐步完善。

详见：`suggestion/builder.py`、`suggestion/ARCHITECTURE_V2.md`、`main.py`（build-suggestions 子命令）。

---

## 三、性能测试报告摘要

以下数据来自 `docs/性能测试报告.md`，测试时间 **2026-03-12**，环境：**8 vCPU**（Intel Xeon Platinum 8255C @ 2.50GHz）、**约 15Gi 可用内存**；租户 **162** 文档数约 **53**（search/search/suggestions/rerank 与文档规模相关）。压测工具：`benchmarks/perf_api_benchmark.py`，场景×并发矩阵，每档 **20s**。

**复现命令（四场景×四并发）**：
```bash
cd /data/saas-search
.venv/bin/python benchmarks/perf_api_benchmark.py \
  --scenario backend_search,backend_suggest,embed_text,rerank \
  --concurrency-list 1,5,10,20 \
  --duration 20 \
  --tenant-id 162 \
  --backend-base http://127.0.0.1:6002 \
  --embedding-base http://127.0.0.1:6005 \
  --translator-base http://127.0.0.1:6006 \
  --reranker-base http://127.0.0.1:6007 \
  --output perf_reports/2026-03-12/perf_matrix_report.json
```
执行前需启动服务：`./scripts/service_ctl.sh start embedding translator reranker backend`，并通过各端口 `/health` 检查。Reranker 单独 386-docs 口径复现见性能测试报告 §7.4 与 §13。

### 3.1 向量化服务（embed_text）

| 并发 | 请求数 | 成功率 | 吞吐(RPS) | Avg(ms) | P95(ms) | Max(ms) |
|-----:|-------:|-------:|----------:|--------:|--------:|--------:|
| 1    | 966   | 100%  | 48.27     | 20.63   | 23.41   | 49.80   |
| 5    | 1796  | 100%  | 89.57     | 55.55   | 69.62   | 109.84  |
| 10   | 2095  | 100%  | 104.42    | 95.22   | 117.66  | 152.48  |
| 20   | 2393  | 100%  | 118.70    | 167.37  | 212.21  | 318.70  |

**结论**：随并发提升吞吐持续增长，延迟平滑上升，扩展性较好。

---

### 3.2 Reranker（rerank）

口径：query 固定 `wireless mouse`，每次请求 **386 docs**，句长 15–40 词随机（从 1000 词池采样）；配置 `rerank_window=384`。复现命令：
```bash
.venv/bin/python benchmarks/perf_api_benchmark.py \
  --scenario rerank --duration 20 --concurrency-list 1,5,10,20 --timeout 60 \
  --rerank-dynamic-docs --rerank-doc-count 386 --rerank-vocab-size 1000 \
  --rerank-sentence-min-words 15 --rerank-sentence-max-words 40 \
  --rerank-query "wireless mouse" --rerank-seed 20260312 \
  --reranker-base http://127.0.0.1:6007 \
  --output perf_reports/2026-03-12/rerank_realistic/rerank_386docs.json
```

| 并发 | 请求数 | 成功率 | 吞吐(RPS) | Avg(ms) | P95(ms) | Max(ms) |
|-----:|-------:|-------:|----------:|--------:|--------:|--------:|
| 1    | 14    | 100%  | 0.67      | 1498.64 | 1799.25 | 2160.96 |
| 5    | 15    | 100%  | 0.62      | 8011.99 | 9725.61 | 9726.02 |
| 10   | 20    | 100%  | 0.61      | 16217.12| 18043.05| 18050.04|
| 20   | 20    | 100%  | 0.60      | 33252.35| 33456.74| 33480.14|

**结论**：在 386 docs/请求 的真实口径下，吞吐约 **0.6 rps**，延迟随并发明显上升（并发 20 时 P95 约 33.5s），是当前整条链路的**最重瓶颈**。与 DashScope 云后端对比：单并发 vLLM 延迟更优；并发 5+ 时云后端吞吐更高（约 1.3x–6x），在线高并发场景可优先考虑云后端。

---

### 3.3 搜索（backend_search）

| 并发 | 请求数 | 成功率 | 吞吐(RPS) | Avg(ms) | P95(ms) | Max(ms) |
|-----:|-------:|-------:|----------:|--------:|--------:|--------:|
| 1    | 160   | 100%  | 7.98      | 124.89 | 228.06 | 345.49 |
| 5    | 161   | 100%  | 7.89      | 628.91 | 1271.49| 1441.02|
| 10   | 181   | 100%  | 8.78      | 1129.23| 1295.88| 1330.96|
| 20   | 161   | 100%  | 7.63      | 2594.00| 4706.44| 4783.05|

**结论**：吞吐约 **8 rps** 平台化，延迟随并发上升明显，符合“检索 + 向量 + 重排”重链路特征。多租户补测（文档数 500–10000，见报告 §12）表明：文档数越大，RPS 下降、延迟升高；tenant 0（10000 doc）在并发 20 出现部分 ReadTimeout（成功率 59.02%），需注意 timeout 与容量规划；补测命令示例：`for t in 0 1 2 3 4; do .venv/bin/python benchmarks/perf_api_benchmark.py --scenario backend_search --concurrency-list 1,5,10,20 --duration 20 --tenant-id $t --output perf_reports/2026-03-12/search_tenant_matrix/tenant_${t}.json; done`。

---

### 3.4 Suggest（backend_suggest）

| 并发 | 请求数 | 成功率 | 吞吐(RPS) | Avg(ms) | P95(ms) | Max(ms) |
|-----:|-------:|-------:|----------:|--------:|--------:|--------:|
| 1    | 3502  | 100%  | 175.09    | 5.68    | 8.70    | 15.98   |
| 5    | 4168  | 100%  | 208.10    | 23.93   | 36.93   | 59.53   |
| 10   | 4152  | 100%  | 207.25    | 48.05   | 59.45   | 127.20  |
| 20   | 4190  | 100%  | 208.99    | 95.20   | 110.74  | 181.37  |

**结论**：吞吐高且稳定（**约 200+ rps**），对并发友好，与 search/rerank 相比压力较小。

---

## 四、总结

| 维度 | 要点 |
|------|------|
| **Embedding** | TEI 替代 SentenceTransformers/vLLM 作为文本向量后端，兼顾性能与工程化（Docker、配置化、T4 调优）；图片向量由 clip-as-service 承担。 |
| **Reranker** | vLLM + Qwen3-Reranker-0.6B，针对 T4 做 float16、prefix caching、CUDA 图、按长度分批及 batch/长度参数搜索；高并发场景可选用 DashScope 云后端。 |
| **翻译** | 因 qwen-mt 限速（RPM≈60），迁移至可配置的 qwen-flash 等方案，支撑在线索引与 query；需金伟侧对索引做流量控制。 |
| **内容理解** | 提供 qanchors/tags 等字段生成接口，支撑 suggest 与检索增强；全量一轮尚未完全跑满。 |
| **架构** | Provider 动态选择翻译；service_ctl 统一监控与拉起；suggest 目前全量脚本触发，增量待做。 |
| **性能基线** | 向量化扩展性良好；reranker 为整链瓶颈（386 docs 约 0.6 rps）；search 约 8 rps；suggest 约 200+ rps。 |

**关键文件与复现**：
- 配置：`config/config.yaml`（services、rerank、query_config）、`.env`（端口与 API Key）。
- 脚本：`scripts/service_ctl.sh`（启停与监控）、`benchmarks/perf_api_benchmark.py`（压测）、`scripts/build_suggestions.sh`（suggest 构建）。
- 完整步骤与多租户/rerank 对比见：`docs/性能测试报告.md`。
