网站做起来之后,最让人难受的一种状态是:流量不算少,页面打开速度也还行,用户点进来逛了两三页就走了,购物车空着,评论区冷清,后台看跳出率一路往上走。这时候团队里一定会有人提一句“上个相关性推荐吧”。但“相关性推荐”这五个字经常被当成一颗万能药吞下去——上了推荐位、调了权重、加了几路召回,数据还是那个样子。问题不在推荐算法本身,而在于很多人从第一天起就没搞清楚:相关性推荐到底是为了解决什么问题而存在的。它不是给网站加一个“猜你喜欢”的装饰模块,也不是为了把首页塞得更满,它真正要处理的是用户意图与站点内容之间的错配,以及流量在站内的低效分发。这篇文章我按从业者的视角,把这个问题的来龙去脉、技术骨架、落地步骤和踩过的坑一次性讲清楚,适合正在做内容站、电商站、工具型产品或者社区产品的同学参考,也适合刚接手推荐模块、不知道从哪儿下手的新人对照着看。
1. 相关性推荐究竟在解决什么问题
1.1 用户不是没得选,而是选不动
绝大多数网站的困境不是内容太少,而是内容太多。一个运营了两年的内容站,随便也有几千篇文章;一个中型电商站的SKU轻松过万;一个社区论坛的帖子数量是以十万计的。用户从搜索引擎或者外部渠道进来,落地页往往只有一篇内容、一个商品详情页,看完之后面临的选择是:要么自己去找,要么关掉页面。而人天生是懒得找的——这不是用户的问题,是设计的问题。相关性推荐的第一层价值,就是把“用户自己找”变成“系统主动递到眼前”,把选择成本从用户身上转移到系统身上。
这件事在体验上的差异非常直观。用户在一篇讲“家用净水器怎么选”的文章底部,如果看到的是站内随机抓取的五篇新闻,他大概率直接划走;如果看到的是“滤芯更换周期”“厨下式与台式对比”“某类水质的处理方案”这类高度同主题的内容,他继续阅读的概率会明显上升。原因很简单:用户当前的注意力还停留在这个主题上,他的意图是连续的,而随机推荐等于把这个连续的意图硬生生掐断。相关性推荐要做的,就是在用户意图还没消散的那几十秒里,把下一个能承接住这个意图的入口递过去。
从数据上看,这类断点非常容易识别。跳出率高、单次会话页面浏览数低、站内搜索使用率低但退出率高、详情页到列表页的回流比例异常——这些都是“选不动”的典型信号。我在多个项目里反复验证过一个结论:只要站点内容量超过用户在一次会话内能浏览的上限,不做相关信息分发,流量就一定会沉淀不下来。这跟内容质量关系不大,跟分发效率关系极大。
1.2 流量分发效率才是真问题
很多人把相关性推荐理解成“提升点击率的手段”,这个理解偏了。点击率只是结果指标,真正的目标是流量分发效率。什么叫分发效率?简单说就是:一份流量进站之后,能不能被尽量充分地消化掉,而不是集中在头部几个页面上反复打转。
这里有个很典型的马太效应。不做推荐的站点,流量几乎全部堆积在首页、类目页前几屏、以及少数几个爆款内容上。长尾内容发出去之后基本没有自然曝光,全靠外部渠道导流,一旦停止投放就彻底沉寂。这对运营的伤害是隐性的但很深:编辑辛苦产出的长尾内容没有回报,慢慢就没人愿意做深度内容了;商品侧的长尾SKU没有曝光,库存周转就上不去;社区的优质老帖沉底,新用户看到的永远是最新的水帖。
相关性推荐解决的就是这个分发结构问题。它把“位置决定曝光”变成“匹配度决定曝光”,让一个排在第三十页但和当前用户意图高度吻合的内容,有机会出现在这个用户的视野里。这不是简单的流量再分配,而是让内容的价值和用户的需求真正对上号。我在一个内容型项目里做过对比,接入基于主题标签的召回之后,站内长尾内容的曝光占比从不到两成提升到了四成上下,同时头部内容的曝光绝对量并没有掉,因为总体的页面浏览深度上去了。这就是效率提升带来的增量,而不是零和博弈。
1.3 四类站点的具体病灶对照
不同形态的站点,“相关性问题”的表现完全不一样。笼统地谈推荐没有意义,得先对号入座。
| 站点类型 | 典型病灶 | 相关性推荐要承接的目标 | 关键指标 |
|---|---|---|---|
| 内容/媒体站 | 单篇阅读后无去处,长尾内容零曝光 | 延续阅读意图,拉动页面浏览深度 | 人均浏览页数、二次阅读率 |
| 电商站 | 详情页跳出高,关联品类不敢推 | 承接当前商品的替代与互补需求 | 加购率、客单价、跨类目点击 |
| 工具/SaaS站 | 用户只用到一个功能就流失 | 引导发现相邻功能,提升功能渗透 | 功能使用广度、次周回访 |
| 社区/论坛 | 老帖沉底,新用户看到的内容质量低 | 按兴趣分发历史优质内容 | 回访率、发帖互动率 |
这张表我建议每个接手推荐模块的人都先填一遍。因为你会发现,同样是“相关性”,内容站关心的是主题连续性,电商站关心的是替代品和互补品,工具站关心的是任务链路,社区关心的是兴趣圈层。这四种“相关”在算法实现上的侧重点完全不同——内容站靠文本语义,电商站靠类目树和行为共现,工具站靠功能使用序列,社区靠用户画像和互动图谱。先搞清楚你在解决哪一类问题,再去选技术方案,顺序反了就是白干。
我见过一个团队,做的是工具型产品,却照搬了内容站的标签相似度方案,结果推荐出来的全是“同一功能的重复入口”,用户点进去发现还是那个页面,体验极差。后来改成按任务链路推荐——用户在用导出功能,就推数据清洗和格式转换——效果立刻不一样。这个教训很贵:相关性不等于相似性,相似是内容层面的,相关是任务层面的。
2. 相关性推荐的技术骨架与选型取舍
2.1 召回、排序、重排三段式的由来
工程上做推荐,几乎绕不开召回、排序、重排这三段。很多人知道这个结构,但说不清为什么非得这么分。原因其实很朴素:候选集太大,不可能对全量内容做精细打分。
假设站点有十万条内容,每次请求都要给这十万条算一个相关性分数,再排序取前六,单次推理的算力开销和时间延迟都扛不住,尤其是页面首屏有明确的加载时间要求。所以必须先用轻量的方法把候选集从十万级压到几百级,这一步叫召回;然后用相对重的模型对几百条候选做精确打分,这一步叫排序;最后再根据业务规则做打散、去重、多样性控制,这一步叫重排。三段式的本质是用计算复杂度换响应速度,每一段都在做“用便宜的方法砍掉大部分,用贵的方法精挑细选”。
这个取舍带来的直接后果是:召回决定上限,排序决定精度,重排决定观感。如果召回阶段漏掉了真正相关的内容,后面排序再准也救不回来,因为候选池里根本没有。这是新手最容易犯的错——一上来就死磕排序模型,AUC 调到零点八几,线上效果却纹丝不动,因为召回压根没把人想要的东西捞出来。我在实际项目里的经验是,先把召回做宽做全,宁可多召回一些再让排序去筛,也不要为了性能把召回砍得太窄。召回的多样性比精确性更重要,排序的精确性才是重点。
另外要提醒一句,对于中小站点,三段式不一定都要上。内容量在几千条以内、并发不高的场景,直接做一层带缓存的相似度计算就够了,硬套三段式反而是过度设计。架构要跟着数据规模走,不是跟着别人的技术博客走。
2.2 “相关”的三种定义:词面、行为、语义
“相关”这个词听起来很直观,落到实现上其实有三种完全不同的计算口径,选错口径是很多推荐效果不佳的根源。
第一种是词面相关,靠文本的词汇重叠度来判断。经典做法是 TF-IDF 加权加余弦相似度,或者 BM25。优点是实现简单、可解释性强、不需要用户行为数据;缺点是完全不理解语义——“手机壳”和“保护套”在词面上几乎没关系,但它们是同一个东西。这类方法适合冷启动阶段,或者内容文本质量高、用词规范的场景。
第二种是行为相关,靠用户群体的行为共现来判断。经典的协同过滤就是这个思路:看过 A 的人也看过 B,那 A 和 B 就相关。优点是能捕捉到词面方法抓不到的关系,缺点是对数据量有要求,新内容新用户完全没有行为数据就没辙,而且容易陷入热门偏置。
第三种是语义相关,用向量化模型把内容映射到稠密向量空间,再算向量距离。这个口径能同时兼顾“手机壳”和“保护套”这类同义表达,也能处理跨语言的场景,是目前实践中用得比较多的方向。代价是需要模型推理,有算力成本和更新维护成本。
实操中的合理做法是三种口径混着用,而不是选一个。我在项目里的配置一般是:语义召回打底保证覆盖面,行为召回补充那些语义上不相似但用户实际会连着看的内容,词面召回作为兜底和冷启动的补充。三路召回各自出候选,合并去重后交给排序。这个混合结构看起来笨,但线上表现比单一路径稳定得多。
2.3 冷启动不是补丁,是设计前置条件
几乎所有人做推荐都会遇到冷启动:新用户没有历史行为,新内容没有曝光数据。很多团队的处理方式是在主流程之外挂一个“冷启动兜底策略”,热门榜单、人工精选、随机推荐之类。这种处理方式的问题在于,冷启动不是异常情况,它是常态——每天都有新用户进来,每天都有新内容发布,如果你的架构把冷启动当异常,那你的系统每天都在异常状态里跑。
正确的做法是把冷启动当成设计的前置条件。对内容侧,新内容发布时就应该带上完整的结构化标签(主题、类目、关键词、实体),让它能够立刻进入基于内容的召回通道,而不是等它积累够曝光数据。对用户侧,新用户进站的前几次会话应该走“探索优先”的策略,用少量高信息量的曝光快速建立初步画像,而不是直接推最热门的内容——热门内容对所有人一样,学不到任何个体差异。
这里有个很实用的技巧:新用户的第一次推荐,用“主题聚类后的代表内容”而不是“最热内容”。比如内容站有二十个主题簇,第一次给新用户每个簇推一条最有代表性的,用户点哪个簇,下次就重点往那个方向推。这样三四次会话之后,基本的兴趣方向就有了。这个策略我实测下来,比直接推热门的次周回访率要好不少,原因是热门内容的点击是“被动点击”,用户点完就完了,而主题代表内容的点击能反映出真实的偏好倾向。
2.4 最小可用特征集怎么定
特征工程这块最容易失控。一开始想着多加点特征总没坏处,结果特征越堆越多,离线评估涨一点,线上没动静,维护成本还上去了。我的建议是先用一个最小可用集跑通链路,再按需增量。
最小可用集一般包含四类:内容侧特征(主题标签、类目、发布时间、文本向量)、用户侧特征(历史点击的主题分布、活跃度分层)、上下文特征(当前页面所属主题、来源渠道、设备类型、时段)、交叉特征(用户主题偏好与内容主题的匹配度)。这四类凑齐,一个能用的相关性推荐基本就有了。
需要强调的是上下文特征的价值经常被低估。用户在移动端和桌面端的行为模式差别很大,前者更偏向短平快的消费,后者更容易深度阅读;从搜索渠道进来的用户意图明确,从信息流渠道进来的用户意图模糊。同样的内容,在不同的上下文里相关性是不同的。我做过一个测试,只加了一个“来源渠道”特征,推荐位的点击率就有可观变化,这说明上下文信息比很多团队想象的重要。
至于要不要上深度学习模型,我的判断标准很简单:当你的样本量到百万级、特征维度到几十维、并且已经有稳定的离线评估体系时,再考虑。在这之前,逻辑回归或者梯度提升树加人工特征,完全够用,而且可解释性更好,出问题的时候能查得动。
3. 从零落地一个相关性推荐模块
3.1 埋点与数据口径先对齐
这一步听起来枯燥,但它是整个项目成败的分水岭。我见过太多项目,模型做得很漂亮,最后发现埋点里的“点击”把页面自动加载也算进去了,或者“曝光”没有做可视区域判定,导致 CTR 这个指标从一开始就是错的。数据口径错了,后面所有的评估、调参、AB 实验全是空中楼阁。
需要对齐的口径至少有这几个:曝光怎么定义(元素进入可视区域多少比例、停留多久算一次有效曝光)、点击怎么定义(是否包含误触、是否去重)、会话怎么切分(三十分钟无操作切断还是按自然日)、以及最重要的——推荐位产生的点击和自然点击怎么区分。最后这一条如果没有做,你就永远不知道自己带来的增量是多少,因为无法归因。
我在实际项目里的做法是给每个推荐位一个独立的位次标识,点击事件里带上“位次、来源模块、召回通道”三个字段。这三个字段的价值在后面排查问题时体现得淋漓尽致——推荐效果不好,你能立刻知道是哪个召回通道出的候选、排在第几位、用户点没点。没有这三个字段,排查就是盲人摸象。
3.2 基线方案:标签相似度推荐
任何推荐项目都应该先上一个最笨的基线,跑通全链路,拿到一组可以对比的数字。基线不需要多聪明,关键是稳定、可解释、能快速上线。标签相似度就是这样一个合适的起点。
思路很直接:给每条内容打上主题标签,用户当前正在看的内容标签集合,和候选内容的标签集合做相似度计算,取分数最高的若干条。相似度用 Jaccard 或者加权余弦都可以。下面是一个可直接跑的最小实现:
import math from collections import Counter def build_tag_vectors(items): # items: [{"id": "a1", "tags": ["净水", "滤芯", "厨下"]}, ...] vocab = {} for item in items: for tag in item["tags"]: if tag not in vocab: vocab[tag] = len(vocab) vectors = {} for item in items: vec = [0.0] * len(vocab) for tag in item["tags"]: vec[vocab[tag]] = 1.0 vectors[item["id"]] = vec return vocab, vectors def cosine(a, b): dot = sum(x * y for x, y in zip(a, b)) na = math.sqrt(sum(x * x for x in a)) nb = math.sqrt(sum(y * y for y in b)) if na == 0 or nb == 0: return 0.0 return dot / (na * nb) def recommend(current_id, vectors, top_k=6): base = vectors[current_id] scores = [] for cid, vec in vectors.items(): if cid == current_id: continue scores.append((cid, cosine(base, vec))) scores.sort(key=lambda x: x[1], reverse=True) return scores[:top_k]这段代码简单到几乎没什么技术含量,但它的价值在于提供了一个可验证的起点。上线之后你会拿到一组基线数据:推荐位的点击率、人均浏览页数、长尾曝光占比。后面所有的优化都拿这组数字做参照,涨了还是跌了一目了然。没有基线的优化是玄学,有基线的优化才是工程。
这个方案的天花板也很明显:标签是人工打的,粒度粗,覆盖不全,而且完全依赖标签体系的质量。所以它注定只是起点。但在做复杂方案之前,先把它跑通,能帮你提前发现数据链路、埋点、渲染性能这些跟算法无关但同样致命的问题。
3.3 混合召回:内容召回加行为召回
基线稳定之后,第二步就是补召回通道。前面提过,单一召回通道的上限很低,混合召回是性价比最高的提升手段。
内容召回负责覆盖面和冷启动,靠文本向量或者标签相似度出候选。行为召回负责挖掘那些语义上不相似但用户实际会连着消费的内容,靠物品协同过滤或者序列模型出候选。两路召回各自取前 N 条,合并去重,再交给排序层统一打分。
行为召回的实现里有个细节值得单独说:共现矩阵要做时间衰减和热门惩罚。不做时间衰减,两年前的热门关联会一直占着位置;不做热门惩罚,所有物品都会跟那几个头部物品“相关”,因为头部物品的曝光基数太大,任何物品和它都有共现。常见的处理是给共现次数乘以一个与时间间隔负相关的系数,再除以物品流行度的某个幂次。这个幂次的取值一般在零点五到一之间,取零点五相当于比较温和的惩罚,取一相当于完全按流行度归一,实践中零点六到零点八之间比较常见,需要根据自己的数据分布调。
合并两路候选的时候,去重是个容易被忽略的点。同一篇文章可能同时被内容召回和行为召回捞出来,如果不去重,它在排序层的候选池里就占了两个位置,等于变相提高了它的权重。去重之后还应该记录它被几路召回命中——被多路同时命中的候选,通常质量更高,可以在排序特征里体现这个信息。
3.4 权重与参数的取值推导
调参这件事,最忌讳拍脑袋。每个参数背后都应该能说出一个理由,哪怕这个理由是“我试过几个值,这个最稳”。
先说召回条数的分配。假设排序层能承受的候选量是三百条,你有三路召回,怎么分?平均分是最省事的做法,每路一百条,但这样会让弱召回通道占据过多候选位。比较合理的做法是按各通道的历史命中率和覆盖率来分。如果内容召回的候选在排序后的点击贡献占比是三成,行为召回是六成,语义召回是一成,那三百条的分配大致可以按这个比例走,同时给每个通道留一个最小条数保底,防止某个通道彻底断供。
再说召回分数的融合权重。如果你不想做复杂的排序模型,直接用加权求和融合各路的召回分数也是一个可行方案:
final_score = w1 * content_score + w2 * behavior_score + w3 * semantic_score权重的选择可以从一个均等起点开始,然后用线上的点击数据做粗调。要注意各路分数的量纲不同,必须先做归一化(比如按本次候选集内的最大值做线性缩放),否则数值范围大的通道会天然占优。我在一个项目里就因为没做归一化,行为召回的分数普遍在几十的量级,内容召回在零点几,融合之后行为召回完全压倒其他两路,推荐结果退化成了纯热门,排查了半天才发现是量纲问题。
最后是重排阶段的多样性控制。最常用的是 MMR(最大边际相关性),核心思路是在相关性和多样性之间做权衡:
MMR = argmax[ λ * sim(item, current) - (1 - λ) * max sim(item, selected) ]λ 取一时退化成纯相关性排序,取零时变成了纯多样性。实践里 λ 在零点六到零点八之间比较常见,具体取值看你的推荐位有几个坑位——坑位越多,λ 可以取得越小,因为你有空间去铺多样性;坑位只有三四个,λ 就得大一些,保证每个坑位都足够相关。这个参数的调整最好用离线指标先粗筛,再上线验证,别在线上一版一版地试,太浪费流量。
3.5 离线评估与线上实验
离线评估最容易被误用。很多人拿离线 AUC 当成效指标,AUC 涨了就觉得方案成了,实际上离线指标只能用来筛掉明显不行的方案,不能用来证明方案可行。离线指标的职责是“排雷”,不是“拍板”。
真正能拍板的是线上实验。做实验有几个前提必须满足:分流要随机且稳定(同一个用户在整个实验周期内分到同一组)、样本量要够(用基础点击率反推,至少要到能检出预期提升幅度的量级)、实验周期要覆盖一个完整的行为周期(内容站一般至少一周,电商要考虑周末效应,至少两周)。这些条件不满足,实验结论就不可信。
实验期间要盯的指标不止一个。核心指标(点击率、人均浏览深度、转化)看方向,护栏指标(页面加载耗时、跳出率、投诉率)看有没有副作用。我见过推荐点击率涨了但页面加载时间翻倍的案例,最后算总账是亏的,因为加载慢导致的整体流失把推荐带来的收益吃掉了。推荐模块必须在性能预算之内,这不是可选项。
4. 常见问题与排查实录
4.1 推荐位永远是那几篇内容
这是最高频的问题。表现是推荐位翻来覆去就那几条,用户刷几次就失去兴趣。原因通常有三个。
第一是热门偏置。召回阶段没有做流行度惩罚,导致高曝光内容在所有候选里都排前面。解决办法是在召回分数里除以流行度的某个幂次,或者在重排阶段强制打散,同主题的内容最多连续出现一条。
第二是候选池太小。如果召回只覆盖了站内很小一部分内容,那自然翻来覆去就那些。这时候要检查召回通道的覆盖率——有多少内容在最近一周内至少被召回出过一次。这个比例如果低于三成,说明召回太窄,需要扩通道或者放宽召回阈值。
第三是没有做已读过滤。用户看过的内容还在推,观感很差。简单的做法是维护一个短期的已读集合(比如最近七天的浏览记录),召回时直接过滤掉。这里要注意过滤的粒度不能太粗,不能因为用户看过一篇讲净水器的文章,就把整个家居类目全部过滤掉。
4.2 点击率涨了但转化没动
这个现象很典型,尤其在电商场景。推荐位点击率上去了,但加购、下单没跟着涨,说明推荐的内容“看起来相关”但“买不动”。常见原因有两个。
一是推荐的全是同类替代品。用户在看一个具体商品,推荐位给的全是同价位的竞品,用户点进去比价,越比越犹豫,最后什么都没买。这时候应该补充互补品而不是替代品——卖相机推存储卡和相机包,比推另一台相机更容易成交。这个逻辑在内容站也成立,看一篇“选购指南”的用户,更需要“使用技巧”和“维护保养”,而不是另一篇“选购指南”。
二是相关性算得太“硬”,缺少探索。所有推荐都严格围绕当前主题,用户的兴趣面就永远打不开。合理的做法是留出百分之十到二十的坑位做探索,推一些相邻主题的内容,用实际点击来验证用户的兴趣边界。纯 exploitation 的系统短期指标好看,长期会把用户的兴趣越推越窄。
4.3 新用户、新内容、新栏目三无场景
新用户没有行为、新内容没有曝光、新栏目没有历史数据,三个“新”撞在一起,是推荐系统最难受的时刻。这种情况没有银弹,只能靠策略组合。
新用户走主题探索策略,前面已经讲过。新内容走内容召回通道,靠结构化标签和文本向量进场,同时给一个观察期的曝光配额,保证它有机会被验证。新栏目最麻烦,因为整个栏目都没有数据。我的做法是先做人工冷启动——运营挑一批代表性内容,人工指定它们的关联关系,跑两周攒够初始数据,再切到算法召回。人工干预在这个阶段不是退步,是必要的启动成本。
这里有个容易被忽略的点:新内容冷启动阶段的曝光配额要控制节奏。如果一次性给它几千次曝光,反馈信号会被稀释;如果只给几十次,统计上又不可靠。一般给到几百次曝光就能得到比较稳定的点击率估计,这个数量级可以根据自己站点的流量规模调整。
4.4 排查速查表
| 现象 | 最可能的原因 | 优先验证方式 |
|---|---|---|
| 推荐位重复率高 | 未做已读过滤或热门惩罚 | 统计一周内召回结果的去重数量 |
| 点击率高但停留时间短 | 相关性算的是词面而非意图 | 抽样看推荐内容与落地页的主题关联 |
| 推荐结果与当前页无关 | 召回通道串了,用了错误的上下文 | 打印请求上下文,核对召回入参 |
| 长尾内容仍无曝光 | 召回覆盖率不足 | 统计召回池覆盖的内容占比 |
| 页面加载变慢 | 推荐接口同步阻塞首屏 | 改成异步加载或服务端降级 |
这张表建议打印出来贴在工位上。推荐出问题的时候,先对照着走一遍,能挡掉八成以上的低级排查。
5. 效果衡量与长期迭代
5.1 指标不能只看点击率
点击率是最容易被操纵的指标。把推荐位做得更花哨、标题更夸张、位置更靠上,点击率都能涨,但这不代表相关性变好了。一个健康的指标体系至少要有三层。
第一层是行为层,比如点击率、人均浏览页数、停留时长、二次回访率。这层反映的是用户有没有被接住。第二层是价值层,内容站看的是阅读完成率和分享率,电商看的是加购率和客单价,工具站看的是功能渗透率。这层反映的是点击有没有产生真实价值。第三层是生态层,包括长尾内容的曝光占比、召回覆盖的内容比例、推荐结果的多样性分布。这层反映的是系统健不健康。
只看第一层,很容易做出一个“点击率很高但用户越来越窄”的系统。三层一起看,才能判断这个推荐是在做增量还是在透支。
5.2 迭代节奏与人力配比
推荐系统不是一个项目,是一个长期运营的东西。我见过的失败案例里,有一半不是技术不行,是上线之后没人管。
合理的节奏是:上线后的第一个月每周看一次数据,重点看召回覆盖率和多样性,把明显不合理的召回通道调掉;之后按月复盘一次,每次只动一个变量,动完观察两周。这个节奏看起来慢,但比一次改一堆东西然后搞不清是哪一项起作用要快得多。
人力配比上,如果只有一个开发同学兼着做,那策略就要收敛:先把内容召回做扎实,加上简单的去重和打散,这套东西能撑很久。不要在没有专职同学的情况下上复杂的排序模型,因为模型是需要持续迭代和监控的,没人维护的模型比规则还糟糕。推荐系统的复杂度应该和团队能承担的维护成本匹配,这是最容易被忽略的一条工程原则。
最后分享一个我自己反复验证过的小经验:每次大改召回策略之前,先用历史数据回放一遍。拿最近一周的请求日志,把新的召回逻辑在离线跑一遍,看看候选池的变化有多大、有没有明显的断层。这个过程花不了多少时间,但能提前发现很多上线之后才会暴露的问题,比如某个通道在特定上下文下完全召回不出东西。回放这一步做了,线上翻车的概率会低很多。