ML Systems Lab Open interactive version →
Advanced 26 min read RecSysrankingpre-rankingcandidate generation

RecSys Stack Deep-Dive (4-Stage Funnel)

Retrieval → pre-ranking → ranking → re-ranking, latency budgets, feedback loops

The gap between a recommender *prototype* and a recommender *system* is the entire engineering stack around the model. A prototype runs a ranker over a few thousand items and prints results. Production has to serve millions of users inside a ~100ms end-to-end budget — of which the four funnel stages below account for roughly 30ms of actual compute, the remainder consumed by network round-trips between services, feature-store lookups, and serialization at each hop — A/B-test each stage independently, degrade gracefully when any component dies, and correct position bias so the ranker doesn't just resurface whatever the last model showed.


Modern stacks have four stages, not three. Between cheap retrieval and the expensive ranker sits a pre-ranking (a.k.a. coarse-ranking) stage: a lightweight model that trims thousands of retrieved candidates to a few hundred before the heavy ranker runs. Without it, the full ranker either blows the latency budget or is forced to score too few candidates. Retrieval (10M→5k, ~1ms) → pre-rank (5k→500, ~5ms) → rank (500→50, ~20ms) → re-rank (50→10, ~5ms). Re-ranking is a distinct final pass, not a smaller repeat of ranking: it takes the ranker's top ~50 scored candidates and applies constraints a per-item relevance score can't express on its own — deduplicating near-identical items, enforcing diversity across categories or sources, and injecting business rules (promotions, freshness floors, do-not-show lists) — before the top ~10 go to the user. See *reranking_diversity* for the algorithms and *recsys_feedback_loops* for how re-ranking's choices feed the position-bias problem below.


Latency is allocated, not hoped for. Each stage has a hard millisecond budget and a single overrunning stage cascades. The Rank stage's ~20ms budget, for example, splits roughly 10ms for feature retrieval and 10ms for the model's forward pass; if feature retrieval slips from 10ms to 15ms, only 5ms remains for scoring — forcing fewer candidates or a simpler model, both of which cost quality. Measure the budget end-to-end in production, never from component microbenchmarks.


The hardest correctness problem is the feedback loop. The ranker itself is a learning-to-rank (LTR) model — trained to order candidates against each other, not just score each one in isolation — and its quality is measured with rank-sensitive metrics like precision@1 (whether the single top-ranked item is actually relevant). The ranker trains on interactions shaped by what the *previous* ranker chose to show. Position 1 gets clicks regardless of quality; train on raw clicks and you teach the model to reproduce position effects, not relevance. Inverse-propensity weighting — reweighting each observed click by 1/P(click|position) to cancel out position's effect on the raw signal — and counterfactual learning are the tools that recover an unbiased relevance estimate.

Key points

Takeaway

Production RecSys is a 4-stage funnel — retrieval, pre-ranking, ranking, re-ranking — where pre-ranking exists precisely because the heavy ranker can't score thousands of candidates in-budget, and every stage runs on its own hard latency allocation with position-bias correction stitched through.

Recap

Check your understanding

Q1. Select the two correct effects of inserting a pre-ranking stage when the ranker takes 95ms for 1000 candidates against a 100ms budget.

Q2. Your LTR ranker shows higher precision@1 for items that were historically shown at low positions than high positions. Cause and fix?

Q3. Why is a pre-ranker that's simply a smaller copy of the ranker still worth having, even though it's less accurate?

Try it interactively

ML Systems Lab is a free interview-prep platform for ML engineers — work through the full interactive module, quizzes, and drills.

Open ML Systems Lab →