哔哩哔哩技术 01月07日
B站搜推大规模召回系统工程实践
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文深入介绍了B站搜索推荐系统中召回系统的架构设计与实现。该系统采用多级漏斗架构,召回作为首要环节,旨在从海量稿件中快速筛选出与用户需求匹配的内容。文章详细阐述了B站召回系统从最初的单体模块到云原生、可扩展框架的演变过程,重点介绍了merge服务和索引服务的架构设计,以及文本召回、x2i召回和向量召回等关键技术。此外,文章还探讨了如何通过稳定性建设来保障系统的可靠运行,并展望了未来在模型召回、组件化和自动化方面的优化方向。文章从工程实践角度出发,为理解大规模召回系统的设计提供了有价值的参考。

⚙️ **整体架构演进**: B站的召回系统经历了从引擎服务子模块到独立服务,再到云原生、可扩展框架的演变,解决了单体服务带来的代码复杂、维护性差等问题,最终实现了搜索推荐统一的召回框架。

🧮 **Merge服务与索引服务**: Merge服务作为召回的业务引擎,负责多通道召回、去重、过滤、打分和结果合并;索引服务则作为召回通道的载体,加载索引并执行检索逻辑,两者共同构建了在线检索侧的两级架构。

🔍 **多样化的召回技术**: 系统采用了文本召回、x2i召回和向量召回等多种技术,分别针对不同场景和需求。文本召回基于倒排索引进行关键词匹配;x2i召回基于NeighborHash索引实现高效的协同过滤;向量召回则利用faiss库进行相似向量检索。

⏱️ **实时性与稳定性保障**: 通过实时数据流更新索引、增量索引构建、分布式索引构建流程等方式,保证了召回的时效性;同时,通过完善的开发测试流程、监控告警体系和降级容灾策略,确保了系统的稳定运行。

🚀 **未来优化方向**: 未来,B站的召回系统将继续跟进新一代模型召回技术,提升组件化和可编排能力,并借助平台化建设提高自动化水平,降低运维成本,从而持续提升搜索推荐系统的效果。

通用工程 2025-01-07 12:01 上海

本文将从工程实践的视角出发,深入介绍B站搜推召回系统的架构设计与实现。

01.前言 


目前包括B站在内的主流搜索和推荐系统均采用多级漏斗的架构,主要涵盖召回、粗排、精排、重排等关键阶段。其中召回作为整个流程的首要环节,作用在于从海量的稿件集合中,快速高效地筛选出一小部分与用户需求和兴趣高度契合的稿件,作为后续排序阶段的输入数据。为了全面覆盖各类用户复杂多样的需求,通常采用多通道召回的策略。召回结果的优劣,也直接决定了搜推系统效果的上限。



召回系统的核心挑战在于如何在有限的时间与算力资源下处理大规模的数据,并且保持较高的召回率与准确率。随着业务的发展,B站搜推召回系统面临着数据规模的增长、时效性要求的提升、算法策略复杂度不断加大等一系列挑战。

面对这些挑战,经过多轮迭代与架构升级,形成了目前基于B站业务特点的大规模召回系统。本文将从工程实践的视角出发,深入介绍B站搜推召回系统的架构设计与实现。


02 整体架构


在搜推系统迭代历程之初,召回策略相对较为简单,召回仅仅被定位为搜推业务引擎服务内的一个子模块。伴随业务的持续拓展,单体巨型的引擎服务暴露出诸多问题,如代码复杂度过高、可维护性大幅下降、内存资源逼近瓶颈等均制约了系统的进一步迭代。将召回模块从引擎服务中拆分出来,构建为独立的服务,成为了当时解决燃眉之急的关键举措。



但很快由于召回策略迭代频率越来越高、召回候选集规模越来越大、召回通道数量快速增长,独立召回服务的架构也逐渐显得力不从心,难以持续支撑业务的高速发展,阻碍了系统效果的提升。具体而言,主要存在以下亟待解决的问题:

为了从根本上解决以上问题,我们重新设计了一套云原生、可扩展、配置化、搜推统一的召回框架。

在线检索侧基于计算存储分离的理念,采用merge服务+索引服务(searcher)两级架构:





离线侧由index-builder读取策略产出在hive/hdfs上的数据构建base索引;同时支持通过实时数据流更新索引,秒级生效。


03.merge服务


召回merge服务定位为召回的业务引擎,采用配置化、算子化的设计模式,能够灵活应对不同业务场景下的召回需求。merge服务在设计上重计算轻存储,依赖多个外部服务提供相应能力:索引服务提供召回候选、kv服务获取用户数据、推理服务计算user-embedding、score计算相关性打分、统一正排提供正排信息用于过滤。一个典型的召回流程主要由trigger生成、请求各召回通道获得候选、通道间候选去重、正排过滤与打分、通道内候选排序、通道结果合并等流程组成。



在不同业务场景中,召回流程大体相似,仅在一些召回参数和局部流程上有所差异。merge服务通过将各模块封装为可复用的算子/组件,通过编排和配置,即可以灵活快速地支持不同业务场景下的召回策略迭代需求。常用的算子包括:不同类型的trigger获取、请求索引服务、请求多分片索引服务及结果合并、正排及过滤、Z字形merge等。为了尽可能地降低耗时,merge服务引入轻量级的DAG框架进行调度。



DAG框架的优势在于能够清晰地描绘出各个任务之间的依赖关系和执行顺序,通过合理规划任务路径,可以最大程度地增加逻辑并发。例如在召回流程中,不同类型的trigger生成往往不存在依赖,可以同时进行;多召回通道间的并发和相互依赖关系也能被方便地构建出来;正排过滤与打分也能够并行执行。


04.索引服务


索引服务的主要职能在于针对单召回通道精准且高效地生成召回结果,通过支持分片的方式横向扩展以应对上亿量级的数据规模,同时具备实时数据更新的能力,提供充足的时效性保障。

架构层面,索引服务从上到下可以分为四层:



常见的召回通道有基于纯文本的语义召回、基于类目/tag的召回、协同过滤、 双塔dssm召回等。

根据不同召回通道的特点,我们相应地设计了不同类型的索引服务以满足召回策略和性能需求,主要分为以下三类:

下面分别介绍各索引服务的设计实现。


4.1 文本召回


文本召回将是稿件的标题、描述、评论等做切词处理后建立倒排索引,再与用户的query进行匹配,常见于搜索场景。

检索时首先根据query的分词建立查询表达式,例如“周杰伦演唱会”,分词后再通过“周杰伦”和“演唱会”分别查询索引做表达式运算获得召回结果。



文本召回的索引由倒排和正排两部分组成:

查询流程为term->DocIdList->DocInfo,其中DocId为稿件插入索引时分配的内部递增Id,DocId小的排在倒排拉链头部。通常离线构建索引前,先根据offline-weight按降序排序,这样可以使优质的稿件优先被召回。



每条倒排链按DocId排序,也帮助在做表达式运算时,可以快速地跳到大于等于当前DocId的下一个DocId。

时效性方面,实时增量数据需要写入索引才能生效,而召回索引服务作为读qps远大于写qps的场景,设计上需要权衡检索性能与时效性。

消费kafka实时增量后,会先写入wal-buffer中,积攒部分数据后,在线builder会构建一份delta索引;query查询时会合并base索引和多个delta索引的查询结果;delta索引的数量变多会导致读放大,定期通过index-merger将多个小的delta索引合并,保证索引查询的性能。



除了基础的文本召回功能,我们也做了大量的优化工作,包括:

文本召回在B站搜索场景中占较大的比重,为综搜和各类垂搜提供标题检索、全文检索、评论检索等召回能力。


4.2 x2i召回


x2i召回多用于服务协同过滤通道,基于itemcf或者swing等算法离线挖掘出每个稿件的近似稿件列表,以用户的足迹作为trigger召回候选;也可以用于基于标签、类目的召回通道如tag2i、cat2i等。

数据类似kkv形式,pkey为trigger稿件id,skey为候选稿件id、value则可以填充候选稿件与trigger稿件的相似分静态数据。trigger从用户足迹中获取,一次召回多至上千trigger,对索引的查询性能要求极高。

我们采用B站自研的NeighborHash【1】【2】作为索引结构,缓存友好、查询性能高、吞吐大:



为了提升排序能力,我们在i2i检索的基础上支持双塔模型打分,用于替换原有的静态分排序;根据模型目标的不同,可以进一步提升通道的个性化、相关性、一致性。



i2i索引检索之后,将候选按照 av_id % shard_num 分配到多个shard中,然后去重、查emb索引、打分多shard并发,以最大程度降低耗时。打分方面,支持fp32、fp16精度及int32、int16、int8的量化打分,也基于simd优化打分规模与性能。此外,我们也支持按trigger分配召回quota,保证召回结果的多样性。


4.3 向量召回


向量召回将user和稿件映射到同一个向量空间,然后将稿件的embeding在离线构建好索引;用户请求时,在线生成user的embedding,然后在向量空间中检索最近的k个稿件作为召回候选。

我们基于facebook开源的向量检索库faiss【3】搭建了向量召回服务,faiss能快速在大规模向量数据中找到相似向量,支持多种索引格式,包括最常用的ivf与hnsw格式。

ivf基于倒排索引的思想:


由于faiss不支持实时增量,在时效性要求较高的场景,如新稿件召回,就法充分发挥向量通道的作用。为此我们设计了一套增量更新方案:



除了提升时效性,我们还在分类目索引、在线召回率监控、量化打分等方面扩展了向量召回的服务能力。


4.4 索引构建


索引构建通常放在离线进行,离线任务将原始的文本数据构建成上述索引格式,并转化为二进制形式,大大提升在线服务的启动速度。单机索引构建受限于内存资源无法支持不断膨胀的数据规模,有限的cpu资源也限制了索引产出的速度。为了提升扩展性和时效性,我们依托公司调度平台搭建了一套分布式的索引构建流程。



数据源产出后,会根据hash(key)%shard_num的方式均匀分到多个分片任务中进行处理。分片任务之间并行执行,首先进行预处理,然后由各种索引的builder将数据构建为便于加载的格式,并导出到文件系统;最后由数据平台配送到线上对应的索引服务上。

对于接入增量的索引服务,启动时除了加载天级基准索引,还需要回追kafka增量数据;一旦基准产出延迟或是增量qps大,则会导致索引服务启动时间大大拉长,给扩容带来潜在风险。为了加速服务启动,除了天级基准,离线还会定期基于增量数据构建major-dump索引。



在线服务启动时依次加载基准索引、major-dump索引,以及服务原地重启前可能产出的delta索引,可以大大降低需要回追的kafka增量,加速启动。


05.稳定性建设


召回作为搜推系统重要的一环,除了功能上持续迭代与性能深度优化,我们在多个维度做了大量工作为召回稳定性保驾护航。

首先,持续完善开发-测试-发布流程,提升上线前的异常发现能力,力求将潜在风险扼杀在摇篮之中;基于功能完善的debug平台,及时验证召回各阶段结果是否符合预期;接入班车流水线,在上线推全前对召回服务进行coredump检测,性能劣化检测等。



第二,大力强化监控告警体系的建设,在召回服务工程指标、在线召回漏斗数据、以及索引数据dqc等多层次进行监控,例如所有召回通道共有的漏斗监控,会实时监控各通道的召回数、粗排数、精排数以及通道独占召回数、粗排数、精排数;另外通道/索引特有的信息,如向量索引的召回率,x2i的拉链长度等,也会进行监控;从而实现线上问题的即时感知与精准预警,为快速响应与处理赢得宝贵时间。




第三,制定完备的降级容灾策略预案,支持通道维度、索引维度以及召回整体按比例的灵活降级方式,结合上游客户端召回兜底机制,在面对线上突发事故时,能够以最小的业务损失实现系统的平稳过渡。

最后,通过优化架构做到云原生全面达标,大幅加快召回服务的启动速度与扩容效率,有效提升系统的弹性与应变能力,从容应对各种复杂多变的业务场景与流量高峰挑战。


06.展望 


当前这套召回系统已经在B站推荐、搜索等多种业务场景下广泛应用。未来还将在以下方面持续迭代优化,助力搜推系统效果提升:


07.引用


【1】https://github.com/slow-steppers/NeighborHash

【2】https://arxiv.org/pdf/2409.0400

【3】https://github.com/facebookresearch/faiss


-End-

作者丨胡迪、明楼、HevLfreis浪小柒

开发者问答

关于搜推系统大规模召回,大家在实践中还有什么优秀的经验和方案?

欢迎在留言区分享你的见解~

转发本文至朋友圈并留言,即可参与下方抽奖⬇️

小编将抽取1位幸运的小伙伴获取扭扭龙+b站pu定制包

抽奖截止时间:01月10日12:00

如果喜欢本期内容的话,欢迎点个“在看”吧!


往期精彩指路


通用工程大前端业务线

大数据AI多媒体


阅读原文

跳转微信打开

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

召回系统 B站 架构设计 索引服务 云原生
相关文章