这一页回答什么
home-mixer 是 For You 信息流的服务进程。本页回答五个问题:它怎样启动;它暴露哪些 gRPC 服务;它怎样把 流水线框架 组装成多条流水线;请求怎样变成 ScoredPostsQuery;候选来自哪些源。候选源总表与热门帖子源(PopularPostsSource)也在本页。
核心结论
- 一个进程,五个 gRPC 服务:
ScoredPostsService、ForYouFeedService、RankedFollowingFeedService、FollowingFeedService、PhoenixScoresService(home-mixer/server.rs:944-1001)。旧版只有前两个。 - For You 使用两层嵌套流水线:外层
ForYouCandidatePipeline(混排层,README 称 Blending Pipeline)把内层PhoenixCandidatePipeline(帖子层,README 称 Post Pipeline)当作候选源ScoredPostsSource调用(home-mixer/sources/scored_posts_source.rs:13-31)。 - 内层流水线有 9 个候选源,按装配顺序:Thunder、热门帖子、TweetMixer、SimClusters、Phoenix 召回、Phoenix 话题召回、Phoenix MoE 召回、SID 召回、缓存帖子(
home-mixer/candidate_pipeline/phoenix_candidate_pipeline.rs:380-390)。开源默认值下 TweetMixer、MoE、SID 三个源关闭。 - 热门帖子源是一份全站共享列表。离线任务从高粉丝活跃作者的近期原创帖里按浏览量挑出 500 条。请求时,源直接读取内存快照,不做个性化(
home-mixer/util/popular_posts.rs:36-86)。 QueryBuilder是所有入口的统一适配器:它校验viewer_id、并发拉取用户数据、评估 feature switches、构造ScoredPostsQuery(home-mixer/server.rs:79-184)。
服务启动
入口是 home-mixer/main.rs。同一个二进制文件按命令行参数进入三种模式之一:
| 模式 | 触发参数 | 做什么 |
|---|---|---|
| 热门作者任务 | --popular-authors-job / --popular-authors-job-once |
从 BigQuery 读作者表,选出热门作者并写入存储(main.rs:135-161) |
| 热门帖子任务 | --popular-posts-job / --popular-posts-job-once |
按热门作者拉帖、取浏览量、选出热门帖子并写入存储(main.rs:165-182) |
| gRPC 服务 | 默认 | 用 XServiceBuilder 启动服务(main.rs:221-239) |
服务模式的 CLI 默认值(main.rs:16-28):grpc_port 8080、metrics_port 9090、shard_coordinate -1(不分片)、shard_total_size 500、datacenter “atla”。旧页面写的 50051 端口已失效。服务启动时还调用 quality_factor::init(70.0, 30.0)(main.rs:187)。Thunder 与 Phoenix 召回的结果数上限都由 quality_factor::apply 调整。
HomeMixerServer::build()(server.rs:815-942)是装配中心。它先并发创建 VF 客户端与安全标签客户端;再并发创建 Gizmoduck 客户端、复活时间客户端、PhoenixCandidatePipeline::prod()、PhoenixScoresPipeline::prod();然后构造 ScoredPostsServer;最后并发构造 ForYouCandidatePipeline、RankedFollowingCandidatePipeline、FollowingCandidatePipeline。
五个 gRPC 服务
| 服务 | 方法 | 跑哪条流水线 | 源码 |
|---|---|---|---|
ScoredPostsService |
get_scored_posts、get_debug_scored_posts |
PhoenixCandidatePipeline |
server.rs:257-328 |
ForYouFeedService |
get_for_you_feed、get_for_you_feed_urt、get_debug_for_you_feed、get_debug_for_you_feed_urt |
ForYouCandidatePipeline |
server.rs:331-523 |
RankedFollowingFeedService |
get_ranked_following_feed_urt、get_debug_ranked_following_feed |
RankedFollowingCandidatePipeline |
server.rs:526-628 |
FollowingFeedService |
get_following_feed_urt、get_debug_following_feed |
FollowingCandidatePipeline |
server.rs:631-728 |
PhoenixScoresService |
get_phoenix_scores、get_debug_phoenix_scores |
PhoenixScoresPipeline |
server.rs:731-809 |
两个 Following 服务在构造查询后强制设置 in_network_only = true(server.rs:559、server.rs:662)。所有服务都启用 gzip 与 zstd 双向压缩,消息上限 MAX_GRPC_MESSAGE_SIZE 为 128 MiB(params/config.rs:16)。
get_for_you_feed_urt 先运行 get_for_you_feed,再用 urt::make_urt_timeline 把 FeedItem 列表转成 X 客户端使用的 URT Thrift,并做二进制序列化(for_you_server.rs:52-92)。
六条流水线
| 流水线 | 候选类型 | 用途 | 结果规模(开源默认值) |
|---|---|---|---|
PhoenixCandidatePipeline |
PostCandidate |
帖子层:召回、水合、过滤、打分、选择 | RESULT_SIZE = 35(params/config.rs:18) |
ForYouCandidatePipeline |
FeedItem |
For You 混排层 | FOR_YOU_MAX_RESULT_SIZE = 35 + 5 + 8 = 48(params/config.rs:19-22) |
RankedFollowingCandidatePipeline |
FeedItem |
排序版“关注”页:复用 ScoredPostsSource 并加广告 |
38(params/config.rs:23) |
FollowingCandidatePipeline |
FeedItem |
时间序“关注”页 | 112(params/config.rs:24-27) |
ReverseChronPostsPipeline |
PostCandidate |
时间序关注页的帖子层,源是 FollowingNightOwlSource |
101(params/config.rs:26) |
PhoenixScoresPipeline |
PostCandidate |
对调用方给定的帖子 ID 打 Phoenix 分,源是 SeedCandidatesSource |
35(phoenix_scores_pipeline.rs:452-454) |
六条流水线都实现同一个 CandidatePipeline trait。下文只展开 For You 使用的两条。
外层 ForYouCandidatePipeline
CandidatePipeline<ScoredPostsQuery, FeedItem>(for_you_candidate_pipeline.rs:62-69)。FeedItem 是 oneof 类型,取值为:帖子、广告、Who to Follow 模块、Prompt、PushToHome、Frame、FeedSurvey、视频轮播。
| 阶段 | 组件 | 源码 |
|---|---|---|
| Query 水合 | ServedHistoryQueryHydrator、PastRequestTimestampsQueryHydrator |
for_you_candidate_pipeline.rs:210-217 |
| 候选源 | ScoredPostsSource、AdsSource、WhoToFollowSource、PromptsSource、PushToHomeSource、JetfuelFrameSource、FeedSurveySource |
:219-234 |
| 过滤 | PushToHomeDedupFilter |
:236-237 |
| 选择 | BlenderSelector |
:239 |
| 选后过滤 | AdAdjacentServedFilter |
:262-263 |
| Side effect | 13 个:广告注入日志、漏斗追踪、广告历史缓存、seen ids 发 Kafka、served candidates 发 Kafka、客户端事件、served 事件、视频轮播候选日志、响应统计、更新请求时间戳、更新与截断 served history、served slate 缓存 | :241-260 |
候选水合、打分、选后水合三个阶段返回空切片(for_you_candidate_pipeline.rs:336-354)。旧版外层没有过滤器,新版有两个。PushToHomeDedupFilter 在 push_to_home_post_id 存在时删掉与它重复的帖子(filters/push_to_home_dedup_filter.rs:8-26)。AdAdjacentServedFilter 检查广告与已服务帖子的相邻关系,开源默认关闭(params/param.rs:1021-1026)。
外层 Query 水合在候选源之前运行。ScoredPostsSource 克隆外层已水合的查询,再交给内层(scored_posts_source.rs:15-19)。因此内层能读到外层写入的 served_ids 与模块疲劳标记。ServedHistoryQueryHydrator 按疲劳时长计算 who_to_follow_eligible、feed_survey_eligible、video_carousel_eligible(query_hydrators/served_history_query_hydrator.rs:38-57)。
内层 PhoenixCandidatePipeline
CandidatePipeline<ScoredPostsQuery, PostCandidate>(phoenix_candidate_pipeline.rs:224-234)。全部组件在 build_with_clients() 里装配(:237-556):
| 阶段 | 数量 | 组件 | 源码 |
|---|---|---|---|
| Query 水合 | 21 | 打分序列、沉浸式打分序列、召回序列、屏蔽 / 静音 / 关注 / 订阅用户、缓存帖子、互关、人口属性、关注的 Grok 话题、关注的 starter pack、已装应用、视频轮播 CTR 模型、近期搜索词、显式与隐式互动信号、曝光 Bloom 过滤器、IP、视频轮播已看、推断性别 | :285-348 |
| 候选源 | 9 | 见下节候选源总表 | :354-390 |
| 候选水合 | 15 | InNetworkCandidateHydrator、BidirectionalFollowHydrator、CoreDataCandidateHydrator、PhoenixReplyAncestorsHydrator、QuoteHydrator、MediaInfoHydrator、SubscriptionHydrator、GizmoduckCandidateHydrator、BlockedByHydrator、FilteredTopicsHydrator、LanguageCodeHydrator、EngagementCountsHydrator、PostEngagersHydrator、SemanticIdHydrator、NewUserSoftNsfwLabelHydrator |
:392-423 |
| 预打分过滤 | 21 | 见 过滤流水线 | :429-453 |
| 打分 | 5 | PhoenixScorer、ImmersiveVideoScorer、VMRanker、PageDecodeRerankScorer、PageRlExplorationScorer,见 打分与排序 |
:475-481 |
| 选择 | 1 | TopKScoreSelector |
:483 |
| 选后水合 | 10 | VFCandidateHydrator、AdsBrandSafetyVfHydrator、TweetTypeMetricsHydrator、FollowingRepliedUsersHydrator、MutualFollowJaccardHydrator、TopicFeedbackContextHydrator、AiTrendFeedbackContextHydrator、VideoMediaHydrator、VideoEmbeddingHydrator、AncestorVideoHydrator |
:485-509 |
| 选后过滤 | 5 | VFFilter、AncillaryVFFilter、AncestorVideoFilter、DedupConversationFilter、VideoCarouselFilter |
:511-517 |
| Side effect | 11 | Phoenix 实验、reranking Kafka、召回候选 Kafka、Redis 候选缓存、打分统计、响应多样性统计、意外发现统计、作者曝光指标、互关统计、调试、Phoenix 请求缓存 | :519-543 |
装配函数构造了 ImpressedPostsQueryHydrator,但没有把它放进 Query 水合列表(:350-352)。因此在开源装配下,impressed_post_ids 保持为空。
ScoredPostsServer::run_pipeline()(scored_posts_server.rs:43-101)对 TEST_USER_IDS(123、234,params/config.rs:13)直接返回空结果。对其余请求,它调用 execute_traced,把选中候选转成 ScoredPost,并附带视频轮播模块。只有 is_under_the_hood_request 为真时,流水线追踪才附到响应里(scored_posts_server.rs:96-98)。追踪的用途见 Under the Hood 透明度工具。
QueryBuilder:请求入口适配器
每个 gRPC 入口都先调用 QueryBuilder::build()(server.rs:79-184):
viewer_id == 0时返回invalid_argument(:88-90)。- 并发拉取 Gizmoduck 用户数据与账号复活时间,各自 200ms 超时(
VIEWER_ROLES_TIMEOUT_MS,:49)。超时或出错时分别退回ViewerData::default()与None(:206-235)。 - 推导
in_network_only = proto.in_network_only || allow_for_you_recommendations == Some(false)(:100-101)。用户关闭“为你推荐”时,For You 只走站内。 - 用
FsRecipientInputs(用户 ID、国家、语言、客户端版本、角色、是否有手机号、复活时间、产品类型等)匹配 feature switches,并应用请求带来的覆盖值(:103-123、:186-204)。feature switches 是 X 的实验开关系统:系统按用户画像命中一组配置值,各组件从Params读取这些值。 - 判定是否影子流量(
:129),构造ScoredPostsQuery,写入request_type与 Under the Hood 标记(:130-167)。Under the Hood 标记还要求EnableUnderTheHood为真,开源默认值为 false(params/param.rs:695-700)。
候选源总表
框架用 join_all 并发执行内层 9 个源(见 candidate-pipeline 框架)。下表按装配顺序列出(phoenix_candidate_pipeline.rs:380-390)。
| 源 | 内容 | enable() 条件 |
开源默认值 | served_type |
|---|---|---|---|---|
ThunderSource |
关注账号的近期帖子 | !has_cached_posts(thunder_source.rs:23-25) |
上限 ThunderMaxResults 1200(param.rs:23-28) |
ForYouInNetwork;站内限定请求为 RankedFollowing(thunder_source.rs:97-101) |
PopularPostsSource |
全站共享热门帖子列表 | 无缓存、非站内限定、EnablePopularPostsSource(popular_posts_source.rs:27-31) |
开启,上限 500(param.rs:1119-1130) |
ForYouPopularPosts |
TweetMixerSource |
TweetMixer 服务召回 | EnableTweetMixerSource、非站内限定、无缓存(tweet_mixer_source.rs:25-29) |
关闭(param.rs:41-46) |
ForYouTweetMixer |
SimclustersSource |
从用户互动过的帖子出发,按社区簇找相似帖 | EnableSimclustersSource、非站内限定、无缓存、有互动信号(simclusters_source.rs:215-220) |
开启(param.rs:48-53) |
ForYouSimclusters |
PhoenixSource |
Phoenix 双塔召回 | EnablePhoenixSource、非话题请求或批量话题请求、非站内限定、无缓存(phoenix_source.rs:103-108) |
开启,上限 1000,冷启动上限 200(param.rs:4-21) |
ForYouPhoenixRetrieval / ...Cold / ...ZeroFav(phoenix_source.rs:165-170) |
PhoenixTopicsSource |
单话题请求的 Phoenix 召回 | 话题请求且非批量、非站内限定、无缓存(phoenix_topics_source.rs:27-32) |
随请求 | ForYouPhoenixRetrieval |
PhoenixMOESource |
Phoenix MoE 集群召回 | EnablePhoenixMOESource、SID 源关闭、非站内限定、无缓存、kill switch 未开(phoenix_moe_source.rs:25-35) |
关闭(param.rs:181-186) |
ForYouPhoenixRetrievalMoe |
SidSource |
以互动过的帖子为种子,按语义 ID 前缀召回 | EnableSidSource、非话题请求、非站内限定、无缓存、有互动信号(sid_source.rs:58-64) |
关闭(param.rs:759-764) |
ForYouPhoenixRetrievalMoe(sid_source.rs:94) |
CachedPostsSource |
上次请求缓存的候选 | has_cached_posts(cached_posts_source.rs:10-12) |
缓存开启(param.rs:689-694) |
沿用缓存值 |
以下规则容易误读:
- 站内限定请求只保留 Thunder。
in_network_only为真时,除 Thunder 外的所有源都关闭。Thunder 的条件只检查缓存。 - 命中缓存时只保留缓存源。
CachedPostsQueryHydrator从 Redis 读回不少于 500 条候选时,设置has_cached_posts(query_hydrators/cached_posts_query_hydrator.rs:11、:69),其余 8 个源全部关闭。RedisPostCandidateCacheSideEffect写入缓存,每次最多MaxPostsToCache750 条(param.rs:701-706)。 - SimClusters 和 SID 需要互动信号。两者都调用
has_post_signals(),要求显式或隐式互动信号非空(simclusters_source.rs:277-285)。SimClusters 只用 90 天内的种子帖(simclusters_source.rs:36)。细节见 SimClusters。 - Phoenix 召回可按新用户路由集群。
PhoenixRetrievalNewUserHistoryThreshold大于 0 且召回序列长度低于阈值时,该源改用新用户集群(phoenix_source.rs:32-68)。开源默认阈值为 0,即不启用(param.rs:260-265)。召回模型见 Phoenix 召回。 - 两个文件没有编进源码树。
sources/popular_topics_source.rs与filters/popular_topics_author_dedup_filter.rs存在,但不在对应的mod.rs里(sources/mod.rs:1-19、filters/mod.rs:1-33),因此不参与编译。
Thunder 的存储与查询见 Thunder 站内帖子内存库。
外层 ForYouCandidatePipeline 的非帖子源:
| 源 | 条件 | 产出 |
|---|---|---|
AdsSource |
EnableAdsSource(默认开启,param.rs:929-934)且非预览请求(ads_source.rs:19-21) |
广告候选 |
WhoToFollowSource |
EnableWhoToFollowModule 且未处于 30 小时疲劳期(who_to_follow_source.rs:24-26、param.rs:1139-1150) |
Who to Follow 模块 |
PromptsSource |
EnablePrompts(默认开启,param.rs:1152) |
Prompt |
PushToHomeSource |
请求带 push_to_home_post_id(push_to_home_source.rs:19-21) |
推送落地帖 |
JetfuelFrameSource |
至少一个 Frame 符合条件(jetfuel_frame_source.rs:27-29);总开关 EnableJetfuelFrames 默认关闭(param.rs:1167-1172) |
体育话题等 Frame 模块 |
FeedSurveySource |
EnableFeedSurvey(默认关闭,param.rs:1204-1209)且未处于 1440 分钟疲劳期 |
信息流问卷,位置 12(params/config.rs:32) |
热门帖子源
README 把 popular_posts_source 列为与 Thunder、Phoenix 召回、SimClusters 并列的候选来源。它由两个离线任务和一个在请求时读取的缓存组成。
flowchart LR
BQ[(BigQuery 作者表)] --> PAJ["popular_authors_job<br/>按粉丝数取前 0.005% 活跃作者"]
PAJ --> AS[(热门作者存储)]
AS --> PPJ["popular_posts_job<br/>经 Thunder 拉原创帖并取浏览量"]
PPJ --> SEL["select_popular_posts<br/>每作者 5 条,总量 500"]
SEL --> PS[(热门帖子存储)]
PS --> CACHE["PopularPostsCache<br/>每 60 秒后台刷新"]
CACHE --> SRC[PopularPostsSource]
SRC --> PIPE[内层流水线候选池]
- 选作者:
select_top_posting_authors计算 7 天活跃发帖人数 ×TOP_POSTING_AUTHORS_FRACTION(0.00005)并向上取整,再从按粉丝数降序的列表头部截取这么多作者(util/popular_authors.rs:11、:25-43)。任务默认每 3600 秒运行一次(main.rs:33-34)。 - 拉帖子:
popular_posts_job每批 20 个作者调用 Thunder,每作者最多 50 条,排除回复与转发(popular_posts_job.rs:22-24、:83-85),只保留原创帖(:67-69),再批量取浏览量。任务默认每 300 秒运行一次(main.rs:52-53)。 - 打分与挑选:
quality = views × (1 − e^(−λ·24)) / (1 − e^(−λ·max(age, 0.5))),λ = ln2 / 半衰期(util/popular_posts.rs:48-53)。这个公式把浏览量折算成“24 小时窗口内的预期浏览量”,使新帖不吃亏。挑选只考虑 24 小时内的帖子,每作者最多 5 条,总量 500,半衰期 8 小时(util/popular_posts.rs:36-46、:61-86)。以上都是开源默认值。 - 请求时读取:
PopularPostsCache持有内存快照,距上次加载满 60 秒时在后台刷新(util/popular_posts.rs:7、:207-239)。PopularPostsSource取前PopularPostsMaxResults条,去掉seen_ids里的帖子,标为ForYouPopularPosts(popular_posts_source.rs:33-64)。
热门帖子对所有用户相同,不经过任何个性化召回。它们进入候选池后,和其他候选一起经过过滤与 Phoenix 打分。
数据模型
ScoredPostsQuery
查询对象贯穿整条流水线(models/query.rs:46-149),约有一百个字段。主要分组如下:
- 身份与上下文:
user_id、client_app_id、country_code、language_code、request_type、request_context、cursor、设备与 IP 字段。 - 去重输入:
seen_ids、served_ids、bloom_filter_entries、impressed_post_ids、seen_carousel_video_ids。 - 模型输入:
scoring_sequence、retrieval_sequence及其列式与沉浸式变体、显式与隐式互动信号、history_topics。 - 用户特征:
user_features(屏蔽、静音、关注、订阅、静音词)、人口属性、推断性别、关注话题与 starter pack 位图、已装应用、近期搜索词。 - 请求形态:
in_network_only、is_bottom_request、topic_ids、excluded_topic_ids、exclude_videos、push_to_home_post_id、seed_candidate_post_ids。 - 跨组件共享的结果槽:
ad_drops、video_carousel、served_post_score_infos等Arc<OnceLock<…>>字段。选择器与过滤器通过这些字段把旁路结果交给后续阶段。
RequestType 有五种:ForYou、RankedFollowing、Following、ScoredPosts、PhoenixScores(query.rs:30-37)。is_bulk_topic_request() 判定话题数大于 6(query.rs:275-277)。
PostCandidate
该类型是内层候选(models/candidate.rs:14)。源阶段只填 tweet_id、author_id、served_type、retrieval_sources。水合器补全正文、媒体、计数、安全标签与可见性结果。打分器写入 weighted_score 与 score。retrieval_sources 记录候选来自哪些源。去重过滤器删除重复候选时,把来源合并到保留的那一条(filters/drop_duplicates_filter.rs:19-28)。
设计决策
| 决策 | 理由 |
|---|---|
| 外层混排、内层排序两条流水线 | 帖子排序与多类型内容组装分开演化;内层可单独作为 ScoredPostsService 复用 |
| 外层 Query 水合先于内层执行 | served history 只拉一次,内外两层共用同一份去重与疲劳信息 |
| 同一进程承载 For You、Following、Phoenix 打分 | 共享客户端与 QueryBuilder,各产品只换流水线组装 |
| 用户数据 200ms 超时并退回默认值 | Gizmoduck 慢调用不拖垮整次请求 |
| 热门帖子离线挑选,请求时只读快照 | 挑选需要扫描热门作者的全部近期帖,放在请求路径上代价过高 |
| 命中缓存时关闭全部召回源 | 翻页请求复用上次召回结果,省掉召回调用 |
FAQ
Q:内层流水线能单独调用吗?
A:可以。ScoredPostsService 直接暴露 PhoenixCandidatePipeline,返回 ScoredPost 列表与可选的视频轮播,不含广告与模块(server.rs:257-328)。
Q:热门帖子源会不会让大号的帖子绕过过滤?
A:不会。它只把候选放进候选池。这些候选之后照常经过 21 个预打分过滤器、Phoenix 打分、选后可见性过滤(phoenix_candidate_pipeline.rs:429-517)。
源码锚点
home-mixer/main.rs:129-239—— 三种运行模式与服务启动home-mixer/server.rs:79-235——QueryBuilderhome-mixer/server.rs:815-1001——HomeMixerServer::build()与register()home-mixer/candidate_pipeline/for_you_candidate_pipeline.rs:189-273—— 外层装配home-mixer/candidate_pipeline/phoenix_candidate_pipeline.rs:237-556—— 内层装配home-mixer/scored_posts_server.rs:43-101——run_pipeline()home-mixer/util/popular_posts.rs:36-239—— 热门帖子挑选与缓存home-mixer/popular_posts_job.rs:134-179—— 热门帖子任务单次运行home-mixer/params/config.rs:13-42—— 结果规模与位置常量
相关页面
- 系统架构总览 —— 请求路径与标签路径总览
- 端到端数据流 —— 一条帖子从发布到被推荐
- candidate-pipeline 框架 —— 流水线 trait 与执行模型
- CandidatePipeline trait —— 执行器方法逐个说明
- 过滤流水线 —— 21 个预打分过滤器与 5 个选后过滤器
- 打分与排序 —— 内层打分器
- 候选选择与成型 —— 选择器与选后成型
- 广告混排 ——
BlenderSelector的广告混排 - SimClusters ——
SimclustersSource背后的社区簇 - Thunder 站内帖子内存库 ——
ThunderSource背后的站内库 - Phoenix 召回 ——
PhoenixSource背后的召回模型 - 算法用了你的哪些数据 ——
ScoredPostsQuery字段的用户侧来源