ML Systems Lab Open interactive version →
Intermediate 30 min read production evaluationshadow modeA/B testingstaged rollout

Evaluation in Production

Shadow mode, staged rollout, guardrail metrics, significance at scale

An A/B test runs for 2 days. The treatment shows +18% CTR. The team ships it. Two weeks later, revenue is down 4%. What happened?

Novelty. People click new things just because they are new. Once the shine wears off, engagement settles back — and here, below baseline. The 2-day test ran entirely inside the novelty window, so it measured novelty, not the model. The team optimised for a signal that was never going to last.

Production evaluation is the craft of measuring a model's real impact in ways that survive traps like this. The guiding rule: every ship / no-ship call should ultimately rest on a *controlled experiment*. Everything else — offline metrics, shadow mode, proxy-metric checks — exists to cheaply filter candidates so you only spend real A/B tests on the few worth it.


Shadow mode first

Before any high-stakes launch, run the new model in shadow mode: it sees the same live traffic and makes predictions, but only the current champion's predictions actually reach users. This proves the serving path works under real load, catches weird output distributions, and surfaces big disagreements between the two models — all before a single user is affected. It catches the infrastructure bugs that offline testing simply cannot.


Three A/B rules people keep breaking

1. Run for at least two full business cycles — a minimum of two weeks, four if the product is strongly seasonal. A 2-day win has almost certainly not outlived novelty.

2. Fix the primary metric and required sample size *before* you start. Glancing at results midway and stopping the moment p < 0.05 wrecks the statistics — with daily peeking at the 0.05 level, your real false-positive rate climbs to roughly 30% by day 20. If you must peek, use a sequential test built for it.

3. Set guardrail metrics up front — secondary numbers that are not allowed to get worse even if the main metric improves. A recommender that lifts CTR by pushing sensational content while tanking session length is worse than nothing; guardrails catch that before it ships.


"Significant" is not the same as "worth it."

A small p-value only says the effect is unlikely to be pure noise. It does not tell you the effect will *last*, that it is *big enough to matter*, or that the experiment was *run properly*. At ten million daily users, a 0.01% CTR bump hits p < 0.0001 in a single day — and that is about a thousand extra clicks. Is a whole new model to retrain, deploy, and maintain worth a thousand clicks a day? "Is this effect real?" and "Is this effect worth acting on?" are different questions, and at scale the first is almost always yes. So a trustworthy test needs all of it: random assignment (a user always in the same arm), enough time, a pre-registered metric and sample size, and guardrails watched throughout.


Check the lift holds across segments

A significant, practically-meaningful average lift can still be an artifact of one subgroup carrying the whole result. If the aggregate lift comes almost entirely from one platform, one region, or one power-user cohort while every other segment is flat or negative, you have found a group whose behaviour moves the average, not a broadly better model -- and it can vanish, or reverse, in segments you did not check. Before shipping, break the primary metric out by the segments that matter for your product (platform, geography, new vs. returning users) and confirm the lift holds directionally in each one, not just in the pooled number.


Check the split before you read any metric: SRM

The very first sanity check on an A/B test is sample ratio mismatch. You assigned 50/50, so the two arms should have ~equal traffic — if you observe 55/45, something is broken (a bug in assignment, a crash that drops one arm's users, bot traffic hitting one side, a logging gap). And a broken split means every downstream metric is untrustworthy, because the arms are no longer comparable populations. Run a chi-squared test on the observed counts *before* looking at the treatment effect; if SRM fails (roughly, the split deviates more than chance allows), stop and debug the pipeline — do not interpret the result.


Power, MDE, and the winner's curse

Size the test *before* running it. Statistical power is the chance of detecting a real effect of a given size; the minimum detectable effect (MDE) is the smallest lift the test can reliably catch, and it's set by the baseline metric's variance and your sample size. An underpowered test isn't just "might miss a win" — worse, the wins it *does* report are exaggerated (the winner's curse): only unusually large noisy estimates cross the significance line, so significant effects from small samples are biased upward. Compute the required sample size from a target MDE and power (usually 80%) up front, and be skeptical of large effects from small tests.


Faster tests with variance reduction: CUPED

You can reach significance sooner without more traffic by *reducing variance*. CUPED (controlled experiment using pre-experiment data) subtracts off each user's pre-experiment behaviour — a heavy pre-period spender is expected to spend heavily regardless of treatment, so removing that predictable component shrinks the metric's variance and tightens the confidence interval. Same effect, smaller error bars, shorter test. Any strong pre-experiment covariate works; CUPED is the standard packaging.


Don't jump straight to 50/50: staged ramp-up

High-stakes launches roll out in stages, each a safety gate: shadow (no user impact) → 1% canary (catch crashes and catastrophic regressions cheaply) → 5% → 10% → 25% → 50% → full, with automated rollback wired to guardrails at every step. The point is to limit blast radius: a bug that would harm 50% of users is caught at 1%. Never take a brand-new model straight to half of traffic.


When one user's treatment leaks to another: interference

Standard A/B analysis assumes SUTVA — one user's outcome depends only on their *own* assignment. Whole classes of systems violate this. In a marketplace, giving treatment sellers a boost takes impressions *away* from control sellers, so the control group is contaminated and the measured lift is inflated. Social networks (a treated user posts more, changing their control-group friends' feeds) and shared-resource systems (a treated user consumes limited inventory) have the same problem. The fixes are cluster-level randomisation (randomise by market/region/social-cluster instead of by user) or switchback designs — plain user-level A/B tests give biased answers under interference.


Short-term wins, long-term harm: delayed metrics

The metric that moves inside the test window is often not the one you care about. CTR responds in minutes; retention, revenue quality, refunds, churn, and lifetime value unfold over weeks or months. A model that boosts short-term clicks by pushing clickbait can simultaneously *reduce* long-term retention — and a two-week test may never see it. Guardrails plus a long-lived holdout group (a fraction of users permanently excluded from all changes) are how you catch effects that only appear after the experiment ends. A holdout group also beats running a *series* of individual A/B tests for measuring a model's real long-term value: each individual test only measures one change's effect over its own short window, and changes that each look like a win in isolation can still cancel out or net negative in aggregate -- one feature's engagement gain cannibalising another's. Comparing the holdout's cumulative trajectory against everyone else over months is how you catch that a year of individually-shipped "wins" actually added up to nothing, or worse.


Overlapping experiments: layers and mutual exclusion

At scale dozens of tests run at once, and they can interact — test A changes the ranking that test B is also modifying, so the effects aren't additive. Two defenses: mutual exclusion (tests touching the same surface can't share users) and experiment layers (an orthogonalisation scheme where each user is in one experiment per layer, so tests in different layers are randomised independently). Use factorial design deliberately when you *want* to measure an interaction, but only after confirming independence. Document which tests ran simultaneously so surprising results can be traced.

Key points

Takeaway

Statistical significance confirms the effect is unlikely to be noise — it says nothing about whether it will persist, whether it is practically meaningful, or whether the experiment was valid; a 2-day test during a novelty window with daily peeking can produce p = 0.001 on an effect that reverses completely in two weeks.

Recap

Check your understanding

Q1. Your A/B test on a recommendation system shows +2% CTR (p=0.001) after 3 days. Should you ship?

Q2. A/B test results show +1% revenue lift (p=0.0001) after 30 days. A colleague says "p < 0.05 means we should ship." What is missing from this reasoning?

Q3. You are running 5 simultaneous A/B tests on your platform. A product manager notices that tests 2 and 4 seem to interact. Which two of the following are the right response? Select two.

Q4. What is a holdout group and why is it more valuable than a series of A/B tests for measuring long-term model value?

Q5. You designed a 50/50 experiment, but the logged data shows 52.5% in control and 47.5% in treatment across 2 million users. What should you do before analysing the CTR lift?

Q6. Your company runs a two-sided marketplace and wants to A/B test giving some sellers a ranking boost. Why is a standard user/seller-level A/B test misleading here, and what's the fix?

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 →