当埃隆・马斯克在 2023 年将 Twitter 的推荐算法开源时,业界第一次有机会深入观察一个日均处理 5 亿条推特的系统是如何将海量内容筛选并排序的。这个名为 the-algorithm 的代码库揭示了一个复杂的多阶段流水线,从候选来源获取、机器学习排序到最终的内容混合与过滤,每一环节都藏着值得借鉴的工程决策。
三阶段流水线的核心挑战
Twitter 的推荐系统遵循经典的信息检索架构:候选来源获取、排序、启发式过滤。这一架构的难点在于如何在严格的服务延迟约束下,从每日产生的 5 亿条推特中筛选出与用户高度相关的 1500 条候选,最终呈现 20-50 条在用户的「为你推荐」时间线中。
三阶段的设计理念是渐进式收敛。第一阶段负责粗筛,从数百亿可能的推特中提取千量级候选;第二阶段使用更复杂的模型对候选进行精细排序;第三阶段则处理业务规则、过滤和内容多样性。整个流水线的设计权衡在于:早期阶段追求吞吐量,后期阶段追求预测精度,这种资源分配策略使得系统在有限算力下达到最佳效果。
候选来源的构成采用了 50:50 的内网与外网混合策略。内网推特来自用户关注的账号,通过搜索索引进行检索和基础排序;外网推特则来自用户未关注的账号,通过图遍历和社会证明机制发现。这种平衡设计既保证了用户看到熟悉账号内容的确定性,又通过图算法引入新鲜血液,避免信息茧房效应。
GraphJet:内存图引擎的工程极限
外网推特推荐的核心引擎是 GraphJet,这是一个专为 Twitter 场景设计的内存图处理引擎。与传统的批处理图计算不同,GraphJet 必须在毫秒级延迟内完成图遍历和推荐生成,这对系统的吞吐量和内存布局提出了极高要求。
GraphJet 的核心数据结构是一个二分交互图,节点分为用户节点和推特节点,边表示用户与推特的交互行为(点赞、转发、回复等)。为了支持实时更新,图被组织为时间分区索引段,每个段存储一组时间窗口内的邻接表。这种设计使得新产生的交互可以快速写入最新的段,而查询则可以并行扫描多个段以获取完整的图信息。
在性能指标上,GraphJet 单服务器能够实现每秒 100 万条边的写入吞吐量,在稳态下每秒生成 500 条推荐,这意味着每秒数百万次的边读取操作。这样的性能得益于两个关键优化:一是紧凑的边编码方案,将交互类型、时间戳等信息压缩存储;二是动态内存分配策略,利用图的网络幂律分布特性,为高频节点预留内存池。
GraphJet 的设计假设是整个图可以容纳在单台服务器的内存中。这一约束虽然限制了图引擎的水平扩展能力,但简化了分布式一致性问题的处理。在 Twitter 的实践中,通过分区策略将不同用户群体的图分布到不同服务器,查询时进行跨服务器聚合,实现了准分布式的扩展能力。
用户推特实体图与图遍历策略
在 GraphJet 之上,Twitter 构建了 User Tweet Entity Graph(UTEG),这是一个专门用于推荐的用户 - 推特交互图。UTEG 维护了用户与推特的实时交互历史,并基于图遍历算法发现潜在感兴趣的内容。
图遍历的典型路径从目标用户出发,首先找到该用户最近互动过的推特,然后沿着这些推特的作者和互动用户进行二跳或三跳扩展。这种协同过滤思路的图实现能够发现与用户历史行为相似用户喜欢的内容。社会证明机制进一步过滤低质量候选:如果一条推特被多个与目标用户有相似兴趣的用户互动过,它获得推荐的可能性就更高。
UTEG 的图特征服务(Graph Feature Service)提供了高效的边查询 API,可以返回用户对之间的多种图特征,例如「用户 A 关注的账号中有多少点赞过用户 B 的推特」。这些预计算的图特征被用作排序模型的输入,避免了在线图遍历的计算开销。
两阶段排序:时延与精度的平衡艺术
排序阶段采用了轻量级排序器 + 重量级排序器的两阶段设计,这是大型推荐系统中常见的工程模式。轻量级排序器(light-ranker)使用梯度提升树(GBM)模型,在候选数量较多时快速筛选出高质量子集;重量级排序器(heavy-ranker)使用神经网络模型,对通过初筛的候选进行更精细的预测。
轻量级排序器的设计目标是低延迟和高吞吐。它接收来自多个候选来源的数千条推特,使用 10-20 个关键特征(如用户 - 推特相似度、作者可信度、交互概率等)进行快速评分。这一阶段将候选数量从 1500 压缩到 300-500 条,为后续的复杂模型减轻计算负担。
重量级排序器采用多任务学习架构,同时预测用户执行多种交互行为(点赞、回复、点击、停留时间)的概率。多任务设计的优势在于共享底层表示,提高了模型的泛化能力,同时减少了训练和部署的资源开销。最终的排序分数是各交互概率的加权组合,权重反映了不同行为对平台目标的贡献度。
在特征工程层面,Twitter 使用了多种图嵌入和聚类特征。SimClusters 是一种社区检测算法,将用户和推特映射到数千个潜在社区空间;TwHIN 是一种稠密知识图嵌入,捕捉用户、推特和实体之间的语义关系;RealGraph 预测用户之间的未来交互概率;TweepCred 类似于 PageRank,计算账号的可信度和影响力分数。
navi 与产品混合器:服务化基础设施
排序模型的在线服务依赖于 navi,这是一个用 Rust 编写的高性能机器学习模型服务器。navi 的设计目标是在严格的服务等级协议(SLA)下提供低延迟的模型推理。Rust 的内存安全特性和零成本抽象使其成为高吞吐量系统的理想选择。
navi 支持多种模型格式的加载和热更新,能够在模型版本切换时保持服务可用。它还实现了批量推理优化,将多个请求合并执行以提高 GPU 利用率。在 Twitter 的部署中,navi 承担了重量级排序器的推理服务,处理来自 home-mixer 的每秒数万次排序请求。
Product Mixer 是另一个关键的 Scala 框架,它提供了构建内容 feed 的通用抽象。home-mixer 是 Product Mixer 的具体实现,负责协调候选获取、排序、过滤和内容混合的全流程。Product Mixer 的设计理念是将推荐流水线的各个环节模块化,使得不同产品表面(时间线、搜索、通知)可以复用相同的底层组件。
产品混合器还实现了内容多样性算法,在最终返回的 20-50 条推特中保证来源、话题和内容形式的均匀分布。这一后处理步骤避免了排序模型过度优化单一指标导致的同质化问题。
工程权衡与可操作的参数
从工程实践的角度,Twitter 推荐系统有几个值得关注的权衡决策。图规模与内存的权衡是最显著的:GraphJet 选择单服务器内存承载整个图,牺牲了水平扩展能力,换取了极致的遍历效率。如果图规模超出单服务器容量,可能需要引入分图策略或近似算法(如局部敏感哈希)。
排序复杂度的权衡体现在两阶段设计中。第一阶段的 GBM 模型推理延迟约为 1-2 毫秒,适合处理大量候选;第二阶段的神经网络模型虽然预测更准确,但单次推理延迟可能达到 10-20 毫秒。通过将模型复杂度集中在排序后期,系统在整体延迟和预测质量之间取得了平衡。
新鲜度与准确性的权衡影响了候选来源的设计。内网推特来自用户关注的账号,通常具有较高的相关性但新鲜度有限;外网推特通过图遍历发现,能够引入更广泛的内容但相关性波动较大。50:50 的混合比例是一个经验值,实际系统可能根据用户的活跃度和兴趣明确度动态调整。
在监控和可观测性层面,推荐系统需要关注的关键指标包括:排序分数分布(检测模型漂移)、候选来源覆盖率(确保各来源正常工作)、交互率与停留时间(反映推荐质量)、端到端延迟(保障用户体验)。当发现指标异常时,需要有回滚到备用候选来源或简化排序模型的能力。
落地建议与演进方向
对于计划构建类似系统的团队,以下参数和实践值得参考。GraphJet 风格的内存图引擎适合交互频率高、查询模式相对固定的场景;如果图规模超过单服务器内存承载能力(通常在数百亿边以内),需要考虑分布式图数据库或近似最近邻搜索方案。
两阶段排序的阈值设置应根据实际流量和模型性能调整。轻量级排序器的候选压缩比通常在 3:1 到 5:1 之间,过高的压缩比可能导致优质候选被误删,过低的压缩比则增加了后续阶段的计算负担。
在服务化基础设施方面,navi 展示的 Rust 模型服务是一个值得探索的方向。Rust 的性能优势在延迟敏感场景尤为明显,但其生态系统相比 Python/TensorFlow 仍不够丰富,需要权衡开发效率与运行效率。
Twitter 推荐系统的开源为业界提供了一个完整的参考实现。从底层的图算法设计到上层的服务化架构,每个组件都有其设计背景和工程约束。理解这些约束并根据自身场景调整,远比直接复制代码更有价值。
资料来源:GitHub twitter/the-algorithm、X Engineering Blog(2023)、GraphJet VLDB 论文(Sharma et al.)。