TL;DR
You want to know how the new model behaves on real production traffic, but you do not want to bet your users on it. Shadow deployment is the answer: mirror a copy of live traffic to the candidate model, log its outputs, compare them to the model that actually answered, and never show the candidate’s responses to a single user. Zero user-facing risk, full real-traffic coverage. It is how you test a model swap against the actual distribution of inputs, the one your golden set can’t fully simulate, before you flip the switch. This is the safest pre-production gate that uses production data.
The cutover problem shadow deployment solves
Every model migration eventually faces the same terrifying moment: the cutover. You have tested the new model offline, the numbers look good, and now you have to point real users at it and hope your test set covered reality. The gap between “passed our eval” and “works on real traffic” is where cutovers go wrong, an input distribution you under-sampled, a format the new model handles differently, a latency profile that only shows under production load.
Shadow deployment removes the gamble. As the deployment-strategy literature describes it, shadow testing (also called dark launching) runs the new model alongside production, sends it a copy of real traffic, but never returns its responses to users. The candidate sees exactly what production sees, the full diversity of real inputs, while users keep getting the proven model’s answers. You get production-grade evidence with production-zero risk.
Shadow vs. canary: pick the right tool
Shadow deployment is often confused with canary releases, but they answer different questions and carry different risk. The distinction is sharp:
- Canary release. Routes a small percentage of real traffic to the new model, and those users do see its responses. Risk is real but limited to the canary slice. It tests the full user-interaction loop, including how users react.
- Shadow deployment. Mirrors traffic to the new model, but users never see its responses. Zero user-facing risk, but it does not test the downstream user reaction, only the model’s output quality, cost, and latency on real inputs.
The right sequence uses both. Martin Fowler’s description of canary releases frames them as progressive exposure; shadowing is the step before that exposure. Shadow first to prove the model’s outputs are sound on real traffic with no risk, then canary to a small slice to test the live user loop, then roll out. Shadowing answers “is the output good?” with zero risk; canary answers “do users do better?” with bounded risk.
How to wire it up
The mechanics are straightforward: when a request comes in, serve the production model synchronously (the user waits only for the real answer), and asynchronously fire the same request at the shadow model, logging its output for later comparison. A load balancer or API gateway with traffic-mirroring support (NGINX, Envoy, or a cloud ALB) can do the duplication at the edge. The shadow call must never block the user response, never count against the user-facing latency budget, and never have its output returned.
# Shadow deployment: production answers the user; shadow runs async, never returned
async def handle(request):
primary = PRODUCTION_MODEL(request) # user waits ONLY for this
asyncio.create_task(run_shadow(request, primary)) # fire-and-forget, non-blocking
return primary # shadow output never reaches the user
async def run_shadow(request, primary):
try:
shadow = await SHADOW_MODEL(request)
log_shadow_comparison({
'input': request,
'primary_out': primary.text,
'shadow_out': shadow.text,
'primary_cost': primary.cost, 'shadow_cost': shadow.cost,
'primary_ms': primary.latency, 'shadow_ms': shadow.latency,
})
except Exception as e:
log_shadow_error(e) # shadow failures must NEVER affect the user
Comparing shadow to production: the eval that matters
Mirroring traffic is only useful if you evaluate the comparison rigorously. There is no gold label on production traffic, so you compare the two models’ outputs along the dimensions that decide a migration: quality (via reference-free graders or an LLM-judge picking the better answer), cost, latency, format adherence, and safety. The output is a head-to-head report on the real input distribution.
# Analyze the shadow log: head-to-head on REAL traffic, no gold labels needed
def analyze_shadow(comparisons):
n = len(comparisons)
report = {
'shadow_better': 0, 'primary_better': 0, 'tie': 0,
'shadow_format_breaks': 0, 'shadow_safety_misses': 0,
'cost_ratio': 0.0, 'latency_ratio': 0.0,
}
for c in comparisons:
verdict = judge_pairwise(c['input'], c['primary_out'], c['shadow_out'])
report[f"{verdict}_better" if verdict != 'tie' else 'tie'] += 1
if not validates_schema(c['shadow_out']): report['shadow_format_breaks'] += 1
if safety_score(c['shadow_out']) < 0.9: report['shadow_safety_misses'] += 1
report['cost_ratio'] += c['shadow_cost'] / max(c['primary_cost'], 1e-9)
report['latency_ratio'] += c['shadow_ms'] / max(c['primary_ms'], 1e-9)
report['cost_ratio'] /= n
report['latency_ratio'] /= n
# Decision: ship only if shadow wins on quality WITHOUT breaching guardrails
report['recommend_ship'] = (report['shadow_better'] > report['primary_better']
and report['shadow_format_breaks'] == 0
and report['shadow_safety_misses'] == 0)
return report
This report is what makes the cutover a decision instead of a leap of faith. Instead of “the new model benchmarked higher, ” you have “on 50,000 real requests, the candidate produced a better answer 62% of the time, broke format zero times, missed no safety checks, and ran at 0.4x the cost.” That is a migration you can defend, to your team, to your customers, and to anyone who later asks why you changed a system that was working.
One subtlety separates a useful pairwise judge from a misleading one: positional and verbosity bias. LLM-as-judge graders tend to favor whichever answer is presented first, or whichever is longer, regardless of quality. If your shadow comparison does not control for this, by randomizing which model’s output is labeled “A, ” and by penalizing needless verbosity rather than rewarding it, you will get a confident verdict that is really measuring presentation order. Validate the judge against a few hundred human-labeled comparisons before you trust it to greenlight a migration, exactly as you would validate any grader carrying that much weight.
The costs and caveats
Shadow deployment is not free. You pay for the shadow model’s inference on mirrored traffic, potentially doubling inference cost for the duration of the test, so run it for a bounded window and a representative sample rather than 100% of traffic forever. It also has a real blind spot: because users never see shadow output, it cannot measure how users react, whether the new answers actually drive better resolution or conversion. That is exactly the gap canary and A/B testing fill. And take care with side effects: if your model triggers real actions (sending emails, writing to databases, calling tools), the shadow path must run in a sandboxed, side-effect-free mode so the mirror does not double-execute anything.
Where shadow fits in the rollout ladder
Shadow deployment is not a standalone technique, it is the first rung of a rollout ladder, and knowing which rung to use when is half the discipline. As the deployment-pattern literature lays out, the staged progression that minimizes risk is shadow first, then canary, then progressive A/B rollout. Each rung answers a question the previous one could not:
- Offline golden set, does the candidate clear the bar on cases we anticipated? Cheapest, no production data.
- Shadow, does it produce sound output on the real input distribution? Zero user risk, real traffic.
- Canary, do real users who receive its answers do at least as well? Bounded user risk, real reactions.
- Progressive A/B, does it win on business metrics at scale? Full measurement, controlled exposure.
Skipping rungs is where teams get burned. Jumping straight from an offline pass to a full cutover skips the two stages that use real traffic, precisely the data that exposes the regressions an offline set cannot. Shadow is the rung that buys you real-distribution confidence for free, which is why it belongs in every serious migration even when timelines are tight.
The bottom line
Blind cutovers bet your users on the assumption that your offline eval matched reality. Shadow deployment removes that bet: mirror real traffic to the candidate model, log and compare its outputs against the production model on quality, cost, latency, format, and safety, and never expose a single user to the candidate. Use it as the first gate, shadow to prove the output is sound on the real distribution, then canary for the user-reaction loop, then roll out. Sandbox side effects, bound the cost window, and feed shadow-only failures back into your golden set. It is the safest way to test a new model on the traffic that actually matters.
Ship AI on Evidence, Not Vibes
alt.qa Eval turns "seems fine" into measurable pass/fail, continuous evaluation, regression gates, and groundedness scoring for your AI outputs.
Try alt.qa Free →