Skip to content
Tanishq Jain
Resume

MLOps drift loop

A model that notices the world changed, retrains itself, and only ships the new version if it wins.

Role
Solo
Timeline
Apr 2026
  • Python 3.12
  • scikit-learn
  • MLflow
  • Evidently
  • FastAPI
  • Prometheus
  • Grafana
  • DVC
  • pandera
  • Docker Compose
  • Kubernetes
  • GitHub Actions

TL;DR

  • Problem

    Code doesn't rot; models do. When traffic drifts, a classifier quietly gets worse until someone notices.

  • What I built

    A closed loop for a network-intrusion classifier: label-free drift detection, automatic retraining, a champion/challenger gate, and a hot reload of the serving API — with no human in the path.

  • Result

    After a synthetic shift that included a new attack type, realized F1 recovered automatically from 0.27 to 0.78, measured on unseen traffic.

Batch 20F1 0.78v2

Tap a point or use the arrow keys to read a batch.

  • v1 · Stale champion
  • v2 · Promoted challenger

The loop

  1. Serve
  2. Monitor
  3. Detect
  4. Retrain
  5. Gate
  6. Reload

Realized F1 on each unseen batch, as the real serving process and controller replay the synthetic shift.Synthetic dataSource
View data
Realized F1 per unseen batch · commit 0a0b95f, exported 20 Oct 2026
BatchMedian tF1ModelEvent
112,199.50.265v1 · Stale champion
212,599.50.226v1 · Stale champion
312,999.50.252v1 · Stale champion
413,399.50.245v1 · Stale champion
513,799.50.352v1 · Stale championDrift detected; challenger promoted, at the controller tick after this batch
614,199.50.803v2 · Promoted challenger
714,599.50.780v2 · Promoted challenger
814,999.50.790v2 · Promoted challenger
915,399.50.763v2 · Promoted challenger
1015,799.50.796v2 · Promoted challenger
1116,199.50.774v2 · Promoted challenger
1216,599.50.781v2 · Promoted challenger
1316,999.50.771v2 · Promoted challenger
1417,399.50.788v2 · Promoted challenger
1517,799.50.788v2 · Promoted challenger
1618,199.50.796v2 · Promoted challenger
1718,599.50.750v2 · Promoted challenger
1818,999.50.761v2 · Promoted challenger
1919,399.50.772v2 · Promoted challenger
2019,799.50.780v2 · Promoted challenger

The loop

  1. A FastAPI service serves predictions and logs every request.
  2. Evidently checks the features for drift; it doesn't wait for labels, which arrive late.
  3. On drift, the system retrains, and MLflow registers a challenger.
  4. The challenger is promoted only if it beats the champion by at least 0.01 F1 and clears a 0.50 floor.
  5. Serving hot-reloads the new model, and Prometheus and Grafana track it all.

The experiment

  • Data: 20,000 synthetic rows over time.
  • The shift: the drift period moves 40% of the features and adds an attack type the old model has never seen.
  • The replay: the real serving process and controller replay it in 20 batches of 400 rows.
  • The result: realized F1 sits around 0.27 while the stale champion serves drifted traffic. Drift is caught and the challenger promoted on the first allowed check, after which F1 holds around 0.78.

Honest notes

  • Easy detection. The synthetic shift trips every feature, so detection was never borderline.
  • Optimistic promotion check. The promotion test overlaps the retraining data, so it's optimistic. That's why the number I quote comes from the per-batch series on unseen batches.
  • Label lag. In production, the F1 line would lag by the label delay.
  • The model isn't the point. A plain logistic regression actually beat the random forest at baseline (F1 0.688 vs 0.576). The deliverable is the loop, not the model.

Running it

  • Deploy targets: a Docker Compose stack, Kubernetes manifests for minikube, and an Oracle Kubernetes Engine overlay.
  • GitHub Actions:
    • CI with a model-quality gate;
    • multi-architecture images pushed to GHCR;
    • a scheduled daily retrain.

Limitations and what's next

  • Data: synthetic, not CICIDS2017; promotion needs a disjoint future holdout.
  • Scale: a single SQLite writer means a single serving worker.
  • Next: real traffic data, a feature store, and concept-drift detection.

What I learned

Code doesn't rot; models do. The model took an afternoon; the honest evaluation took weeks.

How this was built

I wrote the execution spec and owned the evaluation design and its honesty checks; Claude Code wrote much of the implementation.