Commit 208e079afcaceab40b086e3c1dbb0c3140dd8cc3
1 parent
af827ce9
TODO
Showing
3 changed files
with
337 additions
and
13 deletions
Show diff stats
docs/TODO.txt
| ... | ... | @@ -4,6 +4,59 @@ |
| 4 | 4 | |
| 5 | 5 | |
| 6 | 6 | |
| 7 | +融合打分: | |
| 8 | +1. 融合公式 | |
| 9 | +def fuse_scores(self, context: SearchContext) -> None: | |
| 10 | + for result in context.results: | |
| 11 | + # 计算文本相关性分数 | |
| 12 | + text_score = ( | |
| 13 | + result.es_base_score * 0.5 + | |
| 14 | + result.es_phrase_score * 0.3 + | |
| 15 | + result.es_keywords_score * 0.2 | |
| 16 | + ) | |
| 17 | + | |
| 18 | + # 最终融合分数 | |
| 19 | + result.fused_score = ( | |
| 20 | + (result.rerank_text + 0.00001) ** 1.0 * | |
| 21 | + (result.rerank_boost + 0.1) ** 1.0 * | |
| 22 | + (result.es_knn_score + 0.6) ** 0.2 * | |
| 23 | + (text_score + 0.1) ** 0.77 | |
| 24 | + ) | |
| 25 | + | |
| 26 | +2. matched_queries | |
| 27 | + | |
| 28 | + # Add debug information if matched_queries is present and from_search_debug is True | |
| 29 | + if 'matched_queries' in hit: | |
| 30 | + # Extract individual scores from matched_queries | |
| 31 | + matched_queries = hit['matched_queries'] | |
| 32 | + if context.from_search_debug: | |
| 33 | + result.matched_queries = matched_queries | |
| 34 | + if isinstance(matched_queries, dict): | |
| 35 | + result.es_knn_score = matched_queries.get('knn_query', 0.0) | |
| 36 | + result.es_phrase_score = matched_queries.get('phrase_query', 0.0) | |
| 37 | + result.es_base_score = matched_queries.get('base_query', 0.0) | |
| 38 | + result.es_keywords_score = matched_queries.get('keywords_query', 0.0) | |
| 39 | + result.es_tags_score = matched_queries.get('tags_query', 0.0) | |
| 40 | + elif isinstance(matched_queries, list): | |
| 41 | + for query in matched_queries: | |
| 42 | + if query == 'knn_query': | |
| 43 | + result.es_knn_score = 1.0 | |
| 44 | + elif query == 'phrase_query': | |
| 45 | + result.es_phrase_score = 1.0 | |
| 46 | + elif query == 'base_query': | |
| 47 | + result.es_base_score = 1.0 | |
| 48 | + elif query == 'keywords_query': | |
| 49 | + result.es_keywords_score = 1.0 | |
| 50 | + elif query == 'tags_query': | |
| 51 | + result.es_tags_score = 1.0 | |
| 52 | + | |
| 53 | +es_query["track_scores"] = True | |
| 54 | +为了获取 需要打开 track_scores ? 不一定 | |
| 55 | +Track scores | |
| 56 | +When sorting on a field, scores are not computed. By setting track_scores to true, scores will still be computed and tracked. | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 7 | 60 | |
| 8 | 61 | |
| 9 | 62 | 翻译的health很慢 |
| ... | ... | @@ -67,10 +120,38 @@ translation: |
| 67 | 120 | |
| 68 | 121 | |
| 69 | 122 | |
| 123 | +翻译,增加facebook/nllb-200-distilled-600M | |
| 124 | +https://blog.csdn.net/qq_42746084/article/details/154947534 | |
| 125 | +https://huggingface.co/facebook/nllb-200-distilled-600M | |
| 126 | + | |
| 127 | + | |
| 128 | +店铺的语言:英语能占到80%,所以专门增加一个en-zh的 | |
| 129 | +https://huggingface.co/Helsinki-NLP/opus-mt-zh-en | |
| 130 | +https://huggingface.co/Helsinki-NLP/opus-mt-en-zh | |
| 131 | + | |
| 132 | + | |
| 133 | +opus-mt-zh-en | |
| 134 | + | |
| 135 | +from transformers import AutoTokenizer, AutoModelForSeq2SeqLM | |
| 136 | +model_name = "./models/opus-mt-en-zh" | |
| 137 | +tokenizer = AutoTokenizer.from_pretrained(model_name) | |
| 138 | +model = AutoModelForSeq2SeqLM.from_pretrained(model_name) | |
| 139 | +data = 'test' | |
| 140 | +encoded = tokenizer([data], return_tensors="pt") | |
| 141 | +translation = model.generate(**encoded) | |
| 142 | +result = tokenizer.batch_decode(translation, skip_special_tokens=True)[0] | |
| 143 | +print(result) | |
| 144 | + | |
| 70 | 145 | |
| 71 | 146 | |
| 72 | 147 | |
| 73 | 148 | |
| 149 | +Qwen3-Reranker-4B-GGUF | |
| 150 | +https://modelscope.cn/models/dengcao/Qwen3-Reranker-4B-GGUF/summary | |
| 151 | +1. 要确定选择哪种量化方式 | |
| 152 | +2. 确定提示词 | |
| 153 | + | |
| 154 | + | |
| 74 | 155 | |
| 75 | 156 | |
| 76 | 157 | 查看翻译的缓存情况 |
| ... | ... | @@ -114,26 +195,17 @@ HOST:10.200.16.14 / localhost |
| 114 | 195 | 用户名密码:saas:4hOaLaf41y2VuI8y |
| 115 | 196 | |
| 116 | 197 | |
| 117 | -你安装过nvidia-container-toolkit吗 | |
| 118 | -现在有一些开源的推理引擎对向量化模型和重排模型支持的比较好,我们这块也正好要单独拎出来,因此想改造下。 | |
| 119 | -已决策:embedding 先统一为 Qwen3-Embedding-0.6B(Sentence Transformers,本地 6005),后续若要独立高并发服务优先评估 TEI。 | |
| 120 | -最好以 docker 方式部署,让 gpu 对 docker 可见需要 nvidia-container-toolkit, | |
| 121 | -我试了多种方法安装 nvidia-container-toolkit 都失败了 | |
| 198 | +安装 nvidia-container-toolkit (done) | |
| 122 | 199 | https://mirrors.aliyun.com/github/releases/NVIDIA/nvidia-container-toolkit/ |
| 123 | 200 | https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/index.html |
| 124 | 201 | |
| 125 | 202 | |
| 126 | -qwen3-embedding | |
| 127 | -qwen3-reranker | |
| 128 | - | |
| 203 | +qwen3-embedding、qwen3-reranker (done) | |
| 129 | 204 | 选一个推理引擎,相比于我自己直接调 sentence-transformers,主要是多进程和负载均衡、连续批处理,比较有用 |
| 130 | 205 | 当前结论:embedding 场景优先 TEI;vLLM 更偏向生成式与 rerank 场景。 |
| 131 | 206 | |
| 132 | 207 | |
| 133 | 208 | |
| 134 | - | |
| 135 | - | |
| 136 | - | |
| 137 | 209 | 混用 大模型 使用:hunyuan-turbos-latest |
| 138 | 210 | 混元 OpenAI 兼容接口相关调用示例:https://cloud.tencent.com/document/product/1729/111007 |
| 139 | 211 | |
| ... | ... | @@ -144,11 +216,11 @@ hunyuan翻译:使用模型 hunyuan-translation |
| 144 | 216 | https://cloud.tencent.com/document/product/1729/113395#4.-.E7.A4.BA.E4.BE.8B |
| 145 | 217 | |
| 146 | 218 | |
| 147 | - | |
| 148 | 219 | 谷歌翻译 基础版:https://docs.cloud.google.com/translate/docs/reference/rest/v2/translate |
| 149 | 220 | |
| 150 | 221 | |
| 151 | 222 | |
| 223 | + | |
| 152 | 224 | 阿里云 百炼模型 现在使用的apikey是国内的。 |
| 153 | 225 | 各地域的 Base URL 和对应的 API Key 是绑定的。 |
| 154 | 226 | ... | ... |
| ... | ... | @@ -0,0 +1,252 @@ |
| 1 | +# 工作总结:微服务性能优化与架构 | |
| 2 | + | |
| 3 | +本文档汇总**三个微服务的性能优化**、**架构设计**及**性能测试基线**,便于汇报与后续复现。 | |
| 4 | + | |
| 5 | +--- | |
| 6 | + | |
| 7 | +## 一、完成三个微服务的性能优化 | |
| 8 | + | |
| 9 | +### 1. 文本向量(Embedding) | |
| 10 | + | |
| 11 | +**调研与选型**:对 TEI(Text Embeddings Inference)、vLLM 与原有 SentenceTransformers 方案进行对比评估。 | |
| 12 | + | |
| 13 | +| 方案 | 特点 | 结论 | | |
| 14 | +|------|------|------| | |
| 15 | +| **SentenceTransformers** | 本地 Python 进程,单机推理,无独立服务化 | 此前用于 Qwen3-Embedding-0.6B,扩展性与运维性一般 | | |
| 16 | +| **vLLM** | 高吞吐推理框架,更适合生成式与重排混合场景 | 纯 embedding 场景通常不作为首选 | | |
| 17 | +| **TEI** | HuggingFace 官方 embedding 专用推理服务,Docker 部署 | **当前最优选型** | | |
| 18 | + | |
| 19 | +**当前方案**:以 **TEI** 为文本向量后端,模型为 `Qwen/Qwen3-Embedding-0.6B`,embedding 服务(端口 **6005**)将 `POST /embed/text` 请求转发至 TEI(默认端口 **8080**)。 | |
| 20 | + | |
| 21 | +**具体配置与脚本**: | |
| 22 | +- **配置**:`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`。 | |
| 23 | +- **启动**:`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`。 | |
| 24 | +- **编排**:`./scripts/service_ctl.sh start tei` 或 `start embedding`(embedding 会校验 TEI `/health` 后再就绪)。 | |
| 25 | + | |
| 26 | +**工程化收益**: | |
| 27 | +- **独立服务**:TEI 以 Docker 容器运行,与主程序 `.venv` 解耦;embedding 使用 `.venv-embedding`,便于独立扩缩容与升级。 | |
| 28 | +- **协议统一**:与 `local_st`(SentenceTransformers)可插拔,通过 `config.yaml` 的 `services.embedding.backend` 或环境变量 `EMBEDDING_BACKEND` 切换。 | |
| 29 | +- **T4 适配**:按 GPU 架构自动选择镜像(T4 用 `turing-*`,Ampere+ 用 `cuda-*`);`TEI_MAX_BATCH_TOKENS`、`TEI_MAX_CLIENT_BATCH_SIZE`、`TEI_DTYPE` 已按短文本场景调优。 | |
| 30 | +- **健康检查与编排**:`service_ctl.sh` 对 tei 校验容器存在且 `GET http://127.0.0.1:8080/health` 成功;embedding 启动前会等待 TEI 健康。 | |
| 31 | + | |
| 32 | +详见:`docs/TEI_SERVICE说明文档.md`、`docs/QUICKSTART.md` §4。 | |
| 33 | + | |
| 34 | +--- | |
| 35 | + | |
| 36 | +### 2. 重排(Reranker) | |
| 37 | + | |
| 38 | +**后端**:vLLM 部署 **Qwen3-Reranker-0.6B**,对外提供 `POST /rerank`(query + docs → scores),端口 **6007**。 | |
| 39 | + | |
| 40 | +**针对 T4 的优化(已落地配置)**: | |
| 41 | +- **精度**:`dtype: "float16"`,降低显存与计算量。 | |
| 42 | +- **Prefix Caching**:`enable_prefix_caching: true`,对重复前缀(如相同 query)做缓存,减少重复计算。 | |
| 43 | +- **CUDA 图**:`enforce_eager: false`(默认),利用 vLLM 的 CUDA graph 降低 kernel 启动开销。 | |
| 44 | +- **按文档长度分批**:`sort_by_doc_length: true`,请求内先按文档长度排序再按 `infer_batch_size` 分批推理,减少 padding 浪费;`length_sort_mode: "char"`(更快,短文本推荐)或 `"token"`(更精确)。 | |
| 45 | +- **参数搜索结论**:在 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 匹配。 | |
| 46 | + | |
| 47 | +**具体配置**(`config/config.yaml` → `services.rerank.backends.qwen3_vllm`): | |
| 48 | +```yaml | |
| 49 | +model_name: "Qwen/Qwen3-Reranker-0.6B" | |
| 50 | +engine: "vllm" | |
| 51 | +max_model_len: 256 | |
| 52 | +tensor_parallel_size: 1 | |
| 53 | +gpu_memory_utilization: 0.36 | |
| 54 | +dtype: "float16" | |
| 55 | +enable_prefix_caching: true | |
| 56 | +enforce_eager: false | |
| 57 | +infer_batch_size: 64 | |
| 58 | +sort_by_doc_length: true | |
| 59 | +length_sort_mode: "char" | |
| 60 | +instruction: "Given a shopping query, rank product titles by relevance" | |
| 61 | +``` | |
| 62 | +环境变量覆盖:`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`。 | |
| 63 | + | |
| 64 | +详见:`reranker/DEPLOYMENT_AND_TUNING.md`、`reranker/README.md`。 | |
| 65 | + | |
| 66 | +--- | |
| 67 | + | |
| 68 | +### 3. 图片向量(Image Embedding) | |
| 69 | + | |
| 70 | +**方案**:**clip-as-service**(CN-CLIP,ViT-H-14),由独立服务提供图片向量化能力。 | |
| 71 | + | |
| 72 | +**具体内容**: | |
| 73 | +- **端口**:clip-as-service 默认 **51000**(`CNCLIP_PORT`);文本走 TEI(8080),图片走 clip-as-service。 | |
| 74 | +- **API**:embedding 服务(6005)统一暴露 `POST /embed/text` 与 `POST /embed/image`;图片请求由 `embeddings/server.py` 按配置调用实现 `ImageEncoderProtocol` 的后端(clip-as-service 或本地 CN-CLIP)。 | |
| 75 | +- **环境与启停**:CN-CLIP 使用独立虚拟环境 `.venv-cnclip`;启动 `scripts/start_cnclip_service.sh`,或 `./scripts/service_ctl.sh start cnclip`;设备可通过 `CNCLIP_DEVICE=cuda`(默认)或 `cpu` 指定。 | |
| 76 | +- **配置**:图片后端在 `config/config.yaml` 的 `services.embedding` 下配置(若存在 image 相关 backend);clip-as-service 的 flow 配置在 `third-party/clip-as-service/server/torch-flow-temp.yml`。 | |
| 77 | + | |
| 78 | +详见:`docs/CNCLIP_SERVICE说明文档.md`、`embeddings/README.md`。 | |
| 79 | + | |
| 80 | +--- | |
| 81 | + | |
| 82 | +### 4. 翻译(Translation) | |
| 83 | + | |
| 84 | +**背景**:原使用 DeepL,后迁移至 **qwen-mt**(如 `qwen-mt-flash`)。qwen-mt 云端限速约 **RPM=60(每分钟 60 请求)**,此前未做大商品量压测,未暴露问题;高并发索引或查询场景下易触限。 | |
| 85 | + | |
| 86 | +**当前方案**: | |
| 87 | +- **迁移至 qwen-flash**:在配置中将翻译改为 **LLM provider + qwen-flash 模型**,由 DashScope 兼容 API 调用,可配置化切换。 | |
| 88 | +- **可配置化(具体配置)**: | |
| 89 | + - **入口**:`config/config.yaml` → `services.translation`;`provider: "llm"` 时使用 `providers.llm`,`model: "qwen-flash"`,`timeout_sec: 30`,`base_url` 可选(为空则用 `DASHSCOPE_BASE_URL`);环境变量 `DASHSCOPE_API_KEY` 注入 Key。 | |
| 90 | + - **Provider 取值**:`provider` 可为 `http`(走翻译服务 6006)、`qwen-mt`(直连 qwen-mt-flush 等)、`deepl`(DeepL API)、`llm`(对话模型 qwen-flash 等);工厂函数 `providers/translation.py` 的 `create_translation_provider(query_config)` 根据 `get_translation_config()` 解析结果返回对应实现。 | |
| 91 | + - **调用位置**:QueryParser(`query/query_parser.py`)与 Indexer(`indexer/incremental_service.py`、`indexer/indexing_utils.py`)均通过 `create_translation_provider(...)` 获取实例,不写死 URL 或模型名。 | |
| 92 | +- **缓存**:`services.translation.cache` 支持 `key_prefix: "trans:v2"`、`ttl_seconds`、`sliding_expiration` 等,翻译结果写 Redis,减轻重复请求对限速的影响。 | |
| 93 | +- **场景支撑**:在线索引(indexer)与 query 请求(QueryParser)共用同一套 provider 配置;可按环境或租户通过修改 `config.yaml` 或环境变量切换 provider/model。 | |
| 94 | +- **待配合**:**金伟侧对索引侧翻译调用做流量控制**(限流/排队/批量聚合),避免索引高峰打满 qwen 限速,影响在线 query 翻译。 | |
| 95 | + | |
| 96 | +--- | |
| 97 | + | |
| 98 | +### 5. 内容理解字段(支撑 Suggest) | |
| 99 | + | |
| 100 | +**能力**:支持根据商品标题批量生成 **qanchors**(锚文本)、**semantic_attributes**、**tags**,供索引与 suggest 使用。 | |
| 101 | + | |
| 102 | +**具体内容**: | |
| 103 | +- **接口**:`POST /indexer/enrich-content`(Indexer 服务端口 **6004**)。请求体为 `items` 数组,每项含 `spu_id`、`title`(必填)及可选多语言标题等;单次请求最多 **50 条**,建议批量调用。响应 `results` 与 `items` 一一对应,每项含 `spu_id`、`qanchors`(按语言键,如 `qanchors.zh`、`qanchors.en`,逗号分隔短语)、`semantic_attributes`、`tags`。 | |
| 104 | +- **索引侧**:微服务组合方式下,调用方先拿不含 qanchors/tags 的 doc,再调用本接口补齐后写入 ES 的 `qanchors.{lang}` 等字段;索引 transformer(`indexer/document_transformer.py`、`indexer/product_annotator.py`)内也可在构建 doc 时调用内容理解逻辑,写入 `qanchors.{lang}`。 | |
| 105 | +- **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。 | |
| 106 | +- **实现与依赖**:内容理解内部使用大模型(需 `DASHSCOPE_API_KEY`),支持多语言与 Redis 缓存(如 `product_anchors`);逻辑与 `indexer/product_annotator` 一致。 | |
| 107 | + | |
| 108 | +**状态**:内容理解字段已接入索引与 suggest 链路;依赖内容理解(qanchors/tags)的**全量数据尚未全部完成一轮**,后续需持续跑满并校验效果。 | |
| 109 | + | |
| 110 | +详见:`indexer/ANCHORS_AND_SEMANTIC_ATTRIBUTES.md`、`docs/搜索API对接指南.md`(内容理解接口)、`api/routes/indexer.py`(enrich-content 路由)。 | |
| 111 | + | |
| 112 | +--- | |
| 113 | + | |
| 114 | +## 二、架构 | |
| 115 | + | |
| 116 | +### 1. Provider 与动态选择翻译 | |
| 117 | + | |
| 118 | +- **设计**:参考 `docs/系统设计文档.md`、`docs/DEVELOPER_GUIDE.md`,翻译/向量/重排均采用 **Provider + Backend** 解耦;配置单一来源为 `config/config.yaml` 的 `services` 块,环境变量可覆盖。 | |
| 119 | +- **翻译(具体实现)**: | |
| 120 | + - **工厂**:`providers/translation.py` 的 `create_translation_provider(query_config)`;内部调用 `config/services_config.get_translation_config()` 得到 `provider` 与 `providers.<name>` 参数。 | |
| 121 | + - **分支**:`provider in ("qwen-mt", "direct", "local", "inprocess")` → 使用 `query/qwen_mt_translate.py` 的 `Translator`(model 如 qwen-mt-flush);`provider == "http"` 或 `"service"` → `HttpTranslationProvider`(base_url 为翻译服务 6006,model 如 qwen);`provider == "llm"` → `query/llm_translate.py` 的 `LLMTranslatorProvider`(model 如 qwen-flash,base_url 可选);`provider == "deepl"` → `query/deepl_provider.py` 的 `DeepLProvider`。 | |
| 122 | + - **调用方**:`query/query_parser.py`(搜索前翻译)、`indexer/incremental_service.py`、`indexer/indexing_utils.py`(索引时翻译)均通过上述工厂获取实例,不写死 URL 或模型名。 | |
| 123 | +- **效果**:仅改 `config.yaml` 的 `services.translation.provider` 及对应 `providers.<name>` 即可切换 DeepL、qwen-mt、qwen-flash(llm)、HTTP 翻译服务等。 | |
| 124 | + | |
| 125 | +### 2. 服务的监控与拉起机制 | |
| 126 | + | |
| 127 | +- **脚本**:`scripts/service_ctl.sh` 统一负责各服务的生命周期与监控;依赖 `scripts/lib/load_env.sh` 与项目根目录 `.env`。 | |
| 128 | +- **服务与端口**: | |
| 129 | + - 核心:backend **6002**、indexer **6004**、frontend **6003**。 | |
| 130 | + - 可选:embedding **6005**、translator **6006**、reranker **6007**、tei **8080**、cnclip **51000**。 | |
| 131 | + - 端口可由环境变量覆盖:`API_PORT`、`INDEXER_PORT`、`FRONTEND_PORT`、`EMBEDDING_PORT`、`TRANSLATION_PORT`、`RERANKER_PORT`、`TEI_PORT`、`CNCLIP_PORT`。 | |
| 132 | +- **命令**: | |
| 133 | + - `./scripts/service_ctl.sh start [service...]` 或 `start all`(all = tei cnclip embedding translator reranker backend indexer frontend,按依赖顺序);`stop`、`restart` 同参数;`status` 默认列出所有服务。 | |
| 134 | + - 启动时: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 健康则仅校验进程/端口。 | |
| 135 | +- **监控常驻**: | |
| 136 | + - `./scripts/service_ctl.sh monitor-start <targets>` 启动后台监控进程,将 targets 写入 `logs/service-monitor.targets`,pid 写入 `logs/service-monitor.pid`,日志追加到 `logs/service-monitor.log`。 | |
| 137 | + - 轮询间隔 `MONITOR_INTERVAL_SEC` 默认 **10** 秒;连续 **3** 次(`MONITOR_FAIL_THRESHOLD`)健康失败则触发重启;重启冷却 `MONITOR_RESTART_COOLDOWN_SEC` 默认 **30** 秒;每小时最多重启 `MONITOR_MAX_RESTARTS_PER_HOUR` 默认 **6** 次;超限时调用 `scripts/wechat_alert.py` 告警(若存在)。 | |
| 138 | +- **日志**:各服务按日滚动到 `logs/<service>-<date>.log`,通过 `scripts/daily_log_router.sh` 与 `LOG_RETENTION_DAYS`(默认 30)控制保留。 | |
| 139 | + | |
| 140 | +详见:`scripts/service_ctl.sh` 内注释及 `docs/Usage-Guide.md`。 | |
| 141 | + | |
| 142 | +### 3. Suggest 索引与增量 | |
| 143 | + | |
| 144 | +- **当前**:suggest 索引由**全量脚本触发**,入口为 `main.py build-suggestions`,或封装脚本 `scripts/build_suggestions.sh <tenant_id> [args...]`。 | |
| 145 | + - **全量示例**:`./scripts/build_suggestions.sh 162 --mode full --days 30 --publish-alias`(重建建议索引,近 30 天数据,并发布别名)。 | |
| 146 | + - **增量示例**:`./scripts/build_suggestions.sh 162 --mode incremental --overlap-minutes 30`(按 watermark 增量更新);脚本内部调用 `main.py build-suggestions --tenant-id <id> ...`。 | |
| 147 | + - 构建逻辑在 `suggestion/builder.py` 的 `SuggestionIndexBuilder`:从 ES 商品索引(含 `title`、`qanchors`)与查询日志等拉取数据,写入 versioned 建议索引并切换 alias。 | |
| 148 | +- **尚未完成的“增量机制”**:指**自动/事件驱动的增量**(如商品变更或日志写入时自动刷新建议索引);当前 incremental 模式为“按 watermark 再跑一次构建”,仍为脚本主动触发,非持续增量流水线。 | |
| 149 | +- **依赖**:suggest 候选依赖商品侧 **内容理解字段**(qanchors/tags);`sources: ["query_log", "qanchor"]` 表示候选来自查询日志与 qanchor;当前内容理解未全量跑完一轮,suggest 数据会随全量重建逐步完善。 | |
| 150 | + | |
| 151 | +详见:`suggestion/builder.py`、`suggestion/ARCHITECTURE_V2.md`、`main.py`(build-suggestions 子命令)。 | |
| 152 | + | |
| 153 | +--- | |
| 154 | + | |
| 155 | +## 三、性能测试报告摘要 | |
| 156 | + | |
| 157 | +以下数据来自 `docs/性能测试报告.md`,测试时间 **2026-03-12**,环境:**8 vCPU**(Intel Xeon Platinum 8255C @ 2.50GHz)、**约 15Gi 可用内存**;租户 **162** 文档数约 **53**(search/suggest/rerank 与文档规模相关)。压测工具:`scripts/perf_api_benchmark.py`,场景×并发矩阵,每档 **20s**。 | |
| 158 | + | |
| 159 | +**复现命令(四场景×四并发)**: | |
| 160 | +```bash | |
| 161 | +cd /data/saas-search | |
| 162 | +.venv/bin/python scripts/perf_api_benchmark.py \ | |
| 163 | + --scenario backend_search,backend_suggest,embed_text,rerank \ | |
| 164 | + --concurrency-list 1,5,10,20 \ | |
| 165 | + --duration 20 \ | |
| 166 | + --tenant-id 162 \ | |
| 167 | + --backend-base http://127.0.0.1:6002 \ | |
| 168 | + --embedding-base http://127.0.0.1:6005 \ | |
| 169 | + --translator-base http://127.0.0.1:6006 \ | |
| 170 | + --reranker-base http://127.0.0.1:6007 \ | |
| 171 | + --output perf_reports/2026-03-12/perf_matrix_report.json | |
| 172 | +``` | |
| 173 | +执行前需启动服务:`./scripts/service_ctl.sh start embedding translator reranker backend`,并通过各端口 `/health` 检查。Reranker 单独 386-docs 口径复现见性能测试报告 §7.4 与 §13。 | |
| 174 | + | |
| 175 | +### 3.1 向量化服务(embed_text) | |
| 176 | + | |
| 177 | +| 并发 | 请求数 | 成功率 | 吞吐(RPS) | Avg(ms) | P95(ms) | Max(ms) | | |
| 178 | +|-----:|-------:|-------:|----------:|--------:|--------:|--------:| | |
| 179 | +| 1 | 966 | 100% | 48.27 | 20.63 | 23.41 | 49.80 | | |
| 180 | +| 5 | 1796 | 100% | 89.57 | 55.55 | 69.62 | 109.84 | | |
| 181 | +| 10 | 2095 | 100% | 104.42 | 95.22 | 117.66 | 152.48 | | |
| 182 | +| 20 | 2393 | 100% | 118.70 | 167.37 | 212.21 | 318.70 | | |
| 183 | + | |
| 184 | +**结论**:随并发提升吞吐持续增长,延迟平滑上升,扩展性较好。 | |
| 185 | + | |
| 186 | +--- | |
| 187 | + | |
| 188 | +### 3.2 Reranker(rerank) | |
| 189 | + | |
| 190 | +口径:query 固定 `wireless mouse`,每次请求 **386 docs**,句长 15–40 词随机(从 1000 词池采样);配置 `rerank_window=384`。复现命令: | |
| 191 | +```bash | |
| 192 | +.venv/bin/python scripts/perf_api_benchmark.py \ | |
| 193 | + --scenario rerank --duration 20 --concurrency-list 1,5,10,20 --timeout 60 \ | |
| 194 | + --rerank-dynamic-docs --rerank-doc-count 386 --rerank-vocab-size 1000 \ | |
| 195 | + --rerank-sentence-min-words 15 --rerank-sentence-max-words 40 \ | |
| 196 | + --rerank-query "wireless mouse" --rerank-seed 20260312 \ | |
| 197 | + --reranker-base http://127.0.0.1:6007 \ | |
| 198 | + --output perf_reports/2026-03-12/rerank_realistic/rerank_386docs.json | |
| 199 | +``` | |
| 200 | + | |
| 201 | +| 并发 | 请求数 | 成功率 | 吞吐(RPS) | Avg(ms) | P95(ms) | Max(ms) | | |
| 202 | +|-----:|-------:|-------:|----------:|--------:|--------:|--------:| | |
| 203 | +| 1 | 14 | 100% | 0.67 | 1498.64 | 1799.25 | 2160.96 | | |
| 204 | +| 5 | 15 | 100% | 0.62 | 8011.99 | 9725.61 | 9726.02 | | |
| 205 | +| 10 | 20 | 100% | 0.61 | 16217.12| 18043.05| 18050.04| | |
| 206 | +| 20 | 20 | 100% | 0.60 | 33252.35| 33456.74| 33480.14| | |
| 207 | + | |
| 208 | +**结论**:在 386 docs/请求 的真实口径下,吞吐约 **0.6 rps**,延迟随并发明显上升(并发 20 时 P95 约 33.5s),是当前整条链路的**最重瓶颈**。与 DashScope 云后端对比:单并发 vLLM 延迟更优;并发 5+ 时云后端吞吐更高(约 1.3x–6x),在线高并发场景可优先考虑云后端。 | |
| 209 | + | |
| 210 | +--- | |
| 211 | + | |
| 212 | +### 3.3 搜索(backend_search) | |
| 213 | + | |
| 214 | +| 并发 | 请求数 | 成功率 | 吞吐(RPS) | Avg(ms) | P95(ms) | Max(ms) | | |
| 215 | +|-----:|-------:|-------:|----------:|--------:|--------:|--------:| | |
| 216 | +| 1 | 160 | 100% | 7.98 | 124.89 | 228.06 | 345.49 | | |
| 217 | +| 5 | 161 | 100% | 7.89 | 628.91 | 1271.49| 1441.02| | |
| 218 | +| 10 | 181 | 100% | 8.78 | 1129.23| 1295.88| 1330.96| | |
| 219 | +| 20 | 161 | 100% | 7.63 | 2594.00| 4706.44| 4783.05| | |
| 220 | + | |
| 221 | +**结论**:吞吐约 **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 scripts/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`。 | |
| 222 | + | |
| 223 | +--- | |
| 224 | + | |
| 225 | +### 3.4 Suggest(backend_suggest) | |
| 226 | + | |
| 227 | +| 并发 | 请求数 | 成功率 | 吞吐(RPS) | Avg(ms) | P95(ms) | Max(ms) | | |
| 228 | +|-----:|-------:|-------:|----------:|--------:|--------:|--------:| | |
| 229 | +| 1 | 3502 | 100% | 175.09 | 5.68 | 8.70 | 15.98 | | |
| 230 | +| 5 | 4168 | 100% | 208.10 | 23.93 | 36.93 | 59.53 | | |
| 231 | +| 10 | 4152 | 100% | 207.25 | 48.05 | 59.45 | 127.20 | | |
| 232 | +| 20 | 4190 | 100% | 208.99 | 95.20 | 110.74 | 181.37 | | |
| 233 | + | |
| 234 | +**结论**:吞吐高且稳定(**约 200+ rps**),对并发友好,与 search/rerank 相比压力较小。 | |
| 235 | + | |
| 236 | +--- | |
| 237 | + | |
| 238 | +## 四、总结 | |
| 239 | + | |
| 240 | +| 维度 | 要点 | | |
| 241 | +|------|------| | |
| 242 | +| **Embedding** | TEI 替代 SentenceTransformers/vLLM 作为文本向量后端,兼顾性能与工程化(Docker、配置化、T4 调优);图片向量由 clip-as-service 承担。 | | |
| 243 | +| **Reranker** | vLLM + Qwen3-Reranker-0.6B,针对 T4 做 float16、prefix caching、CUDA 图、按长度分批及 batch/长度参数搜索;高并发场景可选用 DashScope 云后端。 | | |
| 244 | +| **翻译** | 因 qwen-mt 限速(RPM≈60),迁移至可配置的 qwen-flash 等方案,支撑在线索引与 query;需金伟侧对索引做流量控制。 | | |
| 245 | +| **内容理解** | 提供 qanchors/tags 等字段生成接口,支撑 suggest 与检索增强;全量一轮尚未完全跑满。 | | |
| 246 | +| **架构** | Provider 动态选择翻译;service_ctl 统一监控与拉起;suggest 目前全量脚本触发,增量待做。 | | |
| 247 | +| **性能基线** | 向量化扩展性良好;reranker 为整链瓶颈(386 docs 约 0.6 rps);search 约 8 rps;suggest 约 200+ rps。 | | |
| 248 | + | |
| 249 | +**关键文件与复现**: | |
| 250 | +- 配置:`config/config.yaml`(services、rerank、query_config)、`.env`(端口与 API Key)。 | |
| 251 | +- 脚本:`scripts/service_ctl.sh`(启停与监控)、`scripts/perf_api_benchmark.py`(压测)、`scripts/build_suggestions.sh`(suggest 构建)。 | |
| 252 | +- 完整步骤与多租户/rerank 对比见:`docs/性能测试报告.md`。 | ... | ... |
docs/性能测试报告.md