Everything for MLOps in one place — pick a section below. 16 reviewed items across 4 content types.
Quick Answer
MLOps applies DevOps discipline (CI/CD, versioning, monitoring) to machine learning systems, but adds concerns unique to ML: data and model versioning, training reproducibility, model performance monitoring in production, and drift — because the "code" that matters includes the data and the trained model artifact, not just source code.
Detailed Answer
Traditional DevOps assumes that the same code plus the same config produces the same behavior forever, and that testing catches most regressions before release. ML systems break that assumption in two ways: the model's behavior depends on training data that changes over time, and a model can silently degrade in production even with unchanged code, because the real-world data it sees starts to differ from what it was trained on.
MLOps extends the standard pipeline with: dataset versioning (so you can reproduce exactly what a model was trained on), experiment tracking (hyperparameters, metrics, artifacts per training run), a model registry with staged promotion (dev → staging → production), automated evaluation gates before deployment, and continuous monitoring for both system health (latency, errors) and model health (prediction drift, data drift, accuracy decay). The deployment unit is a model artifact plus its serving code, not just code.
Code Example
# Minimal MLOps pipeline stages # 1. data validation -> schema checks, drift checks vs. reference distribution # 2. train -> log params/metrics/artifacts to experiment tracker # 3. evaluate -> compare against baseline model on held-out + slice metrics # 4. register -> push to model registry with version + lineage metadata # 5. deploy (canary) -> shadow or partial traffic before full rollout # 6. monitor -> prediction drift, latency, business metric impact
Interview Tip
Lead with "the model is a versioned artifact, and data is part of the contract" — that framing signals you understand what makes MLOps different from CI/CD for regular services.
💬 Comments
Quick Answer
Continuously compare live input feature distributions and prediction distributions against a reference (typically the training set), alert when they diverge beyond a threshold, and separately track ground-truth performance where labels eventually become available — then respond with retraining, feature fixes, or rollback depending on what the drift indicates.
Detailed Answer
There are two related but distinct problems: data drift (the distribution of incoming features changes) and concept drift (the relationship between features and the true label changes, even if feature distributions look stable). Data drift is detectable immediately by comparing live feature statistics against a reference window using tests like population stability index (PSI), KL divergence, or simple summary-statistic monitoring per feature. Concept drift usually requires delayed ground truth (e.g., did the fraud prediction turn out to be correct once a chargeback occurred), so you track a lagging accuracy/precision/recall metric alongside the drift signals.
When drift crosses a threshold, the response depends on cause: if a specific feature's upstream data source changed shape (schema change, new user segment, an outage that shifted traffic mix), fix the pipeline. If the world genuinely changed (seasonality, new product usage patterns), trigger a retraining job on fresher data. If confidence in the current model is low, fall back to a simpler rules-based system or a previous model version while retraining/investigation happens — treat this like an incident with a runbook, not an ad hoc fire drill.
Code Example
# Simple feature drift check (population stability index)
def psi(reference: np.ndarray, current: np.ndarray, bins: int = 10) -> float:
breakpoints = np.percentile(reference, np.linspace(0, 100, bins + 1))
ref_pct = np.histogram(reference, breakpoints)[0] / len(reference)
cur_pct = np.histogram(current, breakpoints)[0] / len(current)
ref_pct, cur_pct = np.clip(ref_pct, 1e-6, None), np.clip(cur_pct, 1e-6, None)
return float(np.sum((cur_pct - ref_pct) * np.log(cur_pct / ref_pct)))
# PSI > 0.2 on a key feature is a common trigger for investigation/retrainingInterview Tip
Distinguish data drift from concept drift explicitly — interviewers use this question to check whether you conflate the two.
💬 Comments
Quick Answer
A feature store centralizes feature computation and storage so the same feature logic is used for both training (batch, historical) and serving (real-time, low-latency); train/serve skew happens when those two paths compute a "same-named" feature differently, so the model sees different inputs in production than it learned from.
Detailed Answer
Without a feature store, teams typically write feature logic twice: once in a batch/offline pipeline (e.g., Spark/SQL) to build training datasets, and once in an online service (e.g., application code) to compute features at request time for real-time predictions. Any divergence between those two implementations — different time windows, different null-handling, different rounding, a bug fixed in one but not the other — produces train/serve skew: the model performs well offline but degrades in production because it's effectively seeing out-of-distribution inputs.
A feature store addresses this by defining feature transformation logic once and materializing it to two stores: an offline store (historical, point-in-time correct, used for training) and an online store (low-latency key-value lookup, used for serving), both fed from the same feature definitions. Point-in-time correctness matters specifically to avoid label leakage — when building training data, you must look up feature values as they existed at the time of the historical event, not the current value. Good feature stores also version feature definitions and track lineage, so you can tell exactly which feature version a given model was trained against.
Code Example
# Feature definition used for both offline training and online serving
@feature_view(entities=["user_id"], ttl="7d")
def user_purchase_features(user_id: str, as_of: datetime) -> dict:
return {
"purchases_7d": count_purchases(user_id, window="7d", as_of=as_of),
"avg_order_value_30d": avg_order_value(user_id, window="30d", as_of=as_of),
}
# Offline: materialized in batch for training set construction (point-in-time joins)
# Online: same definition, precomputed/cached for millisecond lookups at inference timeInterview Tip
Mention point-in-time correctness explicitly — it's the detail that separates a surface-level answer from one that shows real feature-store experience.
💬 Comments
Quick Answer
Roll out in stages — shadow traffic first, then a small canary slice compared against the current model on both system and business metrics, then progressive traffic ramp-up with automated rollback triggers — and always keep the previous model version hot and instantly routable.
Detailed Answer
Start with shadow deployment: the new model receives a copy of live traffic and produces predictions that are logged but never returned to users, so you can compare its outputs against the current model with zero user-facing risk. If shadow metrics look healthy (latency, error rate, prediction distribution sanity), move to a canary: route a small percentage (e.g., 1-5%) of real traffic to the new model and compare both system metrics and downstream business metrics (conversion, false-positive rate, whatever the model drives) against the control group using a proper statistical comparison, not just eyeballing.
Ramp traffic progressively (5% → 25% → 50% → 100%) with automated guardrails that halt or roll back the rollout if key metrics regress beyond a defined threshold — this should be automatic, not dependent on someone watching a dashboard. Keep the previous model version deployed and warm (not just an artifact in the registry) so rollback is a routing change, not a redeploy-and-wait. Log which model version served every prediction so any incident can be traced back precisely, and treat the rollout itself as an experiment with a clear success/failure decision criterion defined before it starts.
Code Example
# Progressive rollout with automatic rollback guard (pseudocode)
stages = [0.01, 0.05, 0.25, 0.50, 1.0]
for pct in stages:
route_traffic(model="v2", percent=pct)
wait(minutes=30)
metrics = compare_metrics(control="v1", treatment="v2")
if metrics.error_rate_delta > THRESHOLD or metrics.business_metric_regressed:
route_traffic(model="v1", percent=1.0) # instant rollback
alert("rollout halted: regression detected at %d%% traffic" % (pct * 100))
breakInterview Tip
Emphasize that rollback needs to be a routing change with the old model already warm — "redeploy the old version" is too slow to count as a real rollback plan.
💬 Comments
Quick Answer
CT extends CI/CD with automated retraining triggered by schedule, data volume, or drift signals — but the retrain trigger and the promotion decision are separate gates. Retraining is cheap and can be liberal; promotion must pass validation (data quality checks, metrics vs current champion including slices, schema/latency budgets) before any traffic shifts.
Detailed Answer
The pipeline shape: trigger (cron, N new labeled samples, drift detector firing, or upstream data-version change) -> data validation (schema, distributions, freshness, leakage checks — bad data must fail here, not train) -> training with full lineage logging (code SHA, data version, params) -> candidate evaluation against the golden set AND the live champion on overall plus slice metrics -> automated promotion gates -> staged rollout (shadow or canary) with online metric confirmation -> alias/pointer switch. The separation matters because retrain-and-auto-ship couples two decisions with different risk profiles: a scheduled retrain on quietly-corrupted upstream data will happily produce a 'successful' model that scores garbage; only the validation gates stand between that and production. Interview-worthy nuances: label lag (fraud/churn labels arrive weeks late, so 'latest data' isn't fully labeled), retrain-on-drift can amplify feedback loops (the model influences the data it will train on), and every automated gate needs a manual-override path with audit for incident response.
Code Example
# pipeline gates (pseudo-config)
trigger: {drift_psi: '>0.2', or_schedule: 'weekly'}
validate_data: [schema_match, null_rate<0.02, psi_vs_train<0.3]
promote_if:
golden_auc: '>= champion - 0.002'
slice_min_auc: '>= 0.80 # every segment'
latency_p99_ms: '< 40'
rollout: {shadow: 48h, then_canary: 10%, auto_rollback_on: online_delta<0}Interview Tip
Say 'retraining is cheap, promotion is the risk' — separating the two triggers is the design insight interviewers listen for, plus label lag as the practical complication.
💬 Comments
Quick Answer
A model is a function of code AND data — reproducing or auditing it requires pinning both. Version data with immutable snapshots or content-addressed references (DVC, lakehouse table versions/time travel, s3 object versions + manifest), and record the exact data reference in the training run's metadata so any model maps back to the bytes that trained it.
Detailed Answer
The failure that motivates it: a model regresses, you retrain from 'the same' code, and get a different model — because the feature table mutated in place. Approaches: (1) content-addressed pipelines (DVC/lakeFS) — data files hashed, referenced by pointer files that live in git, so git checkout reconstructs the exact dataset; (2) lakehouse time travel (Delta/Iceberg table versions) — training reads a pinned snapshot ID, cheap because storage is shared across versions; (3) discipline-level minimum: immutable dated partitions plus a manifest (paths + checksums + row counts) logged with the run. Whichever mechanism, the contract is the same: the training run's metadata records an immutable data reference, and re-running with that reference is byte-identical. This also unlocks: auditability (regulators ask 'what data trained this?'), safe backfills (new dataset version, not in-place mutation), and debugging skew (diff data versions between good and bad models). The trap to name: versioning raw data but not the feature-engineering output — if features are computed by a mutable pipeline at train time, pin the feature table, not just the source.
Code Example
# DVC-style
dvc add data/train.parquet # .dvc pointer file goes in git
git commit -m 'train data v14'
# lakehouse-style
df = spark.read.option('versionAsOf', 412).table('features.churn')
mlflow.log_params({'data_ref': 'features.churn@v412', 'data_rows': df.count()})Interview Tip
Frame it as 'model = f(code, data) — pinning one of two inputs is not reproducibility'. Mentioning the feature-table-vs-raw-data distinction shows you've been burned properly.
💬 Comments
Quick Answer
The lifecycle discipline carries over (versioning, eval gates, staged rollout, monitoring), but the artifacts change: you rarely train the model, so 'training pipeline' becomes prompt/RAG/config engineering; evaluation shifts from labeled metrics to golden sets with LLM-judge scoring; monitoring adds token cost, safety, and hallucination; and the 'model registry' becomes prompts, retrieval configs, and provider/model versions.
Detailed Answer
What transfers directly: treat every behavior-changing artifact as versioned and gated — except now those artifacts are prompts, chat templates, retrieval parameters, tool definitions, and the provider model ID (which vendors update under you; pin dated snapshots where offered). Evaluation changes character: classical ML has ground-truth labels and crisp metrics; LLM outputs need golden sets scored by rubric-driven LLM judges calibrated against periodic human review, plus hard checks where possible (schema validation, citation presence, safety filters). Monitoring adds new axes: token spend per request/tenant (a cost regression is a production incident), time-to-first-token, refusal/fallback rates, and quality sampling on live traffic. New failure modes with no classical analog: prompt injection, context-window truncation, provider-side model updates changing behavior with zero deploys on your side, and non-determinism making 'reproduce the bug' probabilistic. What breaks: drift detection on input distributions means little when inputs are free text — you monitor output quality and task success instead. The punchline: the governance instinct is identical, the artifact list and eval machinery are new.
Code Example
# the versioned unit in LLMOps
release:
prompt: support-answer@v23
model: claude-sonnet-5@2026-05-snapshot
retriever: {index: kb_v7, k: 8, reranker: ce-v2}
gates: [schema_valid>0.995, judge_score>0.87, cost_per_req<\$0.012]Interview Tip
The crisp contrast: 'in MLOps the weights change and the prompt doesn't exist; in LLMOps the weights are frozen and everything around them is the deployable'. Then name provider-side model updates as the drift source teams forget.
💬 Comments
Context
A lender's default-risk model: features computed in Spark for training, re-implemented in Java inside the serving API. Offline AUC 0.86; online performance materially worse, and nobody trusted the gap analysis.
Problem
Dual implementations drifted — a 90-day aggregate used calendar days offline and a rolling window online; null handling differed; time zones disagreed. Every model iteration re-paid the re-implementation tax and added new skew risk.
Solution
Adopted a feature store (definitions written once, materialized to offline store for training and online store for serving). Training reads point-in-time-correct historical values, eliminating label leakage; serving reads the same definitions from Redis-backed online store. A daily consistency job samples live entities, computes features through both paths, and alerts on divergence. Migration went feature-family by feature-family, with the consistency job proving each family before the model switched to it.
Commands
feast apply # feature definitions as code, reviewed in PRs
feast materialize-incremental $(date -u +%F)
python consistency_probe.py --sample 5000 --alert-if divergence>0.001
Outcome
Online-offline metric gap closed from ~6 points to under 1; new-feature lead time dropped from weeks (two implementations + reconciliation) to days; the consistency probe caught two upstream data bugs before they reached the model.
Lessons Learned
Point-in-time correctness was the silent win — the old training set had subtle future leakage that inflated offline metrics. The consistency probe is permanent infrastructure, not migration scaffolding.
💬 Comments
Context
A subscription business whose churn model was retrained 'when someone remembered' — every few months, by hand, with promotion decided by eyeballing a notebook.
Problem
Model staleness tracked seasonal behavior badly; manual retrains were error-prone (one shipped trained on a truncated extract); and there was no audit trail of why any given version went live.
Solution
Built a scheduled weekly pipeline: data extraction pinned to a lakehouse snapshot -> validation suite (schema, null rates, PSI vs the training baseline, volume sanity) -> training with full lineage tags -> evaluation vs live champion on AUC overall plus five business segments -> auto-promotion to challenger with 48h shadow scoring -> champion alias switch only if online agreement holds. Failures at any gate open a ticket with the failing evidence instead of shipping.
Commands
airflow dags trigger churn_ct # weekly schedule + manual trigger
great_expectations checkpoint run churn_training_data
python promote.py --model churn --require 'auc>=champ-0.002' --slices tier,region,tenure
Outcome
Model freshness went from quarters to weeks with zero manual steps in the happy path; two months in, the data-validation gate blocked a retrain when an upstream schema change silently nulled a key feature — previously that would have shipped.
Lessons Learned
The gates get tested by reality faster than you expect — build the failure path (tickets, evidence, override with audit) as carefully as the success path. Slice gates blocked one 'better on average' model that regressed the highest-value tier.
💬 Comments
Context
A marketplace running six production models (ranking, pricing, fraud) monitored like ordinary microservices — latency, errors, saturation — with no visibility into prediction quality between offline evaluations.
Problem
A fraud-model degradation ran for three weeks (an upstream currency-conversion bug shifted a key feature's distribution) and was caught by the finance team's chargeback report, not by engineering.
Solution
Added an ML-specific observability layer per model: input feature distributions (PSI/KL vs training baseline, per feature), prediction distribution and score-band shifts, feature null/staleness rates, and — where labels arrive — rolling online metrics with label-lag-aware windows. Dashboards standardized across models; alerts page the owning team on drift thresholds tuned per feature criticality. A weekly 'model health' review triages drift alerts against business metrics.
Commands
python monitor.py --model fraud --baseline train_v41 --metrics psi,null_rate,score_dist
promql: ml_feature_psi{model='fraud',feature='amount_usd'} > 0.25evidently report --reference train.parquet --current last_24h.parquet
Outcome
Mean time to detect model-quality regressions dropped from weeks to hours; the next upstream data bug (a stale FX feed) was caught in 4 hours via feature-staleness alerts, before measurable business impact.
Lessons Learned
Input monitoring catches most incidents earlier than output monitoring — data breaks before predictions visibly do. Per-feature alert thresholds need criticality tiers or the drift dashboard becomes another ignored alert channel.
💬 Comments
Symptom
Chargeback losses creep upward over three weeks with no model deploys, no infra changes, and green service dashboards. The degradation surfaces via a finance report, not monitoring. Offline eval on the (stale) golden set still scores fine.
Error Message
No error. The serving path returns predictions normally; the only contemporaneous signal was a subtle shift in the score distribution and a jump in PSI for amount_usd — neither of which was monitored.
Root Cause
An upstream service changed its currency-conversion path and began emitting amounts in minor units (cents) for a subset of currencies. The model's amount_usd feature shifted by 100x for those transactions; the model, trained on major units, systematically under-scored their fraud risk. Service-level monitoring saw healthy latency and 200s — nothing watched the data flowing through.
Diagnosis Steps
Solution
Fixed the upstream unit bug; added per-feature distribution monitoring (PSI vs training baseline) and score-distribution alerts for the model. Backfilled analysis quantified the loss window for finance. The affected feature also gained a range/unit sanity check at serving time that hard-fails obviously-wrong magnitudes.
Commands
python drift_report.py --feature amount_usd --window 30d --baseline train_v41
SELECT currency, avg(amount_usd), stddev(amount_usd) FROM scored_txns GROUP BY 1 ORDER BY 2 DESC;
git log --oneline upstream-payments-service --since='6 weeks ago'
Prevention
Monitor model inputs, not just service health — feature drift alerts with per-feature thresholds, score-distribution tracking, and feature-range assertions at serving. Contract tests with upstream data producers so unit/schema changes break CI, not production silently.
💬 Comments
Symptom
The nightly CT pipeline runs green end-to-end; the next morning, recommendation CTR is down 25%. The freshly promoted model's file size is normal, metrics in the training report look plausible at a glance — but its top-K outputs are strangely uniform across users.
Error Message
No pipeline error. Post-hoc: the training row count was 4.1M vs the usual ~38M — the extract ran while an upstream backfill had the table partially rewritten.
Root Cause
The training extract raced an upstream table rewrite (drop-and-reload pattern), reading a mostly-empty snapshot. Data validation checked schema and null rates but not volume vs history; evaluation compared candidate metrics on the same truncated distribution (so 'AUC vs itself' looked fine) rather than against the live champion on a fixed golden set; promotion was fully automatic with no champion comparison. Three independently weak gates lined up.
Diagnosis Steps
Solution
Rolled back by repointing the champion alias (minutes). Fixed the pipeline: read from immutable snapshots only (no racing live rewrites), added volume/distribution gates (row count within ±20% of trailing median, per-segment counts), and made promotion require beating the current champion on a fixed golden set — making self-referential evaluation impossible.
Commands
mlflow runs describe --run-id <bad_run> | grep data_rows
SELECT snapshot_id, committed_at, operation FROM iceberg.features.history ORDER BY committed_at DESC LIMIT 10;
client.set_registered_model_alias('recs', 'champion', last_good) # rollbackPrevention
Every CT gate should be phrased against an external baseline (history, champion, golden set), never only against the candidate's own data. Consume immutable data versions; treat 'source table is mid-rewrite' as an expected condition, not an anomaly. Include a canary sanity check on model behavior (output entropy, per-user variance) before promotion.
💬 Comments
Symptom
A re-launched propensity model performs well in offline replay but visibly worse online; investigation stalls because 'the features are identical by construction — it's a feature store'. Conversion lift from the new model is half the offline projection.
Error Message
No error. feast materialize jobs show intermittent failures retried the next cycle; online-store feature timestamps lag event time by up to 18 hours for high-churn features.
Root Cause
The materialization job (offline -> online store sync) ran on a 6-hour schedule with silent retry-next-cycle failure handling, and Redis TTLs occasionally evicted hot keys between syncs. Training used point-in-time-correct offline values (effectively fresh); serving read stale online values. The 'same definitions' guarantee covered computation, not freshness — the two stores had different effective timestamps for fast-moving features.
Diagnosis Steps
Solution
Moved the top fast-moving features to streaming materialization (event-driven writes to the online store), tightened batch sync to hourly for the rest with hard alerting on job failure and on max(feature_age) per feature family, and set Redis memory/TTL policy so eviction can't silently drop feature keys. Added feature-age to the model's serving logs for future diagnosis.
Commands
feast materialize-incremental --views user_activity # inspect logs/history
redis-cli info stats | grep evicted_keys
python freshness_probe.py --features user_activity --percentile 99
Prevention
Freshness is a per-feature SLO: define max acceptable age per feature family, monitor actual age at read time (not just sync-job success), and match materialization strategy (streaming vs batch) to each feature's velocity. Offline replay evaluations should simulate production staleness, not assume zero-lag features.
💬 Comments