Monitor deployed ML models for performance degradation and data drift. Covers feature drift detection, prediction monitoring, model staleness indicators, automated retraining triggers, and the patterns that ensure AI systems stay accurate after deployment.
AI Model Monitoring and Drift Detection is the practice of continuously observing deployed machine learning models in production, tracking their performance, input data quality, and output behavior over time. It matters when models degrade silently—when predictions become inaccurate not because of new data, but because the underlying data distribution has shifted, or when inference latency, memory usage, or label quality drifts without alert.
Monitor model health during live inference by capturing request-response pairs, latency, and resource usage. A model serving endpoint returns a standard JSON response with a prediction field and a timestamp. To track these, the system logs every inference request to a structured log stream.
{
"request_id": "r-7a2b3c",
"input": {
"user_id": 12345,
"session_duration": 142,
"page_views": 8
},
"prediction": 0.73,
"confidence": 0.89,
"timestamp": "2025-04-05T10:23:15Z",
"model_version": "v3.4.1",
"latency_ms": 42
}
Each inference is stored in a time-series database like InfluxDB or a log aggregation system like Loki. A periodic job runs every 5 minutes to compute metrics:
avg(latency_ms)confidence valuesprediction valuesmodel_versionA failure mode arises when a model version is deployed but the logs show no increase in traffic to it. The root cause is often incorrect routing—e.g., the model service is registered under model-3.4.1 but the load balancer expects model-v3.4.1—causing the new version to be ignored.
Detect when the input data distribution changes over time. The pipeline computes statistical summaries of input features at regular intervals (e.g., daily) and compares them to a reference baseline.
The baseline is computed from the training data or the first 7 days of production inference. It includes:
At runtime, the system computes the same statistics for the latest data batch (e.g., last 24 hours). It then computes a drift score using the Kolmogorov-Smirnov test for numerical features and Chi-squared test for categorical features.
For a feature page_views with a reference distribution ref and current distribution curr, the KS test computes:
from scipy.stats import kstest
ks_result = kstest(
curr["page_views"],
ref["page_views"],
method="auto"
)
drift_score = ks_result.statistic # D-value
p_value = ks_result.pvalue
A drift is flagged when drift_score > 0.1 or p_value < 0.05. The system emits a drift event:
{
"timestamp": "2025-04-05T12:00:00Z",
"feature": "page_views",
"drift_detected": true,
"drift_score": 0.12,
"p_value": 0.03,
"reference_stats": {
"mean": 5.8,
"std": 2.1,
"n": 12345
},
"current_stats": {
"mean": 7.3,
"std": 3.4,
"n": 15678
},
"model_version": "v3.4.1"
}
The most common failure is feature name mismatch—the training pipeline expects user_session_duration but the inference logs use session_duration. This causes the drift detector to operate on an undefined feature, producing a null drift score for that column.
Detect when the relationship between input features and target labels changes. The system maintains a labeled dataset of recent predictions with true labels, which are either provided by a human annotator or derived from a downstream system.
A concept drift detector computes the model accuracy and label distribution over time. Accuracy is calculated as:
accuracy = sum(y_true == y_pred) / len(y_true)
The system tracks the distribution of true labels (e.g., binary: 0 and 1) and compares it to the predicted label distribution.
Drift is detected when:
prediction values with label as target)A subtle failure mode is label delay: true labels arrive hours or days after the prediction. If the drift detector uses predictions as of now but labels are available only at now + 8h, the model appears to perform worse than it actually does.
To mitigate this, the system aligns predictions and labels by prediction timestamp, joining two tables:
SELECT
p.request_id,
p.prediction,
p.timestamp AS prediction_time,
l.label,
l.timestamp AS label_time
FROM predictions p
JOIN labels l ON p.request_id = l.request_id
WHERE p.timestamp >= '2025-04-04T00:00:00Z'
AND l.timestamp >= '2025-04-04T00:00:00Z'
AND l.timestamp <= p.timestamp + INTERVAL '12 hours'
This ensures that labels are considered valid for predictions made within a 12-hour window.
Track how model outputs change over time, especially in regression and classification tasks. The system calculates per-day metrics on a rolling window (e.g., last 7 days) using the labeled dataset.
Key metrics include:
mean(|y_true - y_pred|)sqrt(mean((y_true - y_pred)^2))A model drift alert triggers when any metric drops below a threshold or deviates by more than 2 standard deviations from the moving average.
The system stores metrics in a time-series database with a schema:
CREATE TABLE model_drift_metrics (
model_version TEXT,
date DATE,
metric_name TEXT, -- 'mae', 'auc', 'rmse'
value DOUBLE PRECISION,
window_size INT, -- 7 for 7-day window
updated_at TIMESTAMPTZ
);
A common failure is metric aggregation window misalignment—e.g., the daily job runs at 04:00 UTC but uses data from the previous calendar day, while the model was trained on a 24-hour window starting at 00:00 UTC. This causes the metrics to lag by 4 hours and introduces jitter.
A unified configuration file defines the monitoring setup. It uses standard YAML with no custom keys.
model_name: user_churn_prediction
input_features:
- name: user_id
type: integer
required: true
- name: session_duration
type: numeric
required: true
min: 0
max: 3600
- name: page_views
type: integer
required: true
min: 0
max: 100
drift_detection:
enabled: true
interval: "1d" # daily
reference_period: "7d" # use first 7 days of production
threshold:
ks: 0.1
chi2: 0.05
accuracy_drop: 0.05
performance_metrics:
- name: mae
type: regression
window: "7d"
aggregation: "mean"
- name: auc
type: classification
window: "7d"
aggregation: "mean"
- name: confusion_matrix
type: classification
window: "7d"
aggregation: "sum"
alerts:
- type: drift_detected
severity: warning
condition: "feature_drift_score > 0.1"
notify: ["slack", "email"]
- type: model_accuracy_drop
severity: critical
condition: "accuracy < 0.85"
notify: ["slack", "webhook"]
- type: latency_high
severity: info
condition: "avg_latency_ms > 100"
notify: ["webhook"]
output:
logs:
format: json
target: "loki://logs.example.com:3100"
metrics:
format: "influxdb"
url: "http://influx.example.com:8086"
database: "ai_monitoring"
This configuration is applied by a deployment pipeline. A deploy-monitoring.sh script runs:
#!/bin/bash
set -euo pipefail
# Apply monitoring config to all model services
for model in $(ls models/); do
envsubst < monitoring-config.yaml | \
curl -X POST \
-H "Content-Type: application/yaml" \
-d @- \
"http://monitoring-api.example.com/v1/models/$model/config"
done
# Trigger a full drift detection run
curl -X POST http://monitoring-api.example.com/v1/drift/detect
The envsubst command replaces environment variables like ${MODEL_VERSION} and ${ENVIRONMENT} in the YAML.
user_id is missing in 10% of requests, the data drift detector logs a null value for that feature, but the system assumes it's present. This causes inconsistent drift scores and triggers alerts for a feature that doesn’t exist.user_preferences is added as a JSON column. The drift detector computes statistics on the user_preferences field but assumes it’s a flat table. The result is a single histogram for the entire JSON string, not per key.user_location, product_category) require binning or sampling. Without this, the chi-squared test is too sensitive and flags drift even for minor shifts.session_duration of 10,000 seconds, but the training data max was 3600, the system flags this as an outlier but doesn’t adjust the drift detection window to include this new range.timestamp in UTC, but the monitoring system uses local time. A model deployed at 08:00 UTC is processed at 09:00 in the monitoring system, causing a 1-hour drift in all metrics.v4.0.0, but the monitoring system expects v4.0, and the model_version field is a string. A request with model_version: "v4.0" is not associated with v4.0.0, causing drift detection to miss the new version entirely.error: "failed to write metrics to influxdb" but no alert is generated. A 4-day gap in metrics goes unnoticed until a weekly review.Alerts are generated via a standard webhook interface. When a drift or performance metric triggers an alert, the system sends:
{
"event_id": "alert-2025-04-05-01",
"timestamp": "2025-04-05T11:30:00Z",
"severity": "critical",
"title": "Model Accuracy Drop in v3.4.1",
"description": "Model accuracy dropped below 0.85 over the past 7 days.",
"details": {
"model_version": "v3.4.1",
"metric": "accuracy",
"current_value": 0.81,
"baseline": 0.87,
"window": "7d"
},
"links": [
{
"label": "View Drift Dashboard",
"url": "https://monitoring.example.com/drift?model=v3.4.1"
},
{
"label": "View Raw Metrics",
"url": "https://monitoring.example.com/metrics?model=v3.4.1"
}
]
}
These alerts are consumed by a centralized incident system like Prometheus Alertmanager or a custom HTTP listener. The alerting system supports:
An alert is considered resolved when the metric returns to normal range for at least one full cycle (e.g., 7 consecutive days of accuracy above 0.85).
This page was rewritten on 17 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy