ML System Design, Drift and Feature Stores
Frame the task, price serving, beat baselines, roll out safely, catch drift and training-serving skew.
- Concepts and terms
- Tasks and commands
- Annotated code
- Diagrams
- Defaults and numbers
- Troubleshooting
- Questions and tips
One definition, two readers#
From Lesson 02.21
From goal to safe launch#
From Lesson 02.19
Toolkit calls#
| Task | Code |
|---|---|
| Cheapest plan in budget | choose_serving(workload, |
| Lift over the best baseline | compare_to_baselines(..., |
| Score a design doc | review_design_doc(doc, |
| Numeric drift, decile bins | psi(reference, |
| Largest CDF gap | ks_statistic(sample_a, |
| Categorical drift | chi_square_test(reference_counts, |
| As-of join with TTL | point_in_time_join(labels, |
| Compare logged features | detect_skew(training, |
From 02.19 code/main.py, 02.20 code/main.py, 02.21 code/main.py
Price batch and online serving#
refresh_hours = workload.max_staleness_hours - costs.batch_job_hoursif refresh_hours <= 0:job longer than the limit return ServingPlan("batch", False, ...)runs_per_day = 24.0 / refresh_hourspredictions = workload.entities * runs_per_daydollars = (predictions / 1e6 * costs.batch_dollars_per_million + workload.requests_per_day / 1e6 * costs.lookup_dollars_per_million)replicas = max(costs.min_replicas, math.ceil(workload.peak_qps / costs.replica_qps))dollars = replicas * costs.replica_dollars_per_hour * 24.0online pays for idlefeasible = costs.online_latency_ms <= workload.latency_budget_msFrom 02.19 code/main.py
Drift and skew formulas#
- Covariate drift
P(x)changes. Input monitors see it.- Prior drift
P(y)changes. Prediction and outcome monitors see it.- Concept drift
P(y | x)changes. Only outcome monitors see it.- PSI
- \sum (\text{current} - \text{reference}) \cdot \ln(\text{current} / \text{reference}) over bins.
- KS D
- largest gap between two empirical CDFs. 0 for identical, 1 for no overlap.
- Cramér's V
- square root of chi2 / grand_total for a two-row table.
- Little's law
- in progress = throughput times latency. 400 rps at 50 ms is 20.
- Lift
- model score minus the strongest baseline score.
From Lesson 02.20, Lesson 02.19
Commands#
| Command | What it does |
|---|---|
python3 code/ | Runs each lesson demo with the standard library |
python3 code/ | Reviews your own design document |
stats.ks_2samp(reference_amounts, | KS statistic and p-value in scipy |
stats.chi2_contingency(table) | Chi-square statistic, p-value and dof |
DummyClassifier(strategy= | Majority-class baseline |
feast apply | Registers feature definitions in Feast |
feast materialize-incremental <end time> | Copies latest values to the online store |
store.get_historical_features(entity_df= | Point-in-time training features |
From Lesson 02.19, Lesson 02.20, Lesson 02.21
Defaults and numbers#
| Setting | Default | Note |
|---|---|---|
family_alpha | 0.01 | Bonferroni, 0.005 for each of two tests |
psi_warn | 0.10 | PSI below 0.1 is stable |
psi_drift | 0.25 | Above 0.25 is a significant shift |
ks_drift | 0.10 | D must pass and p below alpha |
min_cramers_v | 0.10 | Effect size for categories |
patience | 2 | Consecutive drift windows before an alert |
window_size | 500 | step=250, label_delay=500 |
| PSI share clip | 0.0001 | Stops an infinite log |
Failures and fixes#
| Symptom | Cause | Fix |
|---|---|---|
| Model service times out | No plan for the failure | Use the heuristic baseline |
| Nightly batch job fails | Scores go missing | Serve last good scores, then the rule |
| Score distribution jumps | Wrong decisions at scale | Pause actions and alert a person |
| Inputs drift, accuracy stable | Covariate drift | Often none. Watch the outcome |
| Accuracy drops, inputs quiet | Concept drift | Collect new labels, then retrain |
| Offline 0.948, live worse | Naive join reads future rows | Point-in-time join |
| Serving default -1, training 365 | Two code paths | One registry definition |
| 60 false alarms per day | 50 features hourly at 0.05 | Bonferroni, effect size, persistence |
From Lesson 02.19, Lesson 02.20, Lesson 02.21
Abbreviations#
- PSI
- Population Stability Index. From credit scoring.
- KS
- Kolmogorov-Smirnov. Two-sample test on CDFs. Needs no bins.
- TTL
- time to live. An older value counts as missing.
- QPS
- queries per second. Size for the peak, not the average.
- p99
- the latency that 99 of 100 requests stay under.
- CBPE
- Confidence-based Performance Estimation. Fails on concept drift.
From Lesson 02.19, Lesson 02.20, Lesson 02.21
Questions#
- When not to use ML?
- A short rule works, no labels, nobody acts, or one error is unacceptable.
- Why not AUC?
- It averages over thresholds you never use. Match the decision instead.
- How to split data?
- Train 60-80%, validation 10-20%, test 10-20%. Hold out the newest period.
- How to break a feedback loop?
- Keep a random holdout that never gets the intervention.
- How to find the skew cause?
- Compare online read, fresh compute and second code path.
- After retraining?
- Test on recent labels, shadow and canary, then reset the reference window.