得物自研 DGraph4.0 推荐核心引擎升级之路

一、前言
DGraph 是得物自主研发的新一代推荐系统核心引擎,基于 C++语言构建,自 2021 年启动以来,经过持续迭代已全面支撑得物社区内容分发、电商交易等核心业务的推荐场景。DGraph 在推荐链路中主要承担数据海选和粗排序功能,为上层精排提供高质量候选集。
核心技术特性:
索引层 - 支持 KV(键值)、KVV(键-多值)、INVERT(倒排)、DENSE-KV(稠密键值)等。索引存储支持磁盘 & 内存两种模式,在预发等延迟压力低场景,通过磁盘索引使用低规格服务器提供基本服务。线上场景使用内存索引保证服务稳定性,提供毫秒级延迟响应。索引更新支持双 buff 热更新【内存足够】、服务下线滚动更新【内存受限】、Kafka 流式数据实时更新等三种模式。
查询层 - 支持向量检索 IVF & HNSW、键值(KV)查询、倒排检索、X2I 关联查询、图查询。对外提供 JavaSDK & C++ SDK。
系统依赖架构:
索引全生命周期管理由得物索引平台 DIP 统一管控。
服务发现基于 ZooKeeper(zk)。
集群资源调度基于得物容器平台,目前已经支持 HPA。
服务规模:
目前在线 100+集群,2024 年双 11 在线突破了 100W qps。
本文主要介绍 DGraph 系统在 2024 年的一些重要改进点。主要包括两次架构调整 + 性能优化 + 用户体验提升方面的一些工作。
二、架构升级
2.1 垂直拆分业务集群支持
在 2023 年前,DGraph 系统始终采用单一集群架构提供服务。该架构模式在平台发展初期展现出良好的经济性和运维便利性,但随着业务规模扩张,单集群架构在系统层面逐渐显露出三重刚性约束:
存储容量瓶颈 - 单节点内存上限导致数据规模受限;
网络带宽瓶颈 - 单物理机 Pod 共享 10Gbps 带宽,实际可用带宽持续承压,推荐引擎业务中部分核心集群 200 余张数据表(单表需 20 分钟级更新)的实时处理需求已遭遇传输瓶颈;
计算能力瓶颈 - 单实例最大 64 核的算力天花板,难以支撑复杂策略的快速迭代,核心场景响应时效与算法复杂度形成显著冲突;
稳定性 - 大规格集群对于容器调度平台不友好,在扩容、集群故障、集群发布时耗时较久;基于得物平台推荐数据量增长和算法迭代需求,我们实施业务垂直拆分的多集群架构升级,通过资源解耦与负载分离,有效突破了单节点资源约束,为复杂算法策略的部署预留出充足的技术演进空间。
系统改进点是在 DGraph 中增加了访问了其他 DGraph 集群 & FeatureStore 特征集群的能力(图 1)。为了成本考虑,我们复用了之前系统的传输协议 flatbuffers,服务发现仍基于 ZooKeeper。

图 1 DGraph 访问架构改进
改造的难点在图化集群!
目前推荐业务的核心场景都进行了图化改造,图化查询是把多路召回、打散、融合、粗排等策略打包到一个 DAG 图中一次发送到 DGraph,DGraph 的算子调度模块根据 DAG 的描述查询索引数据 & 执行算子最终把结果返回给业务系统,但这些 DAG 图规模都很大,部分业务 DAG 图涉及 300+算子,因此如何在垂直拆分业务中把这些 DAG 图拆分到不同的 DGraph 集群中是一个非常复杂的问题,我们主要做了三方面改进:
DAG 管理 - 集群分主集群和从集群【多个】,DAG 图部署在存在主集群中,DIP 平台会分析 DAG 的拓步结构并把属于从集群的部分复制出来分发给从集群,为了保证 DAG 的一致性,只允许从主集群修改 DAG 图;
集群划分 - 通常按召回划分,比如 Embedding 召回、X2I 召回、实验召回可以分别部署在不同的集群,另外也可以把粗排等算力需求大的部分单独放在一个集群,具体根据业务场景调整;
性能优化 - 核心表多个集群存放,减少主集群和从集群间数据交换量。

图 2 DGraph 业务垂直拆分集群
2.2 分布式能力支持
垂直拆分集群,虽然把推荐 N 路召回分散到了 M 个集群,但是每个集群中每个表依然是全量。随着得物业务的发展,扩类目、扩商品,部分业务单表的数据量级已经接近单集群的存储瓶颈。因此需要 DGraph 中引入数据水平拆分的能力。

图 3 DGraph 分布式集群架构图
在 DGraph 分布式架构设计中,重点考虑了部署成本优化与业务迁移工作量:
分布式集群采用【分片数 2】×【双活节点 2】×【数据副本数 2】的最小拓扑结构,理论上需要 8 台物理节点保障滚动更新与异常容灾时的稳定性。针对 CPU 负载较轻的场景,为避免独立 Proxy 集群带来的额外资源开销,DGraph 将 Proxy 模块和 DGraph 引擎以对称架构部署到所有节点,通过本地优先的智能路由策略(本地节点轮询优先于跨节点访问)实现资源利用率与访问效率的平衡;
在业务兼容性方面,基础查询接口(KV 检索、倒排索引、X2I 关联查询)保持完全兼容以降低迁移成本,而 DAG 图查询需业务侧在查询链路中明确指定 Proxy 聚合算子的位置以发挥分布式性能优势。数据链路层面,通过 DIP 平台实现索引无缝适配,支持 DataWorks 原有任务无需改造即可对接分布式集群,同时增量处理模块内置分片过滤机制,可直接复用现有 Flink 实时计算集群进行数据同步。
三、性能优化
3.1 算子执行框架优化
在 DGraph 中,基于 DGraph DAG 图(参考图 9)的一次查询就是图查询,内部简称 graphSearch。在一个 DAG 图中,每个节点都是一个算子(简称 Op),算子通过有向边连接其他算子,构成一个有向无环图,算子执行引擎按 DAG 描述的关系选择串行或者并发执行所有算子,通过组合不同算子 DAG 图能在推荐场景中灵活高效的完成各种复杂任务。
在实际应用场景中受 DAG 图规模 & 超时时间(需要控制在 100ms 内)限制,算子执行框架的效率非常重要。在最开始的版本中我们使用过 Omp & 单队列线程池,集群在 CPU 负载低于 30%时表现尚可,但在集群 CPU 负载超过 30%后,rt99 表现糟糕。在降本增效的背景下,我们重点对算子执行框架进行了优化,引入了更高效的线程池 & 减少了调度过程中锁的使用。优化后目前 DGraph 在 CPU 压力超过 60%依然可以提供稳定服务。

图 4 DGraph 算子执行框架优化
线程池优化:将原 1:N 的线程池-队列架构调整为 M:N 分组模式。具体实现为将 N 个工作线程划分为 M 个执行组(每组 N/M 线程),各组配备独立任务队列。任务提交采用轮询分发机制至对应组队列,通过资源分区有效降低线程调度时的锁竞争强度。
调度器优化:在 DAG 调度过程中存在两个典型多写场景
前驱算子节点完成时需并行更新后继节点标记;
DAG 全局任务计数器归零判断。原方案通过全局锁(Graph 锁+Node 锁)保障原子性,但在高负载场景引发显著锁竞争开销,影响线程执行效率。经分析发现这两个状态变更操作符合特定并发模式:所有写操作均为单调增减操作,因此可将锁机制替换为原子变量操作。针对状态标记和任务计数场景,分别采用原子变量的 FetchAdd 和 FetchSub 指令即可实现无锁化同步,无需引入 CAS 机制即满足线程安全要求。
3.2 传输协议编码解码优化
优化 JavaSDK - DGraph 数据传输过程:在 DGraph 部分场景,由于请求引擎返回的数据量很大,解码编码耗时占整个请求 20%以上。分析已有的解码编码模块,引擎在编码阶段会把待传输数据编码到一个 FlatBuffer 中,然后通过 rpc 协议发送到业务侧的 JavaSDK,sdk 解码 FlatBuffer 封装成 List<map> 返回给业务代码,业务代码再把 List<map> 转化成 List<业务 Object>。过程中没有并发 & sdk 侧多了一层冗余转换。
优化方案如下:
串行编码调整为根据文档数量动态调整编码块数量。各子编码块可以并发编码解码,加快编码 &解码速度,提升整体传输性能;
sdk 侧由 Doc -> Map -> JavaObject 的转化方式调整为 Doc -> JavaObject,减少解码端算力开销。

图 5 DGraph 传输编码解码过程优化
四、用户体验优化
4.1 DAG 图调试功能优化
目前我们已经把 DGraph DAG 图查询的调试能力集成到 DIP 平台。其原理是:DGraph 的算子基类实现了执行结果输出,由于算子的中间结果数据量极大,当调试模块发现调试标志后会先把当前算子的中间结果写入日志中,数据按 TraceID + DAGID+ NodeID 组织,最终这些数据被采集到 SLS 日志平台。

图 6 DGraph DAG 图查询调试
从 DIP 平台调试 DAG 图请求,首先通过 DGraph JavaSDK 的调试入口拿到 DAG 图请求 json,填入 DIP 平台图请求调试入口,发起请求。索引平台会根据请求体自动关联 DAG 图并结合最终执行结果通过页面的方式展示。DIP 平台拿到结果后,在 DAG 图中成功的算子节点标记为绿色,失败的节点标记为红色(图 6)。点击任意节点可以跳转到日志平台查看该节点的中间结果输出。可用于分析 DAG 图执行过程中的各种细节,提升业务排查业务问题效率。
4.2 DAG 图支持 TimeLine 分析
基于 Chrome 浏览器中的 TimeLine 构建,用于 DGraph DAG 图查询时算子性能分析优化工作。TimeLine 功能集成在算子基类中,启动时会记录每个算子的启动时间、等待时间、完成时间、执行线程 pid 等信息,这些信息首先输出到日志,然后被 SLS 日志平台采集。用户可以使用查询时的 TraceID 在日志平台搜索相关的 TimeLine 信息。

图 7 DGraph DAG 图例子

图 8 使用浏览器查看 DGraph DAG 图 TimeLine
当我们拿到请求的 TimeLine 信息后,通过浏览器加载可以通过图形化的方式分析 DAG 执行过程中耗时分布。图 7 是一个 DAG 请求,它有 9 个算子节点,图 8 是它的一次请求的 TimeLine。通过分析这些算子的耗时,可以帮助我们定位当前 DAG 图查询的瓶颈点在哪里,从而精准去解决性能方面的问题。
4.3 DAG 图支持动态子图
在 DAG 图召回中,业务的召回通常都带有一些固定模式,比如一个业务在一个 DAG 图召回中有 N 路召回,每一路召回都是:① 查找数据;② 关联可推池;③ 打散; 它们之间的区别可能仅仅是召回数据表名不同或者传递的参数不同。通常我们业务调整或者算法实验调整只需要增加或者减少部分召回,原有模式下这些操作需要去新增或者修改 DAG 图,加上算法实验很多,业务维护 DAG 图的成本会非常高。
DAG 动态子图的引入就是为了解决这类问题,首先我们在 DAG 图中配置一个模板子图,它仅仅描述一个行为模式,代表会涉及几个算子,算子之间的关系如何,实际的参数以及召回路的数量则由业务方在发起请求时动态决定。子图的执行和主图的执行共用同一套调度框架,共享运行时资源以降低运行开销。

图 9 DGraph 子图
图 9 是一个 DAG 召回使用 DAG 子图后的变化,它有 8 路召回,一个 Merge 节点,这些召回分为两类,一类是基于 KV 表(ForwardSearch)触发的向量召回,另外一类是基于 KVV 表(IvtSearch)触发的向量召回。引入 DAG 子图后,在主图中节点数量由 17 个降为 3 个。
五、展望未来
过去四年,DGraph 聚焦于实现得物推荐引擎体系从 0 到 1 的突破,重点完成了核心系统架构搭建、算法策略支持及业务迭代空间拓展,取得多项基础性成果。基于 2024 年底的用户调研反馈结合 DGraph 当前的发展,后续将重点提升产品易用性、开发与运维效能及用户体验,同时在系统稳定性、可扩展架构和平台化建设方面持续深化。
算法团队大量 HC,欢迎加入我们:得物技术大量算法岗位多地上线,“职”等你来!
文 / 寻风
关注得物技术,每周一、三更新技术干货
要是觉得文章对你有帮助的话,欢迎评论转发点赞~
未经得物技术许可严禁转载,否则依法追究法律责任。
版权声明: 本文为 InfoQ 作者【得物技术】的原创文章。
原文链接:【http://xie.infoq.cn/article/256e3c799b4dd5f4370f79034】。文章转载请联系作者。
评论