AI & integration

AWS SageMaker model monitoring: how to set up drift alerts

A model that performed well last quarter can quietly go wrong this quarter without a single line of code changing. The inputs shift, the world moves on, and the predictions drift away from reality long before anyone notices in a dashboard.

We see this constantly in client work: a model ships, performs well in the demo, and nobody sets up anything to watch it once it's live. Six months later someone asks why approval rates look strange, and nobody knows, because nobody was looking. AWS SageMaker Model Monitor exists to close that gap, watching data quality, model quality, and bias in production and telling you when something has moved far enough from your baseline to matter.

Turning it on properly takes more than flipping a switch. You need a baseline that reflects clean data, a sensible schedule, thresholds that don't cry wolf, and a plan for what happens when something flags. Skip any of that and you get either alerts nobody trusts or a model quietly degrading with nobody watching. This guide covers data capture, baselines, scheduled monitoring, CloudWatch alerts, bias and explainability, and how to triage a violation once it fires rather than retraining on reflex.

How SageMaker Model Monitor actually works

SageMaker Model Monitor is a native AWS service that runs scheduled jobs against traffic flowing through your real-time endpoints. Rather than asking you to write custom logging and comparison logic from scratch, you get a structured framework with four built-in monitor types, each covering a different failure mode. The pipeline underneath is the same for all four, so get that clear before touching any configuration.

Four monitors, four questions

Each monitor answers a different question about your model's behaviour in production, and you don't need to run all four from day one.

  • Data Quality Monitor watches the statistical shape of your input features, distributions, missing values, type mismatches, and fires when they drift against your baseline.
  • Model Quality Monitor compares predictions against ground truth labels once they arrive, tracking accuracy, F1, AUC, or RMSE depending on your task.
  • Model Bias Monitor checks disparate impact and fairness metrics across the demographic groups you define, catching cases where the mix of who's using the model shifts enough to skew outcomes.
  • Model Explainability Monitor tracks SHAP feature attribution values over time, flagging when a different set of inputs starts driving predictions.

Start with data quality and model quality. They're cheaper to set up, they catch the majority of real incidents, and they build your team's confidence in reading violation reports before you add the extra complexity of Clarify-based bias and explainability monitoring on top.

What happens under the hood

Every monitor type runs the same underlying sequence. SageMaker captures a sample of your endpoint's live traffic through a data capture configuration attached at deployment. That capture lands in an S3 bucket as JSON Lines files, one per inference batch, partitioned by date and hour. A scheduled processing job then reads those files, compares them against a pre-computed baseline, and writes a violation report to a separate output path.

The baseline is the single most important input to the whole system. Get it wrong and every constraint SageMaker generates from it is wrong too, which means your monitoring flags perfectly normal traffic as violations.

Scheduling runs on a cron expression you set, typically hourly or daily depending on traffic volume. AWS runs these as fully managed Processing jobs: no compute to provision, no cluster to babysit. A job spins up, runs the comparison, writes its output, and shuts down.

Violations don't shout on their own

SageMaker writes a constraint_violations.json file to your output path, and that's it. Nothing pages anyone. To make violations visible you connect the monitoring schedule to Amazon CloudWatch, which picks up metrics such as feature_baseline_drift_distance and data_missing_values automatically once the schedule is active. From CloudWatch you set alarms with thresholds that trigger SNS notifications, a Lambda function, or whatever incident tooling your team already runs.

Before you start: permissions and a monitoring plan

Before touching any code, sort out two things: the permissions your monitoring jobs need, and a written decision about what you're actually watching for. Rushing past either is the fastest way to build a pipeline that runs on schedule and produces nothing useful.

Permissions your execution role needs

Your SageMaker execution role has to write to S3, publish metrics to CloudWatch, and create and run Processing jobs. If you're reusing an existing role, check it covers:

  • SageMaker access covering monitoring-specific actions, either the full managed policy or a scoped custom one
  • S3 access scoped to your monitoring input and output buckets
  • CloudWatch access for publishing metrics
  • SNS access if alerts will route through an SNS topic

Set up a dedicated S3 bucket for monitoring, with separate prefixes for captured data, baseline artefacts, and job output. Keeping this away from your training data and model artefacts saves a lot of confused debugging later.

Write the plan down before configuring anything

A monitoring plan is a short set of decisions, not a document: which monitor types you'll run, which features actually matter, how often you need results, and who gets paged when something violates. Answer these before Step 1:

  • Which monitor types are you enabling: data quality, model quality, bias, explainability, or some combination
  • Which three to five input features matter most and deserve the tightest thresholds
  • What cadence fits your traffic: hourly, daily, or weekly
  • Are you going strict (flag any deviation) or lenient (flag past a defined percentage)
  • Where do alerts land: an SNS topic, a Slack channel via Lambda, PagerDuty, something else
  • Who actually triages a violation when it fires

Every configuration choice that follows flows from these six answers, so it's worth getting them down before Step 1.

Step 1: turn on data capture

Data capture is the foundation everything else sits on. Without it, monitoring jobs have nothing to compare against. SageMaker captures inference requests and responses from your live endpoint and writes them to S3 in JSON Lines format, partitioned by date and hour. Switch this on at deployment with a DataCaptureConfig, or enable it on an existing endpoint without a full redeploy.

Configure the capture

DataCaptureConfig controls whether capture is on, what percentage of traffic to sample, and where it lands in S3. Set EnableCapture to true with a sampling percentage of 100 during initial setup, which gives your baseline job the most representative sample. Once the baseline is established, drop the sampling rate to cut storage cost.

from sagemaker.model_monitor import DataCaptureConfig
from sagemaker import Session

sagemaker_session = Session()

data_capture_config = DataCaptureConfig(
    enable_capture=True,
    sampling_percentage=100,
    destination_s3_uri="s3://your-bucket/monitoring/captured-data/",
    capture_options=["REQUEST", "RESPONSE"]
)

predictor = model.deploy(
    initial_instance_count=1,
    instance_type="ml.m5.xlarge",
    endpoint_name="your-endpoint-name",
    data_capture_config=data_capture_config
)

Include both REQUEST and RESPONSE in capture_options. You need the inputs and the output for data quality and model quality monitoring later, and leaving RESPONSE out means redeploying just to get prediction distributions you assumed were already there.

Confirm the data is actually landing

Send a handful of test inferences, then check the S3 destination prefix. SageMaker writes one JSON Lines file per inference batch under a path structured as year, month, day, hour.

aws s3 ls s3://your-bucket/monitoring/captured-data/ --recursive

Files should appear within two or three minutes. If the prefix stays empty after several calls, the usual cause is a missing write permission on the destination bucket, not a problem with the endpoint itself.

Step 2: build your baseline

The baseline is what every future monitoring job measures against. SageMaker runs a one-time processing job on a representative slice of your captured data and produces two files: statistics.json, describing each feature's mean, median, standard deviation and distribution shape, and constraints.json, defining the boundaries those properties are allowed to move within.

Run the baseline job

The SDK's DefaultModelMonitor class handles baseline creation in a single method call. Point it at your captured S3 prefix, give it an output location, and let it run.

from sagemaker.model_monitor import DefaultModelMonitor
from sagemaker.model_monitor.dataset_format import DatasetFormat

monitor = DefaultModelMonitor(
    role="arn:aws:iam::your-account-id:role/your-sagemaker-role",
    instance_count=1,
    instance_type="ml.m5.xlarge",
    volume_size_in_gb=20,
    max_runtime_in_seconds=3600
)

monitor.suggest_baseline(
    baseline_dataset="s3://your-bucket/monitoring/captured-data/",
    dataset_format=DatasetFormat.csv(header=True),
    output_s3_uri="s3://your-bucket/monitoring/baseline-results/",
    wait=True,
    logs=False
)

This typically takes five to fifteen minutes depending on dataset size. Both artefacts land in your baseline output prefix, ready to attach to a schedule.

Read the constraints before you trust them

SageMaker's auto-generated constraints are conservative by default, but review them before use. Open constraints.json and check the inferred type for each feature, the completeness threshold, and any string constraints on categorical columns. Features with high cardinality or irregular distributions are the ones where the auto-generated constraints tend to come out too strict or too loose for what production actually looks like. Edit the JSON where needed, and keep both versions in version control for an audit trail.

Step 3: schedule monitoring and wire up CloudWatch

With a baseline in hand, schedule the recurring jobs that watch for drift. A monitoring schedule tells SageMaker to run on a defined cadence, pull captured data, compare it against your baseline, and write a violation report. This is where the setup stops being a one-off task and becomes a live system.

Create the schedule

from sagemaker.model_monitor import CronExpressionGenerator

monitor.create_monitoring_schedule(
    monitor_schedule_name="data-quality-schedule",
    endpoint_input="your-endpoint-name",
    output_s3_uri="s3://your-bucket/monitoring/schedule-output/",
    statistics=monitor.baseline_statistics(),
    constraints=monitor.suggested_constraints(),
    schedule_cron_expression=CronExpressionGenerator.hourly(),
    enable_cloudwatch_metrics=True
)

enable_cloudwatch_metrics=True is not optional if you want alerts to work. Leave it off and violation reports still write to S3, but nothing reaches CloudWatch, so any alarm you build has no data to evaluate. Match your cadence to traffic and detection speed. For low-traffic endpoints, run daily rather than hourly: hourly jobs against a thin sample produce statistics that bounce around on sample size alone, not genuine drift.

Reading a violation report

Each run writes a constraint_violations.json file under a timestamped folder. Every entry names the feature, the constraint breached, and the observed value that triggered it. A typical entry looks like this:

  • Feature name: customer_age
  • Check type: baseline drift check
  • Description: observed distance 0.38 exceeds threshold 0.20

Watch the distance relative to the threshold, not just whether it crossed the line. A distance slightly above threshold on a single run is often noise rather than genuine drift. Track it across several consecutive runs before treating it as something that warrants investigation.

Set up the alarm

Build alarms through the SDK rather than clicking through the console; it's repeatable and lives in version control. Here's an alarm on drift distance for one feature, routed to an SNS topic:

import boto3

cloudwatch = boto3.client("cloudwatch", region_name="eu-west-1")

cloudwatch.put_metric_alarm(
    AlarmName="data-drift-customer-age",
    MetricName="feature_baseline_drift_distance",
    Namespace="/aws/sagemaker/Endpoints/data-metrics",
    Dimensions=[
        {"Name": "Endpoint", "Value": "your-endpoint-name"},
        {"Name": "MonitoringSchedule", "Value": "data-quality-schedule"},
        {"Name": "Feature", "Value": "customer_age"}
    ],
    Statistic="Maximum",
    Period=3600,
    EvaluationPeriods=2,
    Threshold=0.20,
    ComparisonOperator="GreaterThanThreshold",
    AlarmActions=["arn:aws:sns:eu-west-1:your-account-id:your-sns-topic"],
    TreatMissingData="notBreaching"
)

Set EvaluationPeriods to 2 so an alarm needs two consecutive breaches before it fires, filtering out the single-period noise mentioned above. Build one alarm per critical feature rather than a single catch-all: a named feature alarm gets your team to the right place faster than a generic one that just says something, somewhere, changed.

Best for teams that need reliable early warning without babysitting a dashboard. Watch for: alarms on too many features at once, which trains your team to ignore them. Start with three to five features that actually matter to the business, not every column your data happens to have.

An SNS topic fans out to email, SMS, a Lambda function, or a third-party tool like PagerDuty. For teams on Slack, a small Lambda that formats the CloudWatch payload and posts it to a channel, with the metric value and a link to the graph, is the most practical setup we've seen.

Step 4: add model quality, bias, and explainability

Data quality monitoring tells you whether your inputs have moved. It says nothing about whether the model is still right. Model quality monitoring closes that gap by comparing predictions against ground truth labels once they arrive, which is the monitor that most directly reflects business impact.

What model quality needs that the others don't

Unlike data quality, which works purely from captured inference data, model quality needs your predictions and a matching set of ground truth labels supplied after the fact. That creates a built-in time lag, since real-world outcomes often take hours, days, or weeks to materialise. Your ground truth delivery cadence has to match your monitoring schedule, or the job runs and reports nothing useful.

If ground truth never becomes available for a prediction, such as fraud flags on transactions nobody ever investigates, model quality monitoring simply isn't viable, and you lean on data quality and bias monitoring instead.

Baseline it with the ModelQualityMonitor class against a labelled holdout dataset that reflects production conditions, with predictions and ground truth labels in one file:

from sagemaker.model_monitor import ModelQualityMonitor
from sagemaker.model_monitor.dataset_format import DatasetFormat

model_quality_monitor = ModelQualityMonitor(
    role="arn:aws:iam::your-account-id:role/your-sagemaker-role",
    instance_count=1,
    instance_type="ml.m5.xlarge"
)

model_quality_monitor.suggest_baseline(
    baseline_dataset="s3://your-bucket/monitoring/baseline-with-labels.csv",
    dataset_format=DatasetFormat.csv(header=True),
    output_s3_uri="s3://your-bucket/monitoring/model-quality-baseline/",
    problem_type="BinaryClassification",
    inference_attribute="prediction",
    ground_truth_attribute="label",
    wait=True
)

Set problem_type to match your task, since it decides which accuracy metrics SageMaker computes: F1, AUC, or RMSE. Schedule it the same way as data quality, but pass a ground truth S3 URI, and keep those files partitioned by date so SageMaker can match predictions to labels by timestamp.

Bias and explainability

Neither data quality nor model quality tells you whether the model is fair or interpretable. Bias drift happens when the mix of your inference traffic shifts in a way that produces unequal outcomes across protected groups, even when the model weights haven't changed. If a loan approval model starts seeing more applications from one age group, the approval rate for that group can diverge from baseline for reasons that have nothing to do with the model itself. SageMaker's ModelBiasMonitor catches this by computing fairness metrics such as Disparate Impact Ratio against a baseline built from labelled production data, run through SageMaker Clarify.

Explainability monitoring tracks SHAP feature attribution values over time and flags when the features actually driving predictions shift from baseline. It's good at catching silent upstream changes, where a feature that mattered very little suddenly dominates because a pipeline feeding it changed shape. Both run through Clarify configs rather than DefaultModelMonitor, so budget extra setup time. Once all four monitors are live, you have real coverage across accuracy, drift, fairness, and interpretability, not a single number that says the model is fine without saying what that means.

Triage: what to do when an alarm fires

An alarm firing does not automatically mean the model needs retraining. Jump straight to retraining on every alert and you'll burn data science time chasing statistical noise, pipeline blips, and misconfigured thresholds, one of the most common and expensive mistakes we see teams make once monitoring goes live.

Work through the diagnosis in order

Open the violation report for the run that triggered the alarm and read the observed distance, then check the metric's history in CloudWatch over the past five to ten periods to see whether it's a trend or a spike. Ask, in order:

  • Did the captured sample size drop in the flagged period? Small samples produce unreliable statistics on their own.
  • Is the violation confined to one feature, or spread across several at once? Multiple features moving together usually points to an upstream pipeline change rather than genuine model decay.
  • Did anything else change in the same window: a schema update, an infrastructure change, a data source migration?
  • Does the model quality monitor show a matching drop in accuracy, or is it still sitting inside baseline?

A violation that appears on one run and vanishes on the next is almost always a small-sample artefact, not a real shift. Don't escalate on a single data point.

Retrain, adjust, or escalate elsewhere

If the diagnosis points to a genuine shift in the input distribution that lines up with a real accuracy drop, raise a retraining ticket and hand the data science team the statistics comparison from the flagged period alongside the accuracy numbers, not just a raw alarm notification.

If your baseline no longer reflects current production because the data has legitimately moved on, update the baseline instead of retraining the model. Re-run the baseline job against a fresh sample, review the new constraints, and point your schedule at the revised artefacts.

If the root cause sits upstream of your endpoint, a broken pipeline feeding bad values in, route it to data engineering rather than the model owners. Getting each violation type to the right team with the right evidence stops the same false alarm firing every month.

Getting it into production properly

By this point you have data quality, model quality, bias, and explainability monitoring, with CloudWatch alarms routing violations to the right people and a triage process for what happens next. Before production, run one full cycle end to end in staging: confirm captured data flows correctly, baselines generate cleanly, and alarms fire as expected. Document every threshold and baseline version somewhere your team keeps runbooks.

Monitoring isn't something you set once. Revisit baselines every quarter, or sooner if your data shifts meaningfully, and treat each triage cycle as feedback that sharpens your thresholds rather than a one-off firefight. This is the production readiness work that separates a model running quietly for years from one that needs constant hand-holding, and it's the piece teams most often skip when racing to ship.

If you'd rather have someone design the monitoring architecture around your existing ML infrastructure than build it from scratch, talk to us and we'll walk through what your setup actually needs.

Start at your core.

Tell us where your data is today and what you want AI to do. We will come back with a straight answer on what your foundation needs and where the quickest real win is.

Talk to us