
做了这么多年电商后台我越来越觉得搜索是那种看起来谁都能做做好了极其困难的模块。你说它难在哪儿不是写几个接口把数据库里的商品 like 出来而是要在海量商品里用几十毫秒的时间把用户真正想要的东西排在前面同时扛住大促期间每秒几千次的查询冲击。这三个词——精准性、相关性、高并发检索——每一个单拎出来都是一整座山放在一起更是牵一发动全身。这篇文章我就结合自己做过电商搜索系统的经验把商品搜索从相关性优化到高并发实现的完整链路拆开讲一遍适合刚接手搜索模块的后端工程师、想自建搜索能力的团队也适合对搜索引擎原理感兴趣的朋友。1. 先搞清楚一件事精准性和相关性根本不是一回事很多刚入行的同学会把精准性和相关性混着说觉得搜索结果准就是相关相关就是准。这个认知偏差会在后续做优化时把人带进沟里因为这两者的优化手段、评价指标、技术路径完全不一样。1.1 一个最常见的误解我见过最典型的场景业务方拿着一个搜索词过来投诉说搜苹果怎么出来的全是水果我要的是手机。搜索开发解释系统判定苹果这个词和水果商品的文本匹配度更高两边吵半天。其实这个问题既不是相关性做得好不好也不是精准性做得好不好而是用户意图理解的问题。相关性Relevance衡量的是给定一个查询词返回的商品和这个词在文本、语义层面有多匹配。搜苹果一个标题叫新鲜红富士苹果 5斤装的商品和一个Apple iPhone 15 128G的商品从文本相关性看前者得分高因为苹果在标题里就是水果的意思。但用户的真实需求可能是手机这时候**精准性Precision**出问题了——它衡量的是返回结果中有多少是用户真正想要的。1.2 相关性文档与查询的匹配程度相关性是搜索引擎的立身之本。它不关心业务只关心这堆文档里哪些和这个查询最像。经典做法是给每个商品建立文本向量标题、类目、品牌、属性值拼起来再用 BM25 这类算法算查询词和文档的相似度得分。这个得分决定了召回阶段的排序。后来为了处理同义词语义近似这类文本匹配解决不了的问题业界引入了向量检索把查询词和商品都映射成高维向量用向量距离衡量语义相似度。搜苹果手机可以召回标题里只有iPhone的商品因为两个词的向量在语义空间里离得近。这部分在第2章会详细拆解。1.3 精准性用户需求与结果的商业匹配精准性更偏向用户体验和业务结果。它关心的是用户搜这个词真正想买什么系统的排序结果能不能让用户快速找到、愿意点击、最终下单。提升精准性靠的不只是文本匹配算法还需要引入用户行为数据点击率、加购率、转化率、搜索后跳失率。比如搜连衣裙纯文本相关性会把所有标题带连衣裙的商品按文本得分排序但精排模型会用历史行为特征把用户更爱点击的款式、价格带、风格排到前面。这就是 LTRLearning to Rank排序学习模型做的事情。1.4 为什么必须先分清楚分清楚这两个概念直接决定了系统架构怎么设计相关性是底层检索的事决定召回哪些商品、以什么基础分排序核心组件是索引、分词、BM25、向量检索。精准性是上层排序的事决定在基础得分之上怎么结合业务特征重排核心组件是特征平台、精排模型、业务重排规则。评价指标也不同相关性看 NDCG、MRR、RecallK精准性更看重搜索转化率、点击率、无结果率、用户搜索满意度。从系统链路看相关性优化聚焦在召回和粗排环节精准性优化聚焦在精排和重排环节。我在实际项目里通常会把这两部分拆成两个独立的优化迭代模块各做各的评测互不干扰否则混在一起出了问题根本定位不到是哪一环拖了后腿。2. 相关性优化的核心链路关键词召回到语义召回的组合拳相关性是搜索的地基地基不牢后面做再多精准性优化都是空中楼阁。这一章我们按照一条真实查询的流转路径从倒排索引讲到向量召回再到重排把相关性优化的核心组件串起来。2.1 倒排索引与BM25评分传统召回的基础商品搜索最底层的结构是倒排索引。简单说就是把商品标题/属性中包含的每个词作为key把包含这个词的商品ID列表作为value建立映射关系。用户搜跑步鞋系统先分词得到[跑步, 鞋]然后分别找到两个词对应的商品ID列表做求交合并得到候选集合。有了候选集合怎么排序Lucene 系搜索引擎Elasticsearch、OpenSearch、Solr默认用BM25算法打分。它的核心公式长这样score(D,Q) Σ IDF(qi) * (f(qi,D) * (k1 1)) / (f(qi,D) k1 * (1 - b b * |D| / avgdl))其中f(qi,D)是词项 qi 在文档 D 中的出现次数|D|是文档长度avgdl是平均文档长度k1和b是调节参数Lucene 默认 k11.2b0.75。这个公式解决了两件事词频越高得分越高但非线性增长避免一个词出现100次就碾压所有别的因素文档越长得分会被稀释短标题命中通常比长描述命中更相关。实际调优时我踩过一个坑某商品标题故意堆了一长串关键词比如跑步鞋男 跑步鞋女 轻便跑步鞋 减震跑步鞋结果它把好几类查询都召回了还因为词频高排得很靠前。后来把标题和属性的权重分开设置标题权重给 3.0属性权重给 1.0描述类字段干脆不参与 BM25 打分或给 0.1整体相关性立刻正常了很多。2.2 向量检索补充语义鸿沟BM25 有一个硬伤它是字面匹配处理不了搜儿童电话手表但商品标题写的是小天才亲子手表这种情况。词都对不上倒排索引召回不到相关性再好的模型也没用。解决思路是把文本映射成向量用向量距离衡量语义相似度。具体做法用预训练模型如 sentence-transformers 里的中文模型、或者基于商品域数据微调的 embedding 模型把商品标题 类目 品牌 属性编码成一个 768 维向量建索引时写入向量字段。查询时把用户搜索词编码成同样的向量。用KNNK近邻检索在向量索引里找出最接近的 K 个商品。向量索引的数据结构主流用HNSW分层可导航小世界图。它的原理是构建多层图上层图的边稀疏用于快速跳到大概区域下层图的边密集用于精确找邻居。查询时从上往下逐层搜索能在毫秒级返回几百个最相似的向量。用开源向量数据库 Milvus 做向量检索时Java 代码大致长这样// 构建查询向量vector是float[]类型768维 val searchParams SearchParam.create() .withCollectionName(product_embedding) .withVectorField(embedding) .withVectors(listOf(queryVector)) .withTopK(200) .withParams({\nprobe\: 16, \ef\: 64}) .withOutputFields(listOf(product_id, title, category_id)) val results milvusClient.search(searchParams)这里nprobe和ef是召回质量和耗时的调节旋钮。值越大搜索越精确但耗时也越高。实际项目里要把这两个参数放进压测里一起调不要只看单条查询的耗时。2.3 混合检索关键词加向量双路召回向量检索不是万能的它也有两个问题一是 embedding 模型对商品 ID、店铺名、精确型号这类专有名词不敏感搜iPhone 15 Pro Max 256G 原色钛金属向量召回很容易跑偏二是向量索引对实时性要求高——新上架商品如果没及时算向量就永远召不回。所以正规一点的商品搜索都不会只用一路召回而是走混合检索BM25 关键词召回一路向量语义召回一路两路结果融合。融合方式我推荐两种加权分数融合finalScore w1 * bm25Score w2 * vectorScore两个分数在融合前都做 min-max 归一化。优点是简单直观缺点是权重需要反复调而且不同查询的最优权重可能不同。RRFReciprocal Rank Fusion倒数排名融合不关心原始得分只看商品在各自排名里的位置。公式是score(D) Σ 1 / (k rank_i(D))k 取 60 是常识性默认值。RRF 的好处是融合时不需要归一化对两路得分尺度不一致不敏感实现非常简单鲁棒性也更好。我现在的项目底层就是 BM25 Milvus 双路召回然后用 RRF 融合上线后相关性指标有明显提升。2.4 重排模型从几百个候选到几十个精排结果召回和融合之后候选集通常还有几百个商品。这时候要用更精细的模型重新算一遍分把最合适的结果顶到前面。业界普遍的做法是Cross-Encoder 重排把查询词和商品标题、类目拼成一个句子对丢进一个类似 BERT 的模型直接输出一个相关度分数0到1。它比向量检索用的双塔模型查询和商品各自编码再算相似度精度更高因为查询词和商品在模型内部做了充分的交叉注意力计算但这种高精度也意味着更慢——单条查询对几百个商品逐一过模型可能要几百毫秒。所以在工程上重排只对召回融合后的 top 200 做。如果延迟预算充足比如 200ms 以内可以全量重排如果预算紧就先按融合分截断到 top 100 再过模型。算力不够的时候还可以考虑用蒸馏过的小模型比如 6 层或者 4 层的 TinyBERT精度损失不大但 QPS 能翻好几倍。3. 精准性提升的三层漏斗粗排、精排与业务重排的实操细节相关性把和查询像不像的问题解决之后就要解决用户会不会买的问题。这一层才是决定搜索成交转化率的关键。整个排序链路是一个漏斗召回几万→ 粗排几千→ 精排几百→ 重排几十每一层都在用更精细的特征、更复杂的模型、更大的算力消耗去处理更小的候选集。3.1 粗排千万商品怎么快速砍到千级召回阶段可能捞出来几万个商品这些商品不可能全部送进精排模型几万个样本过深度学习模型延迟直接爆炸。粗排的目标是用很轻量的方式在几毫秒内把候选集压缩到几百上千。粗排的常用方案有两类线性加权把 BM25 分、向量相似度、商品销量分、好评率分做加权求和取 top 1000。优点是实现简单、延迟极低缺点是特征太少排序精度一般。双塔模型查询塔和商品塔分别编码成向量粗排时直接用向量内积算分。这个模型和向量召回用的模型结构类似但目标函数是预测点击率而非文本相似度所以更能体现用户的偏好。我自己的经验是粗排阶段不要过度追求精度它的使命是别把好商品漏掉把精力留给精排模型。只要粗排的召回率top 1000 里包含最终成交商品的比例控制在 95% 以上就算合格。3.2 精排特征、样本与模型选择精排是整个排序链路里对精准性贡献最大的一层。业界主流做法是用 LTR 模型输入特征输出商品被点击/购买的概率然后按这个概率排序。特征工程是精排的核心我通常把特征分成三类特征类型特征示例说明文本相关性特征BM25分、向量相似度、重排模型分承接相关性链路商品静态特征价格、品牌、类目、销量、好评率、上架天数商品本身的吸引力用户行为特征该类目历史点击率、搜索词与商品品牌的共现次数、同店购买率反映用户群体的偏好这些特征里最容易忽略的是搜索词与商品品牌的共现次数。比如搜篮球鞋用户历史上点击最多的是某个特定品牌这个特征会把该品牌相关商品顶上去。这类特征需要离线跑大数据任务统计然后灌入在线特征库Redis 或特征平台精排模型在线读取。样本构造上要注意三个坑负样本不能只用曝光未点击否则模型学到的是标题匹配就行的相关性应该加入无结果搜不到和被翻页翻到很后面的商品作为 harder 负样本。样本时间要足够新电商搜索的用户偏好变化很快三个月前的样本权重一定要衰减。要按用户去重避免一个活跃用户贡献大量样本导致模型偏向他的偏好。模型选择方面如果团队没有专职算法先用XGBoost / LightGBM就能拿到不错的效果特征可以直接用原始值不用做太多归一化。如果团队有算法资源再考虑上深度模型DIN、DCN 这类提升空间一般在 3%-5% 的 AUC但工程复杂度会上升不少。3.3 重排去重、多样性、价格带约束与强制位置精排模型输出的是一个完全按照预估转化率排序的列表但这个列表直接给用户看是有问题的。我举几个真实场景前 10 个商品全是同一个店铺的爆款用户会觉得这是店铺广告而非搜索结果。前 20 个商品价格都在 5000 元以上搜索连衣裙的用户只是想买个 200 块的结果全是不符合预期的。精排模型把某个商品排到了第 3 位但这个商品实际库存只剩 1 件用户点进去就缺货。重排阶段就是专门解决这些模型看不到的业务约束的。常用的手段包括同店去重同一店铺的商品在同一页最多出现 2-3 个超出部分顺延到下一位置。多样性打散按类目、价格带、品牌做打散保证结果列表的覆盖面。实现上可以用贪心算法每次选下一个商品时计算它和已选商品的相似度惩罚与已选商品过于相似的结果。价格带约束通过用户历史成交价格或搜索词的属性意图预测用户接受的价格区间过滤掉明显偏离区间的商品。强制位置运营配置的广告位、活动位在指定位置插入自然结果顺延。重排规则看起来简单但实现时要特别小心规则冲突。比如同店去重和强制位置冲突时以哪个为准我的建议是优先级排序强制位置 广告位 业务过滤如库存 排序调整。3.4 用Spearman相关系数评估排序质量排序质量怎么量化光看点击率转化率有滞后性而且受流量分配影响很大。在离线阶段我强烈建议引入Spearman 相关性分析。具体做法是抽一批搜索词让运营或者标注人员对精排结果的前 20 个商品打相关度分0-4 分得到一个人工标注的排序。然后用算法排序的位次和人工标注的位次做 Spearman 相关分析。Spearman 相关系数衡量的是两个排序的单调一致性不关心具体分数只看排序是不是一致。公式上是把两个序列各自转成秩rank然后对秩做 Pearson 相关ρ 1 - (6 * Σ d_i²) / (n * (n² - 1))其中d_i是第 i 条数据在两个排序里的秩差n是数据条数。如果算法排序和人工标注排序高度一致ρ 接近 1如果正相关但不够强说明精排模型还没学透如果接近 0 甚至为负说明模型方向错了需要回去检查特征和样本。我在项目里每周跑一次这个分析把它作为精排模型迭代的离线验收关。模型改动后如果 Spearman 系数没有提升就不会推上线。这个指标比 AUC 更贴近真实排序质量的体感。4. 高并发检索从索引分片到缓存降级的层层防线搜索系统的延迟要求通常很苛刻用户输入关键词几十毫秒内必须出结果。但在大促场景下QPS 可能是平时的几十倍而且还伴随着商品数据频繁变更。这一章讲讲高并发检索的工程防线设计。4.1 高并发的瓶颈到底在哪里很多人以为高并发搜索引擎的瓶颈在 CPU 计算其实更常见的是这三类倒排索引的磁盘 IO索引文件太大内存装不下查询时频繁访问磁盘导致延迟抖动。内存中的排序开销每个搜索请求要合并多个词项的倒排链表、算分、排序候选集越大内存和 CPU 消耗越大。下游服务的依赖开销搜索服务要查商品详情、库存、价格、用户偏好特征每次查询要发起多次下游 RPC下游一慢整个链路就吊死。所以解决高并发问题不是单点优化而是分层治理索引层的分片设计、缓存层的命中率、依赖层的超时和降级缺一不可。4.2 索引分片把一个大索引拆成多份并行处理商品数量到了千万级以后单个索引分片很难同时满足低延迟和高吞吐。常用的做法是主分片 副本分片架构主分片把商品 ID 做哈希均匀分布到 N 个主分片例如 16 个。查询时把请求广播到所有主分片每个分片只搜自己那部分商品然后把各分片的结果合并排序。这样单次查询的耗时不受总数据量影响而是由单分片的数据量决定。副本分片每个主分片复制 M 份副本分散到不同机器。查询时可以负载均衡到任意副本显著提升 QPS 上限。这其实就是 Elasticsearch 的分片模型。但我遇到的实际问题是分片数不是越多越好。分片过多会导致每个分片的数据量太小查询请求广播带来的网络开销、合并结果的内存开销反而占主导延迟上升。经验上单分片数据量控制在 200 万-500 万条单分片 QPS 能力在 500-1000 左右整体容量需要按这个基准去规划分片数。4.3 查询缓存热词结果直接命中高并发场景下最能救命的永远是缓存。搜索系统里我一般会建三层缓存CDN 层针对搜索结果页的静态化场景但电商搜索结果千人千面CDN 缓存命中率不高只在无个性化要求的场景用。Redis 层对高频搜索词的结果做缓存key 是搜索词 分页 排序方式 用户分群value 是商品 ID 列表。TTL 设置一般在 5-30 秒因为商品排序是动态变化的TTL 太长会导致价格库存信息失真。本地缓存层Caffeine/Cache2k挂在搜索服务进程内命中速度是微秒级。但注意本地缓存的一致性维护困难只能缓存少量热点词并且要设置较短的 TTL。缓存命中率是搜索性能的生命线。我遇到过一个极端场景大促开场瞬间某个爆款关键词的 QPS 到了峰值Redis 和本地缓存都扛住了但第一波缓存未命中的请求全部穿透到 ES 集群。这里一定要做缓存击穿保护当某个 key 的查询没有命中时用一个分布式锁保证只有少数请求真正去查 ES其他请求等待第一个请求回填缓存后再复用结果。4.4 连接池与线程池调优这个点看起来基础但很多线上事故都是它引发的。搜索服务通常用 HTTP 或者 RPC 框架连接 ES / Milvus 集群连接池参数设置不当在流量高峰时会出大问题。我调优时重点关注三个参数最大连接数一般按单节点最大并发线程数 × 节点数来设置。比如 ES 集群 10 个节点每个节点并发跑 50 个搜索线程那连接池最大连接数可以给到 500 左右。给太多反而会导致 ES 端线程排队增加超时。最大等待时间连接池满时的等待时间不能太长一般 50-100ms 就该放弃本机连接快速失败让上游重试或者走降级逻辑。线程池隔离把搜索主链路线程池和下游特征查询线程池完全隔离。这样即使特征服务超时拖满了自身线程池也不会把搜索主链路的线程全部吞掉。还要强调一点调用下游必须设置有意义的超时时间。有一次我们某个特征服务的 TP99 从 5ms 涨到 200ms因为超时设置成了 3 秒搜索服务线程被大量占用整条链路跟着雪崩。后来把所有下游统一设成 50ms 超时 熔断问题立刻缓解。4.5 降级与熔断保住核心体验高并发治理的最终手段是有损降级。大促系统不怕功能降级怕的是整个搜索不可用。我设计的降级策略有四级一级降级关闭个性化特征。如果特征服务超时率高直接不查特征服务精排退化成只依赖文本相关性 静态商品的得分排序。损失一部分精准性但保住吞吐和延迟。二级降级关闭精排模型。如果搜索服务自身CPU/内存告急直接跳过精排模型只用粗排 重排规则输出结果。延迟会大幅下降但排序质量下降不少。三级降级只用关键词召回关掉向量召回。向量检索集群是独立组件如果它出问题或者耗时暴涨可以快速摘掉一路召回只用 BM25 顶住。这在大促预案里是必做的因为向量检索依赖的模型推理服务更容易出问题。四级降级缓存兜底 静态页。如果 ES 集群整体不可用就只能靠前面说的 Redis 缓存撑住热词流量冷词直接返回兜底结果或者引导用户换词。熔断器我用的是 Sentinel 或者 Resilience4j设置滑动窗口内错误比例超过 50% 就熔断 10 秒10 秒后自动半开尝试恢复。注意熔断后必须配合降级处理逻辑否则熔断了请求还是直接打到下游等于白做。5. 数据管道搜索引擎里的数据是怎么保持新鲜的搜索系统有一半的复杂度不在查询链路而在索引数据同步。商品上架、价格变更、库存变动、标题优化这些高频变化要在秒级反映到搜索引擎里否则用户搜出来的商品信息是过期的点击进去就缺货或者改价体验和转化都受影响。这一章讲高并发背景下的数据同步管道设计。5.1 从MySQL到搜索引擎的异步管道主流的电商架构里商品数据一般落在 MySQL 或者分布式数据库里搜索引擎ES/OpenSearch是独立的存储。两者之间的同步不能靠同步双写因为双写会让商品写入的RT变高而且每次写操作都耦合一堆下游数据库会先撑不住。正确做法是走异步管道链路是MySQL Binlog → Canal / Debezium → Kafka → 搜索数据消费服务 → 搜索引擎批量写入MySQL 的 Binlog 记录了所有商品表的增量变更Canal 伪装成 MySQL 从库拉取 Binlog把变更解析成结构化消息发到 Kafka。搜索数据消费服务从 Kafka 订阅消息做数据清洗、字段拼接、embedding 计算然后批量写入搜索引擎索引。这套链路的好处是解耦MySQL 写入不关心搜索引擎的吞吐能力搜索服务的写入压力波峰被 Kafka 削掉消费端可以根据搜索引擎的承受能力动态调整消费速率。5.2 Kafka 在高并发数据同步中的角色Kafka 的价值不只是解耦它最大的贡献是削峰填谷。大促期间商品会有集中改价、批量上下架的操作Binlog 消息瞬间暴涨。如果让这些变更直接打向 ESES 的 bulk 队列会被打爆。经过 Kafka 之后消费端按照 ES 的能力匀速写入消息在 Kafka 里排队不会丢失也不会把下游冲垮。消费端还有一个关键操作是合并消息。比如一件商品在 1 秒内改了三次价格Kafka 里会有三条变更消息。如果每条都触发一次 ES 文档更新就会造成重复的索引写入开销。正确做法是按商品 ID 做聚合每 500ms 或者每攒够一批消息把同一商品的最新状态取出来只发一条更新请求。实现上可以用窗口聚合或者借助 Kafka Streams但最简单的是在消费线程里用ConcurrentHashMap按商品 ID 暂存最新消息定时批量刷新。另一个坑是消息顺序。Binlog 天然有序但 Kafka 多分区消费时顺序会被打乱。我建议在 Canal 阶段把同一个商品 ID 的消息都发到同一个 Kafka 分区用商品 ID 作为分区 key这样消费端能保证同一个商品的更新是按发生顺序处理的避免旧数据覆盖新数据的问题。5.3 双写与幂等一致性的底线异步管道做久了一定会遇到消息丢失或者重复投递的问题。所以对写入搜索引擎的操作必须做幂等同一商品的多条更新消息无论执行一次还是多次最终索引里的数据都是同一个正确状态。工程上最简单的幂等方案是全量覆盖写每次更新商品时不是只更新变更的字段而是把整个商品的当前全量快照写入索引。这样即使旧的更新消息晚到它写入的是旧快照但新消息随后到达会覆盖成新快照最终状态一致。这种方案牺牲了一点写入数据量但换来了极强的健壮性。复杂一点的方案是带版本号的version字段更新时用乐观锁控制只有更大版本号才能覆盖。这个需要搜索引擎支持条件更新ES 的internal版本控制已经废弃了现在要用external版本或者if_seq_noif_primary_term实现成本稍高但能精确避免乱序覆盖。5.4 动态库存与价格这种秒级变化字段怎么处理商品的价格和库存是变化最频繁的字段秒级变动是常态。把每一个变动都同步到搜索引擎既不现实也没必要因为搜索结果页只是一个入口真正的价格和库存要以商品详情页接口为准。我的做法是索引里只保证近似的实时性将库存字段拆成几个粗粒度档位有货、少量、无货而不是精确数字这样库存档位变化不会特别频繁搜索结果页用档位做过滤就行。价格字段则在索引里保留一份价格区间精确价格在搜索结果渲染时通过商品详情服务的聚合接口批量获取用缓存顶住。最后管道再健壮也需要兜底。每个小时跑一次全量索引重建任务比对 MySQL 和搜索引擎的数据把漏掉的、错乱的商品全部纠正过来。全量重建期间要避免对线上查询造成影响建议用重建到新索引 → 切换别名的方式而不是原地更新。6. 搜索质量的评测与持续迭代没有评估就没有优化搜索优化是一个永无止境的循环而循环的地基是评测体系。没有评测你就分不清这次改动到底是变好了还是变差了更不知道下一步该优化哪里。我见过很多团队花大力气上了向量检索、上了精排模型但因为评测体系缺失上线后根本说不清楚提升了什么、降低了什么。这一章把评测体系讲透。6.1 离线指标NDCG、MRR与召回率离线评测是搜索优化的第一道关卡。它的核心思路是准备一批带标注或者带用户行为日志的查询数据集用算法跑出排序结果然后计算指标。RecallK前 K 个结果里包含应该被召回的商品的比例。主要衡量召回阶段有没有把真正相关的商品漏掉。工程上我会分别统计 BM25 单路、向量单路、混合召回三个版本的 RecallK用来判断各路召回的独立贡献。MRRMean Reciprocal Rank第一个相关结果出现的位置的倒数衡量用户能不能最快找到第一个想要的商品。MRR 会影响用户对搜索的第一印象。NDCGNormalized Discounted Cumulative Gain最常用的排序质量指标它考虑整个排序列表的质量并且位置越靠后增益衰减越厉害。计算方式是把每个位置的相关性得分除以位置的对数衰减项再除以理想排序下的最大可能得分得到归一化的 0-1 分数。这几个指标在每次迭代中都要同时看因为它们反映的是不同维度。一个常见的误判是NDCG 提升但对用户来说体感没变化很可能是提升集中在第 5-10 名这种用户很少翻到的位置这时候优先优化 MRR 和第一名相关度更有效。6.2 相关性人工标注与Spearman一致性离线指标的准确性取决于标注数据但工业界的数据标注成本很高。所以除了常规标注样本外我一直坚持把人工标注的排序结果和算法排序结果用Spearman 相关系数做对照分析。这个分析特别适合回答一个模糊但关键的问题排序结果看起来是那么回事吗做法是这样的每周抽 500 个搜索词覆盖高频词、中频词、长尾词。让 3 个标注人员对每个词的前 20 个商品独立打 0-4 分的相关度分。汇总每个人对 top 20 的排序和线上算法实际排出的顺序做 Spearman 相关分析。分频次统计相关系数高频词的 ρ 一般要大于 0.8长尾词大于 0.6 就算健康。如果某个词群的 Spearman 系数持续偏低说明算法对这个词群的理解有系统性问题需要专门排查比如是不是分词有问题、是不是缺失了某些同义表达、是不是向量模型在这个类目上没训练好。这个分析我从上线以来每周跑是发现长尾问题最有效的手段。6.3 在线AB实验与用户行为反馈离线指标提升不代表线上效果就一定会变好因为离线数据永远覆盖不了真实的用户行为变化。所以搜索优化的每一个重要改动都要走 AB 实验验证。搜索 AB 实验有两个特殊之处实验流量按用户分桶保证同一个用户多次搜索都落到同一个桶否则用户前一秒看到的是新排序、后一秒看到的是旧排序体验会很奇怪数据也混乱。核心指标和护栏指标要一起看。核心指标一般是搜索点击率CTR、搜索转化率CVR、搜索成交 GMV护栏指标包括搜索无结果率、搜索跳失率、搜索平均点击位次。有时候新算法 CTR 提升了但 CVR 反而下降说明用户被标题吸引点进去但商品不符合预期这时候要回去调特征而不是只看 CTR。AB 实验的样本量计算可以用最小样本量公式但工程上我习惯先小流量比如 5%跑 3-5 天观察指标趋势和置信区间是否稳定再放量到 20%最后全量。千万不要第一天看数据涨了 1% 就急着全量搜索结果的波动受大促、季节、品类活动影响很大至少观察一个完整的自然周。6.4 搜索日志驱动的特征迭代闭环搜索系统的每一项优化最终都要回归到数据闭环。我的日常工作流是这样的从搜索日志里每天提取用户行为搜索词、曝光商品、点击商品、成交商品、翻页深度、无点击会话。把这些行为数据反哺到两个地方一是精排模型的训练样本二是特征平台里的统计特征。每周分析一次搜索词聚类找出高搜索量、低转化率的词群定向排查相关性或者精准性问题。举个例子我们曾经发现蓝牙耳机这个词的搜索量很大但转化率低于平均值。通过日志分析定位到用户点进详情页后大量跳出原因是前排的商品大多是几十块钱的杂牌耳机用户不信任。进一步检查发现精排模型里历史点击率特征对杂牌低价耳机给了过高的权重因为过去这类商品的点击率确实高但转化率低。这个问题的解法不在搜索排序本身而是要引入品牌力、点击后转化率这类更接近转化的特征甚至直接对某些类目做品牌过滤。这一类发现没有日志驱动的闭环分析是不可能定位到的。搜索系统就是这样每一个环节调优之后又会产生新的问题和新的数据逼着你继续往下迭代。所谓精准性和相关性的优化说到底是在不断理解用户意图和数据规律的过程中逼近更好的体验。高并发检索做的是给这个迭代过程提供足够稳定和快速的基础设施两者互相成就缺一不可。