AI/ML

From SageMaker POC to Production: The 6 Infrastructure Problems Every ML Team Hits

2026-04-27 · 9 min read

Your data science team spent three months building a machine learning model. It works beautifully in a SageMaker notebook. Accuracy is high, the stakeholders are excited, and someone says the words every ML engineer dreads: "Great, let's put it in production."

This is where most ML projects stall. The model itself is only 20% of a production ML system. The other 80% is infrastructure: serving, monitoring, retraining, feature engineering, cost management, and deployment pipelines. I have helped a dozen teams navigate this transition on AWS, and the same six problems come up every single time.

Problem 1: Your SageMaker Endpoint Costs $2,500/Month to Serve 50 Requests per Day

The default SageMaker deployment pattern is a real-time endpoint backed by a persistent ML instance. A single ml.m5.xlarge endpoint costs approximately $0.269/hour — that is $194/month running 24/7. For a model that handles a few thousand requests per day with sub-second latency requirements, this makes sense.

But many early-stage ML applications do not need real-time inference. A recommendation engine that updates scores once per hour, a fraud detection model that processes batches of transactions every 5 minutes, or an internal classification tool used by 10 people during business hours — none of these justify a persistent endpoint.

The cost problem gets worse when teams deploy multiple models (one per customer segment, one per region, one per experiment) or maintain staging and production endpoints simultaneously. I have seen teams running six SageMaker endpoints at $2,500/month total when their actual inference volume would cost $30/month on a properly configured setup.

The fix depends on your latency requirements:

For near-real-time (< 1 second): Use SageMaker Serverless Inference. It scales to zero when there are no requests and scales up automatically. You pay per millisecond of compute time and per GB of data processed. For bursty workloads with idle periods, this can cut costs by 90%.

# Create a serverless endpoint configuration
aws sagemaker create-endpoint-config \
  --endpoint-config-name my-model-serverless \
  --production-variants '[{
    "VariantName": "AllTraffic",
    "ModelName": "my-model",
    "ServerlessConfig": {
      "MemorySizeInMB": 2048,
      "MaxConcurrency": 10
    }
  }]'

The trade-off is cold start latency. If the endpoint has been idle, the first request takes 30-60 seconds. For internal tools and batch-adjacent workloads, this is acceptable.

For batch processing (minutes to hours): Use SageMaker Batch Transform or SageMaker Processing jobs. These spin up compute, process your data, write results to S3, and shut down. You pay only for the time the job runs.

For asynchronous inference: SageMaker Asynchronous Inference endpoints queue requests and process them with configurable concurrency. They can scale to zero instances during idle periods and handle payloads up to 1 GB.

Problem 2: No CI/CD Pipeline for Models

In the POC phase, deploying a model means someone opens a notebook, runs all cells, and manually creates an endpoint. This breaks immediately at scale. Questions that need answers:

  • Who approved this model for production?
  • What training data was used?
  • What hyperparameters were set?
  • Can we reproduce this exact model six months from now?
  • How do we roll back if the new model performs worse?

The fix: SageMaker Pipelines.

SageMaker Pipelines is a purpose-built CI/CD system for ML workflows. A pipeline defines the full workflow as a DAG (directed acyclic graph) of steps:

  1. Processing step — data validation and feature engineering
  2. Training step — model training with tracked hyperparameters
  3. Evaluation step — compute metrics on a held-out test set
  4. Condition step — only proceed if accuracy exceeds threshold
  5. Registration step — register the model in SageMaker Model Registry
  6. Approval step — manual or automated approval gate
  7. Deployment step — deploy approved model to endpoint
from sagemaker.workflow.pipeline import Pipeline
from sagemaker.workflow.steps import ProcessingStep, TrainingStep
from sagemaker.workflow.conditions import ConditionGreaterThanOrEqualTo
from sagemaker.workflow.condition_step import ConditionStep

pipeline = Pipeline(
    name="my-model-pipeline",
    steps=[process_step, train_step, eval_step, condition_step],
    parameters=[input_data, instance_type, approval_threshold],
)

pipeline.upsert(role_arn=role)
pipeline.start()

Every pipeline execution is versioned and auditable. The Model Registry tracks which pipeline version produced which model, with full lineage back to the training data. When a model degrades, you can trace back to the exact data and parameters that produced it.

Problem 3: No Feature Store

In the POC, features are computed in the notebook from raw data every time you train. In production, you need the same features available for both training and inference, computed consistently, and versioned.

Without a feature store, teams inevitably end up with training-serving skew — the features computed during training are slightly different from the features computed during inference. This is the most common source of "the model worked in testing but not in production" complaints.

The fix: SageMaker Feature Store.

Feature Store provides two storage layers:

  • Online store — low-latency (single-digit milliseconds) feature retrieval for real-time inference
  • Offline store — full feature history in S3 (Parquet format) for training and batch inference
# Create a feature group
aws sagemaker create-feature-group \
  --feature-group-name customer-features \
  --record-identifier-feature-name customer_id \
  --event-time-feature-name event_time \
  --feature-definitions '[
    {"FeatureName": "customer_id", "FeatureType": "String"},
    {"FeatureName": "avg_transaction_amount_30d", "FeatureType": "Fractional"},
    {"FeatureName": "transaction_count_7d", "FeatureType": "Integral"},
    {"FeatureName": "days_since_last_login", "FeatureType": "Integral"}
  ]' \
  --online-store-config '{"EnableOnlineStore": true}' \
  --offline-store-config '{"S3StorageConfig": {"S3Uri": "s3://my-bucket/features/"}}'

The key benefit is consistency. Your training pipeline reads features from the offline store, and your inference endpoint reads the same features from the online store. Both are populated by the same feature engineering code. No skew.

Problem 4: Model Drift Goes Undetected

Your model was trained on data from January. It is now April. The world has changed — user behavior has shifted, market conditions are different, the distribution of your input data has drifted. Your model is silently making worse predictions, and nobody knows.

This is model drift, and it happens to every ML model in production. The question is not whether it will drift, but how quickly you detect it.

The fix: SageMaker Model Monitor.

Model Monitor continuously analyzes the predictions your model makes in production and compares them against a baseline. It detects three types of drift:

  1. Data quality drift — input features have changed distribution (e.g., a numeric feature that averaged 50 now averages 200)
  2. Model quality drift — prediction accuracy has degraded (requires ground truth labels, which may arrive with a delay)
  3. Bias drift — model fairness metrics have shifted across protected groups
# Create a monitoring schedule
aws sagemaker create-monitoring-schedule \
  --monitoring-schedule-name my-model-monitor \
  --monitoring-schedule-config '{
    "ScheduleConfig": {
      "ScheduleExpression": "cron(0 * ? * * *)"
    },
    "MonitoringJobDefinition": {
      "MonitoringInputs": [{
        "EndpointInput": {
          "EndpointName": "my-model-endpoint",
          "LocalPath": "/opt/ml/processing/input"
        }
      }],
      "MonitoringOutputConfig": {
        "MonitoringOutputs": [{
          "S3Output": {
            "S3Uri": "s3://my-bucket/monitoring/",
            "LocalPath": "/opt/ml/processing/output"
          }
        }]
      },
      "MonitoringResources": {
        "ClusterConfig": {
          "InstanceCount": 1,
          "InstanceType": "ml.m5.large",
          "VolumeSizeInGB": 20
        }
      },
      "MonitoringAppSpecification": {
        "ImageUri": "156813124566.dkr.ecr.us-east-1.amazonaws.com/sagemaker-model-monitor-analyzer"
      }
    }
  }'

When drift is detected, Model Monitor emits CloudWatch metrics. Set up alarms on these metrics to trigger automated retraining or alert the data science team.

Problem 5: Training Costs Are Out of Control

ML training is compute-intensive, and SageMaker training instances are not cheap. A single ml.p3.2xlarge (one V100 GPU) costs $3.825/hour. A hyperparameter tuning job that launches 50 training jobs can easily cost $500-1,000 for a single experiment.

Teams in the POC phase tend to train on the largest instance available "just to be safe," leave training jobs running overnight because they forgot to set a stopping condition, and run hyperparameter searches with unnecessarily broad ranges.

The fix: Managed Spot Training and smart resource management.

SageMaker Managed Spot Training uses EC2 Spot Instances for training, saving up to 70-90% compared to On-Demand pricing. The setup is a single parameter:

aws sagemaker create-training-job \
  --training-job-name my-training-job \
  --enable-managed-spot-training \
  --stopping-condition MaxRuntimeInSeconds=86400,MaxWaitTimeInSeconds=172800 \
  --checkpoint-config S3Uri=s3://my-bucket/checkpoints/ \
  --resource-config '{
    "InstanceType": "ml.p3.2xlarge",
    "InstanceCount": 1,
    "VolumeSizeInGB": 50
  }' \
  --algorithm-specification '{
    "TrainingImage": "your-training-image",
    "TrainingInputMode": "File"
  }' \
  --input-data-config '[...]' \
  --output-data-config '{"S3OutputPath": "s3://my-bucket/models/"}'

The --checkpoint-config parameter is critical. Spot Instances can be interrupted, and checkpointing allows training to resume from where it left off rather than starting over.

Additional cost controls:

  • Set MaxRuntimeInSeconds on every training job. A forgotten training job running for 72 hours on a p3.8xlarge costs $700.
  • Use SageMaker Experiments to track all training runs. This prevents teams from re-running experiments they already completed.
  • Start with smaller instance types and scale up only if training time is unacceptable. Many models train fine on ml.m5.xlarge (CPU) and do not need GPU instances.
  • Use warm pools for iterative development. SageMaker warm pools keep instances provisioned between training jobs, eliminating the 5-10 minute startup time for each run.

Problem 6: No A/B Testing Infrastructure

Your new model is ready. It performs better on offline metrics. But offline metrics do not always translate to real-world improvements. You need to test the new model against the current production model with real traffic.

Without A/B testing infrastructure, teams face a binary choice: deploy and hope, or do not deploy at all. Both options are bad.

The fix: SageMaker endpoint traffic splitting.

SageMaker endpoints support multiple production variants with configurable traffic splitting:

aws sagemaker create-endpoint-config \
  --endpoint-config-name ab-test-config \
  --production-variants '[
    {
      "VariantName": "ModelA-Current",
      "ModelName": "model-v1",
      "InstanceType": "ml.m5.xlarge",
      "InitialInstanceCount": 1,
      "InitialVariantWeight": 90
    },
    {
      "VariantName": "ModelB-Challenger",
      "ModelName": "model-v2",
      "InstanceType": "ml.m5.xlarge",
      "InitialInstanceCount": 1,
      "InitialVariantWeight": 10
    }
  ]'

This sends 90% of traffic to the current model and 10% to the new model. SageMaker emits per-variant CloudWatch metrics (invocations, latency, errors), so you can compare performance in real time.

Once you are confident the new model is better, shift traffic gradually:

# Shift to 50/50
aws sagemaker update-endpoint-weights-and-capacities \
  --endpoint-name my-endpoint \
  --desired-weights-and-capacities '[
    {"VariantName": "ModelA-Current", "DesiredWeight": 50},
    {"VariantName": "ModelB-Challenger", "DesiredWeight": 50}
  ]'

For more sophisticated A/B testing, use SageMaker Inference Experiments, which provide built-in statistical analysis and automatic winner detection.

The Transition Roadmap

If you are an ML team staring at a working POC and wondering how to get to production, here is the order I recommend:

  1. CI/CD first — set up SageMaker Pipelines for reproducible, auditable model training
  2. Monitoring second — deploy Model Monitor so you know when things go wrong
  3. Feature Store third — eliminate training-serving skew
  4. Cost optimization fourth — switch to Spot Training, right-size instances, use serverless endpoints
  5. A/B testing fifth — once you have a deployment pipeline, add traffic splitting
  6. Iterate — each component reinforces the others

The transition from POC to production typically takes 4-8 weeks for a team that has not done it before. The good news is that SageMaker provides managed services for each of these components, so you are building on infrastructure rather than building infrastructure. The hard part is not the AWS services — it is the organizational discipline to treat ML models as software that needs testing, monitoring, and maintenance like any other production system.

Need help with your AWS infrastructure?

Book a free 30-minute consultation to discuss your challenges.