
Aws Lambda Managed Instances
- 9 installs
- 850 repo stars
- Updated August 3, 2026
- awslabs/agent-plugins
AWS Lambda Managed Instances is a Claude skill that helps developers evaluate, cost-compare, configure, and migrate workloads to AWS Lambda Managed Instances (LMI), which run Lambda functions on AWS-managed EC2 instances
About
AWS Lambda Managed Instances is a skill for evaluating, configuring, and migrating workloads to AWS Lambda Managed Instances (LMI), which run Lambda code on EC2 instances that AWS manages. A developer uses it when deciding whether steady high-volume traffic should move off standard Lambda for cost or cold-start reasons, and when sizing instances and checking concurrency safety. It builds a required cost comparison before recommending LMI.
- Evaluates whether AWS Lambda Managed Instances (LMI) fit a workload with a cost comparison
- Guides EC2 instance sizing, multi-concurrency config, and thread-safety review
- Covers migration patterns, IAM/VPC setup, and troubleshooting for LMI
Aws Lambda Managed Instances by the numbers
- 9 all-time installs (skills.sh)
- Ranked #858 of 1,039 Cloud & Infrastructure skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
aws-lambda-managed-instances capabilities & compatibility
Requires an AWS account; LMI runs on billed EC2 instances
- Capabilities
- aws lambda durable functions · serverless deployment · cost optimization
- Works with
- aws
- Use cases
- devops · data analysis
- Pricing
- Bring your own API key
What aws-lambda-managed-instances says it does
Run Lambda functions on current-generation EC2 instances in your account while AWS manages provisioning, patching, scaling, routing, and load balancing.
Rule of thumb: LMI becomes cost-competitive when your Lambda spend exceeds ~$1,000/month with steady traffic.
npx skills add https://github.com/awslabs/agent-plugins --skill aws-lambda-managed-instancesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 9 |
|---|---|
| repo stars | ★ 850 |
| Last updated | August 3, 2026 |
| Repository | awslabs/agent-plugins ↗ |
What it does
Assess a high-volume steady Lambda workload against LMI, produce a cost comparison, and configure EC2-backed capacity.
Who is it for?
Steady, predictable high-volume Lambda workloads (50M+ req/mo) seeking cost savings
Skip if: Bursty, unpredictable workloads with long idle periods that need scale-to-zero
When should I use this skill?
You need to decide whether a steady, duration-heavy Lambda workload should move to EC2-backed Managed Instances and how to size it.
What you get
A cost comparison plus an LMI configuration (instance family, memory/vCPU, concurrency, scaling) and migration plan.
- Lambda-vs-LMI cost comparison
- Instance sizing and concurrency config
- IAM/VPC setup
By the numbers
- ~450 EC2 instance types available
- memory min 2 GB, max 32 GB
- cost-competitive above ~$1,000/month Lambda spend
Files
AWS Lambda Managed Instances (LMI)
Run Lambda functions on current-generation EC2 instances in your account while AWS manages provisioning, patching, scaling, routing, and load balancing. Combines Lambda's developer experience with EC2's pricing and hardware options.
For standard Lambda development, see aws-lambda skill. For SAM/CDK deployment, see aws-serverless-deployment skill.
When to Load Reference Files
- Cost comparison, pricing analysis, Lambda vs LMI cost, Savings Plans, or Reserved Instances -> see references/cost-comparison.md
- Instance types, memory sizing, vCPU ratios, scaling tuning, or capacity provider config -> see references/configuration-guide.md
- Thread safety, concurrency model, code review checklist, Powertools compatibility, or multi-concurrency readiness -> see references/thread-safety.md
- Before/after code examples, runtime-specific migration (Node.js, Python, Java, .NET), or connection pooling -> see references/migration-patterns.md
- IAM roles, VPC setup, CLI commands, SAM template, or CDK example -> see references/infrastructure-setup.md and scripts/setup-lmi.sh
- Errors, throttling, debugging, or stuck deployments -> see references/troubleshooting.md
Quick Decision: Is LMI Right for This Workload?
| Signal | LMI is a strong fit | Standard Lambda is better |
|---|---|---|
| Traffic | Steady, predictable, 50M+ req/mo | Bursty, unpredictable, long idle |
| Cost | Duration-heavy spend at scale | Low or sporadic invocations |
| Cold starts | Unacceptable (LMI eliminates for provisioned capacity; scale-out may have brief delays) | Tolerable or mitigated by SnapStart |
| Compute | Latest CPUs, specific families, high network bandwidth | Standard Lambda memory/CPU sufficient |
| Isolation | Dedicated EC2 instances in your account, full VPC control | Shared Firecracker micro-VMs acceptable |
| Scale-to-zero | Not needed (execution environments always running) | Required (pay nothing when idle) |
| Code readiness | Thread-safe (Node.js/Java/.NET) or any Python code | Non-thread-safe Node.js/Java/.NET, expensive to change |
Instructions
Step 1: Assess the Workload
Gather these signals before recommending:
1. Traffic pattern: Steady vs bursty? Requests per second? 2. Current costs: Monthly Lambda spend? Existing Savings Plans? 3. Runtime: Node.js, Java, .NET, or Python? 4. Memory/CPU: How much memory? CPU-bound or I/O-bound? 5. Execution duration: Average and P99? 6. Concurrency readiness: Thread safety (Node.js/Java/.NET)? Shared /tmp paths? Per-invocation DB connections? 7. VPC: Already in a VPC? Private resource access needed?
Step 2: Build the Cost Comparison
REQUIRED: Present a cost comparison before recommending LMI. Compare at minimum:
| Scenario | When it wins |
|---|---|
| Lambda on-demand | Low volume, bursty traffic |
| LMI on-demand | High volume, steady traffic |
Rule of thumb: LMI becomes cost-competitive when your Lambda spend exceeds ~$1,000/month with steady traffic.
For discount analysis (Savings Plans, Reserved Instances), refer users to the AWS Pricing Calculator and references/cost-comparison.md for formulas and worked examples. Discount recommendations require workload-specific forecasting beyond this skill's scope.
Step 3: Configure the Deployment
Instance families (~450 types): C-series (compute, .xlarge+), M-series (general, .large+), R-series (memory, .large+). ARM (Graviton) for best price-performance.
Memory-to-vCPU ratios: 2:1 (compute), 4:1 (general, default), 8:1 (memory). Min 2 GB, max 32 GB.
Multi-concurrency defaults/vCPU: Node.js 64, Java 32, .NET 32, Python 16.
Scaling: MinExecutionEnvironments (default 3), MaxVCpuCount (default 400), TargetResourceUtilization.
See references/configuration-guide.md for decision trees and detailed tuning.
Step 4: Migrate the Code
Review code for concurrency safety. LMI runs multiple invocations concurrently per execution environment, but the model differs by runtime:
- Python: Process-based isolation — globals are NOT shared. No thread-safety changes needed. Focus on
/tmpconflicts and memory sizing (per-process × concurrency). - Node.js: Worker threads — globals shared within a worker. Requires async safety. Callback handlers not supported on Node.js 22.
- Java/.NET: OS threads/Tasks — handler shared across threads. Requires full thread safety.
Common issues (all runtimes): shared /tmp paths, per-invocation DB connections. Thread-safety issues (Node.js/Java/.NET only): mutable globals, non-thread-safe libs.
See references/thread-safety.md for the review checklist and references/migration-patterns.md for runtime-specific before/after code.
Step 5: Set Up Infrastructure
1. Create two IAM roles: execution role (for the function) and operator role (for capacity provider EC2 management) 2. Configure VPC with subnets across multiple AZs (recommended 3+ for resiliency) 3. Create capacity provider with VPC config and scaling limits 4. Create or update function with capacity provider attachment 5. Publish a version (triggers instance provisioning)
See references/infrastructure-setup.md for CLI commands and SAM templates.
Step 6: Validate and Cut Over
1. Deploy to a non-production environment first 2. Monitor CloudWatch: CPU utilization, memory, concurrency, throttle rate 3. Gradual traffic shift with weighted aliases (10% → 50% → 100%) 4. Compare costs after 1-2 weeks of production data 5. Decommission standard Lambda once stable
Best Practices
Configuration
- Do: Start with 4:1 ratio and runtime default concurrency
- Do: Use ARM (Graviton) unless x86 dependencies exist
- Do: Let Lambda choose instance types unless specific hardware needed
- Do: Set MaxVCpuCount to control cost ceiling
- Don't: Set MinExecutionEnvironments below 3 in production (reduces multi-AZ coverage). Non-prod environments can use 1 as the minimum.
- Don't: Over-restrict instance types (lowers availability)
Migration
- Do: Start with I/O-heavy functions (benefit most from multi-concurrency; CPU-bound functions compete for same CPU)
- Do: Review code for concurrency safety before attaching to capacity provider (thread safety for Node.js/Java/.NET;
/tmpand memory for Python) - Do: Use weighted aliases for gradual traffic shift
- Do: Include request IDs in all log statements
- Do: Initialize DB pools and SDK clients outside the handler
- Do: Estimate total
/tmpusage under max concurrency - Don't: Write to hardcoded
/tmppaths without request-unique naming - Don't: Skip cost comparison — LMI is not always cheaper
Operations
- Do: Set CloudWatch alarms on throttle rate > 1% and CPU > 80%
- Don't: Manually terminate LMI EC2 instances (delete the capacity provider instead)
- Don't: Forget to publish a version — unpublished functions cannot run on LMI
Limits Quick Reference
| Resource | Limit |
|---|---|
| Memory | 2 GB min, 32 GB max |
| Concurrency/vCPU | 64 (Node.js), 32 (Java/.NET), 16 (Python) |
| Instance lifespan | ~12 hours (auto-replaced by Lambda) |
| EE lifespan | ~4 hours (auto-replaced by Lambda) |
| Runtimes | Node.js, Java, .NET, Python |
| Instance families | C (.xlarge+), M (.large+), R (.large+) |
| Scaling | Doubles within 5 min without throttles |
Troubleshooting Quick Reference
| Issue | Cause | Fix |
|---|---|---|
| 429 throttles | Traffic exceeds scaling speed | Increase MinExecutionEnvironments or lower TargetResourceUtilization |
| Function stuck PENDING | Provisioning instances | Wait; check VPC/IAM config |
| Architecture mismatch | Function ≠ capacity provider arch | Align both to same architecture |
| Cannot terminate instances | Managed by capacity provider | Delete capacity provider instead |
| Race conditions | Code not thread-safe | See references/thread-safety.md |
See references/troubleshooting.md for detailed resolution steps.
Configuration
AWS CLI Setup
REQUIRED: AWS credentials configured on the host machine.
Verify access: Run aws sts get-caller-identity
Regional Availability
Currently available: us-east-1, us-east-2, us-west-2, ap-northeast-1, eu-west-1. Expanding to all commercial regions soon.
Check the Lambda Managed Instances documentation for the latest regional availability.
Language Selection
Default: TypeScript
Override: "use Python" → Python, "use JavaScript" → JavaScript. When not specified, ALWAYS use TypeScript.
IaC Framework Selection
Default: CDK
Override: "use SAM" → SAM YAML, "use CloudFormation" → CloudFormation YAML. When not specified, ALWAYS use CDK.
Error Scenarios
Serverless MCP Server Unavailable
- Inform user: "AWS Serverless MCP not responding"
- Ask: "Proceed without MCP support?"
- DO NOT continue without user confirmation
Unsupported Runtime
- State: "Lambda Managed Instances does not yet support [runtime]"
- List supported runtimes
- Suggest standard Lambda as alternative
Unsupported Region
- State: "Lambda Managed Instances is not yet available in [region]"
- List available regions
Resources
LMI Configuration Guide
Instance Type Decision Tree
- CPU-intensive (encoding, ML, compression) → C-series, 2:1 ratio, concurrency=1/vCPU
- Memory-intensive (caching, large datasets) → R-series, 8:1 ratio
- Network-intensive (streaming, data transfer) → Use AllowedInstanceTypes for n-suffix types, 4:1 ratio
- General/balanced (web APIs, microservices) → M-series, 4:1 ratio, default concurrency
Architecture: ARM (Graviton, g-suffix) for price-performance. x86 (i=Intel, a=AMD) when dependencies require it.
Memory-to-vCPU Ratios
| Ratio | Profile | When to use | Memory examples |
|---|---|---|---|
| 2:1 | Compute | CPU-bound work | 2GB/1vCPU, 4GB/2vCPU |
| 4:1 | General | Most workloads (default) | 4GB/1vCPU, 8GB/2vCPU |
| 8:1 | Memory | Caching, data, Python apps | 8GB/1vCPU, 16GB/2vCPU |
Min: 2 GB / 1 vCPU. Max: 32 GB. Memory must align with ratio multiples.
Memory Sizing from Existing Lambda
| Current Lambda | LMI memory | Ratio | Rationale |
|---|---|---|---|
| 128-512 MB | 2048 MB | 4:1 | LMI minimum; multi-concurrency shares memory |
| 512 MB-1 GB | 2048 MB | 4:1 | Room for concurrent requests |
| 1-2 GB | 4096 MB | 4:1 | Standard upgrade path |
| 2-4 GB | 4096-8192 MB | 4:1 or 8:1 | Depends on memory vs CPU bottleneck |
| 4-10 GB | 8192-16384 MB | 8:1 | Likely memory-heavy workload |
Concurrency Tuning
| Runtime | Default/vCPU | I/O-bound | CPU-bound |
|---|---|---|---|
| Node.js | 64 | Keep or increase | 1 per vCPU |
| Java | 32 | Keep | 1 per vCPU |
| .NET | 32 | Keep | 1 per vCPU |
| Python | 16 | Keep | 1 per vCPU |
Total capacity = MinExecutionEnvironments × PerExecutionEnvironmentMaxConcurrency
Capacity Provider Scaling Controls
| Control | Default | Guidance |
|---|---|---|
| MinExecutionEnvironments | 3 | Min 1 (non-prod); 3+ recommended for prod AZ coverage |
| MaxExecutionEnvironments | — | Set based on cost budget |
| MaxVCpuCount | 400 | Set to control cost ceiling; adjust by load |
| TargetResourceUtilization | ~50% headroom | Raise for cost savings (less burst tolerance) |
| AllowedInstanceTypes | All | Restrict only for specific hardware needs |
| ExcludedInstanceTypes | None | Exclude expensive types in dev/test |
Monitoring Thresholds
- CPU > 80%: reduce concurrency or add vCPUs
- CPU < 20%: increase concurrency for better utilization
- Throttle rate (429s) > 1%: increase MinExecutionEnvironments or reduce utilization target
- Memory > 90%: increase memory or reduce concurrency
- ExecutionEnvironmentConcurrency near ExecutionEnvironmentConcurrencyLimit: saturation — reduce concurrency or scale out
CloudWatch Metrics Dimensions
LMI metrics are split across two CloudWatch dimensions:
- Alias (live): Invocations, Errors, Throttles, Duration
- Version ($LATEST or numbered): CPUUtilization, MemoryUtilization, ExecutionEnvironmentConcurrency, ExecutionEnvironmentCount
Create a unified dashboard combining both views to monitor LMI performance effectively.
Lambda vs LMI Cost Comparison
Use the LMI Pricing Calculator for accurate, up-to-date cost comparisons based on your specific workload parameters (region, instance type, request volume, duration).
When building a cost comparison for a user, gather: region, runtime, requests/month, average duration, memory, and architecture (x86 vs ARM). Plug these into the calculator rather than relying on hardcoded estimates.
When LMI is NOT Cheaper
- Lambda spend below ~$1,000/month (fixed minimum execution environment cost exceeds Lambda)
- Very short functions (< 100ms duration)
- Highly bursty, unpredictable traffic
- Workloads needing scale-to-zero
Tools
- LMI Pricing Calculator — interactive comparison tool
- AWS Pricing Calculator — general AWS cost estimation
LMI Infrastructure Setup
IAM Roles (Two Required)
1. Execution Role (for the function)
Trust policy:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": "lambda.amazonaws.com" },
"Action": "sts:AssumeRole"
}]
}Minimum permissions:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:*:*:log-group:/aws/lambda/*"
}
]
}Add VPC permissions only if the function accesses VPC resources:
{
"Effect": "Allow",
"Action": [
"ec2:CreateNetworkInterface",
"ec2:DescribeNetworkInterfaces",
"ec2:DeleteNetworkInterface"
],
"Resource": "*"
}2. Operator Role (for capacity provider EC2 management)
Trust policy:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": "lambda.amazonaws.com" },
"Action": "sts:AssumeRole"
}]
}Minimum permissions (scoped with conditions):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["ec2:RunInstances", "ec2:CreateTags", "ec2:AttachNetworkInterface"],
"Resource": [
"arn:aws:ec2:*:*:instance/*",
"arn:aws:ec2:*:*:network-interface/*",
"arn:aws:ec2:*:*:volume/*"
],
"Condition": {
"StringEquals": {
"ec2:ManagedResourceOperator": "scaler.lambda.amazonaws.com"
}
}
},
{
"Effect": "Allow",
"Action": [
"ec2:DescribeAvailabilityZones",
"ec2:DescribeCapacityReservations",
"ec2:DescribeInstances",
"ec2:DescribeInstanceStatus",
"ec2:DescribeInstanceTypeOfferings",
"ec2:DescribeInstanceTypes",
"ec2:DescribeSecurityGroups",
"ec2:DescribeSubnets"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": ["ec2:RunInstances", "ec2:CreateNetworkInterface"],
"Resource": [
"arn:aws:ec2:*:*:subnet/*",
"arn:aws:ec2:*:*:security-group/*"
]
},
{
"Effect": "Allow",
"Action": "ec2:RunInstances",
"Resource": "arn:aws:ec2:*:*:image/*",
"Condition": {
"StringEquals": { "ec2:Owner": "amazon" }
}
},
{
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": "<execution-role-arn>"
}
]
}The ec2:ManagedResourceOperator condition ensures RunInstances/CreateTags only apply to Lambda-managed instances. First-time capacity provider creation also requires iam:CreateServiceLinkedRole.
VPC Requirements
LMI runs functions on EC2 instances inside the VPC. These instances need VPC endpoints or NAT to reach AWS services.
- Subnets across multiple AZs recommended (minimum 1 required; 3+ for multi-AZ resiliency)
- Security groups: HTTPS egress (port 443) for AWS API calls; no ingress needed
- VPC endpoints (if no NAT gateway) for AWS service access:
| Endpoint | Type | Purpose |
|---|---|---|
| S3 | Gateway | Object storage access |
| DynamoDB | Gateway | Table access |
| SQS | Interface | Queue operations |
| CloudWatch Logs | Interface | Log delivery |
| CloudWatch Monitoring | Interface | Metrics/EMF |
| X-Ray | Interface | Distributed tracing |
CLI Workflow
Required Parameters
| Parameter | Description |
|---|---|
SUBNET_IDS | Comma-separated subnet IDs across 3+ AZs |
SECURITY_GROUP_ID | Security group ID for the capacity provider |
ACCOUNT_ID | AWS account ID |
OPERATOR_ROLE_ARN | ARN of the operator role (see above) |
EXECUTION_ROLE_ARN | ARN of the execution role (see above) |
FUNCTION_NAME | Name for the Lambda function |
CP_NAME | Name for the capacity provider |
ARCHITECTURE | arm64 (Graviton) or x86_64 |
Automated Setup
See `scripts/setup-lmi.sh` — set the environment variables above and run:
./scripts/setup-lmi.sh <function-name> <capacity-provider-name> <architecture>Manual Steps
# 1. Create capacity provider
aws lambda create-capacity-provider \
--capacity-provider-name $CP_NAME \
--vpc-config "SubnetIds=[$SUBNET_IDS],SecurityGroupIds=[$SECURITY_GROUP_ID]" \
--permissions-config "CapacityProviderOperatorRoleArn=$OPERATOR_ROLE_ARN" \
--instance-requirements "Architectures=[$ARCHITECTURE]" \
--capacity-provider-scaling-config "MaxVCpuCount=30"
# 2. Create function
aws lambda create-function --function-name $FUNCTION_NAME --runtime python3.13 \
--handler app.handler --zip-file fileb://function.zip \
--role $EXECUTION_ROLE_ARN --architectures $ARCHITECTURE \
--memory-size 4096 \
--capacity-provider-config \
"LambdaManagedInstancesCapacityProviderConfig={CapacityProviderArn=arn:aws:lambda:$AWS_REGION:$ACCOUNT_ID:capacity-provider:$CP_NAME}"
# 3. Publish version (triggers provisioning — takes several minutes)
aws lambda publish-version --function-name $FUNCTION_NAME
# 4. Invoke (must use versioned ARN)
aws lambda invoke --function-name $FUNCTION_NAME:1 --payload '{}' response.jsonArchitecture must match between function and capacity provider.
SAM Template
Resources:
MyCP:
Type: AWS::Lambda::CapacityProvider
Properties:
CapacityProviderName: my-cp
VpcConfig:
SubnetIds: [!Ref Sub1, !Ref Sub2, !Ref Sub3]
SecurityGroupIds: [!Ref SG]
PermissionsConfig:
CapacityProviderOperatorRoleArn: !GetAtt OpRole.Arn
InstanceRequirements:
Architectures: [arm64]
CapacityProviderScalingConfig:
MaxVCpuCount: 30
MyFn:
Type: AWS::Serverless::Function
Properties:
Runtime: python3.13
Handler: app.handler
MemorySize: 4096
Architectures: [arm64]
CapacityProviderConfig:
LambdaManagedInstancesCapacityProviderConfig:
CapacityProviderArn: !GetAtt MyCP.ArnCleanup
aws lambda delete-function --function-name my-fn
aws lambda delete-capacity-provider --capacity-provider-name my-cpDeleting the capacity provider destroys all associated EC2 instances.
LMI Migration Patterns
Before/after code examples for migrating to multi-concurrency.
Node.js
Global State
// BEFORE (race condition)
let requestCount = 0;
exports.handler = async (event) => {
requestCount++;
return { count: requestCount };
};
// AFTER (request-isolated)
const { AsyncLocalStorage } = require('node:async_hooks');
const als = new AsyncLocalStorage();
exports.handler = async (event) => {
return als.run({ id: event.requestContext?.requestId }, async () => {
return await processEvent(event);
});
};File I/O
// BEFORE (shared path)
fs.writeFileSync('/tmp/output.json', JSON.stringify(data));
// AFTER (request-unique path)
const path = `/tmp/output-${event.requestContext?.requestId}.json`;
try { fs.writeFileSync(path, JSON.stringify(data)); }
finally { fs.unlinkSync(path); }Database
// BEFORE (per-invocation connection)
exports.handler = async (event) => {
const conn = await mysql.createConnection({/*...*/});
const [rows] = await conn.execute('SELECT ...');
await conn.end();
};
// AFTER (shared pool)
const pool = mysql.createPool({ connectionLimit: 10, /*...*/ });
exports.handler = async (event) => {
const [rows] = await pool.execute('SELECT ...');
return rows;
};Python
Python on LMI uses process-based isolation. Each concurrent invocation runs in its own process with independent memory. Global state is NOT shared, so no locking is needed. The main migration concerns are /tmp conflicts, memory sizing, and connection pooling.
Global State (No Changes Needed)
# This is SAFE on LMI — each process has its own copy of cache
cache = {}
def handler(event, context):
cache[event['key']] = compute(event)
return cache[event['key']]
# Module-level clients are also safe (isolated per process)
s3_client = boto3.client('s3')
dynamodb = boto3.resource('dynamodb')File I/O (Change Required — /tmp is shared across processes)
# BEFORE (conflict — all processes share /tmp)
with open('/tmp/data.json', 'w') as f: json.dump(event, f)
# AFTER (request-unique path)
path = f'/tmp/data-{context.aws_request_id}.json'
try:
with open(path, 'w') as f: json.dump(event, f)
finally:
os.unlink(path)Database (Change Required — each process needs pooled connections)
# BEFORE (per-invocation connection — exhausts limits at concurrency)
def handler(event, context):
conn = psycopg2.connect(host='...')
# AFTER (pool per process — initialized at module level)
from psycopg2 import pool
db_pool = pool.SimpleConnectionPool(1, 3, host=os.environ['DB_HOST'])
def handler(event, context):
conn = db_pool.getconn()
try: return query(conn, event)
finally: db_pool.putconn(conn)
# Note: total connections = pool_size × concurrency (e.g., 3 × 16 = 48)Memory Sizing
# A function using 200 MB per process with default concurrency of 16:
# Total memory ≈ 200 MB × 16 = 3.2 GB
# Use 4:1 or 8:1 memory-to-vCPU ratio to accommodate
# Monitor MemoryUtilization metric and adjust as neededJava
Global State
// BEFORE (race condition)
private static Map<String, String> cache = new HashMap<>();
// AFTER (thread-safe)
private static final ConcurrentHashMap<String, String> cache = new ConcurrentHashMap<>();
// Use cache.computeIfAbsent(key, k -> compute(k));Database
// BEFORE (per-invocation)
Connection conn = DriverManager.getConnection("jdbc:...");
// AFTER (HikariCP pool, static init)
private static final HikariDataSource ds;
static {
HikariConfig c = new HikariConfig();
c.setJdbcUrl(System.getenv("DB_URL"));
c.setMaximumPoolSize(10);
ds = new HikariDataSource(c);
}
// Use: try (Connection conn = ds.getConnection()) { ... }Concurrency Safety for LMI
LMI runs multiple invocations concurrently in the same execution environment. The concurrency model differs by runtime — some require thread safety, others provide process isolation.
Code Review Checklist
When reviewing a function for LMI readiness, check each item:
- [ ] No shared
/tmppaths (use request ID in filenames, clean up after — shared across ALL runtimes) - [ ] Estimate total
/tmpusage under max concurrency (concurrent requests × per-request file size) - [ ] Database connections use pools (initialized outside handler, not per-invocation)
- [ ] SDK clients outside handler (module-level singletons are fine — they are thread-safe)
- [ ] Logging includes request ID (for tracing concurrent requests)
- [ ] Node.js/Java/.NET only: No global/static mutable variables (use immutable or request-local state)
- [ ] Node.js/Java/.NET only: Thread-safe libraries only (check DB drivers, HTTP clients, caching libs)
- [ ] Node.js/Java/.NET only: No request state in global scope (use AsyncLocalStorage for Node.js, ThreadLocal for Java, AsyncLocal for .NET)
- [ ] Node.js/Java/.NET only: No environment variable mutation during requests
- [ ] Python only: Memory budget accounts for per-process multiplication (memory × concurrency)
Runtime-Specific Guidance
Python (Process-Based Isolation)
Python uses multiple independent processes, each with its own interpreter and memory space. Global variables, module-level caches, and singleton objects are duplicated per process, not shared. If a function works on standard Lambda today, it works on LMI without code changes related to shared state.
Key concerns:
- Memory consumption: total footprint ≈ per-process memory × concurrency. A 200 MB function with 16 concurrent processes can consume 3+ GB.
/tmpfilesystem is shared across all processes — usecontext.aws_request_idin filenames- Each process needs its own connection pool — size pools per-process, not globally
- Prefer 4:1 or 8:1 memory-to-vCPU ratio to accommodate memory multiplication
- Monitor
MemoryUtilizationmetric and adjust ratio if needed
Safe patterns (no locking needed):
- Module-level mutable globals (isolated per process)
- Module-level SDK clients and caches
os.environreads
Node.js (Worker Threads + Async/Await)
Uses worker threads (configurable via AWS_LAMBDA_NODEJS_WORKER_COUNT) combined with async/await event loops. The handler and global state are shared across concurrent invocations within a worker thread.
The await keyword yields control to the event loop, which may execute another invocation that overwrites shared state before the first resumes.
Key concerns:
- Use
AsyncLocalStoragefromnode:async_hooksfor request context - Keep mutable state within handler local scope
- Initialize SDK clients and DB pools at module level (they are thread-safe)
- Avoid module-level mutable state (
let count = 0is a race condition) - Callback-based handlers are NOT supported on Node.js 22 — use async handlers
Java (OS Threads)
Uses OS-level threads. Lambda loads the handler class once and invokes handleRequest from multiple threads simultaneously (identical to a Java app server).
Key concerns:
- Use immutable objects and thread-safe collections (
ConcurrentHashMap,Collections.synchronizedList) - Initialize SDK clients and connection pools in constructor or static block
- Avoid mutable
staticfields - Use
ThreadLocal<T>for request-specific state - Use HikariCP or similar for connection pooling (AWS SDK for Java 2.x clients are thread-safe)
.NET (Task-Based Concurrency)
Uses a single process with .NET Tasks (same model as ASP.NET Core). The handler object is shared across all Tasks.
Key concerns:
- Use
AsyncLocal<T>for request-scoped data - Inject scoped services via DI container
- Initialize
HttpClientand SDK clients as singletons - Use
ConcurrentDictionary<TKey, TValue>andSemaphoreSlimfor thread-safe access - Invocation timeouts are NOT enforced by the runtime — use
ILambdaContext.RemainingTimeto detect approaching timeouts
Common Anti-Patterns
| Anti-pattern | Affected Runtimes | Risk | Fix |
|---|---|---|---|
| New DB connection per invocation | All | Exhausts connection limits | Module-level connection pool |
Hardcoded /tmp paths | All | File conflicts across processes | Use aws_request_id in path |
| Logging without request ID | All | Unreadable interleaved logs | Include aws_request_id |
| Mutable module-level state | Node.js, Java, .NET | Race condition / state corruption | Request-local scope or concurrent collections |
| Setting env vars during request | Node.js, Java, .NET | Race condition | Pass state via parameters |
| Assuming sequential execution | Node.js, Java, .NET | State corruption | Each invocation must be self-contained |
| Ignoring memory multiplication | Python | OOM at high concurrency | Account for per-process × concurrency |
Powertools for AWS Lambda Compatibility
Powertools handles multi-concurrency transparently (structured logging, tracing, metrics). No code changes needed.
| Runtime | Package | Minimum Version |
|---|---|---|
| Python | Powertools for AWS Lambda (Python) | 3.23.0 |
| TypeScript | Powertools for AWS Lambda (TypeScript) | 2.29.0 |
| Java | Powertools for AWS Lambda (Java) | 2.8.0 |
| .NET | Powertools for AWS Lambda (.NET) | 3.1.0 |
AWS SDK and X-Ray minimum versions:
| Runtime | AWS SDK minimum | X-Ray SDK minimum |
|---|---|---|
| Node.js | AWS SDK for JavaScript v3 (3.933.0) | 3.12.0 |
| Java | AWS SDK for Java 2.0 (2.34.0) | 2.20.0 |
| .NET | AWSSDK.Core (4.0.0.32) | AWSXRayRecorder.Core (2.16.0) |
LMI Troubleshooting
Common Issues
| Issue | Cause | Resolution |
|---|---|---|
| 429 throttles during scale-up | Traffic doubled faster than 5-min scaling window | Increase MinExecutionEnvironments or lower TargetResourceUtilization |
| Function stuck in PENDING | Capacity provider provisioning instances | Wait several minutes; verify VPC subnets have IP capacity and IAM roles are correct |
| Architecture mismatch error | Function architecture ≠ capacity provider | Align both to arm64 or x86_64 |
| Cannot terminate EC2 instances | LMI instances managed by capacity provider | Delete capacity provider to destroy instances; cannot use EC2 console |
| High CPU, low throughput | Concurrency too high for CPU-bound work | Reduce PerExecutionEnvironmentMaxConcurrency to 1/vCPU |
| Race conditions in production | Code not thread-safe for multi-concurrency | Review with checklist in thread-safety.md |
| Function version not ACTIVE | Fewer than 3 execution environments ready | Wait for provisioning; check capacity provider status |
| Unexpected 500 errors | Unhandled concurrent access to shared state | Add thread-safe patterns from migration-patterns.md |
| CloudWatch logs missing | VPC egress not configured | Add NAT Gateway or CloudWatch Logs VPC endpoint |
| High costs despite low traffic | Minimum execution environments always running | Evaluate if standard Lambda is more cost-effective |
Debugging Steps
Function Not Starting
1. Check capacity provider status: aws lambda get-capacity-provider --capacity-provider-name <name> 2. Verify subnets span 3+ AZs with available IPs 3. Confirm security group allows necessary egress 4. Check operator role has required permissions (see infrastructure-setup.md for least-privilege policy) 5. Look for Operator field in EC2 DescribeInstances or aws:lambda:capacity-provider tag
Performance Issues
1. Check CloudWatch metrics (5-min intervals): CPU utilization, memory, concurrency/env 2. If CPU > 80%: reduce concurrency or add vCPUs (increase memory with appropriate ratio) 3. If throttles > 1%: increase MinExecutionEnvironments 4. If CPU < 20%: increase concurrency — resources are underutilized 5. For Python: verify 4:1 or 8:1 ratio (GIL limits CPU parallelism)
Cost Issues
1. Verify instance count matches actual need (not over-provisioned) 2. Check if Savings Plans or RIs are applied to these instances 3. Compare actual costs against the 4-column estimate from cost-comparison.md 4. If traffic is lower than expected, consider reducing MaxVCpuCount 5. For dev/test: use ExcludedInstanceTypes to avoid expensive instance families
#!/usr/bin/env bash
set -euo pipefail
# Setup script for AWS Lambda Managed Instances (LMI)
# Usage: ./setup-lmi.sh <function-name> <capacity-provider-name> <architecture>
#
# Prerequisites:
# - AWS CLI configured with appropriate credentials
# - VPC subnets and security group created
# - IAM roles created (see references/infrastructure-setup.md)
#
# Environment variables (required):
# SUBNET_IDS - Comma-separated subnet IDs (3+ AZs)
# SECURITY_GROUP_ID - Security group ID
# ACCOUNT_ID - AWS account ID
# OPERATOR_ROLE_ARN - ARN of the LMI operator role
# EXECUTION_ROLE_ARN - ARN of the Lambda execution role
#
# Environment variables (optional):
# AWS_REGION - AWS region (default: from AWS CLI config)
# MAX_VCPU_COUNT - Max vCPU limit (default: 30)
# MEMORY_SIZE - Function memory in MB (default: 4096)
# RUNTIME - Lambda runtime (default: python3.13)
# HANDLER - Function handler (default: app.handler)
FUNCTION_NAME="${1:?Usage: $0 <function-name> <capacity-provider-name> <architecture>}"
CP_NAME="${2:?Usage: $0 <function-name> <capacity-provider-name> <architecture>}"
ARCHITECTURE="${3:-arm64}"
: "${SUBNET_IDS:?Set SUBNET_IDS (comma-separated, 3+ AZs)}"
: "${SECURITY_GROUP_ID:?Set SECURITY_GROUP_ID}"
: "${ACCOUNT_ID:?Set ACCOUNT_ID}"
: "${OPERATOR_ROLE_ARN:?Set OPERATOR_ROLE_ARN}"
: "${EXECUTION_ROLE_ARN:?Set EXECUTION_ROLE_ARN}"
MAX_VCPU_COUNT="${MAX_VCPU_COUNT:-30}"
MEMORY_SIZE="${MEMORY_SIZE:-4096}"
RUNTIME="${RUNTIME:-python3.13}"
HANDLER="${HANDLER:-app.handler}"
REGION="${AWS_REGION:-$(aws configure get region)}"
echo "==> Creating capacity provider: ${CP_NAME}"
aws lambda create-capacity-provider \
--capacity-provider-name "${CP_NAME}" \
--vpc-config "SubnetIds=[${SUBNET_IDS}],SecurityGroupIds=[${SECURITY_GROUP_ID}]" \
--permissions-config "CapacityProviderOperatorRoleArn=${OPERATOR_ROLE_ARN}" \
--instance-requirements "Architectures=[${ARCHITECTURE}]" \
--capacity-provider-scaling-config "MaxVCpuCount=${MAX_VCPU_COUNT}"
CP_ARN="arn:aws:lambda:${REGION}:${ACCOUNT_ID}:capacity-provider:${CP_NAME}"
echo "==> Creating function: ${FUNCTION_NAME}"
aws lambda create-function \
--function-name "${FUNCTION_NAME}" \
--runtime "${RUNTIME}" \
--handler "${HANDLER}" \
--zip-file fileb://function.zip \
--role "${EXECUTION_ROLE_ARN}" \
--architectures "${ARCHITECTURE}" \
--memory-size "${MEMORY_SIZE}" \
--capacity-provider-config \
"LambdaManagedInstancesCapacityProviderConfig={CapacityProviderArn=${CP_ARN}}"
echo "==> Publishing version (triggers instance provisioning — may take several minutes)"
VERSION=$(aws lambda publish-version --function-name "${FUNCTION_NAME}" --query 'Version' --output text)
echo "==> Done. Function version: ${VERSION}"
echo " Invoke with: aws lambda invoke --function-name ${FUNCTION_NAME}:${VERSION} --payload '{}' response.json"
echo " Monitor provisioning: aws lambda get-capacity-provider --capacity-provider-name ${CP_NAME}"
Related skills
FAQ
When is LMI a strong fit?
For steady, predictable traffic (50M+ requests/month) with duration-heavy spend and unacceptable cold starts.
When does LMI become cost-competitive?
As a rule of thumb, when Lambda spend exceeds about $1,000/month with steady traffic.