行业资讯
📅 2026/9/8 17:42:40
混合检索落地:BM25+向量双路召回在电商搜索的工程实践
1. 背景搜索无结果率 8.7%业务方下了死命令2025 年初我们接手某跨境电商平台的搜索中台重构。业务线日均搜索请求 420 万商品池 1.2 亿 SKU覆盖中英文及 6 个小语种。当时线上只有一套基于 Elasticsearch 7.10 的 BM25 关键词检索表面够用但业务方长期反馈两个痛点长尾词召回差用户搜防水 户外 手表 男士BM25 对多词组合的 AND 语义处理生硬召回率只有 61%语义匹配缺失搜适合跑步戴的耳机无法召回标题为运动蓝牙耳机 入耳式的商品因为字面零重合。季度复盘会上搜索团队给出的核心指标是搜索转化率 4.2%搜索无结果率 8.7%。相比行业均值无结果率约 3%差距明显。业务方下了死命令无结果率降到 4% 以内转化率提升 0.5 个百分点。2. 踩坑与现状先试同义词和查询改写越改越难维护我们拉取 30 天搜索日志做 bad case 聚类发现三类根因词法鸿沟Lexical Gap用户口语化表达与商品标题的书面语不一致。例如耐穿对耐磨“不夹耳朵对佩戴舒适”。BM25 基于词项重合天然无法跨越同义鸿沟。查询意图漂移多词查询中BM25 把每个词项当作独立证据累加缺少对核心意图词的加权。搜男士 机械表 防水 200米BM25 可能被男士带偏召回一堆时装表。商品标题质量参差部分卖家堆砌关键词导致 BM25 打分被噪声词稀释部分商品标题过短可匹配特征不足。我们最初的尝试是加同义词词典 查询改写结果踩了不少坑同义词词典维护成本极高每两周就要人工补充一批行业黑话“耐穿/耐磨”“不夹耳朵/佩戴舒适”覆盖永远赶不上用户表达查询改写规则写多了互相打架比如跑步耳机被改写成运动耳机后又和运动手表的改写冲突bad case 越修越多长尾词场景依然漏词典覆盖不到的组合查询照样无结果。折腾两个月无结果率只从 8.7% 降到 8.1%投入产出比极低。我们意识到词法层面的修补解决不了语义鸿沟必须引入向量检索做语义召回与 BM25 构成双路混合检索。3. 方案两套候选对比最终选独立向量库 RRF 融合方案 AES 原生 dense_vector script_score 融合在 Elasticsearch 7.10 上直接加dense_vector字段用script_score把bm25和cosine分数加权求和。{script_score:{query:{match:{title:防水 户外 手表}},script:{source:params.bm25_weight * _score params.vec_weight * cosineSimilarity(params.query_vector, title_vector)}}}优点架构简单不引入新组件。缺点一是 ES 7.10 的dense_vector只支持暴力扫描brute force1.2 亿文档的向量索引内存开销巨大实测单节点堆内存 30GB 起二是script_score的分数融合是后置重排两路召回 Top K 都来自同一套倒排索引的候选集向量召回被 BM25 的候选截断语义召回增益有限。方案 B独立向量库 双路召回 分数融合最终采用引入独立的向量数据库与 ES 并行召回各自取 Top N 后在应用层做 RRFReciprocal Rank Fusion融合。我们选型时对比了 Milvus 2.4 和 pgvector 0.7维度Milvus 2.4pgvector 0.7部署模式独立集群需运维 Kafka 等依赖PostgreSQL 插件随库部署索引类型HNSW、IVF_PQ支持 GPUHNSW、IVFPQ1 亿级向量支持分布式分片单机上限约 5000 万受内存约束团队熟悉度低高已有 Postgres 运维经验考虑到商品向量规模约 1.2 亿每 SKU 一个 768 维向量pgvector 单机扛不住最终选Milvus 2.4 HNSW 索引。召回架构如下用户查询Query 理解与向量化ES BM25 召回 Top 200Milvus 向量召回 Top 200RRF 分数融合业务规则重排返回 Top 50选型依据双路独立召回保证语义路不被词法路带偏RRF 不需要跨系统校准分数分布工程上最稳。4. 实操步骤环境版本与核心代码4.1 环境版本Elasticsearch 7.10.2保留原集群Milvus 2.4.1独立部署HNSW 索引向量模型BGE-M3开源输出 768 维支持中英双语离线向量化Spark 3.5 批任务每日全量 每 15 分钟增量应用服务Java 17 Spring Boot 3.24.2 向量化与写入 Milvus# offline/vectorize_job.py Spark 3.5 批任务伪代码frompymilvusimportCollectionSchema,FieldSchema,DataType,connections# 1. 用 BGE-M3 对商品标题关键属性拼接文本做向量化# 拼接格式: title: 运动蓝牙耳机 | brand: 某牌 | category: 数码耳机# 2. 写入 MilvusHNSW 参数: M16, efConstruction200connections.connect(hostmilvus-host,port19530)schemaCollectionSchema([FieldSchema(sku_id,DataType.INT64,is_primaryTrue),FieldSchema(title_vector,DataType.FLOAT_VECTOR,dim768),])# 索引参数: index_typeHNSW, metric_typeIP, params{M: 16, efConstruction: 200}4.3 双路召回与 RRF 融合Java 侧// SearchService.java 核心融合逻辑publicListLonghybridSearch(Stringquery){// 1. BM25 召回ListScoredDocbm25HitsesSearch(query,200);// 2. 向量召回query 经 BGE-M3 编码为 768 维向量ListScoredDocvecHitsmilvusSearch(embed(query),200);// 3. RRF 融合: score sum(1 / (k rank))k 取 60returnrrfFuse(bm25Hits,vecHits,60,50);}预期运行结果双路各取 Top 200RRF 融合后取 Top 50。相比单路 BM25语义相关的新品标题无关键词重合能进入最终结果集。5. 踩坑记录三个真实报错与排查坑 1Milvus 写入限流Kafka 消费积压上线首日增量任务消费积压 40 万条。日志报错[2025-03-12 14:23:11] ERROR [ConsumeWorker-3] - Failed to insert 512 vectors: milvus: insert rate limit exceeded, reason: insert throughput (1024 req/s) exceeds limit排查Milvus 2.4 默认dataCoord写入限流阈值较低我们批量写入用的是 512 条/批但并发 8 个 writer峰值 QPS 超限。解决调大dataCoord.enableCompaction相关参数并把批量大小提到 4096 条/批、写入并发降到 4积压 20 分钟内消化。坑 2RRF 融合后BM25 强相关结果被向量噪声淹没上线后 A/B 测试发现搜iPhone 15 手机壳 透明BM25 第一页全是精准商品融合后混入透明玻璃杯等向量近邻噪声。根因RRF 只看排名不看分数向量路对高频泛词召回了一批语义相近但类目不符的商品。解决在融合前加一道类目约束——向量召回必须与 BM25 召回的 Top 1 类目一致否则降权 50%。坑 3增量向量与 ES 文档删除不同步商品下架后 ES 已删但 Milvus 里向量还在导致融合结果出现已下架商品。解决增量任务消费商品变更 binlog 时先删 Milvus 再删 ES并加一个每日全量对账任务兜底。6. 验证数据与效果上线后跑了 14 天 A/B 测试实验组 30% 流量核心指标如下指标上线前BM25 单路上线后混合检索提升搜索无结果率8.7%3.9%-55.2%搜索转化率4.2%4.8%14.3%长尾词≥4 词召回率61%83%22pp融合链路 P99 延迟—86ms满足 150ms 目标其中无结果率从 8.7% 降到 3.9%直接达成业务方降到 4% 以内的目标转化率提升 0.6 个百分点超出预期 0.1pp。向量路单独贡献了约 18% 的最终成交商品曝光量即这些商品仅靠 BM25 永远不会出现在前 50。7. 权衡与总结什么场景该用、什么场景别照搬适用场景商品/内容库规模大百万级以上且用户查询口语化严重、同义表达多有明确的语义召回痛点无结果率高、长尾词漏召回且能拿到高质量的领域标注做向量模型微调或评测团队有独立的向量库运维能力或愿意引入 Milvus/ES 8.x 等成熟方案。不适用或需谨慎的场景别照搬商品库小于 10 万、查询以精确型号为主如搜iPhone 15 Pro Max 256G 黑色BM25 已足够引入向量是过度设计对延迟极其敏感且无缓存兜底的场景——双路召回 融合必然增加链路耗时我们 P99 86ms 是在加了 Redis 7 查询缓存后才达成的向量模型质量无法保证时不要硬上——如果 embedding 对领域术语区分度差融合反而拉低精度建议先用离线评测集如 Recall100验证模型效果再立项。代价与边界提醒混合检索不是银弹它解决的是召回问题排序质量仍依赖后续的精排模型。我们上线混合检索后紧接着把重排从规则升级为 LightGBM 学习排序转化率又提升了 0.3pp——召回和排序是接力赛别指望一路到底。另外双路召回 融合意味着多维护一套向量库、多一份向量化任务、多一层对账逻辑这些运维成本在立项前就要算清楚。