Long-form essays on performance, architecture, the AI-integrated web, and recent graduate research on data drift in ML systems.
December 5, 2025 · 12 min read
Data Drift in Machine Learning Systems: Detection Methods, Limitations, and Research Gaps
Graduate research with my professor at UMBC — a literature review of data drift, the detection techniques in use today, where they break down, and where the open research lives.
Machine learning systems deployed in dynamic environments are highly susceptible to performance degradation due to shifts in underlying data distributions — commonly referred to as data drift. This paper presents a literature review of data drift, focusing on its types, detection techniques, limitations, and open research challenges. We examine covariate drift, concept drift, and prior probability shift, and analyze existing detection approaches including statistical tests, window-based techniques, and model-based methods. We then critically evaluate the limitations of these methods — dependence on labeled data, high false alarm rates, and scalability issues in real-world deployments — and identify key research gaps for future work.
Index terms: Data Drift, Concept Drift, Machine Learning, Drift Detection, Model Monitoring.
I. Introduction
Machine learning models are increasingly deployed in real-world applications such as healthcare diagnostics, fraud detection, recommendation systems, and autonomous systems. These models are typically trained on historical data under the assumption that future data will follow the same distribution. In practice, that assumption rarely holds — environments evolve, user behavior shifts, and external conditions change. This phenomenon, known as data drift, leads to performance degradation over time.
In fraud detection, attackers continuously modify their strategies, causing previously learned patterns to become obsolete. In healthcare, demographic changes and disease trends affect predictive models. These dynamic changes make continuous monitoring and adaptation of ML systems non-negotiable.
II. Types of Data Drift
Covariate drift occurs when the distribution of input features changes over time while the relationship between inputs and target remains unchanged — for example, a recommendation system whose user preferences shift seasonally: P_train(X) ≠ P_test(X), but P(Y|X) is constant.
Concept drift refers to changes in the relationship between input features and the target itself — fraud patterns evolving in financial transactions, for instance: P(Y|X)_train ≠ P(Y|X)_test. It can be sudden, gradual, incremental, or recurring, and is the most challenging type to detect.
Prior probability shift occurs when the distribution of the target variable changes — a spike in fraudulent transactions during a particular period, for example: P(Y)_train ≠ P(Y)_test.
III. Drift Detection Techniques
Statistical methods compare distributions between training and incoming data. The Kolmogorov–Smirnov test measures the maximum difference between two distributions; the Population Stability Index (PSI) measures distribution shift across bins.
Window-based methods monitor performance over time using sliding windows. ADWIN (Adaptive Windowing) automatically adjusts window size and flags drift when statistical properties change significantly.
Model-based methods watch the model itself. DDM (Drift Detection Method) monitors error rate; EDDM (Early Drift Detection Method) improves sensitivity to gradual drift. Statistical methods are cheap but miss concept drift; window methods react late; model methods catch concept drift but need labels.
IV. Limitations
Most model-based methods require labeled data, which is often unavailable or delayed in production. Statistical methods generate false alarms from noise or minor fluctuations. High-dimensional systems struggle with computational efficiency. Several methods only fire after measurable performance degradation has already happened — by which point user impact is real.
V. Challenges in Real-World Deployment
Production drift detection introduces operational concerns beyond the algorithm: integration with MLOps pipelines, real-time monitoring guarantees, handling high-velocity data streams, and balancing sensitivity against stability. Overly sensitive detectors trigger frequent retraining and run up compute cost; under-sensitive ones quietly let regression in.
VI. Research Gaps and Future Directions
Unsupervised drift detection — most current methods rely on labels, leaving a wide gap for unsupervised and semi-supervised approaches that work without ground truth.
Explainable drift detection — today's systems tell you that drift happened, but rarely why. Practitioners need explanations that point at features, segments, and root causes.
Adaptive, self-healing systems — future detectors should adapt without a human in the loop, retraining or re-weighting on their own schedule.
Benchmarking and standardization — the field still lacks shared benchmarks, making honest comparison between detection methods difficult.
VII. Conclusion
Data drift remains one of the harder problems in maintaining ML systems in production. This review covered the types of drift, the current detection toolkit, where each tool breaks, and where the most useful research still sits. Scalable, unsupervised, and adaptive detectors — backed by real benchmarks — are the direction the field needs to move.
August 12, 2024 · 6 min read
How I Took a Marketing Site From Lighthouse 72 to 90+
A practical retrospective on bundle splitting, image strategy, and the boring discipline that actually moves performance scores.
When I joined the marketing platform team at Rrootshell, the site landed at a Lighthouse performance score of 72 on mobile — fast enough to ship, slow enough to cost conversions. Six weeks later it consistently scored 90+. None of the wins came from a single magic flag.
Measure first, guess never
Every change started with a real trace. Lighthouse, WebPageTest, and Chrome DevTools' performance panel gave us LCP, CLS, TBT, and a flame graph that told us which components were doing the most work. Without that baseline, every optimization is a vibe.
We added a CI step that ran Lighthouse on every PR against a representative URL and posted the delta as a comment. The day a regression cost us 4 points was the day the team stopped landing 200KB of new JS without a reason.
Split the bundle, lazy-load the heavy stuff
Our initial bundle was a single 480KB JavaScript file. We split routes with dynamic imports, moved the marketing video player and the charting library behind IntersectionObserver, and deferred the analytics script. The home route's JS dropped under 140KB gzipped.
The biggest lesson: most users never reach the components you're paying to load on first paint. Lazy-loading is free performance for the people who matter most — first-time visitors.
Images: the unglamorous 30 points
Hero images alone were dragging LCP past 3.5s. We converted them to AVIF with WebP fallbacks, served correctly sized variants via srcset, preloaded the LCP image, and set explicit width/height to lock CLS at 0. That single workstream was worth more than any JS optimization we did.
Boring discipline beats clever tricks
We removed two unused fonts, killed a polyfill bundle no one needed in 2024, and stopped shipping moment.js. None of those are blog-worthy on their own. Stacked together they were the difference between 86 and 92.
Performance work isn't a sprint — it's a habit. The score holds because the CI guardrail holds.
October 4, 2024 · 8 min read
Designing an Async LLM Pipeline With FastAPI
Notes from building a multimodal AI platform: queueing strategy, provider fallbacks, and keeping latency predictable under load.
Calling an LLM provider from a request handler is a one-liner. Doing it reliably under real traffic — across two providers, with retries, fallbacks, and predictable tail latency — is a system. This is a walk through how I built that system on FastAPI for a graduate project.
Async by default, sync only when forced
FastAPI's async story is good, but only if the whole call chain stays non-blocking. We used httpx.AsyncClient for OpenAI and Anthropic, asyncio.gather for parallel fan-out, and a shared connection pool so we didn't pay the TLS handshake on every request. A single synchronous library call in the middle of that chain undoes the whole thing.
Queue requests you can't serve immediately
LLM providers rate-limit aggressively. We put incoming requests through an internal queue with a small async worker pool sized to the provider's TPM budget. When the queue grew past a threshold, we returned a 202 with a job ID instead of holding the connection open. Clients polled or subscribed via SSE. Latency under burst load became predictable instead of catastrophic.
Two providers, one interface
We wrote a tiny Provider protocol and two adapters — OpenAIProvider and AnthropicProvider. The orchestrator picked one based on capability (vision, long context) and live health. When one provider 5xx'd, the orchestrator failed over without the caller knowing. Cost-routing came for free: cheap models for short prompts, expensive models only when context demanded it.
Observability is the whole game
Every request emitted a structured log with provider, model, prompt tokens, completion tokens, latency, and outcome. A small React dashboard rendered p50/p95/p99 latency and cost per route. We caught a misbehaving prompt template within minutes instead of at the end of the month when the bill arrived.
What I'd do differently
Streaming responses earlier. Token-level streaming dramatically improves perceived latency and lets you cancel runaway generations. If I rebuilt the platform today, streaming would be the default path, not the optimization.
November 21, 2024 · 5 min read
Reusable React Components Without the Over-Engineering
Pragmatic patterns for building component libraries that survive contact with real product teams — and which abstractions to skip.
Most internal React libraries die the same way: someone tries to anticipate every future need, builds a 12-prop Button, and a year later the product team forks it because nobody can read the API. Here's the smaller set of rules that has held up across the libraries I've shipped.
Design tokens before components
Color, spacing, radii, and type scale belong in CSS variables — not buried inside a Button. Once tokens are stable, components become thin. Once tokens are unstable, no amount of component cleverness saves you.
Variants, not prop explosions
Use a small enum of variants (primary, secondary, ghost, destructive) and a size enum. Resist adding boolean props for one-off looks — they multiply, conflict, and never get deleted. cva (class-variance-authority) and similar tools make this nearly free.
Composition over configuration
Instead of a Card with header, footer, action, icon, and badge props, ship <Card>, <CardHeader>, <CardFooter>. Consumers reach for the parts they need. Your library stays small; their layouts stay readable.
Skip the abstractions you don't yet need
A theme provider, an i18n layer, an animation framework, a polymorphic 'as' prop — every one of these adds surface area. Add them when a real use case shows up, not in anticipation. The best component libraries I've used were embarrassingly small at v1.