社交媒体平台的推荐系统面临一个独特的工程挑战:需要在用户行为发生的瞬时,将这些交互反映到图结构中,并基于更新后的图立即生成推荐结果。Twitter/X 在 2023 年开源的推荐算法代码库揭示了这一问题的系统性解法,其中核心的图处理引擎 GraphJet 承载了用户与推文之间实时交互的建模与查询职责。本文将从工程实现角度剖析这一图推荐系统的架构设计、核心数据结构与关键性能参数,为构建类似规模的实时图推荐系统提供可落地的参考。
推荐系统的三层图结构
Twitter/X 的推荐系统采用经典的三层架构:候选来源、机器学习排名和启发式过滤。在候选来源阶段,系统需要从一个每日产生约五亿条推文的庞大内容池中,为每位用户筛选出约一千五百条最相关的候选推文。这一筛选过程高度依赖图结构来完成,因为图能够自然地表达用户之间、用户与内容之间的关联关系。
在图结构的组织层面,Twitter/X 构建了三类核心图模型来回答不同维度的推荐问题。第一类是 RealGraph,它是一个用户间的交互预测图,边权重代表两个用户未来发生互动的概率估计。这个图通过监督学习的方式训练,能够回答「用户 A 有多大可能性与用户 B 产生交互」这类问题。第二类是 TweepCred,它采用类似 PageRank 的迭代算法计算用户在网络中的信誉分数,边结构基于用户的关注关系和历史互动行为。第三类则是本文的主角 UTEG(User-Tweet-Entity Graph),它是一个维护在内存中的二分交互图,一端连接用户节点,另一端连接推文和实体节点,基于 GraphJet 引擎进行管理。
这三类图在推荐流程中扮演不同角色。RealGraph 主要用于网络内(In-Network)推文的排序,它帮助系统判断推文发布者与当前用户之间是否存在潜在的互动可能。TweepCred 则作为一个全局声誉信号参与最终的排名计算。而 UTEG 承担了网络外(Out-of-Network)推文发现的核心职责,它通过在用户 - 推文交互图上的随机游走,发现与用户历史兴趣相似的内容。
GraphJet 引擎的内存二分图设计
GraphJet 是 Twitter/X 为实时推荐场景专门研发的内存图处理引擎。与传统的批处理图计算框架不同,GraphJet 从设计之初就考虑了「边摄入与查询服务必须并行进行」这一实时推荐系统的刚性需求。根据 GraphJet 发表在 VLDB 的论文,单个 GraphJet 服务器能够实现每秒摄入高达一百万条图边的吞吐量,同时在稳态下每秒提供五百次推荐计算,这意味着每秒需要处理数百万次的边读取操作。
为了在单机内存中容纳整个交互图并支撑高吞吐的读写操作,GraphJet 采用了时间分区的索引段(temporally-partitioned index segments)来组织图结构。交互图按照边发生的时间被划分到多个连续的索引段中,每个索引段维护一组用户节点和推文节点的邻接表。这种设计的工程考量在于:推荐系统对时效性的要求是递减的,用户更关心最近发生的交互行为,而历史久远的交互信号价值会逐渐衰减。通过时间分区,新写入的边只需要追加到最新的索引段,而查询逻辑可以优先扫描最近的索引段,在必要时再回溯查看历史数据。
在边的编码方面,GraphJet 实现了紧凑的边表示方案,充分利用了社交网络交互的幂律分布特性。社交网络中的用户活跃度呈现显著的长尾分布,少数超级用户拥有海量的粉丝和互动,而绝大多数普通用户的社交圈子相对有限。GraphJet 根据节点的度数(即连接数)采用不同的编码策略:对于高度数节点使用更紧凑的压缩表示,对于低度数节点则采用简单的直接存储。这种自适应编码在内存占用和访问效率之间取得了良好的平衡。
实时图摄入的并发控制策略
图引擎最难解决的工程问题之一是如何在边摄入(写入)和推荐查询(读取)之间实现无锁并发。传统的图数据库往往需要在读写之间加锁或进行版本切换,这会导致查询延迟的尖刺或边的实时性下降。GraphJet 通过动态内存分配方案解决了这一问题,其核心思想是利用图结构的幂律特性来预测内存使用模式并进行预分配。
具体而言,GraphJet 为每个索引段维护一个动态增长的邻接表数组。当新边摄入时,系统首先根据源节点和目标节点的标识符定位到对应的索引段和邻接链表,然后将新边追加到链表末尾。由于邻接链表被设计为可变长的数组结构,追加操作可以在摊销意义上实现 O (1) 的时间复杂度。当数组空间耗尽时,系统会进行局部的重分配,但这种重分配的发生频率可以通过幂律分布的统计特性进行预测和调度,从而将对查询服务的影响降到最低。
在并发控制层面,GraphJet 采用了多版本并发控制(MVCC)思想的简化版本。查询操作总是读取当前时刻的图快照,而边摄入操作在追加到索引段之前会先创建一个私有的工作副本。这种设计使得读操作完全不会被写入操作阻塞,而写入操作的延迟也被限制在单个索引段的范围内。通过这种方式,GraphJet 实现了边摄入与查询服务的真正并行化,这是其能够支撑每秒百万级边摄入的关键技术基础。
基于随机游走的推荐生成
UTEG 中的推荐生成依赖于在二分图上的随机游走算法。算法的核心思想是:如果用户 A 与推文 X 产生了交互(如点赞、转发、回复),而推文 X 与用户 B 的历史推文存在某种关联(如共享相同的话题标签、被相似用户群体互动),那么用户 A 可能也会对用户 B 的推文感兴趣。随机游走正是这种「关联传递」思想的算法化表达。
在工程实现中,GraphJet 支持多种随机游走变体,每种变体针对不同的推荐场景进行了优化。最基础的变体是从当前用户节点出发,以一定的概率沿着「用户→推文」边跳转到推文节点,再以一定的概率沿着「推文→用户」边跳回用户节点,如此迭代若干步后,停留在某个推文节点上,则该推文就成为推荐候选。更复杂的变体会考虑边的类型权重(如转发权重大于点赞权重)和时间衰减因子(近期的交互比远期的交互具有更高的信号强度)。
随机游走算法的计算瓶颈在于每一步都需要访问大量的邻接链表。为了加速这一过程,GraphJet 对邻接表的存储布局进行了精心优化。系统将每个节点的邻接链表按照目标节点的度数或重要性进行预排序,使得高价值的目标节点在列表中更靠前,从而在游走步数有限的情况下更快地接触到高质量的推荐候选。此外,GraphJet 还实现了邻接链表的压缩迭代器,支持在不完全解压缩的情况下进行遍历,进一步降低了内存带宽的消耗。
工程实践的关键参数与监控指标
基于 GraphJet 的工程实践经验,以下参数和指标对于构建可靠的实时图推荐系统具有重要参考价值。在内存配置层面,由于整个图需要常驻内存,服务器的内存容量直接决定了系统能够承载的节点规模和历史深度。对于日活用户数达亿级别的社交平台,单个 GraphJet 服务器通常需要配置 256GB 到 512GB 的内存,其中约 70% 用于存储邻接表和索引结构,20% 用于缓存热点节点的完整邻居信息,10% 作为预留的动态分配空间。
在吞吐配置层面,边摄入延迟(edge ingestion latency)需要控制在毫秒级别才能保证推荐的实时性。具体的工程目标可以设定为:99 分位的边摄入延迟不超过 10 毫秒,99.9 分位不超过 50 毫秒。当延迟超过阈值时,系统需要触发告警并进行容量扩展或负载均衡调整。推荐服务的可用性指标通常要求在 99.99% 以上,这意味着每年的计划外停机时间不能超过约 52 分钟。
在数据新鲜度层面,核心监控指标是「边到可查询状态的端到端延迟」。理想情况下,用户产生的交互行为应该在两到三秒内就能够被纳入推荐计算。为了实现这一目标,GraphJet 采用流水线化的摄入架构:边首先被写入一个高吞吐的日志队列,然后由专门的摄入工作线程解析并追加到图结构的对应位置,最后通过版本同步机制使新数据对查询服务可见。任何一个环节的延迟过长都会导致端到端延迟超标。
在资源调度层面,由于 GraphJet 是内存密集型服务,需要特别关注内存碎片化问题。长期运行后,动态分配导致的内存碎片可能高达总内存的 15% 到 20%,这会严重影响可用的连续内存块大小。工程实践中通常需要配置定期的内存整理操作,或者采用内存池技术来减少碎片产生。同时,服务器的 NUMA 拓扑结构也需要纳入考量,确保图数据在内存中的布局与访问模式匹配,避免跨 NUMA 节点的内存访问成为性能瓶颈。
图推荐系统的容错与扩展
在生产环境中,图推荐系统需要处理各类故障场景。对于单点故障,GraphJet 集群通常部署为多副本架构,每个分片的多个副本之间通过异步复制保持同步。当主节点发生故障时,副本节点可以在秒级时间内接管服务,但新写入的边在故障切换期间可能会有秒级的丢失。对于需要强一致性的场景,可以采用同步复制策略,但这会显著增加边的摄入延迟。
对于容量扩展,当单机无法承载更多节点或更高的吞吐需求时,系统需要进行分片(sharding)。分片策略的选择对系统性能有深远影响。常见的策略包括基于用户 ID 的哈希分片和基于图结构的地理分片。哈希分片的优点是实现简单、负载均衡效果好,但会导致跨分片的随机游走需要额外的网络通信。地理分片则适合用户群体具有明确地理分布的场景,可以减少跨数据中心的网络延迟,但可能导致热点地区的分片负载过高。
在故障恢复方面,图数据的一致性检查是一个容易被忽视但至关重要的环节。由于内存中的图结构可能因为进程崩溃或硬件故障而损坏,系统需要定期执行图完整性验证,包括检测孤立的节点、不连通的子图和异常的边权重。这些检测通常在业务低峰期执行,发现的问题会被记录下来并在下次维护窗口进行修复。
总结与工程建议
Twitter/X 的推荐系统展示了如何通过精心设计的图引擎来支撑大规模实时推荐场景。GraphJet 的成功经验可以归纳为几个关键的设计决策:首先是选择内存优先的架构,将整个图常驻内存以获得极低的数据访问延迟;其次是基于时间分区的索引组织,兼顾了时效性要求和历史数据的有效利用;最后是读写并发的工程实现,通过精巧的内存分配和版本管理策略,实现了边摄入与查询服务的真正并行化。
对于计划构建类似系统的团队,建议从以下几个方面入手。第一,在系统设计初期就明确性能目标和容量规划,包括每秒边摄入量、推荐查询 QPS、99 分位延迟等关键指标。第二,深入理解目标场景的图结构特性,如幂律分布的具体参数和时间衰减模式,这些特性将直接影响内存布局和索引策略的选择。第三,投入足够的精力进行并发控制的正确性验证,多版本并发控制或读写分离方案中的细微错误可能导致推荐结果的不一致或数据的损坏。第四,建立完善的监控和告警体系,实时图系统的故障往往会在秒级内影响大量用户,快速响应是控制影响范围的关键。
资料来源:Twitter/OpenAI 开源的推荐算法代码库(https://github.com/twitter/the-algorithm),GraphJet 原始论文(VLDB 2016)。