概述
CandidatePipeline<Q, C> 是 candidate-pipeline 框架的执行器 trait。它定义在 candidate-pipeline/candidate_pipeline.rs:81-462。它接收一个查询,按固定顺序调度组件,产出排好序的候选和执行追踪。
它与 七个组件 trait 分工:组件 trait 定义每类工作怎么做;执行器定义这些工作的顺序、并行方式与数据传递。业务流水线实现这个 trait 时,只实现返回组件列表的 getter。框架以默认方法提供编排逻辑,业务代码不重写。home-mixer 有六个实现,见 home-mixer 编排层。
trait 定义
// candidate-pipeline/candidate_pipeline.rs:81-103
#[async_trait]
pub trait CandidatePipeline<Q, C>: Send + Sync
where
Q: PipelineQuery,
C: PipelineCandidate,
{
fn query_hydrators(&self) -> &[Box<dyn QueryHydrator<Q>>];
fn dependent_query_hydrators(&self) -> &[Box<dyn QueryHydrator<Q>>] { &[] }
fn sources(&self) -> &[Box<dyn Source<Q, C>>];
fn hydrators(&self) -> &[Box<dyn Hydrator<Q, C>>];
fn filters(&self) -> &[Box<dyn Filter<Q, C>>];
fn scorers(&self) -> &[Box<dyn Scorer<Q, C>>];
fn selector(&self) -> &dyn Selector<Q, C>;
fn post_selection_hydrators(&self) -> &[Box<dyn Hydrator<Q, C>>];
fn post_selection_filters(&self) -> &[Box<dyn Filter<Q, C>>];
fn side_effects(&self) -> Arc<Vec<Box<dyn SideEffect<Q, C>>>>;
fn result_size(&self) -> usize;
fn result_size_for(&self, _query: &Q) -> usize { self.result_size() }
fn finalize(&self, _query: &Q, _candidates: &mut Vec<C>) {}
// 其余方法都是默认实现
}完整方法集
业务必须实现的 getter(10 个)
| 方法 | 返回 | 说明 |
|---|---|---|
query_hydrators |
&[Box<dyn QueryHydrator<Q>>] |
第一批 Query 水合器 |
sources |
&[Box<dyn Source<Q, C>>] |
候选源 |
hydrators |
&[Box<dyn Hydrator<Q, C>>] |
候选水合器 |
filters |
&[Box<dyn Filter<Q, C>>] |
预打分过滤器,按列表顺序执行 |
scorers |
&[Box<dyn Scorer<Q, C>>] |
打分器,按列表顺序执行 |
selector |
&dyn Selector<Q, C> |
唯一的选择器 |
post_selection_hydrators |
&[Box<dyn Hydrator<Q, C>>] |
选后水合器 |
post_selection_filters |
&[Box<dyn Filter<Q, C>>] |
选后过滤器 |
side_effects |
Arc<Vec<Box<dyn SideEffect<Q, C>>>> |
以 Arc 返回,便于移进后台任务 |
result_size |
usize |
最终结果条数上限 |
业务可选重写的钩子(3 个)
| 方法 | 默认 | 用途 |
|---|---|---|
dependent_query_hydrators |
空 | 需要读取第一批 Query 水合结果的水合器 |
result_size_for(query) |
返回 result_size() |
新增:按请求决定结果条数(:100-102) |
finalize(query, candidates) |
空 | 截断后对最终列表做最后调整 |
在 2e2295b 中,home-mixer 的六个实现都没有重写这三个钩子。
框架提供的默认方法
| 方法 | 源码 | 作用 |
|---|---|---|
execute |
:105-107 |
调用 execute_traced,丢弃追踪 |
execute_traced |
:109-119 |
添加 pipeline 指标标签,在追踪作用域里执行,返回结果与追踪 |
execute_instrumented |
:121-124 |
带 #[receive_stats(name = "execute", latency = Bucket500To2500)] 的包装 |
execute_stages |
:126-179 |
真正的阶段编排 |
components |
:181-229 |
按阶段列出全部组件名 |
name |
:231-233 |
类型名最后一段 |
hydrate_query / hydrate_dependent_query |
:239-284 |
两批 Query 水合 |
fetch_candidates |
:291-306 |
候选源 |
hydrate / hydrate_post_selection / run_hydrators |
:312-348 |
候选水合与选后水合 |
filter / filter_post_selection / run_filters |
:358-397 |
两道过滤 |
score |
:403-414 |
打分 |
select |
:416-431 |
选择 |
run_side_effects |
:433-445 |
后台 side effect |
stat_result_size |
:447-461 |
结果条数指标 |
旧版的 record_enabled_components、log_stage、log_stage_size、log_filters 已删除。它们的职责并入 pipeline_summary 模块的 StageStats。
调用链
flowchart LR
A["execute(query)"] --> B["execute_traced"]
B --> C["pipeline_summary::scope"]
C --> D["execute_instrumented"]
D --> E["execute_stages"]
B -.->|"返回"| R["PipelineResult 与可选的 PipelineTrace"]
home-mixer 的服务层调用 execute_traced,并在 Under the Hood 请求里把追踪返回给客户端(home-mixer/scored_posts_server.rs:60-61、:96-98,home-mixer/for_you_server.rs:41-48)。嵌套执行时,内层流水线的追踪挂到外层当前阶段下,内层的 execute_traced 因此返回 None(candidate-pipeline/pipeline_summary.rs:46-53)。
execute_stages:十阶段走读
// candidate-pipeline/candidate_pipeline.rs:126-179(节选)
let hydrated_query = self.hydrate_query(query).await; // ①
let hydrated_query = self.hydrate_dependent_query(hydrated_query).await; // ②
let candidates = self.fetch_candidates(&hydrated_query).await; // ③
let hydrated_candidates = self.hydrate(&hydrated_query, candidates).await; // ④
let (kept_candidates, mut filtered_candidates) =
self.filter(&hydrated_query, hydrated_candidates.clone()); // ⑤
let scored_candidates = self.score(&hydrated_query, kept_candidates).await; // ⑥
let SelectResult { selected, non_selected } =
self.select(&hydrated_query, scored_candidates); // ⑦
let post = self.hydrate_post_selection(&hydrated_query, selected).await; // ⑧
let (mut final_candidates, post_filtered) =
self.filter_post_selection(&hydrated_query, post); // ⑨
// 截断到 result_size_for,finalize,统计,⑩ 后台 side effect| # | 阶段 | 执行方式 | 要点 |
|---|---|---|---|
| ① | Query 水合 | 并行 | 先按 enable() 筛选;只对 Ok 结果调用 update()(:239-257) |
| ② | 依赖型 Query 水合 | 并行 | 列表为空时直接返回,不记阶段统计(:263-284) |
| ③ | 候选源 | 并行 | 成功结果按源顺序拼接;失败源贡献零条(:291-306) |
| ④ | 候选水合 | 并行 | 每个水合器读取同一份候选切片;结果逐个写回(:331-348) |
| ⑤ | 预打分过滤 | 顺序 | 上一个过滤器的 kept 是下一个的输入;删除项累计(:379-397) |
| ⑥ | 打分 | 顺序 | 每个打分器写回后下一个才开始(:403-414) |
| ⑦ | 选择 | 单次 | 选择器未启用时全部视为选中(:416-431) |
| ⑧ | 选后水合 | 并行 | 只作用于入选候选 |
| ⑨ | 选后过滤 | 顺序 | 删除项并入 filtered_candidates(:150-152) |
| ⑩ | side effect | 后台 | tokio::spawn,不等待完成(:433-445) |
⑨ 之后的收尾(:154-171):
- 按
result_size_for(query)截断。截掉的候选并入non_selected。 - 调用
finalize。 stat_result_size记录{name}.execute的结果条数直方图;结果为空时额外计数result_empty。pipeline_summary::emit输出一行汇总日志。- 把查询包进
Arc,构造SideEffectInput(选中候选克隆一份,落选候选直接移入),启动 side effect。
⑤ 之前,框架对 hydrated_candidates 做了一次 clone()(:137)。原件作为 retrieved_candidates 返回,供调试接口展示“水合后、过滤前”的完整候选集。
PipelineStage 枚举
// candidate-pipeline/candidate_pipeline.rs:23-35
pub enum PipelineStage {
QueryHydrator, DependentQueryHydrator, Source, Hydrator, PostSelectionHydrator,
Filter, PostSelectionFilter, Scorer, Selector, SideEffect,
}枚举有 10 个值,其顺序与执行顺序不同:选后水合、选后过滤排在较前位置。各组件的 run() 现在都接收 PipelineStage 参数,用于给统计加 stage 标签(stat_labels,:37-49)与写入追踪。追踪里的阶段名是 snake_case,例如 post_selection_filters(pipeline_summary.rs:9-24)。
PipelineComponents 与 components()
// candidate-pipeline/candidate_pipeline.rs:51-54
pub struct PipelineComponents {
pub stage: PipelineStage,
pub components: Vec<String>,
}components() 按 Query 水合、依赖型 Query 水合、源、水合、过滤、打分、选择、选后水合、选后过滤、side effect 的顺序返回每阶段的组件名(:181-229)。
PipelineResult 结构
// candidate-pipeline/candidate_pipeline.rs:56-61
pub struct PipelineResult<Q, C> {
pub retrieved_candidates: Vec<C>, // 候选水合后、过滤前的全部候选
pub filtered_candidates: Vec<C>, // 两道过滤删掉的候选
pub selected_candidates: Vec<C>, // 最终结果
pub query: Arc<Q>, // 水合后的查询
}PipelineResult::empty()(:63-72)返回全空结果和默认查询。home-mixer 对测试账号直接返回它(home-mixer/scored_posts_server.rs:47-54)。落选与截断的候选不在 PipelineResult 里,框架只把它们交给 side effect。
追踪结构
execute_traced 返回的 PipelineTrace(pipeline_summary.rs:232-264):
| 结构 | 字段 |
|---|---|
PipelineTrace |
流水线名、类型名、总耗时、阶段列表 |
StageTrace |
阶段名、组件总数、启用数、相对起点偏移、耗时、条数、保留数、删除数、组件列表、嵌套流水线 |
ComponentTrace |
组件名、类型名、偏移、耗时、输入数、拉取数(源)、保留数与删除数(过滤器) |
Display 实现把追踪格式化成一行。例如 filters{total=21 enabled=14 … kept=… removed=… removed_per_filter=[…]}(pipeline_summary.rs:266-303)。
PipelineQuery / PipelineCandidate 约束
// candidate-pipeline/candidate_pipeline.rs:73-79
pub trait PipelineQuery: Clone + Send + Sync + 'static {
fn params(&self) -> &xai_feature_switches::Params;
fn decider(&self) -> Option<&xai_decider::Decider>;
}
pub trait PipelineCandidate: Clone + Send + Sync + 'static {}
impl<T> PipelineCandidate for T where T: Clone + Send + Sync + 'static {}ScoredPostsQuery 实现 PipelineQuery(home-mixer/models/query.rs:296-304)。PostCandidate 与 FeedItem 经 blanket impl 成为候选类型。
关键事实速查
| 问题 | 答案 |
|---|---|
| 业务要写哪些方法 | 10 个 getter;3 个钩子可选 |
| 哪些阶段并行 | Query 水合、依赖型 Query 水合、源、候选水合、选后水合、side effect |
| 哪些阶段顺序 | 预打分过滤、打分、选后过滤 |
| 组件失败会怎样 | 框架跳过该组件或该条目;条数不对时,水合器或打分器的整批结果作废 |
| 截断发生在哪 | 选后过滤之后,按 result_size_for |
| side effect 能否影响结果 | 不能。side effect 在后台运行,框架丢弃其结果 |
| 追踪怎么拿 | 调用 execute_traced |
设计决策
| 决策 | 理由 |
|---|---|
| 编排逻辑放在默认方法 | 所有流水线共享同一套顺序、并行与错误应对逻辑 |
新增 execute_traced 而保留 execute |
需要追踪的调用方拿追踪,其余调用方不受影响 |
| 截断放在选后过滤之后 | 选后过滤会删除候选;先过滤再截断,才能保证条数 |
返回 retrieved_candidates 的副本 |
调试接口能看到过滤前的全集 |
side effect 拿 Arc 输入 |
多个 side effect 共享一份数据,不必逐个克隆 |
源码锚点
candidate-pipeline/candidate_pipeline.rs:23-79—— 阶段枚举、结果结构、泛型约束candidate-pipeline/candidate_pipeline.rs:81-124—— trait 定义与执行入口candidate-pipeline/candidate_pipeline.rs:126-179——execute_stages()candidate-pipeline/candidate_pipeline.rs:181-461—— 各阶段方法与统计candidate-pipeline/pipeline_summary.rs:30-327—— 追踪收集与格式化home-mixer/candidate_pipeline/phoenix_candidate_pipeline.rs:1112-1152—— 内层流水线的 trait 实现home-mixer/candidate_pipeline/for_you_candidate_pipeline.rs:326-367—— 外层流水线的 trait 实现
相关页面
- candidate-pipeline 框架与七个组件 trait —— 组件 trait 与执行模型
- home-mixer 编排层 —— 六个实现
- 候选选择与成型 —— ⑦ 到截断的细节
- 过滤流水线 —— ⑤ 与 ⑨ 的过滤器
- Under the Hood 透明度工具 —— 追踪的对外用途
- 系统架构总览 —— 执行器在系统中的位置