TheArchitect

ML Inference + Feature Store

AWS architecture template · Serverless ML serving with a dual feature store: Redis for low-latency online features and S3 for offline training set assembly via Athena.

ML Inference + Feature Store is a free AWS architecture template built with Lambda, S3, API Gateway, Athena, CloudWatch, DynamoDB, ElastiCache, IAM and Kinesis. Open it in TheArchitect to edit the diagram, estimate monthly cost, and export the CloudFormation or Terraform that deploys it.

Inference APIModel ServerPrediction Cac…Online Feature…Model ArtifactsFeature EventsFeature WriterOffline Featur…Training Set B…Inference Rolep99 Latency Al…
ML Inference + Feature Store architecture diagram

AWS services in this template

  • Lambda×2
  • S3×2
  • API Gateway
  • Athena
  • CloudWatch
  • DynamoDB
  • ElastiCache
  • IAM
  • Kinesis

Deployment notes

Needs input

  • Upload model artifact to S3 before invoking model-server Lambda.
  • Set Lambda environment variables for model S3 path and cache endpoint.

Generated CloudFormation — 13 resources

Logical IDResource type
InferenceAPIapigatewayAWS::ApiGateway::RestApi
ModelServerlambdaAWS::Lambda::Function
PredictionCacheelasticacheAWS::ElastiCache::CacheCluster
OnlineFeatureStoredynamodbAWS::DynamoDB::Table
ModelArtifactss3AWS::S3::Bucket
FeatureEventskinesisAWS::Kinesis::Stream
FeatureWriterlambdaAWS::Lambda::Function
OfflineFeatureStores3AWS::S3::Bucket
TrainingSetBuilderathenaAWS::Athena::WorkGroup
InferenceRoleiamAWS::IAM::Role
p99LatencyAlarmcloudwatchAWS::CloudWatch::Alarm
ModelServerlambdaModelArtifactss3PermissionAWS::Lambda::Permission
FeatureWriterlambdaOfflineFeatureStores3PermissionAWS::Lambda::Permission
View the full CloudFormation template
{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "Generated by TheArchitect — https://www.thecloudarchitect.dev",
  "Metadata": {
    "TheArchitect": {
      "Edges": [
        {
          "From": "ModelServerlambda",
          "To": "PredictionCacheelasticache",
          "Label": "cache check"
        },
        {
          "From": "ModelServerlambda",
          "To": "OnlineFeatureStoredynamodb",
          "Label": "fetch features"
        },
        {
          "From": "ModelServerlambda",
          "To": "ModelArtifactss3",
          "Label": "load model"
        },
        {
          "From": "FeatureEventskinesis",
          "To": "FeatureWriterlambda",
          "Label": "feature events"
        },
        {
          "From": "FeatureWriterlambda",
          "To": "OnlineFeatureStoredynamodb",
          "Label": "upsert features"
        },
        {
          "From": "FeatureWriterlambda",
          "To": "OfflineFeatureStores3",
          "Label": "write parquet"
        },
        {
          "From": "OfflineFeatureStores3",
          "To": "TrainingSetBuilderathena",
          "Label": "training queries"
        },
        {
          "From": "InferenceRoleiam",
          "To": "ModelServerlambda",
          "Label": "execution role"
        },
        {
          "From": "InferenceRoleiam",
          "To": "FeatureWriterlambda",
          "Label": "execution role"
        },
        {
          "From": "ModelServerlambda",
          "To": "p99LatencyAlarmcloudwatch",
          "Label": "duration metric"
        }
      ]
    }
  },
  "Resources": {
    "InferenceAPIapigateway": {
      "Type": "AWS::ApiGateway::RestApi",
      "Properties": {
        "Name": "ml-inference-api",
        "Description": "ML Inference REST API",
        "EndpointConfiguration": {
          "Types": [
            "REGIONAL"
          ]
        }
      }
    },
    "ModelServerlambda": {
      "Type": "AWS::Lambda::Function",
      "DependsOn": [
        "InferenceRoleiam"
      ],
      "Properties": {
        "FunctionName": "ml-model-server",
        "Runtime": "python3.12",
        "Handler": "model.handler",
        "MemorySize": 3008,
        "Timeout": 30,
        "Description": "Loads model from S3 on cold-start, checks Redis prediction cache, reads online features from DynamoDB, returns prediction.",
        "Role": {
          "Fn::GetAtt": [
            "InferenceRoleiam",
            "Arn"
          ]
        }
      }
    },
    "PredictionCacheelasticache": {
      "Type": "AWS::ElastiCache::CacheCluster",
      "Properties": {
        "ClusterName": "ml-prediction-cache",
        "Engine": "redis",
        "EngineVersion": "7.1",
        "CacheNodeType": "cache.t3.small",
        "NumCacheNodes": 1,
        "AtRestEncryptionEnabled": true,
        "TransitEncryptionEnabled": true
      }
    },
    "OnlineFeatureStoredynamodb": {
      "Type": "AWS::DynamoDB::Table",
      "Properties": {
        "TableName": "online-feature-store",
        "BillingMode": "PAY_PER_REQUEST",
        "AttributeDefinitions": [
          {
            "AttributeName": "id",
            "AttributeType": "S"
          }
        ],
        "KeySchema": [
          {
            "AttributeName": "id",
            "KeyType": "HASH"
          }
        ]
      }
    },
    "ModelArtifactss3": {
      "Type": "AWS::S3::Bucket",
      "Properties": {
        "BucketName": "ml-model-artifacts",
        "VersioningConfiguration": {
          "Status": "Enabled"
        },
        "PublicAccessBlockConfiguration": {
          "BlockPublicAcls": true,
          "BlockPublicPolicy": true,
          "IgnorePublicAcls": true,
          "RestrictPublicBuckets": true
        },
        "NotificationConfiguration": {
          "LambdaConfigurations": [
            {
              "Event": "s3:ObjectCreated:*",
              "Function": {
                "Fn::GetAtt": [
                  "ModelServerlambda",
                  "Arn"
                ]
              }
            }
          ]
        }
      },
      "DependsOn": [
        "ModelServerlambdaModelArtifactss3Permission"
      ]
    },
    "FeatureEventskinesis": {
      "Type": "AWS::Kinesis::Stream",
      "Properties": {
        "Name": "ml-feature-events",
        "StreamModeDetails": {
          "StreamMode": "PROVISIONED"
        },
        "ShardCount": 2,
        "RetentionPeriodHours": 24
      }
    },
    "FeatureWriterlambda": {
      "Type": "AWS::Lambda::Function",
      "DependsOn": [
        "InferenceRoleiam"
      ],
      "Properties": {
        "FunctionName": "ml-feature-writer",
        "Runtime": "python3.12",
        "Handler": "writer.handler",
        "MemorySize": 512,
        "Timeout": 120,
        "Description": "Reads feature events from Kinesis, upserts DynamoDB online store, and appends Parquet to S3 offline store.",
        "Role": {
          "Fn::GetAtt": [
            "InferenceRoleiam",
            "Arn"
          ]
        }
      }
    },
    "OfflineFeatureStores3": {
      "Type": "AWS::S3::Bucket",
      "Properties": {
        "BucketName": "ml-offline-feature-store",
        "VersioningConfiguration": {
          "Status": "Enabled"
        },
        "PublicAccessBlockConfiguration": {
          "BlockPublicAcls": true,
          "BlockPublicPolicy": true,
          "IgnorePublicAcls": true,
          "RestrictPublicBuckets": true
        },
        "NotificationConfiguration": {
          "LambdaConfigurations": [
            {
              "Event": "s3:ObjectCreated:*",
              "Function": {
                "Fn::GetAtt": [
                  "FeatureWriterlambda",
                  "Arn"
                ]
              }
            }
          ]
        }
      },
      "DependsOn": [
        "FeatureWriterlambdaOfflineFeatureStores3Permission"
      ]
    },
    "TrainingSetBuilderathena": {
      "Type": "AWS::Athena::WorkGroup",
      "Properties": {
        "Name": "ml-training-set-builder",
        "Description": "Assembles training datasets from offline feature store partitions",
        "State": "ENABLED",
        "WorkGroupConfiguration": {
          "PublishCloudWatchMetricsEnabled": true,
          "BytesScannedCutoffPerQuery": 10737418240,
          "ResultConfiguration": {
            "OutputLocation": "s3://ml-offline-feature-store/athena-results/"
          }
        }
      }
    },
    "InferenceRoleiam": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "RoleName": "ml-inference-role",
        "Description": "Lambda execution role for model server and feature writer",
        "AssumeRolePolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Principal": {
                "Service": "lambda.amazonaws.com"
              },
              "Action": "sts:AssumeRole"
            }
          ]
        },
        "ManagedPolicyArns": [
          "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
        ]
      }
    },
    "p99LatencyAlarmcloudwatch": {
      "Type": "AWS::CloudWatch::Alarm",
      "Properties": {
        "AlarmName": "ml-p99-latency-high",
        "AlarmDescription": "Model server p99 duration > 2000 ms — check feature fetch or model load performance",
        "Namespace": "AWS/Lambda",
        "MetricName": "Duration",
        "Period": 60,
        "EvaluationPeriods": 5,
        "Threshold": 2000,
        "ComparisonOperator": "GreaterThanThreshold",
        "TreatMissingData": "notBreaching",
        "ExtendedStatistic": "p99"
      }
    },
    "ModelServerlambdaModelArtifactss3Permission": {
      "Type": "AWS::Lambda::Permission",
      "DependsOn": [
        "ModelServerlambda"
      ],
      "Properties": {
        "Action": "lambda:InvokeFunction",
        "FunctionName": {
          "Ref": "ModelServerlambda"
        },
        "Principal": "s3.amazonaws.com",
        "SourceAccount": {
          "Ref": "AWS::AccountId"
        },
        "SourceArn": {
          "Fn::Sub": "arn:${AWS::Partition}:s3:::ml-model-artifacts"
        }
      }
    },
    "FeatureWriterlambdaOfflineFeatureStores3Permission": {
      "Type": "AWS::Lambda::Permission",
      "DependsOn": [
        "FeatureWriterlambda"
      ],
      "Properties": {
        "Action": "lambda:InvokeFunction",
        "FunctionName": {
          "Ref": "FeatureWriterlambda"
        },
        "Principal": "s3.amazonaws.com",
        "SourceAccount": {
          "Ref": "AWS::AccountId"
        },
        "SourceArn": {
          "Fn::Sub": "arn:${AWS::Partition}:s3:::ml-offline-feature-store"
        }
      }
    }
  }
}

Generated by TheArchitect from the diagram above. Review values such as names, CIDR ranges and instance sizes before deploying to your account.

Related templates